big push
This commit is contained in:
+1
-1
@@ -15,7 +15,7 @@
|
||||
<!--el:navbar-->
|
||||
<!--el:sidebar-->
|
||||
|
||||
<div class="content box">
|
||||
<div class="container centered">
|
||||
<h1 style="font-size: 50px;">404</h1>
|
||||
|
||||
<p>
|
||||
|
||||
+2
-2
@@ -1,4 +1,4 @@
|
||||
import configTemplate from './polaris.config.template.js';
|
||||
import configTemplate from './polaris.config.template.js';
|
||||
|
||||
/**
|
||||
* @type {configTemplate}
|
||||
@@ -6,7 +6,7 @@ import configTemplate from './polaris.config.template.js';
|
||||
export default {
|
||||
port: 8080,
|
||||
mode: 'dev',
|
||||
minify: true,
|
||||
minify: false,
|
||||
assetScrambling: true,
|
||||
allowDangerousTemplateInsert: true
|
||||
};
|
||||
+21
-15
@@ -15,7 +15,7 @@ const app = express();
|
||||
const server = http.createServer();
|
||||
const bareServer = createBareServer('/bare/');
|
||||
const mode = (process.argv[2] === 'prod' || process.argv[2] === 'dev' ? process.argv[2] : (process.argv[3] === 'prod' || process.argv[3] === 'dev' ? process.argv[3] : (config.mode === 'prod' || config.mode === 'dev' ? config.mode : 'prod')));
|
||||
const port = (process.argv[2] !== 'prod' && process.argv[2] !== 'dev' && Boolean(Number(process.argv[2]))) ? process.argv[2] : (Boolean(Number(process.argv[3])) ? process.argv[3] : (Boolean(Number(config.port)) ? config.port : (mode === 'prod' ? 80 : 8080 ) ));
|
||||
const port = (process.argv[2] !== 'prod' && process.argv[2] !== 'dev' && Boolean(Number(process.argv[2]))) ? process.argv[2] : (Boolean(Number(process.argv[3])) ? process.argv[3] : (Boolean(Number(config.port)) ? config.port : (mode === 'prod' ? 80 : 8080)));
|
||||
const __dirname = url.fileURLToPath(new URL('.', import.meta.url));
|
||||
|
||||
app.get('/cdn/*', cors({
|
||||
@@ -61,7 +61,7 @@ app.get('/asset/:token', async (req, res, next) => {
|
||||
TokenManager.delete(req.params.token);
|
||||
|
||||
res.setHeader('content-type', token.data.type);
|
||||
res.end(await rewriter.auto(fs.readFileSync(token.data.asset), token.data.type));
|
||||
res.end(await rewriter.auto(fs.readFileSync(token.data.asset), token.data.type, token.data.asset.replace(path.join(__dirname, '../static'), '')));
|
||||
} else next();
|
||||
} else next();
|
||||
}
|
||||
@@ -73,21 +73,27 @@ app.get('/uv/service*', async (req, res) => {
|
||||
});
|
||||
|
||||
app.use(async (req, res, next) => {
|
||||
const {
|
||||
exists,
|
||||
path: filePath
|
||||
} = pathToFile(req.path, path.join(__dirname, '../static'));
|
||||
if (req.path === '/index') res.redirect('/');
|
||||
else {
|
||||
const {
|
||||
exists,
|
||||
path: filePath
|
||||
} = pathToFile(req.path, path.join(__dirname, '../static'));
|
||||
|
||||
if (exists) {
|
||||
res.setHeader('content-type', mime.getType(filePath));
|
||||
if (exists) {
|
||||
if (req.path.endsWith('.html')) res.redirect(req.path.slice(0, -5));
|
||||
else {
|
||||
res.setHeader('content-type', mime.getType(filePath));
|
||||
|
||||
if (mime.getType(filePath) === 'text/html') res.end(await rewriter.html(fs.readFileSync(filePath)));
|
||||
else if (mime.getType(filePath) === 'text/javascript') res.end(await rewriter.javascript(fs.readFileSync(filePath)));
|
||||
else if (mime.getType(filePath) === 'text/css') res.end(await rewriter.css(fs.readFileSync(filePath)));
|
||||
else res.sendFile(filePath);
|
||||
} else {
|
||||
res.setHeader('content-type', 'text/html');
|
||||
res.status(404).end(await rewriter.html(fs.readFileSync(path.join(__dirname, '../pages/404.html'))));
|
||||
if (mime.getType(filePath) === 'text/html') res.end(await rewriter.html(fs.readFileSync(filePath), req.path));
|
||||
else if (mime.getType(filePath) === 'text/javascript') res.end(await rewriter.javascript(fs.readFileSync(filePath), req.path));
|
||||
else if (mime.getType(filePath) === 'text/css') res.end(await rewriter.css(fs.readFileSync(filePath), req.path));
|
||||
else res.sendFile(filePath);
|
||||
}
|
||||
} else {
|
||||
res.setHeader('content-type', 'text/html');
|
||||
res.status(404).end(await rewriter.html(fs.readFileSync(path.join(__dirname, '../pages/404.html'))));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
+64
-42
@@ -92,43 +92,54 @@ const html = (data) => {
|
||||
});
|
||||
};
|
||||
|
||||
const javascript = (data) => {
|
||||
const javascript = (data, filePath) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
const imports = String(data).split('import ')
|
||||
.map(data => data.split('from ')[1])
|
||||
.filter(data => Boolean(data))
|
||||
.map(data => data.split(';')[0]
|
||||
.map(data => data.split('\n')[0]
|
||||
.replaceAll('\'', '')
|
||||
.replaceAll('`', '')
|
||||
.replaceAll('"', ''))
|
||||
.filter(data => fs.existsSync(path.join(__dirname, '../templates', data + '.javascript')));
|
||||
|
||||
.replaceAll('"', '')
|
||||
.replaceAll(';', ''))
|
||||
.map(data => {
|
||||
if (data.startsWith('./')) return {
|
||||
originalFile: data,
|
||||
newFile: path.join(filePath.split('/').slice(0, -1).join('/'), data)
|
||||
};
|
||||
else if (data.startsWith('../')) return {
|
||||
originalFile: data,
|
||||
newFile: path.join(filePath.split('/').slice(0, -1).join('/'), data)
|
||||
};
|
||||
else return {
|
||||
originalFile: data,
|
||||
newFile: data
|
||||
};
|
||||
})
|
||||
.filter(data => fs.existsSync(path.join(__dirname, '../static', data.newFile)));
|
||||
|
||||
let javascript = String(data);
|
||||
|
||||
if (config.assetScrambling) for (let i = 0; i < imports.length; i++) {
|
||||
javascript = javascript.replace(imports[i], '/asset/' + TokenManager.generate('asset', 20000, {
|
||||
asset: path.join(__dirname, '../static', imports[i]),
|
||||
type: 'text/javascript'
|
||||
}).token);
|
||||
}
|
||||
if (config.assetScrambling) for (let i = 0; i < imports.length; i++) javascript = javascript.replace(imports[i].originalFile, '/asset/' + TokenManager.generate('asset', 20000, {
|
||||
asset: path.join(__dirname, '../static', imports[i].newFile),
|
||||
type: 'text/javascript'
|
||||
}).token);
|
||||
|
||||
if (config.minify) resolve(JavaScriptObfuscator.obfuscate(javascript,
|
||||
{
|
||||
compact: true,
|
||||
controlFlowFlattening: true,
|
||||
controlFlowFlatteningThreshold: 1,
|
||||
numbersToExpressions: true,
|
||||
simplify: true,
|
||||
stringArrayShuffle: true,
|
||||
splitStrings: true,
|
||||
stringArrayThreshold: 1
|
||||
}).getObfuscatedCode());
|
||||
if (config.minify) resolve(JavaScriptObfuscator.obfuscate(javascript, {
|
||||
compact: true,
|
||||
controlFlowFlattening: true,
|
||||
controlFlowFlatteningThreshold: 1,
|
||||
numbersToExpressions: true,
|
||||
simplify: true,
|
||||
stringArrayShuffle: true,
|
||||
splitStrings: true,
|
||||
stringArrayThreshold: 1
|
||||
}).getObfuscatedCode());
|
||||
else resolve(javascript);
|
||||
});
|
||||
};
|
||||
|
||||
const css = (data) => {
|
||||
const css = (data, filePath) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
const imports = String(data).split('url(')
|
||||
.map(data => {
|
||||
@@ -143,37 +154,48 @@ const css = (data) => {
|
||||
else return undefined;
|
||||
})
|
||||
.filter(data => {
|
||||
if (data) {
|
||||
try {
|
||||
new URL(data);
|
||||
if (data) try {
|
||||
new URL(data);
|
||||
|
||||
return false;
|
||||
} catch (e) {
|
||||
if (data.startsWith('/')) return true;
|
||||
else return false;
|
||||
}
|
||||
return false;
|
||||
} catch (e) {
|
||||
return true;
|
||||
} else return false;
|
||||
})
|
||||
.filter(data => fs.existsSync(path.join(__dirname, '../templates', data + mime.getExtension(data))));
|
||||
.map(data => {
|
||||
// console.log(path.join(filePath.split('/').slice(0, -1).join('/')));
|
||||
|
||||
if (data.startsWith('./')) return {
|
||||
originalFile: data,
|
||||
newFile: path.join(filePath.split('/').slice(0, -1).join('/'), data)
|
||||
};
|
||||
else if (data.startsWith('../')) return {
|
||||
originalFile: data,
|
||||
newFile: path.join(filePath.split('/').slice(0, -1).join('/'), data)
|
||||
};
|
||||
else return {
|
||||
originalFile: data,
|
||||
newFile: data
|
||||
};
|
||||
})
|
||||
.filter(data => fs.existsSync(path.join(__dirname, '../static', data.newFile)));
|
||||
|
||||
let css = String(data);
|
||||
|
||||
if (config.assetScrambling) for (let i = 0; i < imports.length; i++) {
|
||||
css = css.replace(imports[i], '/asset/' + TokenManager.generate('asset', 20000, {
|
||||
asset: path.join(__dirname, '../static', imports[i]),
|
||||
type: mime.getType(path.join(__dirname, '../static', imports[i]))
|
||||
}).token);
|
||||
}
|
||||
|
||||
if (config.assetScrambling) for (let i = 0; i < imports.length; i++) css = css.replace(imports[i].originalFile, '/asset/' + TokenManager.generate('asset', 20000, {
|
||||
asset: path.join(__dirname, '../static', imports[i].newFile),
|
||||
type: mime.getType(path.join(__dirname, '../static', imports[i].newFile))
|
||||
}).token);
|
||||
|
||||
if (config.minify) resolve(css.replace(/(\r\n|\n|\r)/gm, '').replaceAll(' ', ' '));
|
||||
else resolve(css);
|
||||
});
|
||||
};
|
||||
|
||||
const auto = async (data, type) => {
|
||||
const auto = async (data, type, filePath) => {
|
||||
if (type === 'text/html') return await html(data);
|
||||
else if (type === 'text/javascript' || type === 'application/javascript') return await javascript(data);
|
||||
else if (type === 'text/css') return await css(data);
|
||||
else if (type === 'text/javascript' || type === 'application/javascript') return await javascript(data, filePath);
|
||||
else if (type === 'text/css') return await css(data, filePath);
|
||||
else return data;
|
||||
};
|
||||
|
||||
|
||||
+3
-1
@@ -15,12 +15,14 @@
|
||||
<!--el:navbar-->
|
||||
<!--el:sidebar-->
|
||||
|
||||
<div class="content">
|
||||
<div class="content centered">
|
||||
<h1>Apps</h1>
|
||||
<br>
|
||||
<div class="apps"></div>
|
||||
</div>
|
||||
|
||||
<!--el:footer-->
|
||||
|
||||
<script src="/assets/js/main.js" type="module"></script>
|
||||
|
||||
<!--el:{{mode === 'dev'}}:development-->
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
[
|
||||
{
|
||||
"date": "some/time/2023?",
|
||||
"simpleDescription": "Version 1.2 Release"
|
||||
"date": "1/1/2024",
|
||||
"simpleDescription": "2024 Update!!!"
|
||||
},
|
||||
{
|
||||
"date": "11/23/2023",
|
||||
|
||||
@@ -0,0 +1,89 @@
|
||||
footer {
|
||||
position: relative;
|
||||
text-align: left;
|
||||
background-image: url('/assets/img/background.jpeg');
|
||||
background-attachment: fixed;
|
||||
background-position: center;
|
||||
background-repeat: no-repeat;
|
||||
background-size: cover;
|
||||
user-select: none;
|
||||
padding: 4% 5% 2% 5%;
|
||||
color: #fff;
|
||||
margin-top: 50px;
|
||||
text-shadow: 0px 0px 20px rgba(0, 0, 0, 0.616);
|
||||
}
|
||||
|
||||
footer p {
|
||||
text-shadow: 0px 0px 20px rgba(0, 0, 0, 0.616);
|
||||
}
|
||||
|
||||
footer h1 {
|
||||
font-family: 'Lato-Black';
|
||||
color: #fff;
|
||||
margin: 0px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
footer::before {
|
||||
display: block;
|
||||
content: '';
|
||||
height: 270px;
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
width: 100vw;
|
||||
z-index: -1;
|
||||
background: linear-gradient(180deg, rgba(255, 255, 255, 0) 0%, rgba(0, 0, 0, 0.8) 100%);
|
||||
}
|
||||
|
||||
footer .socials {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
footer .socials i {
|
||||
margin: 10px;
|
||||
margin-left: 5px;
|
||||
margin-right: 5px;
|
||||
font-size: 20px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
footer .socials a {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
footer .socials a:hover {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
footer .socials a::before {
|
||||
display: none;
|
||||
}
|
||||
|
||||
footer .socials i:is(:first-child) {
|
||||
margin-left: 0px;
|
||||
}
|
||||
|
||||
footer .right {
|
||||
position: absolute;
|
||||
right: 5%;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
footer .right a {
|
||||
display: block;
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
footer .title:not(:last-child) {
|
||||
display: flex;
|
||||
margin: 0px;
|
||||
}
|
||||
|
||||
footer .title img {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
margin-left: 10px;
|
||||
filter: drop-shadow(0px 0px 20px rgba(0, 0, 0, 0.616));
|
||||
border-radius: 1vh;
|
||||
}
|
||||
@@ -1,9 +1,10 @@
|
||||
@import url('https://site-assets.fontawesome.com/releases/v6.2.0/css/all.css');
|
||||
@import url('/assets/css/fonts.css');
|
||||
@import url('/assets/css/themes.css');
|
||||
@import url('/assets/css/nav.css');
|
||||
@import url('/assets/css/sidebar.css');
|
||||
@import url('/assets/css/dropdown.css');
|
||||
@import url('./fonts.css');
|
||||
@import url('./themes.css');
|
||||
@import url('./nav.css');
|
||||
@import url('./sidebar.css');
|
||||
@import url('./dropdown.css');
|
||||
@import url('./footer.css');
|
||||
|
||||
* {
|
||||
font-family: 'Lato';
|
||||
@@ -21,6 +22,10 @@ body {
|
||||
background-repeat: no-repeat;
|
||||
}
|
||||
|
||||
.centered {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
html {
|
||||
min-height: 100%;
|
||||
}
|
||||
@@ -517,6 +522,11 @@ img.featured:hover {
|
||||
transition: border 0.5s linear;
|
||||
}
|
||||
|
||||
.content {
|
||||
margin-left: 100px;
|
||||
margin-right: 100px;
|
||||
}
|
||||
|
||||
@keyframes beat {
|
||||
0%,
|
||||
50%,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import PolarisError from '/assets/js/error.js';
|
||||
import { loadWorker } from '/assets/js/wpm.js';
|
||||
import { loadProxyWorker } from './utils.js';
|
||||
import PolarisError from './error.js';
|
||||
|
||||
const tiltEffectSettings = {
|
||||
max: 8,
|
||||
@@ -18,7 +18,8 @@ const load = () => {
|
||||
document.querySelector('.apps').appendChild(el);
|
||||
|
||||
el.addEventListener('click', async () => {
|
||||
await loadWorker('uv');
|
||||
await loadProxyWorker('uv');
|
||||
|
||||
localStorage.setItem('frameData', JSON.stringify({
|
||||
type: 'app',
|
||||
app
|
||||
@@ -67,4 +68,4 @@ function setTransition(event) {
|
||||
|
||||
export default {
|
||||
load
|
||||
};
|
||||
};
|
||||
@@ -1,4 +1,4 @@
|
||||
import PolarisError from '/assets/js/error.js';
|
||||
import PolarisError from './error.js';
|
||||
|
||||
const tiltEffectSettings = {
|
||||
max: 8,
|
||||
|
||||
@@ -161,7 +161,7 @@ easterEggs.push({
|
||||
font-size: 5.5vh;
|
||||
left: 50%;
|
||||
-ms-transform: translate(-50%);
|
||||
transform: translate(-50%);">Hamster</span>`;
|
||||
transform: translate(-50%);">Hamter</span>`;
|
||||
menu.appendChild(caller);
|
||||
|
||||
const call = document.createElement('div');
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
const load = () => {
|
||||
let frameData = JSON.parse(localStorage.getItem('frameData'));
|
||||
if (!frameData) location.href = '/';
|
||||
|
||||
const iframe = document.querySelector('.frame');
|
||||
|
||||
@@ -29,7 +28,7 @@ const load = () => {
|
||||
document.querySelector('#gameicon').src = "https://cdn3.iconfinder.com/data/icons/feather-5/24/search-512.png";
|
||||
document.querySelector('#gametitle').textContent = "Proxy";
|
||||
} else document.querySelector('#gametitle').textContent = 'Failed to load proxy.';
|
||||
} else location.href = '/';
|
||||
}
|
||||
|
||||
document.querySelector('#fullscreen').addEventListener('click', () => {
|
||||
const iframe = document.querySelector('.frame');
|
||||
|
||||
+38
-56
@@ -1,31 +1,28 @@
|
||||
import PolarisError from './error.js';
|
||||
import { workerLoaded, loadWorker } from './wpm.js';
|
||||
import PolarisError from '/assets/js/error.js';
|
||||
import { loadProxyWorker } from '/assets/js/utils.js';
|
||||
|
||||
const tiltEffectSettings = {
|
||||
max: 8, // max tilt rotation (degrees (deg))
|
||||
perspective: 1000, // transform perspective, the lower the more extreme the tilt gets (pixels (px))
|
||||
scale: 1.05, // transform scale - 2 = 200%, 1.5 = 150%, etc..
|
||||
speed: 800, // speed (transition-duration) of the enter/exit transition (milliseconds (ms))
|
||||
easing: 'cubic-bezier(.03,.98,.52,.99)' // easing (transition-timing-function) of the enter/exit transition
|
||||
max: 8,
|
||||
perspective: 1000,
|
||||
scale: 1.05,
|
||||
speed: 800,
|
||||
easing: 'cubic-bezier(.03,.98,.52,.99)'
|
||||
};
|
||||
|
||||
let games = []; // store all games
|
||||
let filteredGames = []; // store filtered games
|
||||
let games = [];
|
||||
let filteredGames = [];
|
||||
|
||||
const load = () => {
|
||||
fetch('/assets/JSON/games.json').then(res => res.json()).then(data => {
|
||||
games = data;
|
||||
filteredGames = games; // initialize filtered games with all games
|
||||
filteredGames = games;
|
||||
|
||||
renderGames(filteredGames); // render games initially
|
||||
renderGames(filteredGames);
|
||||
|
||||
// Add event listener to search input
|
||||
const searchInput = document.getElementById('searchInput');
|
||||
searchInput.addEventListener('input', filterGames);
|
||||
})
|
||||
.catch(e => {
|
||||
new PolarisError('Failed to load games');
|
||||
});
|
||||
.catch(e => new PolarisError('Failed to load games'));
|
||||
};
|
||||
|
||||
function filterGames() {
|
||||
@@ -34,20 +31,14 @@ function filterGames() {
|
||||
|
||||
filteredGames = games.filter(game => game.name.toLowerCase().includes(searchTerm));
|
||||
|
||||
renderGames(filteredGames); // render filtered games
|
||||
renderGames(filteredGames);
|
||||
}
|
||||
|
||||
function renderGames(gamesToRender) {
|
||||
const gamesContainer = document.querySelector('.games');
|
||||
const popularGamesContainer = document.querySelector('.popular-games');
|
||||
gamesContainer.innerHTML = ''; // clear previous games
|
||||
popularGamesContainer.innerHTML = ''; // clear previous popular games
|
||||
|
||||
function openGameInNewTab(game) {
|
||||
const x = window.open('about:blank', '_blank');
|
||||
const index = game.source;
|
||||
x.document.write(`<iframe src="${index}" style="position:fixed; top:0; left:0; bottom:0; right:0; width:100%; height:100%; border:none; margin:0; padding:0; overflow:hidden; z-index:999999;"></iframe>`);
|
||||
}
|
||||
gamesContainer.innerHTML = '';
|
||||
popularGamesContainer.innerHTML = '';
|
||||
|
||||
gamesToRender.forEach(game => {
|
||||
const el = document.createElement('div');
|
||||
@@ -62,20 +53,15 @@ function renderGames(gamesToRender) {
|
||||
popularGamesContainer.appendChild(popularEl);
|
||||
|
||||
popularEl.addEventListener('click', async () => {
|
||||
if (!workerLoaded) await loadWorker();
|
||||
const frameData = {
|
||||
type: 'game',
|
||||
game
|
||||
};
|
||||
if (game.openinnewtab === 'yes') {
|
||||
window.open(game.source, '_blank');
|
||||
console.log('Open game in new tab:', frameData);
|
||||
} else if (game.openinaboutblank === 'yes') {
|
||||
openGameInNewTab(game);
|
||||
console.log('Open game in about:blank:', frameData);
|
||||
} else {
|
||||
localStorage.setItem('frameData', JSON.stringify(frameData));
|
||||
location.href = '/view';
|
||||
await loadProxyWorker('uv');
|
||||
|
||||
if (game.openinnewtab === 'yes') window.open(game.source);
|
||||
else {
|
||||
localStorage.setItem('frameData', JSON.stringify({
|
||||
type: 'game',
|
||||
game
|
||||
}));
|
||||
location.href = '/view';
|
||||
}
|
||||
});
|
||||
|
||||
@@ -85,21 +71,20 @@ function renderGames(gamesToRender) {
|
||||
}
|
||||
|
||||
el.addEventListener('click', async () => {
|
||||
if (!workerLoaded) await loadWorker();
|
||||
await loadProxyWorker();
|
||||
|
||||
const frameData = {
|
||||
type: 'game',
|
||||
game
|
||||
};
|
||||
|
||||
if (game.openinnewtab === 'yes') {
|
||||
window.open(game.source, '_blank');
|
||||
console.log('Open game in new tab:', frameData);
|
||||
} else if (game.openinaboutblank === 'yes') {
|
||||
openGameInNewTab(game);
|
||||
console.log('Open game in about:blank:', frameData);
|
||||
} else {
|
||||
localStorage.setItem('frameData', JSON.stringify(frameData));
|
||||
location.href = '/view';
|
||||
}
|
||||
window.open(game.source, '_blank');
|
||||
console.log('Open game in new tab:', frameData);
|
||||
} else {
|
||||
localStorage.setItem('frameData', JSON.stringify(frameData));
|
||||
location.href = '/view';
|
||||
}
|
||||
});
|
||||
|
||||
el.addEventListener('mouseenter', gameMouseEnter);
|
||||
@@ -122,10 +107,8 @@ function gameMouseMove(event) {
|
||||
const mouseY = event.clientY - centerY;
|
||||
const rotateXUncapped = (+1) * tiltEffectSettings.max * mouseY / (gameHeight / 2);
|
||||
const rotateYUncapped = (-1) * tiltEffectSettings.max * mouseX / (gameWidth / 2);
|
||||
const rotateX = rotateXUncapped < -tiltEffectSettings.max ? -tiltEffectSettings.max :
|
||||
(rotateXUncapped > tiltEffectSettings.max ? tiltEffectSettings.max : rotateXUncapped);
|
||||
const rotateY = rotateYUncapped < -tiltEffectSettings.max ? -tiltEffectSettings.max :
|
||||
(rotateYUncapped > tiltEffectSettings.max ? tiltEffectSettings.max : rotateYUncapped);
|
||||
const rotateX = rotateXUncapped < -tiltEffectSettings.max ? -tiltEffectSettings.max : (rotateXUncapped > tiltEffectSettings.max ? tiltEffectSettings.max : rotateXUncapped);
|
||||
const rotateY = rotateYUncapped < -tiltEffectSettings.max ? -tiltEffectSettings.max : (rotateYUncapped > tiltEffectSettings.max ? tiltEffectSettings.max : rotateYUncapped);
|
||||
|
||||
game.style.transform = `perspective(${tiltEffectSettings.perspective}px) rotateX(${rotateX}deg) rotateY(${rotateY}deg) scale3d(${tiltEffectSettings.scale}, ${tiltEffectSettings.scale}, ${tiltEffectSettings.scale})`;
|
||||
}
|
||||
@@ -139,11 +122,10 @@ function setTransition(event) {
|
||||
const game = event.currentTarget;
|
||||
clearTimeout(game.transitionTimeoutId);
|
||||
game.style.transition = `transform ${tiltEffectSettings.speed}ms ${tiltEffectSettings.easing}`;
|
||||
game.transitionTimeoutId = setTimeout(() => {
|
||||
game.style.transition = '';
|
||||
}, tiltEffectSettings.speed);
|
||||
|
||||
game.transitionTimeoutId = setTimeout(() => game.style.transition = '', tiltEffectSettings.speed);
|
||||
}
|
||||
|
||||
export default {
|
||||
load
|
||||
};
|
||||
};
|
||||
+39
-51
@@ -1,15 +1,13 @@
|
||||
// Don't touch
|
||||
import { load } from './settings.js';
|
||||
import Games from './games.js';
|
||||
import Apps from './apps.js';
|
||||
import loadEasterEggs from './eastereggs.js';
|
||||
import PolarisError from './error.js';
|
||||
import Settings from './settings.js';
|
||||
import Search from './search.js';
|
||||
import Cheats from './cheats.js';
|
||||
import Games from './games.js';
|
||||
import Frame from './frame.js';
|
||||
import PolarisError from './error.js';
|
||||
import Apps from './apps.js';
|
||||
|
||||
const Settings = {
|
||||
load: load
|
||||
};
|
||||
loadEasterEggs();
|
||||
|
||||
onbeforeunload = (e) => {
|
||||
if (localStorage.getItem('prevent_close') === 'true') {
|
||||
@@ -25,62 +23,52 @@ window.onhashchange = () => {
|
||||
else document.querySelector('.sidebar').classList.remove('active');
|
||||
};
|
||||
|
||||
if (window.self === window.top) {
|
||||
setTimeout(async () => {
|
||||
Settings.load();
|
||||
if (window.self === window.top) setTimeout(async () => {
|
||||
Settings.load();
|
||||
|
||||
if (location.pathname === '/games') Games.load();
|
||||
if (location.pathname === '/apps') Apps.load();
|
||||
if (location.pathname === '/search') Search.load();
|
||||
if (location.pathname === '/cheats') Cheats.load();
|
||||
if (location.pathname === '/view') Frame.load();
|
||||
}, 500);
|
||||
}
|
||||
if (location.pathname === '/games') Games.load();
|
||||
if (location.pathname === '/apps') Apps.load();
|
||||
if (location.pathname === '/search') Search.load();
|
||||
if (location.pathname === '/cheats') Cheats.load();
|
||||
if (location.pathname === '/view') Frame.load();
|
||||
}, 500);
|
||||
|
||||
if (location.pathname === '/') {
|
||||
fetch('/assets/JSON/games.json').then(res => res.json()).then(games => {
|
||||
const gameName = 'Tiny Fishing';
|
||||
const game = games.filter(g => g.name === gameName)[0];
|
||||
|
||||
document.querySelector('.featuredimg').addEventListener('click', () => {
|
||||
document.querySelector('.featured').addEventListener('click', () => {
|
||||
localStorage.setItem('frameData', JSON.stringify({
|
||||
type: 'game',
|
||||
game
|
||||
}));
|
||||
|
||||
|
||||
location.href = '/view';
|
||||
});
|
||||
document.querySelector('.featuredimg').src = '/assets/img/wide/tinyfishing.png';
|
||||
document.querySelector('.featured').src = '/assets/img/wide/tinyfishing.png';
|
||||
}).catch(e => new PolarisError('Failed to load featured game.'));
|
||||
|
||||
fetch('/assets/JSON/changelog.json').then(res => res.json()).then(changelog => changelog.forEach(change => {
|
||||
const date = document.createElement('p');
|
||||
date.textContent = change.date;
|
||||
date.classList = 'small';
|
||||
document.querySelector('#changelog').appendChild(date);
|
||||
|
||||
const descwrap = document.createElement('p');
|
||||
|
||||
fetch('/assets/JSON/changelog.json').then(res => res.json()).then(changelog => changelog.forEach(change => {
|
||||
const date = document.createElement('p');
|
||||
date.textContent = change.date;
|
||||
date.classList = 'small';
|
||||
document.querySelector('#changelog').appendChild(date);
|
||||
|
||||
const descwrap = document.createElement('p');
|
||||
const description = document.createElement('i');
|
||||
description.textContent = change.simpleDescription;
|
||||
description.classList = 'small';
|
||||
document.querySelector('#changelog').appendChild(description);
|
||||
}));
|
||||
/*
|
||||
var items = ['the start', 'What are you doing here?', '"School"', 'I dont get paid enough','What Up Son?','help','i like bagle','3.14159265359','Who thought this was a good idea?','Stage 4','i have a concerning lump on my back','Bean was here','Your Mother','Pacer Test','Why did he leave','by the way...','Kilroy was here','Kilroy is here','look behind you','West Virginia','theres a reason','Country road','Thats a wrap','Pretty','No','Yes','leave me','What square?','uhhh','Plutocracy','Practically Free*','capitalize this','Place Holder','Try me','fine','Why are we doing this again?','half eaten saltine crackers are underated','Javascript > Java','L + Ratio','Cope','I Love Refrigerators','That Happened.','Pedicure','(insert message here)','terminal','💀💀💀','finnish','who writes these?','reference','I am going to peel the skin off your face (:','bye','no','the fact is','run','uh what','hello world','Positively awful','tax fraud','comatose state','Not me','my second job is a discord mod','kids bop','Is it just me or','Hello people','74% Incomplete','wake up','Monster Energy','ew','The amount of pain I am in right now is unimaginable','chicken','men','What?','Your opinion is invalid','gay pride','Im going','4skin','/0','Who said that?','No Fair.','Famous... Enough','Parent Approved!','Teacher Approved!','Treason','Just do it already!','You\'re Fired','Not worth it','was there a reason?','the egg came first','patriotism','Family Friendly','Do you ever feel like a plastic bag Drifting through the windWanting to start again? Do you ever feel, feel so paper thin Like a house of cards One blow from caving in?','Why?','discord is in the first o','Shane Dawson likes cats','who stole this','unblock linux','darn you','gushers','yummy','charles loves you','mekhi loves anime', 'pls dont type smurf :)'];
|
||||
|
||||
function getRandomFact() {
|
||||
var randomIndex = Math.floor(Math.random() * items.length);
|
||||
return items[randomIndex];
|
||||
}
|
||||
|
||||
// When the page loads, set the innerHTML of elements with class 'blue' to a random item
|
||||
window.addEventListener('load', function() {
|
||||
var blue = document.getElementsByClassName('blue');
|
||||
for (var i = 0; i < blue.length; i++) {
|
||||
blue[i].innerHTML = getRandomFact();
|
||||
}
|
||||
});
|
||||
*/
|
||||
description.textContent = change.simpleDescription;
|
||||
description.classList = 'small';
|
||||
document.querySelector('#changelog').appendChild(description);
|
||||
}));
|
||||
}
|
||||
const Polaris = { Settings, Games, Apps, Frame, PolarisError };
|
||||
export default Polaris;
|
||||
|
||||
if (window.scrollY !== 0) document.querySelector('.navbar').classList.add('scrolling');
|
||||
else document.querySelector('.navbar').classList.remove('scrolling');
|
||||
|
||||
window.onscroll = () => {
|
||||
if (window.scrollY !== 0) document.querySelector('.navbar').classList.add('scrolling');
|
||||
else document.querySelector('.navbar').classList.remove('scrolling');
|
||||
}
|
||||
|
||||
export default { Settings, Games, Apps, Frame, PolarisError };
|
||||
@@ -0,0 +1,131 @@
|
||||
import PolarisError from '/assets/js/error.js';
|
||||
import { loadProxyWorker } from '/assets/js/utils.js';
|
||||
|
||||
const tiltEffectSettings = {
|
||||
max: 8,
|
||||
perspective: 1000,
|
||||
scale: 1.05,
|
||||
speed: 800,
|
||||
easing: 'cubic-bezier(.03,.98,.52,.99)'
|
||||
};
|
||||
|
||||
let games = [];
|
||||
let filteredGames = [];
|
||||
|
||||
const load = () => {
|
||||
fetch('/assets/JSON/games.json').then(res => res.json()).then(data => {
|
||||
games = data;
|
||||
filteredGames = games;
|
||||
|
||||
renderGames(filteredGames);
|
||||
|
||||
const searchInput = document.getElementById('searchInput');
|
||||
searchInput.addEventListener('input', filterGames);
|
||||
})
|
||||
.catch(e => new PolarisError('Failed to load games'));
|
||||
};
|
||||
|
||||
function filterGames() {
|
||||
const searchInput = document.getElementById('searchInput');
|
||||
const searchTerm = searchInput.value.toLowerCase();
|
||||
|
||||
filteredGames = games.filter(game => game.name.toLowerCase().includes(searchTerm));
|
||||
|
||||
renderGames(filteredGames);
|
||||
}
|
||||
|
||||
function renderGames(gamesToRender) {
|
||||
const gamesContainer = document.querySelector('.games');
|
||||
const popularGamesContainer = document.querySelector('.popular-games');
|
||||
gamesContainer.innerHTML = '';
|
||||
popularGamesContainer.innerHTML = '';
|
||||
|
||||
gamesToRender.forEach(game => {
|
||||
const el = document.createElement('div');
|
||||
el.classList = 'game';
|
||||
el.innerHTML = `<img loading='lazy' src='${game.image}'><h3>${game.name}</h3>`;
|
||||
gamesContainer.appendChild(el);
|
||||
|
||||
if (game.popular === 'yes') {
|
||||
const popularEl = document.createElement('div');
|
||||
popularEl.classList = 'game';
|
||||
popularEl.innerHTML = `<img loading='lazy' src='${game.image}'><h3>${game.name}</h3>`;
|
||||
popularGamesContainer.appendChild(popularEl);
|
||||
|
||||
popularEl.addEventListener('click', async () => {
|
||||
await loadProxyWorker('uv');
|
||||
|
||||
if (game.openinnewtab === 'yes') window.open(game.source);
|
||||
else {
|
||||
localStorage.setItem('frameData', JSON.stringify({
|
||||
type: 'game',
|
||||
game
|
||||
}));
|
||||
location.href = '/view';
|
||||
}
|
||||
});
|
||||
|
||||
popularEl.addEventListener('mouseenter', gameMouseEnter);
|
||||
popularEl.addEventListener('mousemove', gameMouseMove);
|
||||
popularEl.addEventListener('mouseleave', gameMouseLeave);
|
||||
}
|
||||
|
||||
el.addEventListener('click', async () => {
|
||||
await loadProxyWorker();
|
||||
|
||||
const frameData = {
|
||||
type: 'game',
|
||||
game
|
||||
};
|
||||
|
||||
if (game.openinnewtab === 'yes') {
|
||||
window.open(game.source, '_blank');
|
||||
console.log('Open game in new tab:', frameData);
|
||||
} else {
|
||||
localStorage.setItem('frameData', JSON.stringify(frameData));
|
||||
location.href = '/view';
|
||||
}
|
||||
});
|
||||
|
||||
el.addEventListener('mouseenter', gameMouseEnter);
|
||||
el.addEventListener('mousemove', gameMouseMove);
|
||||
el.addEventListener('mouseleave', gameMouseLeave);
|
||||
});
|
||||
}
|
||||
|
||||
function gameMouseEnter(event) {
|
||||
setTransition(event);
|
||||
}
|
||||
|
||||
function gameMouseMove(event) {
|
||||
const game = event.currentTarget;
|
||||
const gameWidth = game.offsetWidth;
|
||||
const gameHeight = game.offsetHeight;
|
||||
const centerX = game.offsetLeft + gameWidth / 2;
|
||||
const centerY = game.offsetTop + gameHeight / 2;
|
||||
const mouseX = event.clientX - centerX;
|
||||
const mouseY = event.clientY - centerY;
|
||||
const rotateXUncapped = (+1) * tiltEffectSettings.max * mouseY / (gameHeight / 2);
|
||||
const rotateYUncapped = (-1) * tiltEffectSettings.max * mouseX / (gameWidth / 2);
|
||||
const rotateX = rotateXUncapped < -tiltEffectSettings.max ? -tiltEffectSettings.max : (rotateXUncapped > tiltEffectSettings.max ? tiltEffectSettings.max : rotateXUncapped);
|
||||
const rotateY = rotateYUncapped < -tiltEffectSettings.max ? -tiltEffectSettings.max : (rotateYUncapped > tiltEffectSettings.max ? tiltEffectSettings.max : rotateYUncapped);
|
||||
|
||||
game.style.transform = `perspective(${tiltEffectSettings.perspective}px) rotateX(${rotateX}deg) rotateY(${rotateY}deg) scale3d(${tiltEffectSettings.scale}, ${tiltEffectSettings.scale}, ${tiltEffectSettings.scale})`;
|
||||
}
|
||||
|
||||
function gameMouseLeave(event) {
|
||||
event.currentTarget.style.transform = `perspective(${tiltEffectSettings.perspective}px) rotateX(0deg) rotateY(0deg) scale3d(1, 1, 1)`;
|
||||
setTransition(event);
|
||||
}
|
||||
|
||||
function setTransition(event) {
|
||||
const game = event.currentTarget;
|
||||
clearTimeout(game.transitionTimeoutId);
|
||||
game.style.transition = `transform ${tiltEffectSettings.speed}ms ${tiltEffectSettings.easing}`;
|
||||
|
||||
game.transitionTimeoutId = setTimeout(() => game.style.transition = '', tiltEffectSettings.speed);
|
||||
}
|
||||
|
||||
export default {
|
||||
load
|
||||
};
|
||||
@@ -1,5 +1,5 @@
|
||||
import PolarisError from '/assets/js/error.js';
|
||||
import { loadWorker } from '/assets/js/wpm.js';
|
||||
import PolarisError from './error.js';
|
||||
import { loadProxyWorker } from './utils.js';
|
||||
|
||||
const load = () => {
|
||||
const xor = {
|
||||
@@ -20,7 +20,7 @@ const load = () => {
|
||||
e.preventDefault();
|
||||
|
||||
if (typeof navigator.serviceWorker === 'undefined') new PolarisError('Failed to load Proxy');
|
||||
await loadWorker('uv');
|
||||
await loadProxyWorker('uv');
|
||||
|
||||
const url = /^(http(s)?:\/\/)?([\w-]+\.)+[\w]{2,}(\/.*)?$/.test(query.value) ? ((!query.value.startsWith('http://') && !query.value.startsWith('https://')) ? 'https://' + query.value : query.value) : 'https://www.google.com/search?q=' + encodeURIComponent(query.value);
|
||||
|
||||
@@ -28,6 +28,7 @@ const load = () => {
|
||||
type: 'proxy',
|
||||
source: `/uv/service/${xor.encode(url)}`
|
||||
}));
|
||||
|
||||
location.href = '/view';
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import PolarisError from '/assets/js/error.js';
|
||||
import Theme from '/assets/js/themes.js';
|
||||
import PolarisError from './error.js';
|
||||
import Theme from './themes.js';
|
||||
|
||||
const isScrollable = (element) => element.scrollWidth > element.clientWidth || element.scrollHeight > element.clientHeight;
|
||||
|
||||
@@ -205,4 +205,4 @@ const load = () => {
|
||||
new Settings();
|
||||
};
|
||||
|
||||
export { load, Settings };
|
||||
export default { load, Settings };
|
||||
|
||||
+19
-32
@@ -1,47 +1,34 @@
|
||||
const set = (name, value) => {
|
||||
if (!localStorage.getItem('settings')) {
|
||||
localStorage.setItem('settings', JSON.stringify({}));
|
||||
} else {
|
||||
try {
|
||||
JSON.parse(localStorage.getItem('settings'));
|
||||
} catch (e) {
|
||||
localStorage.setItem('settings', JSON.stringify({}));
|
||||
}
|
||||
}
|
||||
import { storage } from './utils.js';
|
||||
|
||||
const settings = JSON.parse(localStorage.getItem('settings'));
|
||||
settings[name] = value;
|
||||
localStorage.setItem('settings', JSON.stringify(settings));
|
||||
};
|
||||
|
||||
const get = (name) => {
|
||||
if (!localStorage.getItem('settings')) {
|
||||
localStorage.setItem('settings', JSON.stringify({}));
|
||||
} else {
|
||||
try {
|
||||
JSON.parse(localStorage.getItem('settings'));
|
||||
} catch (e) {
|
||||
localStorage.setItem('settings', JSON.stringify({}));
|
||||
}
|
||||
}
|
||||
|
||||
const settings = JSON.parse(localStorage.getItem('settings'));
|
||||
return settings[name];
|
||||
}
|
||||
const settingsStorage = storage('settings');
|
||||
|
||||
class Theme {
|
||||
constructor() {
|
||||
this.theme = get('theme');
|
||||
this.theme = settingsStorage.get('theme');
|
||||
|
||||
if (this.theme) this.set(this.theme);
|
||||
else this.set('system-default');
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the theme of the page
|
||||
* @param {string} theme The name of the theme
|
||||
* @param {boolean} save Whether or not the theme should be saved
|
||||
*/
|
||||
set = (theme, save) => {
|
||||
document.body.setAttribute('data-theme', theme);
|
||||
this.theme = theme;
|
||||
|
||||
if (save !== false) set('theme', theme);
|
||||
}
|
||||
if (save !== false) settingsStorage.set('theme', theme);
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the current theme
|
||||
* @returns {string}
|
||||
*/
|
||||
get = () => {
|
||||
return document.body.getAttribute('data-theme');
|
||||
};
|
||||
}
|
||||
|
||||
export default new Theme();
|
||||
@@ -0,0 +1,72 @@
|
||||
/**
|
||||
* The storage interface for polaris
|
||||
* @param {string} containerName
|
||||
*/
|
||||
const storage = (containerName) => {
|
||||
return {
|
||||
/**
|
||||
* Get a value from the storage container
|
||||
* @param {string} name The name of the value
|
||||
* @returns {string}
|
||||
*/
|
||||
get: (name) => {
|
||||
if (!localStorage.getItem(containerName)) localStorage.setItem(containerName, JSON.stringify({}));
|
||||
else {
|
||||
try {
|
||||
JSON.parse(localStorage.getItem(containerName));
|
||||
} catch (e) {
|
||||
localStorage.setItem(containerName, JSON.stringify({}));
|
||||
}
|
||||
}
|
||||
|
||||
const container = JSON.parse(localStorage.getItem(containerName));
|
||||
return container[name];
|
||||
},
|
||||
/**
|
||||
* Set a value from a storage container
|
||||
* @param {string} name The name of the value
|
||||
* @param {string | object} value The value to be set
|
||||
*/
|
||||
set: (name, value) => {
|
||||
if (!localStorage.getItem(containerName)) localStorage.setItem(containerName, JSON.stringify({}));
|
||||
else {
|
||||
try {
|
||||
JSON.parse(localStorage.getItem(containerName));
|
||||
} catch (e) {
|
||||
localStorage.setItem(containerName, JSON.stringify({}));
|
||||
}
|
||||
}
|
||||
|
||||
const container = JSON.parse(localStorage.getItem(containerName));
|
||||
container[name] = value;
|
||||
|
||||
localStorage.setItem(containerName, JSON.stringify(container));
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Register a proxy service worker
|
||||
* @param {'uv' | 'dynamic'} proxy
|
||||
*/
|
||||
const loadProxyWorker = async (proxy) => await navigator.serviceWorker.register(`/${proxy}/sw.js`, {
|
||||
scope: `/${proxy}/service/`
|
||||
});
|
||||
|
||||
/**
|
||||
* Load the page javascript
|
||||
*/
|
||||
const loadPageScript = () => {
|
||||
if (location.href) {
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
export default {
|
||||
storage,
|
||||
loadProxyWorker
|
||||
};
|
||||
export {
|
||||
storage,
|
||||
loadProxyWorker
|
||||
};
|
||||
@@ -1,7 +0,0 @@
|
||||
const loadWorker = async (proxy) => await navigator.serviceWorker.register(`/${proxy}/sw.js`, {
|
||||
scope: `/${proxy}/service/`,
|
||||
});
|
||||
|
||||
export {
|
||||
loadWorker
|
||||
};
|
||||
+3
-1
@@ -11,7 +11,7 @@
|
||||
<title>Cheats | Polaris</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<body class="centered">
|
||||
<!--el:navbar-->
|
||||
<!--el:sidebar-->
|
||||
|
||||
@@ -21,6 +21,8 @@
|
||||
<div class="games"></div>
|
||||
</div>
|
||||
|
||||
<!--el:footer-->
|
||||
|
||||
<script src="/assets/js/main.js" type="module"></script>
|
||||
|
||||
<!--el:{{mode === 'dev'}}:development-->
|
||||
|
||||
+2
-10
@@ -15,7 +15,7 @@
|
||||
<!--el:navbar-->
|
||||
<!--el:sidebar-->
|
||||
|
||||
<div class="content">
|
||||
<div class="content centered">
|
||||
<h1 style="font-size: 6vh;">Games</h1>
|
||||
|
||||
<h1 style="font-size: 4vh;" class="gamesectionheader">Popular</h1>
|
||||
@@ -27,18 +27,10 @@
|
||||
|
||||
<input type="text" id="searchInput" class="settings-input" placeholder="Search Games...">
|
||||
|
||||
<br>
|
||||
|
||||
<div style="padding-bottom: 2vh;">
|
||||
</div>
|
||||
|
||||
<br>
|
||||
|
||||
<div class="games">
|
||||
</div>
|
||||
|
||||
<div style="margin-top: 5vh;">
|
||||
</div>
|
||||
<!--el:footer-->
|
||||
|
||||
<script src="/assets/js/main.js" type="module"></script>
|
||||
|
||||
|
||||
@@ -36,6 +36,7 @@
|
||||
-
|
||||
<a href="/tos" class="link">Terms of Service</a>
|
||||
</p>
|
||||
|
||||
<hr>
|
||||
|
||||
<h2>Changelog</h2>
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
<script async src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?client=ca-pub-9661054437111080" crossorigin="anonymous"></script>
|
||||
<script data-cfasync="false">window.nitroAds = window.nitroAds || { createAd: function () { return new Promise(e => { window.nitroAds.queue.push(["createAd", arguments, e]) }) }, addUserToken: function () { window.nitroAds.queue.push(["addUserToken", arguments]) }, queue: [] };</script>
|
||||
<script data-cfasync="false" async src="https://s.nitropay.com/ads-1751.js"></script>
|
||||
<script data-cfasync="false" async src="https://s.nitropay.com/ads-1751.js"></script>
|
||||
<script data-cfasync="false">window.nitroAds = window.nitroAds || { createAd: function () { return new Promise(e => { window.nitroAds.queue.push(["createAd", arguments, e]) }) }, addUserToken: function () { window.nitroAds.queue.push(["addUserToken", arguments]) }, queue: [] };</script>
|
||||
@@ -0,0 +1,31 @@
|
||||
<footer>
|
||||
<div class="title">
|
||||
<h1>Polaris</h1>
|
||||
|
||||
<img src="/assets/img/logo.png" title="Polaris Logo">
|
||||
</div>
|
||||
|
||||
<div class="socials">
|
||||
<a href="https://discord.gg/RXBbxQ4wuJ" target="_blank" rel="noopener noreferrer" title="EmberNetwork Discord">
|
||||
<i class="fa-brands fa-discord"></i>
|
||||
</a>
|
||||
|
||||
<a href="https://github.com/Skoolgq/Polaris" target="_blank" rel="noopener noreferrer" title="Our Github">
|
||||
<i class="fa-brands fa-github"></i>
|
||||
</a>
|
||||
|
||||
<a href="mailto:support@polarislearning.org" target="_blank" title="Contact Email">
|
||||
<i class="fa-solid fa-envelope"></i>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="right">
|
||||
<a href="https://forms.gle/9knPLmyAua5Z3wZv5">Suggest a Game</a>
|
||||
|
||||
<a href="/privacy">Privace Policy</a>
|
||||
|
||||
<a href="/TOS">Terms of Service</a>
|
||||
</div>
|
||||
|
||||
<p>©2022-2023 Skool. All rights reserved.</p>
|
||||
</footer>
|
||||
Reference in New Issue
Block a user