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,17 +40,28 @@
</head> </head>
<body> <body>
<input type="text" class="searchbar" id="gamesearch" placeholder="Type here to search.." /> <div class="gamesToolbar">
<select id="gamesort"> <input type="text" class="searchbar" id="gamesearch" placeholder="Type here to search.." />
<option value="az">a -> z</option> <button id="filtersToggle" type="button">filters</button>
<option value="za">z -> a</option> <button id="randomGame">random app</button>
<option value="new">newest</option> </div>
<option value="played">your top apps</option> <div id="tagFilters">
<option value="global">global top apps</option> <p class="filterLabel">sorting</p>
</select> <select id="gamesort">
<option value="az">a -> z</option>
<option value="za">z -> a</option>
<option value="new">newest</option>
<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="gameCount">xx apps loaded..</p>
<p id="starredHeader">starred apps</p> <p id="starredHeader">starred apps</p>
<div id="starredgames"></div> <div id="starredgames"></div>
<p id="recentHeader">recently played</p>
<div id="recentgames"></div>
<p id="allHeader" class="title">all apps</p> <p id="allHeader" class="title">all apps</p>
<div id="games"> <div id="games">
<p id="loadingMsg">apps loading..</p> <p id="loadingMsg">apps loading..</p>

View File

@ -50,7 +50,7 @@
transform: scale(1.1); transform: scale(1.1);
filter: drop-shadow(0 0 5px var(--color-1)) filter: drop-shadow(0 0 5px var(--color-1))
} }
#games, #starredgames, #topGames { #games, #starredgames, #topGames, #recentgames {
width: 90vw; width: 90vw;
display: flex; display: flex;
flex-direction: row; flex-direction: row;
@ -64,16 +64,107 @@
font-size: 24px; font-size: 24px;
color: var(--color-1); color: var(--color-1);
} }
#starredHeader, #allHeader, #topHeader { #starredHeader, #allHeader, #topHeader, #recentHeader {
font-size: 32px; font-size: 32px;
color: var(--color-1); color: var(--color-1);
} }
#noResults, #starredHeader, #allHeader, #topHeader { #noResults, #starredHeader, #allHeader, #topHeader, #recentHeader {
display: none; display: none;
} }
body { body {
justify-content: flex-start; 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 { #gamesort {
border-style: solid; border-style: solid;
border-width: 2px; 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 { #pjs {
width: 100vw; width: 100vw;
height: 100vh; height: 100vh;
position: absolute; position: fixed;
z-index: -1000; z-index: -1000;
border: none; border: none;
overflow-y: hidden; 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 { section {
min-width: 20%; min-width: 20%;
height: 420px; min-height: 420px;
padding: 8px; padding: 8px;
margin: 16px; margin: 16px;
background-color: color-mix(in srgb, var(--color-2) 40%, #00000000 60%); background-color: color-mix(in srgb, var(--color-2) 40%, #00000000 60%);
@ -27,14 +31,132 @@ sections {
margin: 6px; 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 { #hiddenGamesList {
width: min(280px, 90%); width: auto;
height: 200px; flex: 1 1 260px;
align-self: flex-start;
height: 340px;
min-height: 0;
overflow-y: auto; overflow-y: auto;
contain: layout;
border: 1px solid color-mix(in srgb, var(--color-1) 50%, #00000000 50%); border: 1px solid color-mix(in srgb, var(--color-1) 50%, #00000000 50%);
border-radius: 12px; border-radius: 12px;
padding: 8px; padding: 8px;
margin: 8px 0; margin: 0;
box-sizing: border-box;
} }
.hidden-game-row { .hidden-game-row {
@ -45,6 +167,58 @@ sections {
width: 100%; 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 { #hiddenGamesSearch {
width: min(280px, 90%); width: min(280px, 90%);
} }

View File

@ -5,11 +5,11 @@
<!-- Google tag (gtag.js) --> <!-- Google tag (gtag.js) -->
<script async src="https://www.googletagmanager.com/gtag/js?id=G-J0Y3T4HN4N"></script> <script async src="https://www.googletagmanager.com/gtag/js?id=G-J0Y3T4HN4N"></script>
<script> <script>
window.dataLayer = window.dataLayer || []; window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);} function gtag() { dataLayer.push(arguments); }
gtag('js', new Date()); gtag('js', new Date());
gtag('config', 'G-J0Y3T4HN4N'); gtag('config', 'G-J0Y3T4HN4N');
</script> </script>
<!-- initialize theme vars <!-- initialize theme vars
@ -37,6 +37,7 @@
<!-- initialize my stuff --> <!-- initialize my stuff -->
<link rel="stylesheet" href="/css/main.css" /> <link rel="stylesheet" href="/css/main.css" />
<link rel="stylesheet" href="/css/pages.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" <link rel="preload" href="https://fonts.googleapis.com/css2?family=Mulish&display=swap" as="style"
onload="this.onload=null;this.rel='stylesheet'" /> onload="this.onload=null;this.rel='stylesheet'" />
<script src="/js/main.js" defer></script> <script src="/js/main.js" defer></script>
@ -59,6 +60,24 @@
document.getElementById("randomquote").innerHTML = randomQuote; document.getElementById("randomquote").innerHTML = randomQuote;
}) })
.catch((error) => console.error("Error loading quotes:", error)); .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> </script>
<link rel="manifest" href="/manifest.json" /> <link rel="manifest" href="/manifest.json" />
@ -74,16 +93,39 @@
<h1 class="title">selenite.</h1> <h1 class="title">selenite.</h1>
<noscript>enable javascript if you want the games to actually load</noscript> <noscript>enable javascript if you want the games to actually load</noscript>
<p id="randomquote">better thank me for this unblocked website</p> <p id="randomquote">better thank me for this unblocked website</p>
<div class="samerow"> <div class="homeNav">
<div class="img-container"> <a class="navCard" href="projects.html">
<a href="/users"><img src="img/user_index.svg" /></a> <img src="img/games.svg" />
</div> <span>games</span>
<div class="img-container"> </a>
<a href="projects.html"><img src="img/games.svg" /></a> <a class="navCard" href="apps.html">
</div> <img src="img/apps.svg" />
<div class="img-container"> <span>apps</span>
<a target="_blank" href="https://discord.gg/7jyufnwJNf"><img src="img/discord.svg" /></a> </a>
</div> <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> </div>
</body> </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

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

View File

@ -5,11 +5,11 @@
<!-- Google tag (gtag.js) --> <!-- Google tag (gtag.js) -->
<script async src="https://www.googletagmanager.com/gtag/js?id=G-J0Y3T4HN4N"></script> <script async src="https://www.googletagmanager.com/gtag/js?id=G-J0Y3T4HN4N"></script>
<script> <script>
window.dataLayer = window.dataLayer || []; window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);} function gtag() { dataLayer.push(arguments); }
gtag('js', new Date()); gtag('js', new Date());
gtag('config', 'G-J0Y3T4HN4N'); gtag('config', 'G-J0Y3T4HN4N');
</script> </script>
<!-- initialize theme vars <!-- initialize theme vars
@ -66,6 +66,7 @@
<h2>credits</h2> <h2>credits</h2>
<p>whilst the majority of the work has been done by the creator, sky, selenite would not be possible without some <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> 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://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://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> <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 isDev = [];
let pageData; let pageData;
let starredGames = []; let starredGames = [];
let recentGames = [];
let globalPlays = null; let globalPlays = null;
let selectedTags = new Set();
function getHiddenGames() { function getHiddenGames() {
try { 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() { async function loadGames() {
if (type == "g") pageData = { path: "/resources/games.json", prefix: "semag", type: "g" }; 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 if (type == "a") pageData = { path: "/resources/apps.json", prefix: "sppa", type: "a" };
else { sAlert("this is not valid"); return; } else { sAlert("this is not valid"); return; }
let data = await (await fetch(pageData.path)).json(); 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.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); 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.name = element._key;
newElement.dataset.top = element.tags && element.tags.includes("top") ? "1" : ""; newElement.dataset.top = element.tags && element.tags.includes("top") ? "1" : "";
newElement.dataset.index = element._index; newElement.dataset.index = element._index;
newElement.dataset.tags = element.tags ? element.tags.join(",") : "";
newElement.className = "game"; newElement.className = "game";
newElement.href = `/loader.html?title=${encodeURIComponent(element.name)}&dir=${element.directory}&img=${element.image}&type=${pageData.type}`; 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(image);
newElement.appendChild(holder); newElement.appendChild(holder);
newElement.appendChild(warnings); newElement.appendChild(warnings);
holder.appendChild(star);
if (element.tags) { if (element.tags) {
[["18+", "thirteen", "18+"], ["13+", "thirteen", "13+"], [["18+", "thirteen", "18+"], ["13+", "thirteen", "13+"],
@ -91,18 +105,16 @@ async function loadGames() {
w.className = "thirteen"; w.className = "thirteen";
warnings.appendChild(w); warnings.appendChild(w);
} }
if (element.tags.includes("top")) { if (element.tags.includes("top") && !topGamesDisabled) {
topFragment.appendChild(newElement); topFragment.appendChild(newElement);
topElements.push(newElement); topElements.push(newElement);
} else { } else {
gamesFragment.appendChild(newElement); gamesFragment.appendChild(newElement);
gameElements.push(newElement); gameElements.push(newElement);
holder.appendChild(star);
} }
} else { } else {
gamesFragment.appendChild(newElement); gamesFragment.appendChild(newElement);
gameElements.push(newElement); gameElements.push(newElement);
holder.appendChild(star);
} }
elements.push(newElement); elements.push(newElement);
@ -117,9 +129,10 @@ async function loadGames() {
if (topGElement) topGElement.appendChild(topFragment); if (topGElement) topGElement.appendChild(topFragment);
const visibleGameCount = elements.length; const visibleGameCount = elements.length;
const hiddenCount = hiddenGames.length; const hiddenCount = hiddenGames.length;
const itemLabel = pageData.type === "a" ? "apps" : "games";
document.getElementById("gameCount").innerText = hiddenCount > 0 document.getElementById("gameCount").innerText = hiddenCount > 0
? `${visibleGameCount} games loaded (${hiddenCount} hidden)` ? `${visibleGameCount} ${itemLabel} loaded (${hiddenCount} hidden)`
: `${visibleGameCount} games loaded!`; : `${visibleGameCount} ${itemLabel} loaded!`;
document.getElementById("loadingMsg").style.display = "none"; document.getElementById("loadingMsg").style.display = "none";
document.getElementById("allHeader").style.display = "block"; document.getElementById("allHeader").style.display = "block";
const topHeader = document.getElementById("topHeader"); const topHeader = document.getElementById("topHeader");
@ -134,6 +147,94 @@ async function loadGames() {
e.addEventListener("click", (e) => { e.preventDefault(); e.stopPropagation(); starEvent(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; let searchRaf = null;
@ -141,17 +242,7 @@ document.addEventListener("DOMContentLoaded", () => {
document.getElementById("gamesearch").addEventListener("input", () => { document.getElementById("gamesearch").addEventListener("input", () => {
if (searchRaf) cancelAnimationFrame(searchRaf); if (searchRaf) cancelAnimationFrame(searchRaf);
searchRaf = requestAnimationFrame(() => { searchRaf = requestAnimationFrame(() => {
const input = document.getElementById("gamesearch").value.toUpperCase(); applyFilters();
let hidden = 0;
elements.forEach((el) => {
if (el.dataset.name.includes(input)) {
el.style.display = "flex";
} else {
el.style.display = "none";
hidden++;
}
});
document.getElementById("noResults").style.display = hidden >= elements.length ? "flex" : "none";
searchRaf = null; searchRaf = null;
}); });
}); });
@ -160,15 +251,32 @@ document.addEventListener("DOMContentLoaded", () => {
function _rebuildStarred() { function _rebuildStarred() {
const container = document.getElementById("starredgames"); const container = document.getElementById("starredgames");
const gamesEl = document.getElementById("games"); const gamesEl = document.getElementById("games");
const topGEl = document.getElementById("topGames");
container.innerHTML = ""; container.innerHTML = "";
const fragment = document.createDocumentFragment(); const fragment = document.createDocumentFragment();
starredGames.forEach((dir) => { 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)); if (el) fragment.appendChild(el.cloneNode(true));
}); });
container.appendChild(fragment); 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) { function getTotalPlayTime(directory) {
const playTime = JSON.parse(localStorage.getItem("selenite.playTime") || "{}"); const playTime = JSON.parse(localStorage.getItem("selenite.playTime") || "{}");
return Object.values(playTime).reduce((total, day) => total + (day[directory] || 0), 0); 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) { function starEvent(e) {
const game = e.target.parentNode.parentNode.getAttribute("data-target"); const game = e.target.parentNode.parentNode.getAttribute("data-target");
starredGames = JSON.parse(localStorage.getItem("selenite.starred") || "[]"); starredGames = JSON.parse(localStorage.getItem("selenite.starred") || "[]");

View File

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

View File

@ -20,6 +20,31 @@
<script src="/js/all.min.js" defer></script> <script src="/js/all.min.js" defer></script>
<script defer> <script defer>
let gameData = {}; 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", () => { document.addEventListener("DOMContentLoaded", () => {
const gameFrame = document.getElementById("gameFrame"); const gameFrame = document.getElementById("gameFrame");
const gameImage = document.getElementById("gameImage"); const gameImage = document.getElementById("gameImage");
@ -35,6 +60,7 @@
gameFrame.src = `/resources/${prefix}/${gameData.path}/index.html`; gameFrame.src = `/resources/${prefix}/${gameData.path}/index.html`;
gameImage.src = `/resources/${prefix}/${gameData.path}/${params.get("img")}`; gameImage.src = `/resources/${prefix}/${gameData.path}/${params.get("img")}`;
gameName.innerText = decodeURIComponent(params.get("title")); gameName.innerText = decodeURIComponent(params.get("title"));
recordRecentlyPlayed(gameData.path);
} else if (localStorage.getItem("selenite.lastGame")) { } else if (localStorage.getItem("selenite.lastGame")) {
const game = JSON.parse(decodeURIComponent(atob(localStorage.getItem("selenite.lastGame")))); const game = JSON.parse(decodeURIComponent(atob(localStorage.getItem("selenite.lastGame"))));
gameData.type = game[3]; gameData.type = game[3];
@ -43,8 +69,15 @@
gameFrame.src = `/resources/${prefix}/${gameData.path}/index.html`; gameFrame.src = `/resources/${prefix}/${gameData.path}/index.html`;
gameImage.src = `/resources/${prefix}/${gameData.path}/${decodeURIComponent(game[1])}`; gameImage.src = `/resources/${prefix}/${gameData.path}/${decodeURIComponent(game[1])}`;
gameName.innerText = game[2]; 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("fullscreen").addEventListener("click", () => gameFrame.requestFullscreen());
document.getElementById("reload").addEventListener("click", () => { gameFrame.src = gameFrame.src; }); document.getElementById("reload").addEventListener("click", () => { gameFrame.src = gameFrame.src; });
document.getElementById("open").addEventListener("click", () => window.open(gameFrame.src)); document.getElementById("open").addEventListener("click", () => window.open(gameFrame.src));

View File

@ -5,11 +5,11 @@
<!-- Google tag (gtag.js) --> <!-- Google tag (gtag.js) -->
<script async src="https://www.googletagmanager.com/gtag/js?id=G-J0Y3T4HN4N"></script> <script async src="https://www.googletagmanager.com/gtag/js?id=G-J0Y3T4HN4N"></script>
<script> <script>
window.dataLayer = window.dataLayer || []; window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);} function gtag() { dataLayer.push(arguments); }
gtag('js', new Date()); gtag('js', new Date());
gtag('config', 'G-J0Y3T4HN4N'); gtag('config', 'G-J0Y3T4HN4N');
</script> </script>
<!-- initialize theme vars <!-- initialize theme vars
@ -28,7 +28,7 @@
<!-- seo + other things --> <!-- seo + other things -->
<title>Projects | Selenite</title> <title>Selenite</title>
<link rel="icon" href="/favicon.ico" /> <link rel="icon" href="/favicon.ico" />
<meta name="viewport" content="width=device-width,initial-scale=1" /> <meta name="viewport" content="width=device-width,initial-scale=1" />
@ -40,17 +40,28 @@
</head> </head>
<body> <body>
<input type="text" class="searchbar" id="gamesearch" placeholder="Type here to search.." /> <div class="gamesToolbar">
<select id="gamesort"> <input type="text" class="searchbar" id="gamesearch" placeholder="Type here to search.." />
<option value="az">a -> z</option> <button id="filtersToggle" type="button">filters</button>
<option value="za">z -> a</option> <button id="randomGame">random game</button>
<option value="new">newest</option> </div>
<option value="played">your top games</option> <div id="tagFilters">
<option value="global">global top games</option> <p class="filterLabel">sorting</p>
</select> <select id="gamesort">
<option value="az">a -> z</option>
<option value="za">z -> a</option>
<option value="new">newest</option>
<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="gameCount">xx games loaded..</p>
<p id="starredHeader">starred games</p> <p id="starredHeader">starred games</p>
<div id="starredgames"></div> <div id="starredgames"></div>
<p id="recentHeader">recently played</p>
<div id="recentgames"></div>
<p id="topHeader" class="title">top games</p> <p id="topHeader" class="title">top games</p>
<div id="topGames"></div> <div id="topGames"></div>
<p id="allHeader" class="title">all games</p> <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 async src="https://www.googletagmanager.com/gtag/js?id=G-J0Y3T4HN4N"></script>
<script> <script>
window.dataLayer = window.dataLayer || []; window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);} function gtag() { dataLayer.push(arguments); }
gtag('js', new Date()); gtag('js', new Date());
gtag('config', 'G-J0Y3T4HN4N'); gtag('config', 'G-J0Y3T4HN4N');
@ -84,9 +84,19 @@
if (localStorage.getItem("selenite.super-fast-mode") == 'true') { if (localStorage.getItem("selenite.super-fast-mode") == 'true') {
document.getElementById("superFastMode").setAttribute("checked", "") document.getElementById("superFastMode").setAttribute("checked", "")
}; };
if (localStorage.getItem("selenite.disableTopGames") == 'false') { if (localStorage.getItem("selenite.disableTopGames") == 'true') {
document.getElementById("topGamesToggle").setAttribute("checked", "") 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) => { document.getElementById("themes").addEventListener("input", (e) => {
localStorage.setItem("selenite.theme", e.target.value); localStorage.setItem("selenite.theme", e.target.value);
document.body.setAttribute("theme", e.target.value); document.body.setAttribute("theme", e.target.value);
@ -114,26 +124,93 @@
location.reload(); location.reload();
}); });
document.getElementById("topGamesToggle").addEventListener("click", (e) => { 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) => { document.getElementById("recentlyPlayedToggle").addEventListener("click", (e) => {
if (document.getElementById("cloaks").value) { localStorage.setItem("selenite.disableRecentlyPlayed", e.target.checked ? "true" : "false");
localStorage.setItem("selenite.tab-cloak", JSON.stringify({ });
"name": cloaks[document.getElementById("cloaks").value][0], document.getElementById("submitCloak").addEventListener("click", () => {
"icon": cloaks[document.getElementById("cloaks").value][1] 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 { } else {
localStorage.setItem("selenite.tab-cloak", JSON.stringify({ name = document.getElementById("cloakName").value.trim();
"name": document.getElementById("cloakName").value || "", icon = document.getElementById("cloakIcon").value.trim();
"icon": document.getElementById("cloakIcon").value || "" }
})); 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) => { document.getElementById("submitPanic").addEventListener("click", () => {
localStorage.setItem("selenite.panic-mode", JSON.stringify({ const feedback = document.getElementById("panicFeedback");
"key": document.getElementById("panicKey").value || "", const key = document.getElementById("panicKey").value.trim();
"url": document.getElementById("panicURL").value || "" 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", () => { document.getElementById("toggleCDN").addEventListener("change", () => {
let toggle = document.getElementById("toggleCDN"); let toggle = document.getElementById("toggleCDN");
@ -207,10 +284,14 @@
updateHiddenGamesCount(games.length); updateHiddenGamesCount(games.length);
}); });
const box = document.createElement("span");
box.className = "checkboxBox";
const text = document.createElement("span"); const text = document.createElement("span");
text.innerText = game.name; text.innerText = game.name;
row.appendChild(checkbox); row.appendChild(checkbox);
row.appendChild(box);
row.appendChild(text); row.appendChild(text);
list.appendChild(row); list.appendChild(row);
}); });
@ -234,6 +315,27 @@
searchInput.addEventListener("input", () => { searchInput.addEventListener("input", () => {
renderHiddenGameList(games, new Set(getHiddenGames()), searchInput.value); 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() { 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() { function downloadData() {
let storage = JSON.stringify(localStorage); try {
let cookies = document.cookie; const data = JSON.stringify({
let data = btoa(JSON.stringify({ localStorage: storage, cookies: cookies })); localStorage: { ...localStorage },
cookies: document.cookie
let file = new Blob([data], { type: "text" }); });
let a = document.createElement("a"); const file = new Blob([data], { type: "application/json" });
a.href = URL.createObjectURL(file); const url = URL.createObjectURL(file);
a.download = "selenite.dat"; const a = document.createElement("a");
a.click(); 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() { function uploadData() {
let input = document.createElement("input"); const input = document.createElement("input");
input.type = "file"; input.type = "file";
input.accept = "dat"; input.accept = ".dat,application/json";
input.click();
input.addEventListener("change", e => { 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") { const file = e.target.files[0];
let fileReader = new FileReader(); if (!file) return;
fileReader.addEventListener("load", 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") return;
let data = JSON.parse(atob(e.target.result));
let local = JSON.parse(data["localStorage"]);
let cookies = data["cookies"];
document.cookie = cookies; const fileReader = new FileReader();
Object.keys(local).forEach(e => { fileReader.addEventListener("load", e => {
localStorage.setItem(e, local[e]); try {
}) const { localStorage: local, cookies } = parseBackup(e.target.result);
});
fileReader.readAsText(e.target.files[0]); 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";
});
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() { async function deleteAllCaches() {
try { try {
@ -308,12 +457,18 @@
</select> </select>
<input type="color" id="colorPicker" style="display: none"> <input type="color" id="colorPicker" style="display: none">
<p style="font-size:20px;margin:6px;">toggles</p> <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> 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> 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> 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>
<section> <section>
<h2>tab cloak</h2> <h2>tab cloak</h2>
@ -332,6 +487,7 @@
</select> </select>
<button id="submitCloak">set tab cloak</button> <button id="submitCloak">set tab cloak</button>
<p id="cloakFeedback" class="feedback"></p>
</section> </section>
<section> <section>
<h2>panic mode</h2> <h2>panic mode</h2>
@ -340,6 +496,7 @@
<p title="the url itll direct you to">website</p> <p title="the url itll direct you to">website</p>
<input type="text" id="panicURL"> <input type="text" id="panicURL">
<button id="submitPanic">set panic mode</button> <button id="submitPanic">set panic mode</button>
<p id="panicFeedback" class="feedback"></p>
</section> </section>
<section> <section>
<h2>manage data</h2> <h2>manage data</h2>
@ -347,13 +504,29 @@
<button onclick="uploadData()">upload</button> <button onclick="uploadData()">upload</button>
<button onclick="wipeData()">wipe (erases all game data)</button> <button onclick="wipeData()">wipe (erases all game data)</button>
<button onclick="deleteAllCaches()">wipe all cache</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> <section id="hiddenGamesSection">
<h2>hidden games</h2> <h2>hidden games</h2>
<p id="hiddenGamesCount">0 hidden</p> <p id="hiddenGamesCount">0 hidden</p>
<input type="text" id="hiddenGamesSearch" placeholder="search games"> <div class="hiddenGamesRow">
<div id="hiddenGamesList"></div> <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> </section>
</sections> </sections>
</body> </body>