├── .devcontainer
├── Dockerfile
└── devcontainer.json
├── .gitignore
├── .gitmodules
├── .prettierignore
├── LICENSE
├── README.md
├── broken-sites.json
├── config.js
├── example.env
├── index.js
├── package-lock.json
├── package.json
├── public
├── 404.html
├── ai.html
├── assets
│ ├── imgs
│ │ ├── backgrounds
│ │ │ └── home
│ │ │ │ ├── blue.jpg
│ │ │ │ ├── green.jpg
│ │ │ │ ├── indigo.png
│ │ │ │ ├── operagx.jpg
│ │ │ │ ├── purple.jpg
│ │ │ │ └── red.jpg
│ │ ├── icons
│ │ │ ├── Plus_symbol.svg.webp
│ │ │ ├── billigerhost.ico
│ │ │ ├── cloaks
│ │ │ │ ├── Calendar.ico
│ │ │ │ ├── Canvas.ico
│ │ │ │ ├── Classroom.png
│ │ │ │ ├── Gmail.ico
│ │ │ │ ├── Google Drive.ico
│ │ │ │ ├── Google Search.ico
│ │ │ │ ├── Khan Academy.ico
│ │ │ │ ├── Meet.ico
│ │ │ │ ├── YouTube.ico
│ │ │ │ └── Zoom.ico
│ │ │ ├── default-extension.png
│ │ │ ├── loading.gif
│ │ │ ├── logo.png
│ │ │ ├── pages
│ │ │ │ ├── ai.png
│ │ │ │ ├── credits.png
│ │ │ │ ├── extensions.png
│ │ │ │ ├── games.png
│ │ │ │ ├── history.png
│ │ │ │ ├── home.png
│ │ │ │ ├── new.png
│ │ │ │ └── settings.png
│ │ │ └── plain_logo.webp
│ │ └── users
│ │ │ ├── blanky.jpg
│ │ │ ├── ghostly.jpg
│ │ │ ├── nc.png
│ │ │ ├── peak.png
│ │ │ ├── vendfr.webp
│ │ │ └── zxyp.webp
│ └── js
│ │ ├── ai.js
│ │ ├── autoblank.js
│ │ ├── bookmarks.js
│ │ ├── context.js
│ │ ├── contextmenu.html
│ │ ├── debugger.js
│ │ ├── devtools.js
│ │ ├── eruda.js
│ │ ├── extension-downloader.js
│ │ ├── extensions-loader.js
│ │ ├── extensions.js
│ │ ├── history.js
│ │ ├── history_helper.js
│ │ ├── history_helper_sw.js
│ │ ├── keybinds.js
│ │ ├── main.js
│ │ ├── notifications.js
│ │ ├── options.js
│ │ ├── panic.js
│ │ ├── search.js
│ │ ├── settings.js
│ │ ├── settings_manager.js
│ │ ├── settings_manager_sw.js
│ │ ├── tabs.js
│ │ ├── themes.js
│ │ ├── versioncheck.js
│ │ ├── wispchecker.js
│ │ └── wispclient.js
├── books
│ ├── games.json
│ ├── index.html
│ └── script.js
├── chat
│ └── index.html
├── credits.html
├── css
│ ├── 404.css
│ ├── ai.css
│ ├── books.css
│ ├── context.css
│ ├── credits.css
│ ├── history.css
│ ├── home.css
│ ├── index.css
│ ├── login.css
│ ├── main.css
│ ├── overlay.css
│ ├── settings.css
│ ├── subscriptions.css
│ └── themes.css
├── extensions
│ ├── discovery.html
│ ├── index.html
│ ├── manage
│ │ ├── index.html
│ │ ├── script.js
│ │ └── style.css
│ ├── script.js
│ └── style.css
├── favicon.ico
├── history.html
├── home
│ ├── index.html
│ ├── quotes.json
│ └── script.js
├── index.html
├── legacy_uv
│ └── uv.config.js
├── login.html
├── new.html
├── pages
│ ├── ai.html
│ ├── books.html
│ ├── chat.html
│ ├── credits.html
│ ├── extensions.html
│ ├── extensions
│ │ └── manage.html
│ ├── games.html
│ ├── history.html
│ ├── home.html
│ ├── login.html
│ ├── new.html
│ ├── privacy.html
│ └── settings.html
├── privacy.html
├── settings
│ ├── index.html
│ ├── script.js
│ └── themes.json
├── subscriptions.html
├── sw.js
└── uv
│ └── uv.config.js
├── render.yaml
├── useragents.js
└── vercel.json
/.devcontainer/Dockerfile:
--------------------------------------------------------------------------------
1 | FROM node:18
2 |
3 | # Install basic development tools
4 | RUN apt update
5 |
6 | # Set `DEVCONTAINER` environment variable to help with orientation
7 | ENV DEVCONTAINER=true
8 |
--------------------------------------------------------------------------------
/.devcontainer/devcontainer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Shadow",
3 | "build": {
4 | "dockerfile": "Dockerfile"
5 | },
6 | "remoteUser": "node",
7 | "features": {
8 | "ghcr.io/devcontainers-contrib/features/node-asdf:0": {}
9 | },
10 | "postCreateCommand": "npm start"
11 | }
12 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | #goofy
2 | node_modules
3 | .env
--------------------------------------------------------------------------------
/.gitmodules:
--------------------------------------------------------------------------------
1 | [submodule "public/books/files"]
2 | path = public/books/files
3 | url = https://github.com/Nailington/3kh0-assets/js.git
4 |
--------------------------------------------------------------------------------
/.prettierignore:
--------------------------------------------------------------------------------
1 | node_modules/
2 | .devcontainer/
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 | # Shadow Browser V3
3 | **ShadowV3** is a fast and feature-rich tab proxy designed with performance and user experience at its core. Finding proxies with smooth, functional tabs can be challenging, but we make it effortless. **THE BEST TABBED PROXY.**
4 |
5 | ---
6 |
7 | # Changes
8 | - 🔄 Added Proxy Switcher with Dynamic (Scramjet Coming Soon)
9 | - 📈 Update UV to v3, uses eproxy/baremux.
10 | - 🔧 Improved tabs.js, more optimized, better handling.
11 | - 💻 Rewrote tabs.js code from scratch for optimizations.
12 | - 🌐 Favicon Updates when a new URL is pressed.
13 | - ⏳ Neat Loading Screen
14 | - 🚀 Improved Load Times. Overall Site & Proxy
15 | - 🎨 New Look. The user interface has been completely redone
16 | - 🗂️ Shortcuts on New Tab Page
17 | - 📚 Overhauled on Themes, Bookmarks, and all scripts.
18 | - 🌈 Added tons of new & working themes.
19 | - 🎮 Games have been added. The games are hosted files, not proxied.
20 | - 📋 Easy & Accessible Menu to access pages
21 | - 🔗 Added direct page access. Ex: shadow://settings shadow://new
22 | - 🔄 WispURL Change
23 |
24 | ## Deployment
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 | # Credits
39 | - [NC](https://github.com/NCCoder2) |Developer
40 | - [NotPeak](https://github.com/NottPeak)|Developer
41 | - [Ultraviolet](https://github.com/titaniumnetwork-dev/Ultraviolet)|Proxy
42 | - [Dynamic](https://github.com/NebulaServices/Dynamic)|Proxy
43 |
44 | # [Join the Discord](https://discord.gg/goshadow)
45 |
46 |
--------------------------------------------------------------------------------
/broken-sites.json:
--------------------------------------------------------------------------------
1 | {
2 | "https://now.gg": "https://www.easyfun.gg/cloud-games/roblox.html"
3 | }
4 |
--------------------------------------------------------------------------------
/config.js:
--------------------------------------------------------------------------------
1 | import fs from "node:fs";
2 | const users = {
3 | // 'john': "password" //Add user pass login
4 | }
5 |
6 | const port = 8080; //Change the port it binds to
7 |
8 | //Edit broken-sites.json to prompt the user to redirect to a fixed version of a site (ex. now.gg --> nowgg.nl)
9 | const brokenSites = async () => {
10 | const sites = JSON.parse(fs.readFileSync('./broken-sites.json', 'utf8'));
11 | sites.lastUpdate = Date.now();
12 | return sites;
13 | };
14 |
15 | export { users, port, brokenSites };
--------------------------------------------------------------------------------
/example.env:
--------------------------------------------------------------------------------
1 | SHUTTLEAI_API_KEY="" # OPTIONAL, but required for AI to function.
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | import express from "express";
2 | import basicAuth from "express-basic-auth"
3 | import wisp from "wisp-server-node";
4 | import http from "http";
5 | import cookieParser from 'cookie-parser';
6 | import * as cheerio from "cheerio";
7 | import { doubleCsrf } from "csrf-csrf";
8 | import { createServer } from "http";
9 | import { fileURLToPath } from "url";
10 | import { epoxyPath } from "@mercuryworkshop/epoxy-transport";
11 | import { libcurlPath } from "@mercuryworkshop/libcurl-transport";
12 | import { baremuxPath } from "@mercuryworkshop/bare-mux/node";
13 | import { uvPath } from "@titaniumnetwork-dev/ultraviolet";
14 | import { join } from "path";
15 | import { users, port, brokenSites } from "./config.js";
16 | import dotenv from 'dotenv';
17 | dotenv.config();
18 |
19 | const version = process.env.npm_package_version;
20 | const publicPath = fileURLToPath(new URL("./public/", import.meta.url));
21 | const app = express();
22 | const server = createServer();
23 | if (Object.keys(users).length > 0) app.use(basicAuth({ users, challenge: true }));
24 | app.use(express.static(publicPath, { maxAge: 604800000 })); //1 week
25 | app.use('/books/files/', (req, res) => {
26 | const sourceUrl = `http://phantom.lol/books/files${req.url}`;
27 | http.get(sourceUrl, (sourceResponse) => {
28 | res.writeHead(sourceResponse.statusCode, sourceResponse.headers);
29 | sourceResponse.pipe(res);
30 | }).on('error', (err) => {
31 | res.statusCode = 500;
32 | res.end(`Error fetching file: ${err.message}`);
33 | });
34 | });
35 | app.use("/epoxy/", express.static(epoxyPath));
36 | app.use("/libcurl/", express.static(libcurlPath));
37 | app.use("/baremux/", express.static(baremuxPath));
38 | app.use("/uv/", express.static(uvPath));
39 | app.use("/privacy", express.static(publicPath + "/privacy.html"));
40 |
41 | app.get("/v1/api/version", (req, res) => {
42 | if (req.query.v && req.query.v != version) {
43 | res.status(400).send(version);
44 | return;
45 | }
46 | res.status(200).send(version);
47 | });
48 |
49 | app.get("/v1/api/broken-sites", async (req, res) => {
50 | res.status(200).send(await brokenSites());
51 | })
52 |
53 | app.get("/v1/api/search-suggestions", async (req, res) => {
54 | let response;
55 | let results = [];
56 | const query = req.query.query;
57 | switch (req.headers.engine ?? "google") {
58 | case "duckduckgo":
59 | response = await fetch(
60 | `http://api.duckduckgo.com/ac?q=${query}&format=json`
61 | ).then((i) => i.json());
62 | results = response.map(result => result.phrase);
63 | break;
64 |
65 | case "google":
66 | response = await fetch(
67 | `http://suggestqueries.google.com/complete/search?client=firefox&q=${query}`
68 | ).then((i) => i.json());
69 | results = response[1];
70 | break;
71 |
72 | case "yandex":
73 | response = await fetch(
74 | `https://suggest.yandex.com/suggest?part=${query}`
75 | ).then((i) => i.json());
76 | results = response[1].map(suggestion => suggestion);
77 | break;
78 |
79 | default:
80 | res.status(400).send('How?');
81 | return;
82 | }
83 |
84 | res.send(results);
85 | });
86 |
87 |
88 | // AI STUFF
89 |
90 | app.use(cookieParser());
91 | app.use(express.json());
92 |
93 | // Create the CSRF protector with secure options
94 | const { generateToken, validateRequest } = doubleCsrf({
95 | getSecret: () => "your-secret-key",
96 | cookieName: "x-csrf-token",
97 | cookieOptions: {
98 | httpOnly: true,
99 | secure: process.env.NODE_ENV === 'production',
100 | sameSite: "strict"
101 | },
102 | size: 64,
103 | getTokenFromRequest: (req) => req.headers["x-csrf-token"]
104 | });
105 |
106 | // Middleware to protect routes
107 | const csrfProtection = (req, res, next) => {
108 | try {
109 | validateRequest(req, res);
110 | next();
111 | } catch (error) {
112 | res.status(403).json({
113 | error: "Invalid CSRF token",
114 | message: error.message
115 | });
116 | }
117 | };
118 |
119 | // Route to get CSRF token
120 | app.get('/csrf-token', (req, res) => {
121 | res.json({ token: generateToken(req, res) });
122 | });
123 |
124 | // Protected route example
125 | app.post('/ask', csrfProtection, async (req, res) => {
126 | const { messages } = req.body;
127 | const temperature = req.body.temperature || 0.7;
128 | const max_tokens = req.body.max_tokens || 512;
129 |
130 | if (!messages || !Array.isArray(messages)) {
131 | return res.status(400).json({ error: 'msgs need to be in an array format.' });
132 | }
133 |
134 | try {
135 | const response = await fetch('https://api.shuttleai.com/v1/chat/completions', {
136 | method: 'POST',
137 | headers: {
138 | 'Content-Type': 'application/json',
139 | 'Authorization': `Bearer ${process.env.SHUTTLEAI_API_KEY}`
140 | },
141 | body: JSON.stringify({
142 | model: 'shuttle-3.5',
143 | messages: messages,
144 | temperature: temperature,
145 | max_tokens: max_tokens
146 | })
147 | });
148 |
149 | const data = await response.json();
150 | res.json(data.choices[0].message.content);
151 | } catch (error) {
152 | console.error(error);
153 | res.status(500).json({ error: 'Failed to Retrieve Request' });
154 | }
155 | });
156 |
157 | app.get("/v1/api/user-agents", async (req, res) => {
158 | let text = await fetch("https://useragents.me/");
159 | text = await text.text();
160 | const $ = cheerio.load(text);
161 | res.send(
162 | $("#most-common-desktop-useragents-json-csv > div:eq(0) > textarea").val(),
163 | );
164 | });
165 |
166 | app.use((req, res) => {
167 | res.status(404);
168 | res.sendFile(join(publicPath, "404.html"));
169 | });
170 |
171 | server.on("request", (req, res) => {
172 | res.setHeader("Cross-Origin-Opener-Policy", "same-origin");
173 | app(req, res);
174 | });
175 |
176 | server.on("upgrade", (req, socket, head) => {
177 | if (req.url.endsWith("/wisp/"))
178 | wisp.routeRequest(req, socket, head);
179 | else socket.end();
180 | });
181 |
182 | server.on("listening", () => {
183 | const address = server.address();
184 | console.log(
185 | "\n\n\n\x1b[35m\x1b[2m\x1b[1m%s\x1b[0m\n",
186 | `Shadow ${version} has started!\nSprinting on port ${address.port}`,
187 | );
188 |
189 | setTimeout(function () {
190 | console.log("\n");
191 | }, 750);
192 | setTimeout(function () {
193 | console.log("\n");
194 | }, 1000);
195 | setTimeout(function () {
196 | console.log(`
197 | ┌────────────┬─────────────┬────────────┐
198 | │ Wisp │ Site │ API's │
199 | ├────────────┼─────────────┼────────────┤
200 | │ \x1b[32mrunning \x1b[0m │ \x1b[32mrunning \x1b[0m │ \x1b[32mrunning \x1b[0m│
201 | └────────────┴─────────────┴────────────┘
202 | `);
203 | }, 1500);
204 | });
205 |
206 | server.listen(process.argv[2] || port);
207 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "shadow",
3 | "version": "3.2.1",
4 | "description": "Shadow proxy",
5 | "type": "module",
6 | "engines": {
7 | "npm": ">=7.0.0",
8 | "node": ">=16.0.0"
9 | },
10 | "scripts": {
11 | "start": "node index.js",
12 | "dev": "node index.js 9090",
13 | "shadow": "node index.js",
14 | "pretty": "prettier --write \"./**/*.{js,jsx,mjs,cjs,ts,tsx,json,html,css}\""
15 | },
16 | "keywords": [
17 | "proxy"
18 | ],
19 | "author": "",
20 | "license": "GPL-3.0-only",
21 | "dependencies": {
22 | "@mercuryworkshop/bare-mux": "^2.1.7",
23 | "@mercuryworkshop/epoxy-tls": "^2.1.17-1",
24 | "@mercuryworkshop/epoxy-transport": "^2.1.27",
25 | "@mercuryworkshop/libcurl-transport": "^1.4.0",
26 | "@titaniumnetwork-dev/ultraviolet": "^3.2.10",
27 | "cheerio": "^1.0.0-rc.12",
28 | "compression": "^1.7.4",
29 | "cookie-parser": "^1.4.7",
30 | "csrf-csrf": "^3.1.0",
31 | "dotenv": "^16.4.7",
32 | "express": "^5.0.0",
33 | "express-basic-auth": "^1.2.1",
34 | "http": "^0.0.1-security",
35 | "jsdom": "^24.0.0",
36 | "terser": "^5.31.6",
37 | "wisp-server-node": "^1.0.4",
38 | "ws": "^8.16.0"
39 | },
40 | "devDependencies": {
41 | "prettier": "^3.3.3"
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/public/404.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | 404
8 |
9 |
10 |
14 |
18 |
19 |
20 |
21 | 404
22 |
23 | An error has occured.
24 | If this error persits, seek help at
25 | discord.gg/goshadow
26 |
27 | Go Back
28 |
29 |
38 |
39 |
--------------------------------------------------------------------------------
/public/ai.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | ShadowAssistant
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
20 |
21 |
22 |
32 |
33 |
34 |
35 |
36 |
37 |
--------------------------------------------------------------------------------
/public/assets/imgs/backgrounds/home/blue.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ShadowDevLabs/ShadowV3/b0877288e596419f09eecf31ed0d638b2563e64c/public/assets/imgs/backgrounds/home/blue.jpg
--------------------------------------------------------------------------------
/public/assets/imgs/backgrounds/home/green.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ShadowDevLabs/ShadowV3/b0877288e596419f09eecf31ed0d638b2563e64c/public/assets/imgs/backgrounds/home/green.jpg
--------------------------------------------------------------------------------
/public/assets/imgs/backgrounds/home/indigo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ShadowDevLabs/ShadowV3/b0877288e596419f09eecf31ed0d638b2563e64c/public/assets/imgs/backgrounds/home/indigo.png
--------------------------------------------------------------------------------
/public/assets/imgs/backgrounds/home/operagx.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ShadowDevLabs/ShadowV3/b0877288e596419f09eecf31ed0d638b2563e64c/public/assets/imgs/backgrounds/home/operagx.jpg
--------------------------------------------------------------------------------
/public/assets/imgs/backgrounds/home/purple.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ShadowDevLabs/ShadowV3/b0877288e596419f09eecf31ed0d638b2563e64c/public/assets/imgs/backgrounds/home/purple.jpg
--------------------------------------------------------------------------------
/public/assets/imgs/backgrounds/home/red.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ShadowDevLabs/ShadowV3/b0877288e596419f09eecf31ed0d638b2563e64c/public/assets/imgs/backgrounds/home/red.jpg
--------------------------------------------------------------------------------
/public/assets/imgs/icons/Plus_symbol.svg.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ShadowDevLabs/ShadowV3/b0877288e596419f09eecf31ed0d638b2563e64c/public/assets/imgs/icons/Plus_symbol.svg.webp
--------------------------------------------------------------------------------
/public/assets/imgs/icons/billigerhost.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ShadowDevLabs/ShadowV3/b0877288e596419f09eecf31ed0d638b2563e64c/public/assets/imgs/icons/billigerhost.ico
--------------------------------------------------------------------------------
/public/assets/imgs/icons/cloaks/Calendar.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ShadowDevLabs/ShadowV3/b0877288e596419f09eecf31ed0d638b2563e64c/public/assets/imgs/icons/cloaks/Calendar.ico
--------------------------------------------------------------------------------
/public/assets/imgs/icons/cloaks/Canvas.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ShadowDevLabs/ShadowV3/b0877288e596419f09eecf31ed0d638b2563e64c/public/assets/imgs/icons/cloaks/Canvas.ico
--------------------------------------------------------------------------------
/public/assets/imgs/icons/cloaks/Classroom.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ShadowDevLabs/ShadowV3/b0877288e596419f09eecf31ed0d638b2563e64c/public/assets/imgs/icons/cloaks/Classroom.png
--------------------------------------------------------------------------------
/public/assets/imgs/icons/cloaks/Gmail.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ShadowDevLabs/ShadowV3/b0877288e596419f09eecf31ed0d638b2563e64c/public/assets/imgs/icons/cloaks/Gmail.ico
--------------------------------------------------------------------------------
/public/assets/imgs/icons/cloaks/Google Drive.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ShadowDevLabs/ShadowV3/b0877288e596419f09eecf31ed0d638b2563e64c/public/assets/imgs/icons/cloaks/Google Drive.ico
--------------------------------------------------------------------------------
/public/assets/imgs/icons/cloaks/Google Search.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ShadowDevLabs/ShadowV3/b0877288e596419f09eecf31ed0d638b2563e64c/public/assets/imgs/icons/cloaks/Google Search.ico
--------------------------------------------------------------------------------
/public/assets/imgs/icons/cloaks/Khan Academy.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ShadowDevLabs/ShadowV3/b0877288e596419f09eecf31ed0d638b2563e64c/public/assets/imgs/icons/cloaks/Khan Academy.ico
--------------------------------------------------------------------------------
/public/assets/imgs/icons/cloaks/Meet.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ShadowDevLabs/ShadowV3/b0877288e596419f09eecf31ed0d638b2563e64c/public/assets/imgs/icons/cloaks/Meet.ico
--------------------------------------------------------------------------------
/public/assets/imgs/icons/cloaks/YouTube.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ShadowDevLabs/ShadowV3/b0877288e596419f09eecf31ed0d638b2563e64c/public/assets/imgs/icons/cloaks/YouTube.ico
--------------------------------------------------------------------------------
/public/assets/imgs/icons/cloaks/Zoom.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ShadowDevLabs/ShadowV3/b0877288e596419f09eecf31ed0d638b2563e64c/public/assets/imgs/icons/cloaks/Zoom.ico
--------------------------------------------------------------------------------
/public/assets/imgs/icons/default-extension.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ShadowDevLabs/ShadowV3/b0877288e596419f09eecf31ed0d638b2563e64c/public/assets/imgs/icons/default-extension.png
--------------------------------------------------------------------------------
/public/assets/imgs/icons/loading.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ShadowDevLabs/ShadowV3/b0877288e596419f09eecf31ed0d638b2563e64c/public/assets/imgs/icons/loading.gif
--------------------------------------------------------------------------------
/public/assets/imgs/icons/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ShadowDevLabs/ShadowV3/b0877288e596419f09eecf31ed0d638b2563e64c/public/assets/imgs/icons/logo.png
--------------------------------------------------------------------------------
/public/assets/imgs/icons/pages/ai.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ShadowDevLabs/ShadowV3/b0877288e596419f09eecf31ed0d638b2563e64c/public/assets/imgs/icons/pages/ai.png
--------------------------------------------------------------------------------
/public/assets/imgs/icons/pages/credits.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ShadowDevLabs/ShadowV3/b0877288e596419f09eecf31ed0d638b2563e64c/public/assets/imgs/icons/pages/credits.png
--------------------------------------------------------------------------------
/public/assets/imgs/icons/pages/extensions.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ShadowDevLabs/ShadowV3/b0877288e596419f09eecf31ed0d638b2563e64c/public/assets/imgs/icons/pages/extensions.png
--------------------------------------------------------------------------------
/public/assets/imgs/icons/pages/games.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ShadowDevLabs/ShadowV3/b0877288e596419f09eecf31ed0d638b2563e64c/public/assets/imgs/icons/pages/games.png
--------------------------------------------------------------------------------
/public/assets/imgs/icons/pages/history.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ShadowDevLabs/ShadowV3/b0877288e596419f09eecf31ed0d638b2563e64c/public/assets/imgs/icons/pages/history.png
--------------------------------------------------------------------------------
/public/assets/imgs/icons/pages/home.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ShadowDevLabs/ShadowV3/b0877288e596419f09eecf31ed0d638b2563e64c/public/assets/imgs/icons/pages/home.png
--------------------------------------------------------------------------------
/public/assets/imgs/icons/pages/new.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ShadowDevLabs/ShadowV3/b0877288e596419f09eecf31ed0d638b2563e64c/public/assets/imgs/icons/pages/new.png
--------------------------------------------------------------------------------
/public/assets/imgs/icons/pages/settings.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ShadowDevLabs/ShadowV3/b0877288e596419f09eecf31ed0d638b2563e64c/public/assets/imgs/icons/pages/settings.png
--------------------------------------------------------------------------------
/public/assets/imgs/icons/plain_logo.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ShadowDevLabs/ShadowV3/b0877288e596419f09eecf31ed0d638b2563e64c/public/assets/imgs/icons/plain_logo.webp
--------------------------------------------------------------------------------
/public/assets/imgs/users/blanky.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ShadowDevLabs/ShadowV3/b0877288e596419f09eecf31ed0d638b2563e64c/public/assets/imgs/users/blanky.jpg
--------------------------------------------------------------------------------
/public/assets/imgs/users/ghostly.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ShadowDevLabs/ShadowV3/b0877288e596419f09eecf31ed0d638b2563e64c/public/assets/imgs/users/ghostly.jpg
--------------------------------------------------------------------------------
/public/assets/imgs/users/nc.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ShadowDevLabs/ShadowV3/b0877288e596419f09eecf31ed0d638b2563e64c/public/assets/imgs/users/nc.png
--------------------------------------------------------------------------------
/public/assets/imgs/users/peak.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ShadowDevLabs/ShadowV3/b0877288e596419f09eecf31ed0d638b2563e64c/public/assets/imgs/users/peak.png
--------------------------------------------------------------------------------
/public/assets/imgs/users/vendfr.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ShadowDevLabs/ShadowV3/b0877288e596419f09eecf31ed0d638b2563e64c/public/assets/imgs/users/vendfr.webp
--------------------------------------------------------------------------------
/public/assets/imgs/users/zxyp.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ShadowDevLabs/ShadowV3/b0877288e596419f09eecf31ed0d638b2563e64c/public/assets/imgs/users/zxyp.webp
--------------------------------------------------------------------------------
/public/assets/js/ai.js:
--------------------------------------------------------------------------------
1 | import { marked } from "https://cdn.jsdelivr.net/npm/marked/lib/marked.esm.js";
2 |
3 | const textarea = document.querySelector('.input-container textarea');
4 | const sendBtn = document.querySelector('.send-btn');
5 | const messagesContainer = document.querySelector('.messages');
6 |
7 | const uValue = 'user';
8 | const aiValue = 'assistant';
9 | const chatHistoryLimit = 6;
10 |
11 | let chatHistory = [];
12 |
13 | const getCsrfToken = async () => {
14 | try {
15 | const response = await fetch('/csrf-token');
16 | const data = await response.json();
17 | return data.csrfToken;
18 | } catch (error) {
19 | console.error('Error Fetching the CSRF TOKEN: ', error);
20 | return null;
21 | }
22 | };
23 |
24 | const csrfToken = await getCsrfToken();
25 |
26 | if (!csrfToken) {
27 | console.error("Failed to Retrevie CSRF TOKEN");
28 | }
29 |
30 | sendBtn.addEventListener('click', async () => {
31 | await sendMsg();
32 | });
33 |
34 | textarea.addEventListener('keydown', (event) => {
35 | if (event.key === 'Enter') {
36 | if (!(event.shiftKey || event.ctrlKey || event.altKey)) {
37 | event.preventDefault();
38 | sendMsg();
39 | }
40 | }
41 | });
42 |
43 | async function sendMsg() {
44 | const message = textarea.value.trim();
45 | if (!message) return;
46 |
47 | addMsg(message, uValue);
48 | chatHistory.push({ role: uValue, content: message });
49 |
50 | if (chatHistory.length > chatHistoryLimit) chatHistory.shift();
51 | textarea.value = '';
52 |
53 | const payload = { messages: chatHistory };
54 |
55 | const loadingMsg = addMsg('', aiValue, true);
56 |
57 | try {
58 | const response = await fetch('/ask', {
59 | method: 'POST',
60 | headers: {
61 | 'Content-Type': 'application/json',
62 | 'CSRF-Token': csrfToken
63 | },
64 | body: JSON.stringify(payload)
65 | });
66 |
67 | const data = await response.json();
68 | const aiMsg = data.error ? 'Error: ' + data.error : data;
69 |
70 | loadingMsg.innerHTML = marked(aiMsg);
71 | loadingMsg.classList.remove('loading');
72 | loadingMsg.classList.add('show');
73 | loadingMsg.addEventListener("click", () => {
74 | navigator.clipboard.writeText(aiMsg);
75 | });
76 | chatHistory.push({ role: aiValue, content: aiMsg });
77 |
78 | if (chatHistory.length > chatHistoryLimit) chatHistory.shift();
79 | } catch {
80 | loadingMsg.innerHTML = 'Failed to reach Shadow Assistant. Try again.';
81 | loadingMsg.classList.remove('loading');
82 | loadingMsg.classList.add('show');
83 | }
84 | }
85 |
86 | function addMsg(content, role, isLoading = false) {
87 | const msgElement = document.createElement('div');
88 | msgElement.classList.add('message', role);
89 |
90 | if (isLoading) {
91 | msgElement.classList.add('loading');
92 | msgElement.innerHTML = `
93 |
94 | ${content}
95 | `;
96 | } else {
97 | msgElement.innerHTML = marked(content);
98 | }
99 |
100 | messagesContainer.appendChild(msgElement);
101 | messagesContainer.scrollTop = messagesContainer.scrollHeight;
102 |
103 | setTimeout(() => {
104 | msgElement.classList.add('show');
105 | }, 10);
106 |
107 | return msgElement;
108 | }
109 |
110 | addMsg("**Hello, how may I help you?**", aiValue);
111 |
--------------------------------------------------------------------------------
/public/assets/js/autoblank.js:
--------------------------------------------------------------------------------
1 | import { SettingsManager } from "./settings_manager.js";
2 | const settings = new SettingsManager();
3 |
4 | if (await settings.get("auto-blank")) abtblank();
5 |
6 | function abtblank() {
7 | const url = location.href;
8 | const width = window.innerWidth;
9 | const height = window.innerHeight;
10 |
11 |
12 | let inFrame;
13 |
14 |
15 | try {
16 | inFrame = window !== top;
17 | } catch (e) {
18 | inFrame = true;
19 | }
20 |
21 |
22 | if (!inFrame && !navigator.userAgent.includes("Firefox")) {
23 | const popup = window.open(
24 | "about:blank",
25 | name,
26 | `width=${width},height=${height}`,
27 | );
28 |
29 | if (!popup || popup.closed) {
30 | alert(
31 | "Allow popups and redirects to hide this from showing up in your history.",
32 | );
33 | } else {
34 | const doc = popup.document;
35 | const iframe = doc.createElement("iframe");
36 | const style = iframe.style;
37 | const link = doc.createElement("link");
38 |
39 | iframe.src = url;
40 | style.position = "fixed";
41 | style.top = style.bottom = style.left = style.right = 0;
42 | style.border = style.outline = "none";
43 | style.width = style.height = "100%";
44 |
45 |
46 | doc.head.appendChild(link);
47 | doc.body.appendChild(iframe);
48 | window.location.replace("https://google.com");
49 | }
50 | }
51 | }
--------------------------------------------------------------------------------
/public/assets/js/bookmarks.js:
--------------------------------------------------------------------------------
1 | import { SettingsManager } from "./settings_manager.js";
2 |
3 | class BookmarksManager {
4 | constructor() {
5 | this.settings = new SettingsManager();
6 | this.container = document.getElementById("bookmarks-container");
7 | window.addEventListener("load", this.load.bind(this));
8 | }
9 |
10 | addBookmark(title, url) {
11 | const bookmark = document.createElement("div");
12 | bookmark.className = "bookmark";
13 | bookmark.setAttribute("data-title", title);
14 | bookmark.setAttribute("data-url", url);
15 | const icon = document.createElement("i");
16 | icon.className = "fas fa-bookmark";
17 | const text = document.createElement("span");
18 | text.textContent = title;
19 | bookmark.appendChild(icon);
20 | bookmark.appendChild(text);
21 | bookmark.addEventListener("click", (e) => this.onClick(e, bookmark));
22 | bookmark.addEventListener("contextmenu", (event) =>
23 | this.showBookmarkContextMenu(
24 | event,
25 | title,
26 | url,
27 | Array.from(this.container.children).indexOf(bookmark),
28 | ),
29 | );
30 |
31 | this.container.appendChild(bookmark);
32 | this.save();
33 | }
34 |
35 | async save() {
36 | const bookmarks = Array.from(this.container.children).map((bookmark) => ({
37 | title: bookmark.getAttribute("data-title"),
38 | url: bookmark.getAttribute("data-url"),
39 | }));
40 |
41 | await this.settings.set("bookmarks", bookmarks);
42 | }
43 |
44 | async load() {
45 | const storedBookmarks = await this.settings.get("bookmarks");
46 | if (storedBookmarks) {
47 | const bookmarks = JSON.parse(storedBookmarks);
48 | bookmarks.forEach((bookmark) =>
49 | this.addBookmark(bookmark.title, bookmark.url),
50 | );
51 | }
52 | }
53 |
54 | showBookmarkContextMenu(event, title, url, index) {
55 | event.preventDefault();
56 | //Rework code: very inefficient to make an entirely new element each right click, clutters and overlaps, easier way is to just use 1 and move it, then check the events target to get the bookmark info
57 | const contextMenu = document.createElement("div");
58 | contextMenu.className = "context-menu";
59 | contextMenu.innerHTML = `
60 |
61 |
62 | `;
63 | contextMenu.style.left = `${event.clientX}px`;
64 | contextMenu.style.top = `${event.clientY}px`;
65 |
66 | document.body.appendChild(contextMenu);
67 |
68 | document.addEventListener("click", () => {
69 | document.body.removeChild(contextMenu);
70 | });
71 | }
72 |
73 | editBookmark(index) {
74 | const bookmarks = Array.from(this.container.children);
75 | const bookmark = bookmarks[index];
76 | const textElement = bookmark.querySelector("span");
77 |
78 | const newTitle = prompt(
79 | "Enter new title:",
80 | bookmark.getAttribute("data-title"),
81 | );
82 | const newUrl = prompt("Enter new URL:", bookmark.getAttribute("data-url"));
83 |
84 | if (newTitle !== null && newUrl !== null) {
85 | bookmark.setAttribute("data-title", newTitle);
86 | bookmark.setAttribute("data-url", newUrl);
87 | textElement.textContent = newTitle;
88 | this.save();
89 | }
90 | }
91 |
92 | deleteBookmark(index) {
93 | const bookmarks = Array.from(this.container.children);
94 | const bookmark = bookmarks[index];
95 |
96 | if (
97 | confirm(
98 | `Are you sure you want to delete the bookmark: ${bookmark.getAttribute("data-title")}?`,
99 | )
100 | ) {
101 | this.container.removeChild(bookmark);
102 | this.save();
103 | }
104 | }
105 |
106 | onClick(e, bookmark) {
107 | const url = bookmark.getAttribute("data-url");
108 | if (e.shiftKey) {
109 | // Open in new tab
110 | tabs.createTab(url);
111 | } else {
112 | // Open in current tab
113 | tabs.load(url);
114 | }
115 | }
116 |
117 | newBookmark() {
118 | const iframesContainer = document.getElementById("iframes-container");
119 | const iframe = iframesContainer.children[activeTabIndex];
120 | this.addBookmark(iframe.contentWindow.document.title, tabs.parseUrl());
121 | }
122 | }
123 |
124 | const bookmarksManager = new BookmarksManager();
125 |
--------------------------------------------------------------------------------
/public/assets/js/context.js:
--------------------------------------------------------------------------------
1 | var selectedText;
2 | var activeElement;
3 | document.addEventListener("DOMContentLoaded", function () {
4 | var customMenu = document.getElementById("custom-menu");
5 | var body = document.getElementById("body");
6 |
7 | document.addEventListener("contextmenu", function (e) {
8 | e.preventDefault();
9 | selectedText = window.getSelection().toString();
10 | activeElement = document.activeElement;
11 | customMenu.style.left = e.clientX + "px";
12 | customMenu.style.top = e.clientY + "px";
13 | customMenu.style.display = "block";
14 | });
15 |
16 | document.addEventListener("click", function (e) {
17 | customMenu.style.display = "none";
18 | });
19 | });
20 |
21 | function copy() {
22 | navigator.clipboard.writeText(selectedText);
23 | }
24 |
25 | function paste() {
26 | navigator.clipboard
27 | .readText()
28 | .then(function (text) {
29 | var pasteText = text;
30 | activeElement.value = activeElement.value + pasteText;
31 | })
32 | .catch(function (error) {
33 | console.error("Failed to read text from clipboard: ", error);
34 | });
35 | activeElement.focus();
36 | }
37 |
38 | function selectAll() {
39 | var selection = window.getSelection();
40 | var focusedElement = document.activeElement;
41 |
42 | if (
43 | focusedElement &&
44 | (focusedElement.tagName === "INPUT" ||
45 | focusedElement.tagName === "TEXTAREA")
46 | ) {
47 | // If focused element is an input field or textarea
48 | focusedElement.select();
49 | } else {
50 | // If focused element is not an input field or textarea
51 | var range = document.createRange();
52 | range.selectNodeContents(document.body);
53 | selection.removeAllRanges();
54 | selection.addRange(range);
55 | }
56 | }
57 |
58 | window.addEventListener("message", (e) => {
59 | if (e.origin !== location.origin) return;
60 | if (e.message === "click") {
61 | document.getElementsByClassName("context").forEach((i) => {
62 | if (
63 | i.getAttribute("hidden") === false ||
64 | i.className.includes("active")
65 | ) {
66 | i.hidden;
67 | }
68 | });
69 | }
70 | });
71 |
--------------------------------------------------------------------------------
/public/assets/js/contextmenu.html:
--------------------------------------------------------------------------------
1 |
71 |
72 |
--------------------------------------------------------------------------------
/public/assets/js/debugger.js:
--------------------------------------------------------------------------------
1 | class Dewasper {
2 | constructor() {
3 | this.errors = [];
4 | window.onerror = (message, source, line, column) => this.log(message, source, line, column);
5 | }
6 |
7 | log(message, source, line, column, error) {
8 | const errorObject = {
9 | message,
10 | source,
11 | line,
12 | column,
13 | time: new Date().toLocaleString(),
14 | stack: error ? error.stack : null
15 | };
16 | this.errors.push(errorObject);
17 | }
18 |
19 | dump(count) {
20 | if (!Array.isArray(window.errors) || window.errors.length === 0) {
21 | return "No errors found.";
22 | }
23 |
24 | const errors = count ? this.errors.slice(-count) : this.errors;
25 |
26 | const formattedErrors = errors.map((errorObj, index) => {
27 | return `Error ${index + 1}:
28 | Message: ${errorObj.message || 'N/A'}
29 | Source: ${errorObj.source || 'N/A'}
30 | Line: ${errorObj.line || 'N/A'}
31 | Column: ${errorObj.column || 'N/A'}
32 | Time: ${errorObj.time || 'N/A'}
33 | `;
34 | });
35 |
36 | return formattedErrors.join('\n');
37 | }
38 | }
39 |
40 |
--------------------------------------------------------------------------------
/public/assets/js/devtools.js:
--------------------------------------------------------------------------------
1 | function devtoolToggle() {
2 | if (toggleState) {
3 | eruda.hide();
4 | eruda.destroy();
5 | } else {
6 | var script = document.createElement("script");
7 | script.src = "//cdn.jsdelivr.net/npm/eruda";
8 | document.body.appendChild(script);
9 | script.onload = function () {
10 | eruda.init();
11 | eruda.show();
12 | };
13 | }
14 | toggleState = !toggleState;
15 | }
16 |
--------------------------------------------------------------------------------
/public/assets/js/eruda.js:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ShadowDevLabs/ShadowV3/b0877288e596419f09eecf31ed0d638b2563e64c/public/assets/js/eruda.js
--------------------------------------------------------------------------------
/public/assets/js/extension-downloader.js:
--------------------------------------------------------------------------------
1 | import { SettingsManager } from "./settings_manager.js";
2 |
3 | class ExtensionDownloader extends Extensions {
4 | constructor() {
5 | this.settings = new SettingsManager();
6 | }
7 |
8 | async download(id) {
9 | const i = await fetch(`${location.host}/v1/extensions/ext?${id}`);
10 | if (i.status !== 404) {
11 | let exts = this.settings.get("extensions");
12 | exts[i.constructor.name] = JSON.parse(i[info]);
13 | this.settings.set("extensions", exts);
14 | return true;
15 | } else {
16 | console.log("Extension ID not found");
17 | return false;
18 | }
19 | }
20 |
21 | async getInfo(id) {
22 | const i = await fetch(`${location.host}/v1/extensions/info?${id}`);
23 | return 0;
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/public/assets/js/extensions-loader.js:
--------------------------------------------------------------------------------
1 | // var extLoaded;
2 | // if (typeof window != null) {
3 | // if (typeof window != null) {
4 | // onload = init;
5 |
6 |
7 | // function init() {
8 | // loadExtensions();
9 | // let tries;
10 | // try {
11 | // parent.updateOmni();
12 | // parent.setTab();
13 | // } catch (e) {
14 | // if (tries <= 2) setTimeout(init, 2500);
15 | // tries++;
16 | // }
17 | // }
18 |
19 |
20 | // function loadExtensions() {
21 | // if (!extLoaded) {
22 | // try {
23 | // const extensionController = new Extensions();
24 |
25 | // extLoaded = true;
26 | // console.log("[EXT] Extensions loaded!");
27 | // console.log("[EXT] "+tag);
28 | // } catch (e) {
29 | // console.warn("Error loading extensions: ", e);
30 | // }
31 | // }
32 | // }
33 | // }}
34 |
--------------------------------------------------------------------------------
/public/assets/js/extensions.js:
--------------------------------------------------------------------------------
1 | //NO TOUCHY
2 | //Steal 95% of the code from inject.js and add proper extension loading and stuffs
3 | /*
4 | Manifest layout:
5 | {
6 | "id": {
7 | "enabled": true
8 | "name": "",
9 | "code": "",
10 | "icon": "",
11 | "features": {
12 | "menu-bar": {
13 | "icon": "",
14 | "onclick": "",
15 | "label": ""
16 | }
17 | TODO: Add permissions for manipulating tabs (Open, close, load, etc)
18 | }
19 | }
20 | }
21 |
22 | */
23 |
24 | import { SettingsManager } from "./settings_manager.js";
25 |
26 | class Extensions {
27 | constructor() {
28 | this.settings = new SettingsManager();
29 | (async function () {
30 | this.extensions = await this.settings.get("extensions");
31 | init(await checkDev());
32 | })();
33 | }
34 |
35 | init(a) {
36 | this.extensions.forEach((i) => {
37 | if (!a.includes(i)) {
38 | this.load(i.constructor.name /* Extension ID*/);
39 | } else {
40 | console.log(
41 | `[EXT] Extension with ID ${i.constructor.name} will not be loaded. User denied due to being a ${i.origin} extension`,
42 | );
43 | }
44 | });
45 | }
46 |
47 | async checkDev() {
48 | let devExts = [];
49 | this.extensions.forEach((i) => {
50 | if (i.origin !== "store") {
51 | devExts.append(i.name);
52 | }
53 | });
54 | if (devExts.length > 0 && (await parent.devAlert(devExts))) {
55 | return [];
56 | }
57 | return devExts;
58 | }
59 |
60 | load(id) {
61 | const extension = this.get(id);
62 | if (extension && extension.enabled) {
63 | switch (extension.features) {
64 | case menu - bar:
65 | parent.updateMenu(extension.features["menu-bar"]);
66 | break;
67 | default:
68 | break;
69 | }
70 | switch (extension.type) {
71 | case listen:
72 | document.addEventListener("fetch", (i) => {
73 | eval(extension.code);
74 | });
75 | break;
76 | case run:
77 | eval(extension.code);
78 | break;
79 | default:
80 | let tag = document.createElement("script");
81 | tag.innerHTML = extension.code;
82 | document.head.append(tag);
83 | break;
84 | }
85 | }
86 | }
87 |
88 | get(id) {
89 | this.update();
90 | return this.extensions[id] || false;
91 | }
92 | get(id) {
93 | this.update();
94 | return this.extensions[id] || false;
95 | }
96 |
97 | async update() {
98 | this.extensions = await this.settings.get("extensions");
99 | }
100 | }
101 |
--------------------------------------------------------------------------------
/public/assets/js/history.js:
--------------------------------------------------------------------------------
1 | (async function () {
2 | const text = !await window.settings.get("history") ? "Enable your history to begin!" : "Start by searching something!";
3 | const savedHistory = await self.history_helper.get();
4 | const historyData = Array.isArray(savedHistory) ? savedHistory : [{ time: Date.now(), title: `Nothings here yet! - ${text}`, url: "http://example.com", icon: "https://google.com/favicon.ico" }];
5 | historyData.forEach(data => add(data));
6 | })();
7 |
8 |
9 | function add(obj) {
10 | const dateContainer = document.querySelector(`[data-date="${getDay(new Date(obj.time))}"]`) ?? null;
11 | if (dateContainer) {
12 | dateContainer.children[1].appendChild(createItem(obj));
13 | } else {
14 | const today = new Date();
15 | const yesterday = new Date(today);
16 | yesterday.setDate(today.getDate() - 1);
17 | const objDate = new Date(obj.time);
18 |
19 | const newDateContainer = document.createElement('div');
20 | newDateContainer.classList = "day-section";
21 | newDateContainer.dataset.date = getDay(objDate);
22 |
23 | const dayHeader = document.createElement('h2');
24 | const day = (objDate.toDateString() === today.toDateString()) ? "Today - " : (objDate.toDateString() === yesterday.toDateString()) ? "Yesterday - " : "";
25 | dayHeader.textContent = day + objDate.toLocaleDateString("default", { weekday: "long", month: "long", day: "numeric", year: "numeric" });
26 | newDateContainer.prepend(dayHeader);
27 |
28 | const historyList = document.createElement("div");
29 | historyList.classList = "history-list";
30 | newDateContainer.append(historyList);
31 | historyList.prepend(createItem(obj));
32 |
33 | const entries = Array.from(document.querySelector("#historyEntries").children);
34 | if (entries.length > 0) {
35 | let previous = null;
36 | entries.forEach(elem => { if (new Date(elem.dataset.date).getTime() > obj.time) previous = elem });
37 | if (previous) {
38 | previous.after(newDateContainer);
39 | } else {
40 | document.querySelector("#historyEntries").prepend(newDateContainer);
41 | }
42 | } else {
43 | document.querySelector("#historyEntries").appendChild(newDateContainer);
44 | }
45 | }
46 | }
47 |
48 | function createItem(obj) {
49 | const item = document.createElement('div');
50 | item.classList.add('history-item');
51 | item.onclick = () => parent.tabs.load(obj.url);
52 | item.innerHTML = `
53 |
54 |
55 |
56 |
${obj.title}
57 |
${new Date(obj.time).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })}
58 |
59 |
60 |
63 | `;
64 | return item;
65 | }
66 |
67 | function getDay(d) {
68 | d = new Date(d);
69 | return `${d.getDate()}-${d.getMonth()}-${d.getFullYear()}`;
70 | }
71 |
--------------------------------------------------------------------------------
/public/assets/js/history_helper.js:
--------------------------------------------------------------------------------
1 | class HistoryHelper {
2 | constructor(user, dbName) {
3 | this.user = user ?? "shadow";
4 | this.dbName = dbName ?? "history";
5 | this.storeName = `${this.user}-history`;
6 | this.dbPromise = this.initDB();
7 | }
8 |
9 | initDB() {
10 | return new Promise((resolve, reject) => {
11 | const request = indexedDB.open(this.dbName, 1);
12 | request.onupgradeneeded = (e) => {
13 | const db = e.target.result;
14 | if (!db.objectStoreNames.contains(this.storeName)) {
15 | db.createObjectStore(this.storeName);
16 | }
17 | };
18 |
19 | request.onsuccess = (e) => {
20 | this.db = e.target.result;
21 | resolve(this.db);
22 | };
23 |
24 | request.onerror = (e) => {
25 | console.error("Settings database error:", e.target.errorCode);
26 | reject(e.target.errorCode);
27 | };
28 | });
29 | }
30 |
31 | async getOpen() {
32 | return await this.get("open-tabs");
33 | }
34 |
35 | async setOpen(arr) {
36 | await this.set([...arr], "open-tabs");
37 | }
38 |
39 | async add(obj) {
40 | let arr = await this.get() ?? [];
41 | arr.unshift(obj);
42 | this.set(arr);
43 | }
44 |
45 | async set(value, key = "history-array") {
46 | try {
47 | const db = await this.dbPromise;
48 | const oldValue = await this.get(key);
49 | return new Promise((resolve, reject) => {
50 | const transaction = db.transaction([this.storeName], "readwrite");
51 | const store = transaction.objectStore(this.storeName);
52 | const request = store.put(value, key);
53 | request.onsuccess = () => {
54 | resolve();
55 | const event = new CustomEvent("historyupdate", {
56 | detail: {
57 | key: key,
58 | value: value,
59 | oldValue: oldValue,
60 | database: this.dbName,
61 | success: true,
62 | },
63 | });
64 | self.dispatchEvent(event);
65 | };
66 | request.onerror = (e) => {
67 | reject(e.target.error);
68 | };
69 | });
70 | } catch (error) {
71 | console.error("Error setting value:", error);
72 | }
73 | }
74 |
75 | async get(key = "history-array") {
76 | try {
77 | const db = await this.dbPromise;
78 |
79 | return new Promise((resolve, reject) => {
80 | const transaction = db.transaction([this.storeName], "readonly");
81 | const store = transaction.objectStore(this.storeName);
82 |
83 | const request = store.get(key);
84 | request.onsuccess = (e) => {
85 | resolve(e.target.result);
86 | };
87 | request.onerror = (e) => {
88 | reject(e.target.error);
89 | };
90 | }
91 | );
92 | } catch (error) {
93 | console.error("Error getting value:", error);
94 | }
95 | }
96 |
97 | async remove(key) {
98 | try {
99 | const db = await this.dbPromise;
100 | return new Promise((resolve, reject) => {
101 | const transaction = db.transaction([this.storeName], "readwrite");
102 | const store = transaction.objectStore(this.storeName);
103 | const request = store.delete(key);
104 | request.onsuccess = () => {
105 | resolve();
106 | };
107 | request.onerror = (e) => {
108 | reject(e.target.error);
109 | };
110 | });
111 | } catch (error) {
112 | console.error("Error removing value:", error);
113 | }
114 | }
115 |
116 | async clear(key) {
117 | try {
118 | const db = await this.dbPromise;
119 | return new Promise((resolve, reject) => {
120 | const transaction = db.transaction([this.storeName], "readwrite");
121 | const store = transaction.objectStore(this.storeName);
122 |
123 | const getAllKeysRequest = store.getAllKeys();
124 | getAllKeysRequest.onsuccess = (e) => {
125 | let keysToDelete;
126 | const allKeys = e.target.result;
127 | if (!key) {
128 | keysToDelete = allKeys.filter(i => i !== "open-tabs");
129 | } else {
130 | keysToDelete = allKeys.filter(i => i === key)
131 | }
132 | localStorage.setItem("deleting", JSON.stringify(keysToDelete))
133 | keysToDelete.forEach(key => {
134 | store.delete(key);
135 | });
136 |
137 | resolve();
138 | };
139 |
140 | getAllKeysRequest.onerror = (e) => {
141 | reject(e.target.error);
142 | };
143 | });
144 | } catch (error) {
145 | console.error("Error clearing store:", error);
146 | }
147 | }
148 | }
149 |
150 | export { HistoryHelper };
--------------------------------------------------------------------------------
/public/assets/js/history_helper_sw.js:
--------------------------------------------------------------------------------
1 | class HistoryHelper {
2 | constructor(user, dbName) {
3 | this.user = user ?? "shadow";
4 | this.dbName = dbName ?? "history";
5 | this.storeName = `${this.user}-history`;
6 | this.dbPromise = this.initDB();
7 | }
8 |
9 | initDB() {
10 | return new Promise((resolve, reject) => {
11 | const request = indexedDB.open(this.dbName, 1);
12 | request.onupgradeneeded = (e) => {
13 | const db = e.target.result;
14 | if (!db.objectStoreNames.contains(this.storeName)) {
15 | db.createObjectStore(this.storeName);
16 | }
17 | };
18 |
19 | request.onsuccess = (e) => {
20 | this.db = e.target.result;
21 | resolve(this.db);
22 | };
23 |
24 | request.onerror = (e) => {
25 | console.error("Settings database error:", e.target.errorCode);
26 | reject(e.target.errorCode);
27 | };
28 | });
29 | }
30 |
31 | async getOpen() {
32 | return await this.get("open-tabs");
33 | }
34 |
35 | async setOpen(arr) {
36 | await this.add(arr, "open-tabs");
37 | }
38 |
39 | async add(value, key) {
40 | try {
41 | if (!key) {
42 | value = await this.get();
43 | value.push(value);
44 | key = "history-array";
45 | }
46 | const db = await this.dbPromise;
47 | const oldValue = await this.get(key);
48 | return new Promise((resolve, reject) => {
49 | const transaction = db.transaction([this.storeName], "readwrite");
50 | const store = transaction.objectStore(this.storeName);
51 | const request = store.put(value, key);
52 | request.onsuccess = () => {
53 | resolve();
54 | const event = new CustomEvent("historyupdate", {
55 | detail: {
56 | key: key,
57 | value: value,
58 | oldValue: oldValue,
59 | database: this.dbName,
60 | success: true,
61 | },
62 | });
63 | self.dispatchEvent(event);
64 | };
65 | request.onerror = (e) => {
66 | reject(e.target.error);
67 | };
68 | });
69 | } catch (error) {
70 | console.error("Error setting value:", error);
71 | }
72 | }
73 |
74 | async get(key = "history-array") {
75 | try {
76 | const db = await this.dbPromise;
77 |
78 | return new Promise((resolve, reject) => {
79 | const transaction = db.transaction([this.storeName], "readonly");
80 | const store = transaction.objectStore(this.storeName);
81 |
82 | const request = store.get(key);
83 | request.onsuccess = (e) => {
84 | resolve(e.target.result);
85 | };
86 | request.onerror = (e) => {
87 | reject(e.target.error);
88 | };
89 | }
90 | );
91 | } catch (error) {
92 | console.error("Error getting value:", error);
93 | }
94 | }
95 |
96 | async remove(key) {
97 | try {
98 | const db = await this.dbPromise;
99 | return new Promise((resolve, reject) => {
100 | const transaction = db.transaction([this.storeName], "readwrite");
101 | const store = transaction.objectStore(this.storeName);
102 | const request = store.delete(key);
103 | request.onsuccess = () => {
104 | resolve();
105 | };
106 | request.onerror = (e) => {
107 | reject(e.target.error);
108 | };
109 | });
110 | } catch (error) {
111 | console.error("Error removing value:", error);
112 | }
113 | }
114 |
115 | async clear(key) {
116 | try {
117 | const db = await this.dbPromise;
118 | return new Promise((resolve, reject) => {
119 | const transaction = db.transaction([this.storeName], "readwrite");
120 | const store = transaction.objectStore(this.storeName);
121 |
122 | const getAllKeysRequest = store.getAllKeys();
123 | getAllKeysRequest.onsuccess = (e) => {
124 | let keysToDelete;
125 | const allKeys = e.target.result;
126 | if (!key) {
127 | keysToDelete = allKeys.filter(i => i !== "open-tabs");
128 | } else {
129 | keysToDelete = allKeys.filter(i => i === key)
130 | }
131 | localStorage.setItem("deleting", JSON.stringify(keysToDelete))
132 | keysToDelete.forEach(key => {
133 | store.delete(key);
134 | });
135 |
136 | resolve();
137 | };
138 |
139 | getAllKeysRequest.onerror = (e) => {
140 | reject(e.target.error);
141 | };
142 | });
143 | } catch (error) {
144 | console.error("Error clearing store:", error);
145 | }
146 | }
147 | }
148 |
149 | self.HistoryHelper = HistoryHelper;
--------------------------------------------------------------------------------
/public/assets/js/keybinds.js:
--------------------------------------------------------------------------------
1 | /*Close tab with alt + w*/ document.addEventListener("keydown", (e) => {
2 | if (e.key === "w" && e.altKey) {
3 | parent.closeTab();
4 | }
5 | });
6 | /*Open new tab with alt + T */ document.addEventListener("keydown", (e) => {
7 | if (e.key === "t" && e.altKey) {
8 | parent.closeTab();
9 | }
10 | });
11 |
--------------------------------------------------------------------------------
/public/assets/js/main.js:
--------------------------------------------------------------------------------
1 | const maxShortcuts = 10;
2 | const addShortcutButton = document.querySelector(".shortcut.add");
3 | const modal = document.getElementById("modal");
4 | const closeButton = document.querySelector(".close");
5 | addShortcutButton.addEventListener("click", () => {
6 | modal.style.display = "block";
7 | });
8 | closeButton.addEventListener("click", () => {
9 | modal.style.display = "none";
10 | });
11 | window.addEventListener("click", (event) => {
12 | if (event.target === modal) {
13 | modal.style.display = "none";
14 | }
15 | });
16 | const addShortcut = document.getElementById("add-shortcut");
17 | const shortcutName = document.getElementById("shortcut-name");
18 | const shortcutUrl = document.getElementById("shortcut-url");
19 | const shortcutForm = document.getElementById("shortcut-form");
20 | const shortcutsContainer = document.querySelector(".shortcuts");
21 | shortcutUrl.addEventListener("keypress", (e) => {
22 | if (e.key == "Enter") addShortcutClicked();
23 | });
24 | shortcutName.addEventListener("keypress", (e) => {
25 | if (e.key == "Enter") addShortcutClicked();
26 | });
27 | addShortcut.addEventListener("click", addShortcutClicked);
28 | async function addShortcutClicked() {
29 | if (shortcutsContainer.querySelectorAll(".shortcut").length >= maxShortcuts) {
30 | alert(
31 | "You've reached the maximum number of shortcuts (10). Please delete some shortcuts to add new ones.",
32 | );
33 | return;
34 | }
35 | const name = shortcutName.value;
36 | const url = shortcutUrl.value;
37 | if (name && url) {
38 | const newShortcut = document.createElement("a");
39 | newShortcut.className = "shortcut";
40 | const domain = url;
41 | const size = 64;
42 | const imgSrc = `https://www.google.com/s2/favicons?domain=${domain}&sz=${size}`;
43 | newShortcut.innerHTML = `
44 |
45 | ${name}
46 | `;
47 | newShortcut.setAttribute("data-url", url);
48 | shortcutsContainer.insertBefore(newShortcut, addShortcutButton);
49 | const shortcuts = await settings.get("shortcuts") || [];
50 | shortcuts.push({ name, url });
51 | await settings.set("shortcuts", shortcuts);
52 | modal.style.display = "none";
53 | shortcutForm.reset();
54 | }
55 | }
56 | async function loadShortcuts() {
57 | const shortcuts = await settings.get("shortcuts") || [];
58 | shortcuts.slice(0, maxShortcuts).forEach((shortcut) => {
59 | const { name, url } = shortcut;
60 | const newShortcut = document.createElement("a");
61 | newShortcut.className = "shortcut";
62 | const domain = url;
63 | const size = 64;
64 | const imgSrc = `https://www.google.com/s2/favicons?domain=${domain}&sz=${size}`;
65 | newShortcut.innerHTML = `
66 |
67 | ${name}
68 | `;
69 | newShortcut.setAttribute("data-url", url);
70 | shortcutsContainer.insertBefore(newShortcut, addShortcutButton);
71 | });
72 | }
73 | document.addEventListener("DOMContentLoaded", () => {
74 | loadShortcuts();
75 | });
76 | const contextMenu = document.getElementById("shortcut-context-menu");
77 | let selectedShortcut;
78 | shortcutsContainer.addEventListener("contextmenu", (event) => {
79 | event.preventDefault();
80 | const shortcut = event.target.closest(".shortcut");
81 | if (shortcut && shortcut !== addShortcutButton) {
82 | selectedShortcut = shortcut;
83 | contextMenu.style.left = `${event.pageX}px`;
84 | contextMenu.style.top = `${event.pageY}px`;
85 | contextMenu.style.display = "block";
86 | }
87 | });
88 | document.addEventListener("click", () => {
89 | contextMenu.style.display = "none";
90 | });
91 | document
92 | .getElementById("edit-context-menu-option")
93 | .addEventListener("click", () => {
94 | contextMenu.style.display = "none";
95 | const name = selectedShortcut.querySelector("p").textContent;
96 | const url = selectedShortcut.getAttribute("data-url");
97 | const imgSrc = `https://www.google.com/s2/favassets/imgs/icons?domain=${url}&sz=64`;
98 | const editModal = document.getElementById("edit-shortcut-modal");
99 | const editNameInput = document.getElementById("edit-shortcut-name");
100 | const editUrlInput = document.getElementById("edit-shortcut-url");
101 | editNameInput.value = name;
102 | editUrlInput.value = url;
103 | editModal.style.display = "block";
104 | document.getElementById("edit-shortcut").addEventListener("click", edited);
105 | editNameInput.addEventListener("keypress", (e) => {
106 | if (e.key == "Enter") edited();
107 | });
108 | editUrlInput.addEventListener("keypress", (e) => {
109 | if (e.key == "Enter") edited();
110 | });
111 | async function edited() {
112 | selectedShortcut.querySelector("p").textContent = editNameInput.value;
113 | const newURL = editUrlInput.value;
114 | selectedShortcut.setAttribute("data-url", newURL);
115 | const newImgSrc = `https://www.google.com/s2/favassets/imgs/icons?domain=${newURL}&sz=64`;
116 | selectedShortcut.querySelector("img").src = newImgSrc;
117 | const shortcuts = await settings.get("shortcuts") || [];
118 | const selectedShortcutIndex = shortcuts.findIndex(
119 | (shortcut) => shortcut.name === name,
120 | );
121 | if (selectedShortcutIndex !== -1) {
122 | shortcuts[selectedShortcutIndex] = {
123 | name: editNameInput.value,
124 | url: newURL,
125 | };
126 | await settings.set("shortcuts", shortcuts);
127 | }
128 | editModal.style.display = "none";
129 | }
130 | });
131 | document.getElementById("closeedit").addEventListener("click", () => {
132 | const editModal = document.getElementById("edit-shortcut-modal");
133 | editModal.style.display = "none";
134 | });
135 | document
136 | .getElementById("delete-context-menu-option")
137 | .addEventListener("click", async () => {
138 | contextMenu.style.display = "none";
139 | if (selectedShortcut !== addShortcutButton) {
140 | selectedShortcut.remove();
141 | const name = selectedShortcut.querySelector("p").textContent;
142 | const shortcuts = await settings.get("shortcuts") || [];
143 | const updatedShortcuts = shortcuts.filter(
144 | (shortcut) => shortcut.name !== name,
145 | );
146 | await settings.set("shortcuts", updatedShortcuts);
147 | }
148 | });
149 |
150 | function handleShortcutClick(event) {
151 | event.preventDefault();
152 | const shortcut = event.target.closest(".shortcut");
153 | if (shortcut && !shortcut.classList.contains("add")) {
154 | if (event.shiftKey) {
155 | parent.createTab(shortcut.getAttribute("data-url"));
156 | } else {
157 | parent.load(shortcut.getAttribute("data-url"));
158 | }
159 | }
160 | }
161 |
162 | shortcutsContainer.addEventListener("click", handleShortcutClick);
163 |
164 | document.getElementById("uv-form").addEventListener("submit", (e) => {
165 | e.preventDefault();
166 | parent.load(document.getElementById("uv-address").value);
167 | });
168 |
--------------------------------------------------------------------------------
/public/assets/js/notifications.js:
--------------------------------------------------------------------------------
1 | function ShowNotification(text, id) {
2 | const element = document.getElementById(id);
3 | if (element) {
4 | const notification = document.createElement('div');
5 | notification.className = 'notification';
6 | notification.textContent = text;
7 |
8 | element.appendChild(notification);
9 |
10 | notification.classList.add('fade-in');
11 |
12 | setTimeout(() => {
13 | notification.classList.remove('fade-in');
14 | notification.classList.add('fade-out');
15 | setTimeout(() => {
16 | notification.remove();
17 | }, 1000);
18 | }, 3000);
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/public/assets/js/options.js:
--------------------------------------------------------------------------------
1 | const menuButton = document.getElementById("menu-button");
2 | const dropdown = document.getElementById("menu-dropdown");
3 | let isDropdownVisible = false;
4 | let menuButtons = [];
5 | try { menuButtons = dropdown.querySelectorAll("button[data-add-tab]"); } catch (_) { };
6 | const iframesContainer = document.getElementById("iframes-container");
7 |
8 | menuButtons.forEach((button) => {
9 | button.addEventListener("click", () => {
10 | dropdown.style.display = "none";
11 | isDropdownVisible = false;
12 | });
13 | });
14 |
15 | function toggleDropdown() {
16 | if (isDropdownVisible) {
17 | dropdown.style.display = "none";
18 | } else {
19 | dropdown.style.display = "block";
20 | }
21 | isDropdownVisible = !isDropdownVisible;
22 | }
23 |
24 | function closeMenu(event) {
25 | if (isDropdownVisible) {
26 | if (
27 | event.target !== menuButton &&
28 | !dropdown.contains(event.target) &&
29 | event.target !== menuButton
30 | ) {
31 | dropdown.style.display = "none";
32 | isDropdownVisible = false;
33 | }
34 | }
35 | }
36 |
37 | try { menuButton.addEventListener("click", toggleDropdown); } catch (_) { }
38 |
39 | // Menu Buttons
40 |
41 | function iframefullscreen() {
42 | const iframe = iframesContainer.children[tabs.activeTabIndex];
43 | if (iframe.requestFullscreen) {
44 | iframe.requestFullscreen();
45 | } else if (iframe.mozRequestFullScreen) {
46 | iframe.mozRequestFullScreen();
47 | } else if (iframe.webkitRequestFullscreen) {
48 | iframe.webkitRequestFullscreen();
49 | } else if (iframe.msRequestFullscreen) {
50 | iframe.msRequestFullscreen();
51 | }
52 | }
53 |
54 | function abtblank() {
55 | const url = location.href;
56 | const width = window.innerWidth;
57 | const height = window.innerHeight;
58 |
59 |
60 | let inFrame;
61 |
62 |
63 | try {
64 | inFrame = window !== top;
65 | } catch (e) {
66 | inFrame = true;
67 | }
68 |
69 |
70 | if (!inFrame && !navigator.userAgent.includes("Firefox")) {
71 | const popup = window.open(
72 | "about:blank",
73 | name,
74 | `width=${width},height=${height}`,
75 | );
76 |
77 | if (!popup || popup.closed) {
78 | alert(
79 | "Allow popups and redirects to hide this from showing up in your history.",
80 | );
81 | } else {
82 | const doc = popup.document;
83 | const iframe = doc.createElement("iframe");
84 | const style = iframe.style;
85 | const link = doc.createElement("link");
86 |
87 | iframe.src = url;
88 | style.position = "fixed";
89 | style.top = style.bottom = style.left = style.right = 0;
90 | style.border = style.outline = "none";
91 | style.width = style.height = "100%";
92 |
93 |
94 | doc.head.appendChild(link);
95 | doc.body.appendChild(iframe);
96 | window.location.replace("https://google.com");
97 | }
98 | }
99 | }
100 |
101 | function iframeabtblank() {
102 | const iframe = iframesContainer.children[tabs.activeTabIndex];
103 | const url = iframe.src;
104 | const width = window.innerWidth;
105 | const height = window.innerHeight;
106 |
107 |
108 | let inFrame;
109 |
110 |
111 | try {
112 | inFrame = window !== top;
113 | } catch (e) {
114 | inFrame = true;
115 | }
116 |
117 |
118 | if (!inFrame && !navigator.userAgent.includes("Firefox")) {
119 | const popup = window.open("about:blank", `width=${width},height=${height}`);
120 |
121 |
122 | if (!popup || popup.closed) {
123 | alert(
124 | "Allow popups and redirects to hide this from showing up in your history.",
125 | );
126 | } else {
127 | const doc = popup.document;
128 | const iframe = doc.createElement("iframe");
129 | const style = iframe.style;
130 | const link = doc.createElement("link");
131 |
132 | iframe.src = url;
133 | style.position = "fixed";
134 | style.top = style.bottom = style.left = style.right = 0;
135 | style.border = style.outline = "none";
136 | style.width = style.height = "100%";
137 |
138 |
139 | doc.head.appendChild(link);
140 | doc.body.appendChild(iframe);
141 | window.location.replace("https://google.com");
142 | }
143 | }
144 | }
145 |
146 |
147 | function exit() {
148 | if (window.confirm("Are you sure you want to close this tab?")) {
149 | window.close();
150 | }
151 | }
152 |
--------------------------------------------------------------------------------
/public/assets/js/panic.js:
--------------------------------------------------------------------------------
1 | //remaking bc it was bad
--------------------------------------------------------------------------------
/public/assets/js/search.js:
--------------------------------------------------------------------------------
1 | //I really have no clue where this came from, I updated it a bit for shadow:// urls, but uh thanks whoever made it
2 |
3 | "use strict";
4 |
5 | /**
6 | *
7 | * @param {string} input
8 | * @param {string} template
9 | * @returns {string}
10 | */
11 | function search(input, template, backend) {
12 | let url;
13 |
14 | //Local shadow:// urls
15 | try {
16 | if (input.includes("shadow://")) {
17 | url = "/pages/" + input.replace("shadow://", "") + ".html";
18 | return url;
19 | }
20 | } catch (err) {}
21 |
22 | try {
23 | url = new URL(input);
24 | if (url.hostname.includes(".")) {
25 | return (
26 | `/${backend}/service/` + self.__uv$config.encodeUrl(url.toString())
27 | );
28 | }
29 | } catch (err) {}
30 |
31 | try {
32 | url = new URL(`https://${input}`);
33 | if (url.hostname.includes(".")) {
34 | return (
35 | `/${backend}/service/` + self.__uv$config.encodeUrl(url.toString())
36 | );
37 | }
38 | } catch (err) {}
39 | return (
40 | `/${backend}/service/` +
41 | self.__uv$config.encodeUrl(
42 | template.replace("%s", encodeURIComponent(input)),
43 | )
44 | );
45 | }
46 |
--------------------------------------------------------------------------------
/public/assets/js/settings.js:
--------------------------------------------------------------------------------
1 | import { SettingsManager } from "./settings_manager.js";
2 |
3 | (async function () {
4 | const settings = new SettingsManager();
5 |
6 | if (await settings.get("close-protection")) {
7 | top.onbeforeunload = function (e) { e.preventDefault(); return true; /* Prevent automatic tab closing */ };
8 | }
9 |
10 | var tab = await settings.get("tab");
11 | if (tab) {
12 | try {
13 | var tabData = tab;
14 | } catch {
15 | var tabData = {};
16 | }
17 | } else {
18 | var tabData = {};
19 | }
20 |
21 | if (tabData.title) {
22 | document.getElementById("title").value = tabData.title;
23 | }
24 | if (tabData.icon) {
25 | document.getElementById("icon").value = tabData.icon;
26 | }
27 |
28 | var settingsDefaultTab = {
29 | title: "Shadow",
30 | icon: "/assets/imgs/icons/logo.png",
31 | };
32 |
33 | self.setTitle = async function (title = "") {
34 | if (title) {
35 | document.title = title;
36 | } else {
37 | document.title = settingsDefaultTab.title;
38 | }
39 | var tab = await settings.get("tab");
40 | if (tab) {
41 | try {
42 | var tabData = tab;
43 | } catch {
44 | var tabData = {};
45 | }
46 | } else {
47 | var tabData = {};
48 | }
49 | if (title) {
50 | tabData.title = title;
51 | } else {
52 | delete tabData.title;
53 | }
54 | await settings.set("tab", tabData);
55 | }
56 |
57 | self.setFavicon = async function (icon) {
58 | if (icon) {
59 | document.querySelector("link[rel='icon']").href = icon;
60 | } else {
61 | document.querySelector("link[rel='icon']").href = settingsDefaultTab.icon;
62 | }
63 | var tab = await settings.get("tab");
64 | if (tab) {
65 | try {
66 | var tabData = tab;
67 | } catch {
68 | var tabData = {};
69 | }
70 | } else {
71 | var tabData = {};
72 | }
73 | if (icon) {
74 | tabData.icon = icon;
75 | } else {
76 | delete tabData.icon;
77 | }
78 | await settings.set("tab", tabData);
79 | }
80 |
81 | self.setCloak = function () {
82 | var cloak = document.getElementById("premadecloaks").value;
83 | switch (cloak) {
84 | case "search":
85 | setTitle("Google Search");
86 | setFavicon("/assets/imgs/icons/cloaks/Google Search.ico");
87 | break;
88 | case "drive":
89 | setTitle("Google Drive");
90 | setFavicon("/assets/imgs/icons/cloaks/Google Drive.ico");
91 | break;
92 | case "youtube":
93 | setTitle("YouTube");
94 | setFavicon("/assets/imgs/icons/cloaks/YouTube.ico");
95 | break;
96 | case "gmail":
97 | setTitle("Gmail");
98 | setFavicon("/assets/imgs/icons/cloaks/Gmail.ico");
99 | break;
100 | case "calendar":
101 | setTitle("Google Calendar");
102 | setFavicon("/assets/imgs/icons/cloaks/Calendar.ico");
103 | break;
104 | case "meets":
105 | setTitle("Google Meet");
106 | setFavicon("/assets/imgs/icons/cloaks/Meet.ico");
107 | break;
108 | case "classroom":
109 | setTitle("Google Classroom");
110 | setFavicon("/assets/imgs/icons/cloaks/Classroom.png");
111 | break;
112 | case "canvas":
113 | setTitle("Canvas");
114 | setFavicon("/assets/imgs/icons/cloaks/Canvas.ico");
115 | break;
116 | case "zoom":
117 | setTitle("Zoom");
118 | setFavicon("/assets/imgs/icons/cloaks/Zoom.ico");
119 |
120 | break;
121 | case "khan":
122 | setTitle("Khan Academy");
123 | setFavicon("/assets/imgs/icons/cloaks/Khan Academy.ico");
124 |
125 | break;
126 | }
127 | }
128 |
129 | self.resetTab = async function () {
130 | document.title = settingsDefaultTab.title;
131 | document.querySelector("link[rel='icon']").href = settingsDefaultTab.icon;
132 | document.getElementById("title").value = "";
133 | document.getElementById("icon").value = "";
134 | var tabData = {
135 | title: settingsDefaultTab.title,
136 | icon: settingsDefaultTab.icon,
137 | };
138 | await settings.get("tab", tabData);
139 | }
140 |
141 | document.addEventListener("DOMContentLoaded", async () => {
142 | if (await settings.get("tab")) {
143 | updateTabItem();
144 | }
145 |
146 | window.addEventListener("settings", function (event) {
147 | if (event.detail.key === "tab") {
148 | updateTabItem();
149 | }
150 | });
151 |
152 | async function updateTabItem() {
153 | var tabItem = await settings.get("tab");
154 | document.title = tabItem.title;
155 | var favicon = document.querySelector('link[rel="icon"]');
156 | if (favicon) {
157 | favicon.href = tabItem.icon;
158 | }
159 | }
160 | });
161 | })();
--------------------------------------------------------------------------------
/public/assets/js/settings_manager.js:
--------------------------------------------------------------------------------
1 | class SettingsManager {
2 | constructor(user, dbName) {
3 | this.user = user || "shadow";
4 | this.dbName = dbName || "settingsDB";
5 | this.storeName = `${this.user}-settings` || "fluid-settings";
6 | this.dbPromise = this.initDB();
7 | }
8 |
9 | initDB() {
10 | return new Promise((resolve, reject) => {
11 | const request = indexedDB.open(this.dbName, 1);
12 |
13 | request.onupgradeneeded = (e) => {
14 | const db = e.target.result;
15 | if (!db.objectStoreNames.contains(this.storeName)) {
16 | db.createObjectStore(this.storeName);
17 | }
18 | };
19 |
20 | request.onsuccess = (e) => {
21 | this.db = e.target.result;
22 | resolve(this.db);
23 | };
24 |
25 | request.onerror = (e) => {
26 | console.error("Settings database error:", e.target.errorCode);
27 | reject(e.target.errorCode);
28 | };
29 | });
30 | }
31 |
32 | async default(key, value) {
33 | if (await this.get(key) === undefined) {
34 | this.set(key, value);
35 | return "set";
36 | } else {
37 | return "preset";
38 | }
39 | }
40 |
41 | async toggle(key) {
42 | const initialVal = await this.get(key);
43 | if (typeof initialVal === "boolean" || typeof initialVal === "undefined") {
44 | await this.set(key, !initialVal);
45 | return "Toggled";
46 | } else {
47 | return new TypeError(`Value at ${key} is not a boolean`);
48 | }
49 | }
50 |
51 | async set(key, value) {
52 | try {
53 | const db = await this.dbPromise;
54 | const oldValue = await this.get(key);
55 | return new Promise((resolve, reject) => {
56 | const transaction = db.transaction([this.storeName], "readwrite");
57 | const store = transaction.objectStore(this.storeName);
58 | const request = store.put(value, key);
59 | request.onsuccess = () => {
60 | resolve();
61 | const event = new CustomEvent("settings", {
62 | bubbles: true,
63 | detail: {
64 | key: key,
65 | newValue: value, // Updated here
66 | oldValue: oldValue,
67 | database: this.dbName,
68 | success: true,
69 | }
70 | });
71 | self.dispatchEvent(event);
72 | };
73 | request.onerror = (e) => {
74 | reject(e.target.error);
75 | };
76 | });
77 | } catch (error) {
78 | console.error("Error setting value:", error);
79 | }
80 | }
81 |
82 |
83 | async get(key) {
84 | try {
85 | const db = await this.dbPromise;
86 | return new Promise((resolve, reject) => {
87 | const transaction = db.transaction([this.storeName]);
88 | const store = transaction.objectStore(this.storeName);
89 | const request = store.get(key);
90 | request.onsuccess = (e) => {
91 | resolve(e.target.result);
92 | };
93 | request.onerror = (e) => {
94 | reject(e.target.error);
95 | };
96 | });
97 | } catch (error) {
98 | console.error("Error getting value:", error);
99 | }
100 | }
101 |
102 | async remove(key) {
103 | try {
104 | const db = await this.dbPromise;
105 | return new Promise((resolve, reject) => {
106 | const transaction = db.transaction([this.storeName], "readwrite");
107 | const store = transaction.objectStore(this.storeName);
108 | const request = store.delete(key);
109 | request.onsuccess = () => {
110 | resolve();
111 | };
112 | request.onerror = (e) => {
113 | reject(e.target.error);
114 | };
115 | });
116 | } catch (error) {
117 | console.error("Error removing value:", error);
118 | }
119 | }
120 |
121 | async clear() {
122 | try {
123 | const db = await this.dbPromise;
124 | return new Promise((resolve, reject) => {
125 | const transaction = db.transaction([this.storeName], "readwrite");
126 | const store = transaction.objectStore(this.storeName);
127 | const request = store.clear();
128 | request.onsuccess = () => {
129 | resolve();
130 | };
131 | request.onerror = (e) => {
132 | reject(e.target.error);
133 | };
134 | });
135 | } catch (error) {
136 | console.error("Error clearing store:", error);
137 | }
138 | }
139 | }
140 |
141 | export { SettingsManager };
--------------------------------------------------------------------------------
/public/assets/js/settings_manager_sw.js:
--------------------------------------------------------------------------------
1 | //Required to use importScripts("") syntax on service workers instead of traditional import {} from ""
2 |
3 | class SettingsManager {
4 | constructor(user, dbName) {
5 | this.user = user || "shadow";
6 | this.dbName = dbName || "settingsDB";
7 | this.storeName = `${this.user}-settings` || "fluid-settings";
8 | this.dbPromise = this.initDB();
9 | }
10 |
11 | initDB() {
12 | return new Promise((resolve, reject) => {
13 | const request = indexedDB.open(this.dbName, 1);
14 |
15 | request.onupgradeneeded = (e) => {
16 | const db = e.target.result;
17 | if (!db.objectStoreNames.contains(this.storeName)) {
18 | db.createObjectStore(this.storeName);
19 | }
20 | };
21 |
22 | request.onsuccess = (e) => {
23 | this.db = e.target.result;
24 | resolve(this.db);
25 | };
26 |
27 | request.onerror = (e) => {
28 | console.error("Settings database error:", e.target.errorCode);
29 | reject(e.target.errorCode);
30 | };
31 | });
32 | }
33 |
34 | async default(key, value) {
35 | if (await this.get(key) === undefined) {
36 | this.set(key, value);
37 | return "set";
38 | } else {
39 | return "preset";
40 | }
41 | }
42 |
43 | async toggle(key) {
44 | const initialVal = await this.get(key);
45 | if (typeof initialVal === "boolean" || typeof initialVal === "undefined") {
46 | await this.set(key, !initialVal);
47 | return "Toggled";
48 | } else {
49 | return new TypeError(`Value at ${key} is not a boolean`);
50 | }
51 | }
52 |
53 | async set(key, value) {
54 | try {
55 | const db = await this.dbPromise;
56 | const oldValue = await this.get(key);
57 | return new Promise((resolve, reject) => {
58 | const transaction = db.transaction([this.storeName], "readwrite");
59 | const store = transaction.objectStore(this.storeName);
60 | const request = store.put(value, key);
61 | request.onsuccess = () => {
62 | resolve();
63 | const event = new CustomEvent("settings", {
64 | bubbles: true,
65 | detail: {
66 | key: key,
67 | newValue: value, // Updated here
68 | oldValue: oldValue,
69 | database: this.dbName,
70 | success: true,
71 | }
72 | });
73 | self.dispatchEvent(event);
74 | };
75 | request.onerror = (e) => {
76 | reject(e.target.error);
77 | };
78 | });
79 | } catch (error) {
80 | console.error("Error setting value:", error);
81 | }
82 | }
83 |
84 |
85 | async get(key) {
86 | try {
87 | const db = await this.dbPromise;
88 | return new Promise((resolve, reject) => {
89 | const transaction = db.transaction([this.storeName]);
90 | const store = transaction.objectStore(this.storeName);
91 | const request = store.get(key);
92 | request.onsuccess = (e) => {
93 | resolve(e.target.result);
94 | };
95 | request.onerror = (e) => {
96 | reject(e.target.error);
97 | };
98 | });
99 | } catch (error) {
100 | console.error("Error getting value:", error);
101 | }
102 | }
103 |
104 | async remove(key) {
105 | try {
106 | const db = await this.dbPromise;
107 | return new Promise((resolve, reject) => {
108 | const transaction = db.transaction([this.storeName], "readwrite");
109 | const store = transaction.objectStore(this.storeName);
110 | const request = store.delete(key);
111 | request.onsuccess = () => {
112 | resolve();
113 | };
114 | request.onerror = (e) => {
115 | reject(e.target.error);
116 | };
117 | });
118 | } catch (error) {
119 | console.error("Error removing value:", error);
120 | }
121 | }
122 |
123 | async clear() {
124 | try {
125 | const db = await this.dbPromise;
126 | return new Promise((resolve, reject) => {
127 | const transaction = db.transaction([this.storeName], "readwrite");
128 | const store = transaction.objectStore(this.storeName);
129 | const request = store.clear();
130 | request.onsuccess = () => {
131 | resolve();
132 | };
133 | request.onerror = (e) => {
134 | reject(e.target.error);
135 | };
136 | });
137 | } catch (error) {
138 | console.error("Error clearing store:", error);
139 | }
140 | }
141 | }
142 |
143 | self.SettingsManager = SettingsManager;
--------------------------------------------------------------------------------
/public/assets/js/themes.js:
--------------------------------------------------------------------------------
1 | import { SettingsManager } from "./settings_manager.js";
2 |
3 | const settings = new SettingsManager();
4 |
5 | export async function changeTheme(selectedTheme) {
6 | setTheme(selectedTheme);
7 | if (parent !== window) parent.changeTheme(selectedTheme);
8 | await settings.set("theme", selectedTheme);
9 | localStorage.setItem("theme", selectedTheme);
10 | }
11 |
12 | function setRootVars(variables) {
13 | const root = document.documentElement;
14 | for (const [key, value] of Object.entries(variables)) {
15 | root.style.setProperty(key, value);
16 | }
17 | }
18 |
19 | function clearRootVars(variableKeys) {
20 | const root = document.documentElement;
21 | variableKeys.forEach(key => root.style.removeProperty(key));
22 | }
23 |
24 | export async function setTheme(theme) {
25 | const root = document.documentElement;
26 |
27 | const cssVariables = {
28 | '--background': '',
29 | '--home-bg': '',
30 | '--primary': '',
31 | '--primary-dark': '',
32 | '--secondary': '',
33 | '--accent': '',
34 | '--accent-light': '',
35 | '--accent-glow': '',
36 | '--border': '',
37 | '--text': '',
38 | '--text-muted': '',
39 | '--hover-danger': '',
40 | '--active-tab': ''
41 | };
42 |
43 | if (theme === "custom") {
44 | const customTheme = JSON.parse(await settings.get("custom"));
45 | root.className = "custom";
46 | cssVariables['--background'] = customTheme.background;
47 | cssVariables['--home-bg'] = customTheme.homeBg;
48 | cssVariables['--primary'] = customTheme.primary;
49 | cssVariables['--primary-dark'] = customTheme.primaryDark;
50 | cssVariables['--secondary'] = customTheme.secondary;
51 | cssVariables['--accent'] = customTheme.accent;
52 | cssVariables['--accent-light'] = customTheme.accentLight;
53 | cssVariables['--accent-glow'] = customTheme.accentGlow;
54 | cssVariables['--border'] = customTheme.border;
55 | cssVariables['--text'] = customTheme.text;
56 | cssVariables['--text-muted'] = customTheme.textMuted;
57 | cssVariables['--hover-danger'] = customTheme.hoverDanger;
58 | cssVariables['--active-tab'] = customTheme.activeTab;
59 | } else {
60 | root.className = theme;
61 | clearRootVars(Object.keys(cssVariables));
62 | }
63 |
64 | setRootVars(cssVariables);
65 | }
66 |
67 | window.addEventListener("storage", function (e) {
68 | if (e.key === "theme") {
69 | setTheme(e.newValue);
70 | } else if (e.key === "custom") {
71 | if (localStorage.getItem("theme") === "custom") {
72 | setTheme("custom");
73 | }
74 | }
75 | });
76 |
77 | document.addEventListener("DOMContentLoaded", async () => {
78 | const theme = localStorage.getItem("theme") || await settings.get("theme");
79 | if (theme) {
80 | setTheme(theme);
81 | const themeSelector = document.getElementById("themeSelector");
82 | if (themeSelector) {
83 | themeSelector.value = theme;
84 | }
85 | }
86 | });
87 |
88 | function shadeColor(color, percent) {
89 | let R = parseInt(color.substring(1, 3), 16);
90 | let G = parseInt(color.substring(3, 5), 16);
91 | let B = parseInt(color.substring(5, 7), 16);
92 |
93 | R = parseInt((R * (100 + percent)) / 100);
94 | G = parseInt((G * (100 + percent)) / 100);
95 | B = parseInt((B * (100 + percent)) / 100);
96 |
97 | R = R < 255 ? R : 255;
98 | G = G < 255 ? G : 255;
99 | B = B < 255 ? B : 255;
100 |
101 | const RR = R.toString(16).padStart(2, '0');
102 | const GG = G.toString(16).padStart(2, '0');
103 | const BB = B.toString(16).padStart(2, '0');
104 |
105 | return `#${RR}${GG}${BB}`;
106 | }
107 |
108 | function hexToRgb(hex) {
109 | const r = parseInt(hex.substring(1, 3), 16);
110 | const g = parseInt(hex.substring(3, 5), 16);
111 | const b = parseInt(hex.substring(5, 7), 16);
112 | return `${r}, ${g}, ${b}`;
113 | }
114 |
115 |
116 | function genTheme(baseColor) {
117 | const themeVariables = {
118 | background: `linear-gradient(145deg, ${shadeColor(baseColor, -75)}, ${shadeColor(baseColor, -60)})`,
119 | homeBg: `linear-gradient(145deg, ${shadeColor(baseColor, -45)}, ${shadeColor(baseColor, -75)}, ${shadeColor(baseColor, -90)})`,
120 | primary: shadeColor(baseColor, -75),
121 | primaryDark: shadeColor(baseColor, -85),
122 | secondary: shadeColor(baseColor, -65),
123 | accent: shadeColor(baseColor, 20),
124 | accentLight: shadeColor(baseColor, 40),
125 | accentGlow: `rgba(${hexToRgb(baseColor)}, 0.2)`,
126 | border: `rgba(${hexToRgb(baseColor)}, 0.3)`,
127 | text: '#fff',
128 | textMuted: `rgba(${hexToRgb(shadeColor(baseColor, 60))}, 0.8)`,
129 | hoverDanger: '#f87171',
130 | activeTab: `linear-gradient(145deg, ${shadeColor(baseColor, -20)}, ${shadeColor(baseColor, -65)})`
131 | };
132 | return themeVariables;
133 | }
134 |
135 | export async function applyCustomTheme() {
136 | const color = document.getElementById('themeColorPicker').value;
137 | const themeVars = genTheme(color);
138 | await settings.set("custom", JSON.stringify(themeVars, null, 2));
139 | changeTheme("custom");
140 | }
141 |
--------------------------------------------------------------------------------
/public/assets/js/versioncheck.js:
--------------------------------------------------------------------------------
1 | import { SettingsManager } from "./settings_manager.js";
2 |
3 | const settings = new SettingsManager();
4 |
5 | onload = async () => {
6 | if (
7 | (await fetch("/version")).headers["version"] !=
8 | await settings.get.getItem("version")
9 | )
10 | caches.keys().then((list) => list.map((key) => caches.delete(key)));
11 | location.reload();
12 | };
13 |
--------------------------------------------------------------------------------
/public/assets/js/wispchecker.js:
--------------------------------------------------------------------------------
1 | import { WispWebSocket } from "./wispclient.js";
2 |
3 | String.prototype.delete = function (snippet) {
4 | return this.replace(new RegExp(snippet, 'g'), '');
5 | };
6 |
7 | export async function checkWispUrl(url) {
8 | if(checkWispServer(url)) return url;
9 |
10 | url = `wss://${url.delete("https://").delete("http://").delete("ws://").delete("wss://").delete("/wisp/").delete("/wisp").delete("/")}/wisp/`
11 | if (await checkWispServer(url)) {
12 | return url
13 | }
14 |
15 | return `wss://${location.origin}/wisp/`
16 | }
17 |
18 | export async function checkWispServer(url) {
19 | console.log("[WISP] Checking url: " + url);
20 | const ws = new WispWebSocket(url);
21 |
22 | return new Promise((resolve) => {
23 | ws.addEventListener("open", () => {
24 | ws.close(); // Close the WebSocket once opened
25 | resolve(true); // Resolve promise as successful
26 | });
27 |
28 | ws.addEventListener("error", () => {
29 | console.error(`WebSocket connection failed for: ${url}`);
30 | resolve(false); // Resolve promise as unsuccessful on error
31 | });
32 |
33 | ws.addEventListener("close", (event) => {
34 | if (event.code !== 1000) { // 1000 is normal closure
35 | console.warn(`WebSocket closed unexpectedly: ${event.reason}`);
36 | }
37 | });
38 |
39 | // Timeout to handle cases where the connection never opens
40 | const timeout = setTimeout(() => {
41 | ws.close(); // Close WebSocket if not opened
42 | resolve(false); // Resolve as unsuccessful
43 | }, 500); // Wait 5 seconds for a connection
44 |
45 | ws.addEventListener("close", () => {
46 | clearTimeout(timeout); // Clear timeout on closure
47 | });
48 | });
49 | }
--------------------------------------------------------------------------------
/public/books/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 | Games
13 |
14 |
15 |
16 | Games
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
--------------------------------------------------------------------------------
/public/books/script.js:
--------------------------------------------------------------------------------
1 | document.addEventListener('DOMContentLoaded', () => {
2 | fetchGames();
3 | setupEventListeners();
4 | });
5 |
6 | async function fetchGames() {
7 | try {
8 | const response = await fetch("games.json");
9 | if (!response.ok) throw new Error('Network response was not ok');
10 | const games = await response.json();
11 | renderGames(games);
12 | } catch (error) {
13 | console.error("Error loading games:", error);
14 | }
15 | }
16 |
17 | function renderGames(games) {
18 | const container = document.getElementById("gcontainer");
19 |
20 | games.sort((a, b) => a.name.localeCompare(b.name));
21 |
22 | const fragment = document.createDocumentFragment();
23 |
24 | const observer = createLazyLoadObserver();
25 |
26 | games.forEach((game) => {
27 | const gElement = createGameElement(game);
28 | fragment.appendChild(gElement);
29 |
30 | observer.observe(gElement);
31 | });
32 |
33 | container.appendChild(fragment);
34 |
35 | setupSearchFunctionality(container);
36 | }
37 |
38 | function createLazyLoadObserver() {
39 | return new IntersectionObserver((entries, observer) => {
40 | entries.forEach(entry => {
41 | if (entry.isIntersecting) {
42 | const gameElement = entry.target;
43 | const imgElement = gameElement.querySelector('img[data-src]');
44 |
45 | if (imgElement) {
46 | imgElement.src = imgElement.dataset.src;
47 | imgElement.removeAttribute('data-src');
48 | }
49 |
50 | observer.unobserve(gameElement);
51 | }
52 | });
53 | }, {
54 | rootMargin: '200px',
55 | threshold: 0.01
56 | });
57 | }
58 |
59 | function createGameElement(game) {
60 | const gElement = document.createElement("div");
61 | gElement.classList.add("g");
62 |
63 | const imgElement = document.createElement("img");
64 | imgElement.dataset.src = `files/${game.root}/${game.img}`;
65 | imgElement.alt = game.name;
66 | imgElement.classList.add("game-image");
67 |
68 | imgElement.src = "data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 1 1'%3E%3C/svg%3E";
69 |
70 | const gameName = document.createElement("p");
71 | gameName.classList.add("game-name");
72 | gameName.textContent = game.name;
73 |
74 | const playButton = document.createElement("button");
75 | playButton.classList.add("game-play-button");
76 | playButton.innerHTML = ' ';
77 |
78 | gElement.dataset.root = game.root;
79 | gElement.dataset.file = game.file;
80 | gElement.dataset.name = game.name;
81 | gElement.dataset.img = `files/${game.root}/${game.img}`;
82 |
83 | gElement.appendChild(imgElement);
84 | gElement.appendChild(gameName);
85 | gElement.appendChild(playButton);
86 |
87 | return gElement;
88 | }
89 |
90 | function setupSearchFunctionality(container) {
91 | const searchBar = document.getElementById("__shadow-search-bar");
92 |
93 | let debounceTimeout;
94 | searchBar.addEventListener("input", () => {
95 | clearTimeout(debounceTimeout);
96 | debounceTimeout = setTimeout(() => {
97 | const query = searchBar.value.toLowerCase();
98 |
99 | requestAnimationFrame(() => {
100 | const gameElements = container.querySelectorAll(".g");
101 | gameElements.forEach((gameElement) => {
102 | const gameName = gameElement.dataset.name.toLowerCase();
103 | const match = gameName.includes(query);
104 | gameElement.style.display = match ? "flex" : "none";
105 | });
106 | });
107 | }, 150);
108 | });
109 | }
110 |
111 | function setupEventListeners() {
112 |
113 | document.getElementById("gcontainer").addEventListener("click", (e) => {
114 | const gameElement = e.target.closest(".g");
115 | if (gameElement) {
116 | const root = gameElement.dataset.root;
117 | const file = gameElement.dataset.file;
118 | const name = gameElement.dataset.name;
119 | const img = gameElement.dataset.img;
120 | launch(`files/${root}/${file}`, name, img);
121 | }
122 | });
123 |
124 | document.getElementById("fullscreen").addEventListener("click", () => {
125 | const iframeContainer = document.querySelector(".gDisplay");
126 | gameFullscreen(iframeContainer);
127 | });
128 |
129 | document.getElementById("exit").addEventListener("click", () => {
130 | showiframe();
131 | });
132 |
133 | document.getElementById("refresh").addEventListener("click", () => {
134 | const gIframe = document.getElementById("gIframe");
135 | gIframe.contentWindow.location.reload();
136 | });
137 | }
138 |
139 | function launch(src, name, gameimg) {
140 | const gDisplay = document.querySelector(".gDisplay");
141 | const gDisplayImg = document.getElementById("gDisplayImg");
142 | const gDisplayName = document.getElementById("gDisplayName");
143 | const gIframe = document.getElementById("gIframe");
144 |
145 | gDisplayImg.src = gameimg;
146 | gDisplayName.textContent = name;
147 |
148 | gDisplay.classList.add("active");
149 | document.body.classList.add("no-scroll");
150 |
151 | setTimeout(() => {
152 | gIframe.src = src;
153 | }, 50);
154 | }
155 |
156 | function showiframe() {
157 | const gDisplay = document.querySelector(".gDisplay");
158 | const gIframe = document.getElementById("gIframe");
159 | gDisplay.classList.remove("active");
160 |
161 | setTimeout(() => {
162 | gIframe.src = "";
163 | }, 100);
164 | document.body.classList.remove("no-scroll");
165 | }
166 |
167 | function gameFullscreen(element) {
168 | if (!document.fullscreenElement) {
169 | element.requestFullscreen().catch(err => {
170 | console.error(`Error attempting to enable fullscreen: ${err.message}`);
171 | });
172 | } else {
173 | document.exitFullscreen();
174 | }
175 | }
--------------------------------------------------------------------------------
/public/chat/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Shadow Chat
6 |
7 |
22 |
23 |
24 |
28 |
29 |
30 |
--------------------------------------------------------------------------------
/public/css/404.css:
--------------------------------------------------------------------------------
1 | * {
2 | margin: 0;
3 | padding: 0;
4 | box-sizing: border-box;
5 | font-family: 'Poppins', sans-serif;
6 | }
7 |
8 | body {
9 | background: var(--background);
10 | color: var(--text);
11 | min-height: 100vh;
12 | display: flex;
13 | flex-direction: column;
14 | justify-content: center;
15 | align-items: center;
16 | overflow: hidden;
17 | position: relative;
18 | }
19 |
20 | body::before {
21 | content: '';
22 | position: absolute;
23 | width: 100%;
24 | height: 100vh;
25 | background-image:
26 | radial-gradient(var(--accent-light) 1px, transparent 1px),
27 | radial-gradient(var(--text-muted) 1px, transparent 1px);
28 | background-size: 50px 50px, 100px 100px;
29 | background-position: 0 0, 25px 25px;
30 | animation: starsMove 60s linear infinite;
31 | opacity: 0.4;
32 | z-index: -1;
33 | }
34 |
35 | @keyframes starsMove {
36 | 0% {
37 | transform: translateY(0);
38 | }
39 | 100% {
40 | transform: translateY(-100%);
41 | }
42 | }
43 |
44 | h1 {
45 | font-size: 12rem;
46 | font-weight: 900;
47 | color: transparent;
48 | background: linear-gradient(45deg, var(--accent), var(--accent-light));
49 | -webkit-background-clip: text;
50 | background-clip: text;
51 | text-shadow: 0 0 20px var(--accent-glow), 0 0 40px var(--accent-glow);
52 | margin-bottom: 1rem;
53 | letter-spacing: -5px;
54 | position: relative;
55 | animation: pulse 4s infinite ease-in-out;
56 | }
57 |
58 | h1::before, h1::after {
59 | content: "404";
60 | position: absolute;
61 | top: 0;
62 | left: 0;
63 | width: 100%;
64 | height: 100%;
65 | background: linear-gradient(45deg, var(--accent), var(--accent-light));
66 | -webkit-background-clip: text;
67 | background-clip: text;
68 | color: transparent;
69 | z-index: -1;
70 | }
71 |
72 | h1::before {
73 | text-shadow: 3px 0 var(--accent-glow);
74 | animation: glitch 2s infinite linear alternate-reverse;
75 | }
76 |
77 | h1::after {
78 | text-shadow: -3px 0 var(--accent-light);
79 | animation: glitch 3s infinite linear alternate-reverse;
80 | }
81 |
82 | @keyframes glitch {
83 | 0% {
84 | transform: translateX(-2px) skew(0deg);
85 | }
86 | 10% {
87 | transform: translateX(2px) skew(0deg);
88 | }
89 | 20% {
90 | transform: translateX(-2px) skew(0deg);
91 | }
92 | 30% {
93 | transform: translateX(0px) skew(5deg);
94 | }
95 | 40% {
96 | transform: translateX(0px) skew(0deg);
97 | }
98 | 100% {
99 | transform: translateX(0px) skew(0deg);
100 | }
101 | }
102 |
103 | @keyframes pulse {
104 | 0%, 100% {
105 | opacity: 1;
106 | }
107 | 50% {
108 | opacity: 0.8;
109 | }
110 | }
111 |
112 | p {
113 | font-size: 1.5rem;
114 | font-weight: 300;
115 | color: var(--text-muted);
116 | text-align: center;
117 | line-height: 1.8;
118 | margin-bottom: 2rem;
119 | max-width: 80%;
120 | text-shadow: 0 0 10px var(--accent-glow);
121 | }
122 |
123 | .hyper-link {
124 | color: var(--accent-light);
125 | text-decoration: none;
126 | position: relative;
127 | font-weight: 500;
128 | transition: color 0.3s;
129 | display: inline-block;
130 | padding: 0 5px;
131 | }
132 |
133 | .hyper-link:hover {
134 | color: var(--accent);
135 | cursor: pointer;
136 | }
137 |
138 | .hyper-link::after {
139 | content: '';
140 | position: absolute;
141 | width: 100%;
142 | height: 2px;
143 | bottom: -2px;
144 | left: 0;
145 | background: var(--accent);
146 | box-shadow: 0 0 8px var(--accent-glow);
147 | transform: scaleX(0);
148 | transform-origin: bottom right;
149 | transition: transform 0.3s ease-out;
150 | }
151 |
152 | .hyper-link:hover::after {
153 | transform: scaleX(1);
154 | transform-origin: bottom left;
155 | }
156 |
157 | .btn {
158 | padding: 0.8rem 2.5rem;
159 | background: linear-gradient(145deg, var(--primary), var(--secondary));
160 | color: var(--text);
161 | font-size: 1.1rem;
162 | font-weight: 500;
163 | border: 2px solid var(--border);
164 | border-radius: 50px;
165 | cursor: pointer;
166 | transition: all 0.3s ease;
167 | position: relative;
168 | overflow: hidden;
169 | box-shadow: 0 0 15px var(--accent-glow);
170 | z-index: 1;
171 | text-decoration: none;
172 | display: inline-block;
173 | }
174 |
175 | .btn::before {
176 | content: '';
177 | position: absolute;
178 | top: 0;
179 | left: 0;
180 | width: 0%;
181 | height: 100%;
182 | background: var(--accent);
183 | transition: all 0.4s ease;
184 | z-index: -1;
185 | border-radius: 40px;
186 | }
187 |
188 | .btn:hover {
189 | transform: translateY(-3px);
190 | box-shadow: 0 5px 20px var(--accent-glow);
191 | color: var(--primary-dark);
192 | text-shadow: 0 0 2px rgba(255, 255, 255, 0.5);
193 | }
194 |
195 | .btn:hover::before {
196 | width: 100%;
197 | }
198 |
199 | .btn:active {
200 | transform: translateY(2px);
201 | box-shadow: 0 2px 10px var(--accent-glow);
202 | }
203 |
204 | @media (max-width: 768px) {
205 | h1 {
206 | font-size: 8rem;
207 | }
208 |
209 | p {
210 | font-size: 1.2rem;
211 | }
212 |
213 | .btn {
214 | padding: 0.7rem 2rem;
215 | font-size: 1rem;
216 | }
217 | }
218 |
219 | @media (max-width: 480px) {
220 | h1 {
221 | font-size: 6rem;
222 | }
223 |
224 | p {
225 | font-size: 1rem;
226 | margin-bottom: 1.5rem;
227 | }
228 |
229 | .btn {
230 | padding: 0.6rem 1.8rem;
231 | font-size: 0.9rem;
232 | }
233 | }
--------------------------------------------------------------------------------
/public/css/ai.css:
--------------------------------------------------------------------------------
1 | body {
2 | margin: 0;
3 | font-family: 'Poppins', sans-serif;
4 | background: var(--background);
5 | color: var(--text);
6 | display: flex;
7 | flex-direction: column;
8 | align-items: center;
9 | justify-content: space-between;
10 | height: 100vh;
11 | }
12 |
13 | .header {
14 | display: flex;
15 | justify-content: space-between;
16 | align-items: center;
17 | width: 90%;
18 | max-width: 1200px;
19 | margin-bottom: 10px;
20 | flex-wrap: wrap;
21 | }
22 |
23 | .header h1 {
24 | font-size: 2.5rem;
25 | background: linear-gradient(90deg, var(--accent), var(--text));
26 | background-size: 200% 100%;
27 | background-position: 0% 50%;
28 | -webkit-background-clip: text;
29 | background-clip: text;
30 | -webkit-text-fill-color: transparent;
31 | transition: background-position 0.7s ease;
32 | cursor: pointer;
33 | }
34 |
35 | .header h1:hover {
36 | background-position: 250% 50%;
37 | }
38 |
39 | .header p a {
40 | color: var(--accent);
41 | text-decoration: none;
42 | transition: color 0.3s ease;
43 | font-size: 1.2rem;
44 | font-weight: bold;
45 | }
46 |
47 | .header p a:hover {
48 | color: #fff;
49 | text-shadow: 0 0 5px var(--accent);
50 | }
51 |
52 | .messages {
53 | width: 100%;
54 | max-width: 1200px;
55 | margin: 10px 0;
56 | flex-grow: 1;
57 | overflow-y: auto;
58 | }
59 |
60 | .message {
61 | padding: 1px 20px;
62 | margin-top: 10px;
63 | margin-bottom: 10px;
64 | border-radius: 8px;
65 | cursor: pointer;
66 | transition: transform 0.3s ease, background-color 0.3s ease;
67 | opacity: 0;
68 | animation: fadeIn 0.5s forwards;
69 | }
70 |
71 | .messages::-webkit-scrollbar {
72 | display: none;
73 | }
74 |
75 | .message.assistant {
76 | background: var(--active-tab);
77 | align-self: flex-start;
78 | border-radius: 20px 20px 20px 0;
79 | margin-right: auto;
80 | width: 70%;
81 | }
82 |
83 | .message.user {
84 | background: linear-gradient(145deg, var(--accent), var(--border));
85 | align-self: flex-start;
86 | width: 70%;
87 | border-radius: 20px 20px 0 20px;
88 | margin-left: auto;
89 | }
90 |
91 | .message:hover {
92 | transform: translateY(-5px);
93 | }
94 |
95 | .input-container {
96 | display: flex;
97 | align-items: center;
98 | background-color: var(--secondary);
99 | padding: 10px;
100 | border-radius: 10px;
101 | box-shadow: 0 4px 10px rgba(0, 0, 0, 0.4);
102 | width: 90%;
103 | max-width: 1200px;
104 | margin-bottom: 25px;
105 | }
106 |
107 | textarea {
108 | flex: 1;
109 | background: transparent;
110 | border: none;
111 | color: var(--text);
112 | font-size: 1rem;
113 | font-family: 'Poppins', sans-serif;
114 | outline: none;
115 | overflow-y: scroll;
116 | scrollbar-width: none;
117 | -ms-overflow-style: none;
118 | resize: none;
119 | }
120 |
121 | textarea::placeholder {
122 | color: rgba(255, 255, 255, 0.7);
123 | }
124 |
125 | .send-btn {
126 | background-color: var(--accent);
127 | border: none;
128 | padding: 12px;
129 | border-radius: 8px;
130 | cursor: pointer;
131 | display: flex;
132 | align-items: center;
133 | justify-content: center;
134 | transition: background-color 0.3s ease, transform 0.2s ease;
135 | }
136 |
137 | .send-btn:hover {
138 | background-color: var(--primary);
139 | transform: translateY(-3px);
140 | }
141 |
142 | .send-btn svg {
143 | width: 20px;
144 | height: 20px;
145 | fill: #fff;
146 | transition: transform 0.3s ease;
147 | }
148 |
149 | .send-btn:hover svg {
150 | transform: scale(1.2);
151 | }
152 |
153 | @keyframes fadeIn {
154 | to {
155 | opacity: 1;
156 | }
157 | }
158 |
159 | .spinner {
160 | border: 4px solid var(--secondary);
161 | border-top: 4px solid var(--accent);
162 | border-radius: 50%;
163 | width: 20px;
164 | height: 20px;
165 | animation: spin 1s linear infinite;
166 | margin-right: 4px;
167 | }
168 |
169 | @keyframes spin {
170 | 0% { transform: rotate(0deg); }
171 | 100% { transform: rotate(360deg); }
172 | }
173 |
174 | .message.loading {
175 | display: flex;
176 | align-items: center;
177 | width: fit-content;
178 | padding: 15px;
179 | justify-content: flex-start;
180 | color: var(--text);
181 | }
182 |
183 | .message.show {
184 | opacity: 1;
185 | }
186 |
187 | .message strong { font-weight: bold; }
188 |
189 | .message em { font-style: italic; }
190 |
191 | .message a {
192 | color: var(--accent);
193 | text-decoration: none;
194 | border-bottom: 1px solid var(--accent);
195 | transition: all 0.3s ease;
196 | }
197 |
198 | .message a:hover {
199 | color: var(--primary);
200 | background-color: var(--accent);
201 | padding: 2px 4px;
202 | border-radius: 4px;
203 | }
204 |
205 | .message pre {
206 | background-color: var(--secondary);
207 | color: var(--text);
208 | padding: 15px;
209 | border-radius: 6px;
210 | overflow-x: auto;
211 | font-family: 'Courier New', Courier, monospace;
212 | white-space: pre-wrap;
213 | word-wrap: break-word;
214 | }
215 |
216 | .message code {
217 | background-color: var(--secondary);
218 | color: var(--text);
219 | padding: 2px 4px;
220 | border-radius: 4px;
221 | font-family: 'Courier New', Courier, monospace;
222 | }
223 |
224 | .message ul, .message ol {
225 | margin-left: 20px;
226 | }
227 |
228 | .message li {
229 | margin-bottom: 5px;
230 | }
231 |
232 | .message blockquote {
233 | background-color: var(--secondary);
234 | color: var(--text);
235 | border-left: 5px solid var(--accent);
236 | padding: 10px;
237 | margin: 10px 0;
238 | font-style: italic;
239 | }
240 |
241 | @media (max-width: 768px) {
242 | .header {
243 | flex-direction: column;
244 | text-align: center;
245 | }
246 |
247 | .header h1 {
248 | font-size: 2rem;
249 | margin-bottom: 10px;
250 | }
251 |
252 | .header p a {
253 | font-size: 1rem;
254 | }
255 |
256 | .input-container {
257 | flex-direction: column;
258 | align-items: stretch;
259 | }
260 |
261 | .send-btn {
262 | margin-top: 10px;
263 | width: 100%;
264 | }
265 |
266 | .message {
267 | width: 90%;
268 | margin: 5px auto;
269 | }
270 |
271 | .message.assistant,
272 | .message.user {
273 | width: 100%;
274 | }
275 | }
276 |
277 | @media (max-width: 480px) {
278 | .header h1 {
279 | font-size: 1.5rem;
280 | }
281 |
282 | .header p a {
283 | font-size: 1rem;
284 | }
285 |
286 | .message {
287 | padding: 8px 15px;
288 | }
289 |
290 | .input-container {
291 | width: 95%;
292 | }
293 |
294 | .send-btn {
295 | width: 100%;
296 | }
297 |
298 | .message.assistant,
299 | .message.user {
300 | width: 100%;
301 | }
302 | }
303 |
--------------------------------------------------------------------------------
/public/css/context.css:
--------------------------------------------------------------------------------
1 | :root {
2 | --background-color: #1c1c38;
3 | --tab-background: #262648;
4 | --tab-border: #4d4d86;
5 | --text-color: #ffffff;
6 | --accent-color: #6c3da3;
7 | }
8 |
9 | :root.light {
10 | --background-color: #f0e8f9;
11 | --tab-background: #262648;
12 | --tab-border: #4d4d86;
13 | --text-color: #333;
14 | --accent-color: #8f6caf;
15 | }
16 |
17 | :root.blueneon {
18 | --background-color: #001a33;
19 | --tab-background: #00203c;
20 | --tab-border: #00a3e0;
21 | --text-color: #ffffff;
22 | --accent-color: #00a6fb;
23 | }
24 |
25 | :root.sunset {
26 | --background-color: #fce6cf;
27 | --tab-background: #ffd9b3;
28 | --tab-border: #e27d60;
29 | --text-color: #2c3e50;
30 | --accent-color: #ffb6b9;
31 | }
32 |
33 | :root.ocean {
34 | --background-color: #003366;
35 | --tab-background: #005cbf;
36 | --tab-border: #0077cc;
37 | --text-color: #ffffff;
38 | --accent-color: #00a1ff;
39 | }
40 |
41 | :root.crimsonblaze {
42 | --background-color: #2c0104;
43 | --tab-background: #492727;
44 | --tab-border: #854c4c;
45 | --text-color: #ffffff;
46 | --accent-color: #db0f0f;
47 | }
48 |
49 | :root.amoled {
50 | --background-color: #000000;
51 | --tab-background: #000000;
52 | --tab-border: #ffffff;
53 | --text-color: #ffffff;
54 | --accent-color: #f2f2f2;
55 | }
56 | :root.crimsonpurple {
57 | --background-color: #2a0238;
58 | --tab-background: #3d2749;
59 | --tab-border: #734c85;
60 | --text-color: #ffffff;
61 | --accent-color: #583163;
62 | }
63 |
64 | :root.immortal {
65 | --background-color: #083344;
66 | --tab-background: #0a3b4f;
67 | --tab-border: #0e7490;
68 | --text-color: #ffffff;
69 | --accent-color: #0891b2;
70 | }
71 |
72 | #custom-menu {
73 | position: fixed;
74 | top: 0;
75 | left: 0;
76 | width: 100%;
77 | height: 100%;
78 | display: block;
79 | background-color: rgba(0, 0, 0, 0.7);
80 | z-index: 9999;
81 | }
82 |
83 | #custom-menu ul {
84 | position: absolute;
85 | top: 50%;
86 | left: 50%;
87 | transform: translate(-50%, -50%);
88 | list-style: none;
89 | padding: 0;
90 | }
91 |
92 | #custom-menu li {
93 | margin: 10px 0;
94 | }
95 |
96 | #custom-menu li a {
97 | color: var(--main-text-color);
98 | text-decoration: none;
99 | font-family: "Exo 2", sans-serif;
100 | }
101 |
102 | #custom-menu li a i {
103 | margin-right: 5px;
104 | }
105 |
106 | #custom-menu li:hover {
107 | background-color: rgba(255, 255, 255, 0.3);
108 | padding: 8px 15px;
109 | border-radius: 5px;
110 | cursor: pointer;
111 | }
112 |
113 | #custom-menu li:hover a {
114 | color: var(--main-text-color);
115 | }
116 |
117 | #custom-menu li:focus {
118 | outline: none;
119 | }
120 |
121 | #custom-menu li:focus a {
122 | color: var(--main-text-color);
123 | }
124 |
--------------------------------------------------------------------------------
/public/css/credits.css:
--------------------------------------------------------------------------------
1 | * {
2 | margin: 0;
3 | padding: 0;
4 | box-sizing: border-box;
5 | font-family: 'Poppins', sans-serif;
6 | transition: all 0.3s ease;
7 | }
8 |
9 | body,
10 | html {
11 | height: 100%;
12 | background: var(--background);
13 | background-color: var(--primary);
14 | color: var(--text);
15 | overflow-x: hidden;
16 | position: relative;
17 | text-align: center;
18 | scroll-behavior: smooth;
19 | }
20 |
21 |
22 | @keyframes pulse-bg {
23 | 0% {
24 | opacity: 0.2;
25 | transform: scale(1);
26 | }
27 | 50% {
28 | opacity: 0.6;
29 | transform: scale(1.05);
30 | }
31 | 100% {
32 | opacity: 0.4;
33 | transform: scale(1);
34 | }
35 | }
36 |
37 |
38 | h1 {
39 | font-size: 80px;
40 | font-weight: 700;
41 | margin-top: 30px;
42 | background: linear-gradient(45deg, var(--accent), var(--accent-light));
43 | -webkit-background-clip: text;
44 | background-clip: text;
45 | -webkit-text-fill-color: transparent;
46 | animation: title-glow 2s ease-in-out infinite alternate;
47 | }
48 |
49 | @keyframes title-glow {
50 | 0% {
51 | text-shadow: 0 2px 8px var(--accent-glow);
52 | }
53 | 100% {
54 | text-shadow: 0 8px 20px rgba(59, 130, 246, 0.8);
55 | }
56 | }
57 |
58 | .menu-bar {
59 | display: flex;
60 | padding: 20px;
61 | justify-content: center;
62 | margin: 5px auto;
63 | gap: 35px;
64 | max-width: 900px;
65 | flex-wrap: wrap;
66 | }
67 |
68 | .option {
69 | background: rgba(0, 0, 0, 0.6);
70 | padding: 14px 35px;
71 | border-radius: 50px;
72 | width: 180px;
73 | color: var(--text);
74 | font-size: 18px;
75 | font-weight: 600;
76 | text-align: center;
77 | display: flex;
78 | align-items: center;
79 | justify-content: center;
80 | gap: 12px;
81 | cursor: pointer;
82 | border: 1px solid var(--border);
83 | backdrop-filter: blur(6px);
84 | transition: transform 0.3s ease, background-color 0.3s ease;
85 | }
86 |
87 | .option:hover {
88 | background: var(--accent);
89 | transform: translateY(-5px);
90 | }
91 |
92 | .option.active {
93 | background: var(--active-tab);
94 | transform: translateY(-3px) scale(1.05);
95 | border-color: var(--accent);
96 | }
97 |
98 | .option svg {
99 | fill: var(--text);
100 | width: 20px;
101 | height: 20px;
102 | }
103 |
104 | .option:hover svg {
105 | transform: scale(1.2);
106 | }
107 |
108 | .content {
109 | width: 100%;
110 | margin: 0 auto;
111 | margin-top: -30px;
112 | padding: 30px;
113 | display: flex;
114 | justify-content: center;
115 | flex-direction: column;
116 | gap: 50px;
117 | }
118 |
119 |
120 |
121 |
122 | .owners, .staff {
123 | display: grid;
124 | grid-template-columns: repeat(3, 1fr);
125 | gap: 40px;
126 | justify-content: center;
127 | width: 100%;
128 | perspective: 1000px;
129 | margin-top: 25px;
130 | }
131 |
132 | .user-card {
133 | background: var(--primary);
134 | padding: 30px;
135 | border-radius: 18px;
136 | box-shadow: 0 0 15px var(--accent-glow);
137 | text-align: center;
138 | cursor: pointer;
139 | border: 1px solid var(--border);
140 | backdrop-filter: blur(12px);
141 | position: relative;
142 | overflow: hidden;
143 | width: 500px;
144 | transition: all 0.3s ease;
145 | }
146 |
147 | .user-card:hover {
148 | transform: translateY(-15px) rotateX(6deg) scale(1.05);
149 | box-shadow: 0 15px 30px var(--accent-glow);
150 | border-color: var(--accent-light);
151 | }
152 |
153 | .user-card .intro {
154 | display: flex;
155 | flex-direction: column;
156 | align-items: center;
157 | }
158 |
159 | .user-card .profile {
160 | width: 120px;
161 | height: 120px;
162 | border-radius: 50%;
163 | object-fit: cover;
164 | margin-bottom: 12px;
165 | box-shadow: 0 0 25px var(--accent-glow);
166 | border: 4px solid var(--border);
167 | }
168 |
169 | .user-card:hover .profile {
170 | box-shadow: 0 0 35px var(--accent-glow);
171 | transform: scale(1.1);
172 | }
173 |
174 | .user-card .name {
175 | font-size: 26px;
176 | font-weight: 700;
177 | margin: 10px 0;
178 | color: var(--text);
179 | position: relative;
180 | display: inline-block;
181 | }
182 |
183 | .user-card .name::after {
184 | content: '';
185 | position: absolute;
186 | bottom: -7px;
187 | left: 50%;
188 | transform: translateX(-50%);
189 | width: 0;
190 | height: 2px;
191 | background: var(--accent);
192 | }
193 |
194 | .user-card:hover .name::after {
195 | width: 85%;
196 | }
197 |
198 | .roles {
199 | display: flex;
200 | flex-direction: row;
201 | justify-content: center;
202 | gap: 12px;
203 | margin: 18px 0;
204 | flex-wrap: wrap;
205 | }
206 |
207 | .role {
208 | background-color: transparent;
209 | padding: 8px 18px;
210 | border-radius: 30px;
211 | min-width: 110px;
212 | font-size: 14px;
213 | font-weight: 600;
214 | color: var(--accent-light);
215 | border: 1px solid var(--border);
216 | }
217 |
218 | .user-card:hover .role {
219 | background-color: var(--accent);
220 | color: var(--primary);
221 | transform: translateY(-3px);
222 | }
223 |
224 | .description {
225 | font-size: 16px;
226 | line-height: 1.7;
227 | color: var(--text-muted);
228 | margin: 25px 0;
229 | }
230 |
231 | .user-card:hover .description {
232 | color: var(--text);
233 | }
234 |
235 | .location {
236 | font-size: 15px;
237 | color: var(--text-muted);
238 | display: flex;
239 | align-items: center;
240 | justify-content: center;
241 | gap: 10px;
242 | margin-top: 20px;
243 | }
244 |
245 | .location svg {
246 | width: 18px;
247 | height: 18px;
248 | fill: var(--accent);
249 | animation: pulse 2s infinite;
250 | }
251 |
252 | @keyframes pulse {
253 | 0% {
254 | transform: scale(0.8);
255 | opacity: 0.7;
256 | }
257 | 50% {
258 | transform: scale(1.2);
259 | opacity: 1;
260 | }
261 | 100% {
262 | transform: scale(0.8);
263 | opacity: 0.7;
264 | }
265 | }
266 |
267 | @media (max-width: 768px) {
268 | .menu-bar {
269 | gap: 20px;
270 | }
271 |
272 | .option {
273 | width: 160px;
274 | padding: 12px 25px;
275 | font-size: 15px;
276 | }
277 |
278 | .user-card {
279 | padding: 25px;
280 | }
281 |
282 | .user-card .profile {
283 | width: 110px;
284 | height: 110px;
285 | }
286 | }
287 |
288 | @keyframes fadeIn {
289 | from {
290 | opacity: 0;
291 | transform: translateY(30px);
292 | }
293 | to {
294 | opacity: 1;
295 | transform: translateY(0);
296 | }
297 | }
298 |
299 | .animate-in {
300 | animation: fadeIn 0.6s ease forwards;
301 | }
302 |
--------------------------------------------------------------------------------
/public/css/history.css:
--------------------------------------------------------------------------------
1 | body {
2 | background: var(--background);
3 | overflow-y: hidden;
4 | background-image: var(--home-bg);
5 | background-size: cover;
6 | background-repeat: no-repeat;
7 | background-position: 0% 50%;
8 | font-family: 'Poppins', sans-serif;
9 | color: var(--text);
10 | min-height: 100vh;
11 | margin: 0;
12 | padding: 0;
13 | }
14 |
15 | .container {
16 | width: 90%;
17 | margin: 20px auto;
18 | }
19 |
20 | .header {
21 | display: flex;
22 | justify-content: space-between;
23 | align-items: center;
24 | margin-bottom: 20px;
25 | }
26 |
27 | .header h1 {
28 | font-size: 24px;
29 | }
30 |
31 | .searchBar {
32 | width: 200px;
33 | padding: 12px;
34 | border-radius: 4px;
35 | background-color: var(--secondary);
36 | color: var(--text);
37 | font-family: 'Poppins', sans-serif;
38 | border: 1px solid var(--border);
39 | transition: background-color 0.3s;
40 | }
41 |
42 | .searchBar:focus {
43 | outline: none;
44 | border-color: var(--accent);
45 | }
46 |
47 | .day-section {
48 | margin-bottom: 20px;
49 | }
50 |
51 | .day-section h2 {
52 | margin-bottom: 10px;
53 | font-size: 18px;
54 | color: var(--accent);
55 | }
56 |
57 | .history-list {
58 | background-color: var(--secondary);
59 | border-radius: 8px;
60 | padding: 10px;
61 | border: 1px solid var(--border);
62 | }
63 |
64 | .history-item {
65 | cursor: pointer;
66 | display: flex;
67 | justify-content: space-between;
68 | align-items: center;
69 | padding: 12px;
70 | border-bottom: 1px solid var(--border);
71 | }
72 |
73 | .history-item:last-child {
74 | border-bottom: none;
75 | }
76 |
77 | .history-info {
78 | display: flex;
79 | align-items: center;
80 | gap: 10px;
81 | }
82 |
83 | .favicon {
84 | width: 20px;
85 | height: 20px;
86 | }
87 |
88 | .history-time {
89 | font-size: 14px;
90 | color: var(--text-muted);
91 | }
92 |
93 | .history-title {
94 | color: var(--text);
95 | font-size: 16px;
96 | }
97 |
98 | .history-actions {
99 | display: flex;
100 | align-items: center;
101 | }
102 |
103 | .history-actions input[type="checkbox"] {
104 | appearance: none;
105 | width: 18px;
106 | height: 18px;
107 | border: 2px solid var(--accent);
108 | border-radius: 4px;
109 | cursor: pointer;
110 | margin-right: 10px;
111 | transition: background-color 0.3s;
112 | }
113 |
114 | .history-actions input[type="checkbox"]:checked {
115 | background-color: var(--accent);
116 | border: none;
117 | }
118 |
119 | .deleteBtn {
120 | display: inline-block;
121 | background-color: var(--border);
122 | color: var(--text);
123 | font-family: 'Poppins', sans-serif;
124 | padding: 12px 16px;
125 | margin-left: auto;
126 | margin-right: 15px;
127 | border: none;
128 | border-radius: 4px;
129 | cursor: pointer;
130 | }
131 |
132 | ::-webkit-scrollbar {
133 | width: 10px;
134 | }
135 |
136 | ::-webkit-scrollbar-thumb {
137 | background-color: var(--accent);
138 | border-radius: 10px;
139 | }
140 |
--------------------------------------------------------------------------------
/public/css/home.css:
--------------------------------------------------------------------------------
1 | * {
2 | box-sizing: border-box;
3 | font-family: 'Poppins', sans-serif;
4 | }
5 |
6 | body {
7 | color: var(--text);
8 | display: flex;
9 | flex-direction: column;
10 | align-items: center;
11 | text-align: center;
12 | gap: 1.4rem;
13 | min-height: 100vh;
14 | overflow: hidden;
15 | background:var(--home-bg);
16 | background-position: 0% 50%;
17 | background-repeat: no-repeat;
18 | background-size: cover;
19 |
20 | }
21 |
22 |
23 | img {
24 | width: 90px;
25 | height: 90px;
26 | filter: drop-shadow(0 0 12px var(--accent-glow));
27 | transition: 0.3s ease;
28 | margin-top: 3rem;
29 | }
30 |
31 | h1 {
32 | font-size: 10rem;
33 | font-weight: 800;
34 | background: linear-gradient(90deg, var(--accent), var(--accent-light));
35 | -webkit-background-clip: text;
36 | background-clip: text;
37 | -webkit-text-fill-color: transparent;
38 | text-shadow: 0 0 12px var(--accent-glow);
39 | cursor:pointer;
40 | letter-spacing: -1px;
41 | margin-top: 0.5rem;
42 | }
43 |
44 | @keyframes textGlow {
45 | 0% {
46 | text-shadow: 0 0 12px var(--accent-glow);
47 | }
48 | 50% {
49 | text-shadow: 0 0 24px var(--accent), 0 0 12px var(--accent-glow);
50 | }
51 | 100% {
52 | text-shadow: 0 0 12px var(--accent-glow);
53 | }
54 | }
55 |
56 | p {
57 | font-size: 1.1rem;
58 | color: var(--text-muted);
59 | max-width: 700px;
60 | letter-spacing: 0.4px;
61 | cursor:pointer;
62 | line-height: 1.6;
63 | margin-top: -9.9rem;
64 | opacity: 0.9;
65 | }
66 |
--------------------------------------------------------------------------------
/public/css/login.css:
--------------------------------------------------------------------------------
1 | * {
2 | margin: 0;
3 | padding: 0;
4 | box-sizing: border-box;
5 | font-family: 'Poppins', 'Segoe UI', system-ui, -apple-system, sans-serif;
6 | }
7 |
8 | body {
9 | background: var(--background);
10 | color: var(--text);
11 | min-height: 100vh;
12 | display: flex;
13 | align-items: center;
14 | justify-content: center;
15 | padding: 20px;
16 | }
17 |
18 | .container {
19 | width: 100%;
20 | max-width: 420px;
21 | margin: 0 auto;
22 | }
23 |
24 | .login-card {
25 | background-color: var(--secondary);
26 | border-radius: 16px;
27 | box-shadow: 0 10px 25px rgba(0, 0, 0, 0.4);
28 | padding: 32px;
29 | backdrop-filter: blur(10px);
30 | border: 1px solid var(--border);
31 | position: relative;
32 | overflow: hidden;
33 | }
34 |
35 | .login-card::before {
36 | content: '';
37 | position: absolute;
38 | top: -50%;
39 | left: -50%;
40 | width: 200%;
41 | height: 200%;
42 | background: radial-gradient(
43 | circle at center,
44 | var(--accent-glow),
45 | transparent 50%
46 | );
47 | opacity: 0.4;
48 | z-index: -1;
49 | }
50 |
51 | .card-header {
52 | text-align: center;
53 | margin-bottom: 32px;
54 | }
55 |
56 | .logo {
57 | display: flex;
58 | justify-content: center;
59 | margin-bottom: 24px;
60 | }
61 |
62 | .logo-circle {
63 | width: 64px;
64 | height: 64px;
65 | border-radius: 50%;
66 | background: var(--active-tab);
67 | display: flex;
68 | align-items: center;
69 | justify-content: center;
70 | box-shadow: 0 0 15px var(--accent-glow);
71 | border: 1px solid var(--border);
72 | }
73 |
74 | .shadowicon{
75 | width:40px;
76 | height:40px;
77 | }
78 |
79 | .card-header h1 {
80 | font-size: 24px;
81 | font-weight: 600;
82 | margin-bottom: 8px;
83 | background: linear-gradient(to right, var(--text), var(--accent-light));
84 | -webkit-background-clip: text;
85 | -webkit-text-fill-color: transparent;
86 | }
87 |
88 | .card-header p {
89 | color: var(--text-muted);
90 | font-size: 14px;
91 | }
92 |
93 | .login-form {
94 | display: flex;
95 | flex-direction: column;
96 | gap: 20px;
97 | }
98 |
99 | .input-group {
100 | display: flex;
101 | flex-direction: column;
102 | gap: 8px;
103 | }
104 |
105 | .label-group {
106 | display: flex;
107 | justify-content: space-between;
108 | align-items: center;
109 | }
110 |
111 | label {
112 | font-size: 14px;
113 | font-weight: 500;
114 | color: var(--text);
115 | }
116 |
117 | .forgot-password {
118 | font-size: 12px;
119 | color: var(--accent);
120 | text-decoration: none;
121 | transition: color 0.2s;
122 | }
123 |
124 | .forgot-password:hover {
125 | color: var(--accent-light);
126 | text-decoration: underline;
127 | }
128 |
129 | .input-with-icon {
130 | position: relative;
131 | display: flex;
132 | align-items: center;
133 | }
134 |
135 | .input-icon {
136 | position: absolute;
137 | left: 12px;
138 | color: var(--text-muted);
139 | pointer-events: none;
140 | }
141 |
142 | .input-with-icon input {
143 | width: 100%;
144 | padding: 12px 12px 12px 42px;
145 | background-color: var(--primary);
146 | border: 1px solid var(--border);
147 | border-radius: 8px;
148 | color: var(--text);
149 | font-size: 14px;
150 | transition: all 0.2s ease;
151 | }
152 |
153 | .input-with-icon input:focus {
154 | outline: none;
155 | border-color: var(--accent);
156 | box-shadow: 0 0 0 3px var(--accent-glow);
157 | }
158 |
159 | .input-with-icon input::placeholder {
160 | color: rgba(186, 230, 253, 0.5);
161 | }
162 |
163 | .toggle-password {
164 | position: absolute;
165 | right: 12px;
166 | background: none;
167 | border: none;
168 | color: var(--text-muted);
169 | cursor: pointer;
170 | padding: 0;
171 | display: flex;
172 | align-items: center;
173 | justify-content: center;
174 | }
175 |
176 | .toggle-password:hover svg {
177 | fill: var(--accent);
178 | }
179 |
180 | .remember-me {
181 | display: flex;
182 | align-items: center;
183 | gap: 8px;
184 | }
185 |
186 | .remember-me input[type="checkbox"] {
187 | appearance: none;
188 | width: 16px;
189 | height: 16px;
190 | border: 1px solid var(--border);
191 | border-radius: 4px;
192 | background-color: var(--primary);
193 | cursor: pointer;
194 | position: relative;
195 | }
196 |
197 | .remember-me input[type="checkbox"]:checked {
198 | background-color: var(--accent);
199 | border-color: var(--accent);
200 | }
201 |
202 | .remember-me input[type="checkbox"]:checked::after {
203 | content: '✓';
204 | position: absolute;
205 | color: var(--text);
206 | font-size: 12px;
207 | top: 50%;
208 | left: 50%;
209 | transform: translate(-50%, -50%);
210 | }
211 |
212 | .remember-me label {
213 | font-size: 13px;
214 | color: var(--text-muted);
215 | cursor: pointer;
216 | }
217 |
218 | .login-button {
219 | background: linear-gradient(145deg,var(--primary), var(--accent), var(--primary));
220 | color: var(--text);
221 | border: none;
222 | border-radius: 8px;
223 | padding: 12px;
224 | font-size: 14px;
225 | font-weight: 600;
226 | cursor: pointer;
227 | transition: all 0.2s ease;
228 | margin-top: 8px;
229 | }
230 |
231 | .login-button:hover {
232 | transform: translateY(-2px);
233 | box-shadow: 0 6px 15px var(--accent-glow);
234 | }
235 |
236 | .login-button:active {
237 | transform: translateY(0);
238 | }
239 |
240 | .sign-up {
241 | text-align: center;
242 | margin-top: 24px;
243 | font-size: 14px;
244 | color: var(--text-muted);
245 | }
246 |
247 | .sign-up a {
248 | color: var(--accent);
249 | text-decoration: none;
250 | transition: color 0.2s;
251 | }
252 |
253 | .sign-up a:hover {
254 | color: var(--accent-light);
255 | text-decoration: underline;
256 | }
257 |
258 | @media (max-width: 480px) {
259 | .login-card {
260 | padding: 24px;
261 | }
262 |
263 | .card-header h1 {
264 | font-size: 22px;
265 | }
266 |
267 | .logo-circle {
268 | width: 56px;
269 | height: 56px;
270 | }
271 | }
--------------------------------------------------------------------------------
/public/css/main.css:
--------------------------------------------------------------------------------
1 | * {
2 | font-family: "Poppins", sans-serif;
3 | box-sizing: border-box;
4 | }
5 |
6 | body,
7 | html {
8 | margin: 0;
9 | padding: 0;
10 | flex-direction: column;
11 |
12 | height: 100%;
13 | background: var(--background);
14 | color: var(--text);
15 | overflow: hidden;
16 | }
17 |
18 | .bg-dots {
19 | position: fixed;
20 | width: 100%;
21 | height: 100%;
22 | background: radial-gradient(var(--accent) 8%, transparent 10%) 0 0 / 25px 25px;
23 | opacity: 0.2;
24 | animation: dotsMove 6s linear infinite;
25 | mask-image: radial-gradient(circle, rgba(0,0,0,1) 50%, rgba(0,0,0,0) 100%);
26 | -webkit-mask-image: radial-gradient(circle, rgba(0,0,0,1) 50%, rgba(0,0,0,0) 100%);
27 | }
28 |
29 | @keyframes dotsMove {
30 | from {
31 | background-position: 0 0;
32 | }
33 | to {
34 | background-position: -40px -40px;
35 | }
36 | }
37 |
38 | .container {
39 | display: flex;
40 | align-items: center;
41 | flex-direction: column;
42 | justify-content: center;
43 | height: 100vh;
44 | margin-top: -5%;
45 | }
46 |
47 | h1 {
48 | font-size: 90px;
49 | font-weight: bold;
50 | text-transform: capitalize;
51 | text-align: center;
52 | background: linear-gradient(45deg, var(--border), var(--accent), var(--primary));
53 | -webkit-background-clip: text;
54 | background-clip: text;
55 | color: transparent;
56 | margin-top: 0;
57 | animation: fadeIn 0.6s ease forwards;
58 | }
59 |
60 | .search-container {
61 | display: flex;
62 | flex-direction: row;
63 | align-items: center;
64 | justify-content: space-between;
65 | height: 50px;
66 | width: 35vw;
67 | padding: 0 15px;
68 | background: linear-gradient(145deg, var(--primary), var(--secondary), var(--primary-dark));
69 | border-radius: 50px;
70 | box-shadow: 0 4px 20px var(--accent-glow);
71 | margin-top: -3vw;
72 | position: relative;
73 | transition: all 0.3s ease-in-out;
74 | }
75 |
76 | .search-container:hover {
77 | box-shadow: 0 8px 30px rgba(0, 0, 0, 0.3);
78 | transform: translateY(-5px);
79 | }
80 |
81 | .search-container input {
82 | border: none;
83 | outline: none;
84 | width: 34vw;
85 | font-size: 16px;
86 | color: var(--text);
87 | background: transparent;
88 | padding: 10px 15px;
89 | transition: all 0.3s ease;
90 | }
91 |
92 | .search-container:focus-within {
93 | background-color: var(--primary-dark);
94 | box-shadow: 0 0 5px var(--accent);
95 | border-radius: 15px;
96 | }
97 |
98 | .search-icon {
99 | color: var(--accent);
100 | cursor: pointer;
101 | transition: color 0.3s ease;
102 | position: absolute;
103 | right: 20px;
104 | }
105 |
106 | .search-icon:hover {
107 | color: var(--border);
108 | }
109 |
110 | .search-icon svg {
111 | fill: var(--accent);
112 | width: 20px;
113 | height: 20px;
114 | }
115 |
116 | .shortcuts {
117 | display: grid;
118 | grid-template-columns: repeat(auto-fit, minmax(100px, 30px));
119 | gap: 15px;
120 | justify-content: center;
121 | align-items: center;
122 | width: 36vw;
123 | margin-top: 30px;
124 | animation: scaleIn 0.8s ease forwards;
125 | }
126 |
127 | .shortcut {
128 | display: flex;
129 | flex-direction: column;
130 | align-items: center;
131 | justify-content: center;
132 | text-align: center;
133 | text-decoration: none;
134 | color: var(--text);
135 | transition: all 0.3s;
136 | width: 100px;
137 | height: 90px;
138 | background-color: var(--secondary);
139 | border-radius: 5px;
140 | padding: 10px;
141 | position: relative;
142 | }
143 |
144 | .shortcut:hover {
145 | background-color: rgba(0, 0, 0, 0.3);
146 | cursor: pointer;
147 | }
148 |
149 | .shortcut img {
150 | width: 50px;
151 | height: 50px;
152 | border-radius: 50%;
153 | object-fit: cover;
154 | }
155 |
156 | .shortcut p {
157 | margin-top: 10px;
158 | font-size: 15px;
159 | color: var(--text);
160 | display: none;
161 | position: absolute;
162 | bottom: -15px;
163 | background-color: var(--secondary);
164 | padding: 5px;
165 | font-weight: 500;
166 | width: 100%;
167 | text-align: center;
168 | opacity: 0;
169 | transition: opacity 0.3s ease, transform 0.3s ease;
170 | transform: translateY(10px);
171 | }
172 |
173 | .shortcut:hover p {
174 | display: block;
175 | opacity: 1;
176 | transform: translateY(0);
177 | }
178 |
179 |
180 | .add img {
181 | background-color: var(--accent);
182 | }
183 |
184 | .modal {
185 | display: none;
186 | position: fixed;
187 | z-index: 1;
188 | padding-top: 100px;
189 | left: 0;
190 | top: 0;
191 | width: 100%;
192 | height: 100%;
193 | overflow: auto;
194 | background-color: rgba(0, 0, 0, 0.6);
195 | }
196 |
197 | .modal-content {
198 | background-color: var(--primary);
199 | margin: auto;
200 | padding: 20px;
201 | border: 1px solid var(--border);
202 | border-radius: 5px;
203 | box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
204 | width: 300px;
205 | color: var(--text);
206 | }
207 |
208 | .modal h2 {
209 | color: var(--text);
210 | }
211 |
212 | .close {
213 | color: var(--text);
214 | float: right;
215 | font-size: 28px;
216 | font-weight: bold;
217 | cursor: pointer;
218 | }
219 |
220 | .close:hover {
221 | color: var(--accent);
222 | }
223 |
224 | #shortcut-form,
225 | #edit-shortcut-form {
226 | display: flex;
227 | flex-direction: column;
228 | }
229 |
230 | #shortcut-form label,
231 | #edit-shortcut-form label {
232 | color: var(--text);
233 | margin-top: 10px;
234 | }
235 |
236 | #shortcut-form input,
237 | #edit-shortcut-form input {
238 | border: 1px solid var(--border);
239 | border-radius: 5px;
240 | padding: 8px;
241 | margin-top: 5px;
242 | }
243 |
244 | #add-shortcut,
245 | #edit-shortcut {
246 | background-color: var(--accent);
247 | color: var(--text);
248 | border: none;
249 | border-radius: 5px;
250 | padding: 8px;
251 | margin-top: 10px;
252 | cursor: pointer;
253 | }
254 |
255 | #add-shortcut:hover {
256 | background-color: var(--border);
257 | }
258 |
259 | #shortcut-context-menu {
260 | position: absolute;
261 | display: none;
262 | z-index: 1;
263 | background-color: var(--primary);
264 | border: 1px solid var(--border);
265 | border-radius: 5px;
266 | box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
267 | text-align: left;
268 | padding: 8px;
269 | width: fit-content;
270 | margin: 0;
271 | }
272 |
273 | .shortcut-context-menu ul {
274 | text-decoration: none;
275 | list-style: none;
276 | }
277 |
278 | .context-menu li {
279 | padding: 5px;
280 | cursor: pointer;
281 | display: flex;
282 | align-items: center;
283 | gap: 5px;
284 | }
285 |
286 | .context-menu li:hover {
287 | background-color: rgba(0, 0, 0, 0.2);
288 | border-radius: 5px;
289 | }
290 |
291 | svg {
292 | width: 20px;
293 | height: 20px;
294 | vertical-align: text-top;
295 | fill: var(--text);
296 | }
297 |
298 | .sponsor {
299 | display: block;
300 | border-radius: 5px;
301 | opacity: 0.8;
302 | margin: 0 auto;
303 | max-width: 100%;
304 | box-shadow: 0 0 10px 5px red;
305 | position: fixed;
306 | bottom: 15%;
307 | left: 50%;
308 | transform: translateX(-50%);
309 | }
310 |
311 | /* ANIMATIONS */
312 | @keyframes fadeIn {
313 | from {
314 | opacity: 0;
315 | transform: translateY(20px);
316 | }
317 | to {
318 | opacity: 1;
319 | transform: translateY(0);
320 | }
321 | }
322 |
323 | @keyframes scaleIn {
324 | from {
325 | transform: scale(0.5);
326 | }
327 | to {
328 | transform: scale(1);
329 | }
330 | }
331 |
--------------------------------------------------------------------------------
/public/css/overlay.css:
--------------------------------------------------------------------------------
1 | .overlay {
2 | position: fixed;
3 | top: 0;
4 | left: 0;
5 | width: 100%;
6 | height: 100%;
7 | background: rgba(0, 0, 0, 0.8);
8 | backdrop-filter: blur(12px);
9 | display: flex;
10 | justify-content: center;
11 | align-items: center;
12 | z-index: 1000;
13 | color: white;
14 | font-family: "Poppins", sans-serif;
15 | font-size: 2rem;
16 | text-align: center;
17 | }
18 |
--------------------------------------------------------------------------------
/public/css/subscriptions.css:
--------------------------------------------------------------------------------
1 |
2 | * {
3 | margin: 0;
4 | padding: 0;
5 | box-sizing: border-box;
6 | }
7 |
8 | body {
9 | font-family: 'Poppins', sans-serif;
10 | background: var(--home-bg);
11 | color: var(--text);
12 | line-height: 1.6;
13 | position: relative;
14 | overflow-x: hidden;
15 | }
16 |
17 | body::before {
18 | content: '';
19 | position: fixed;
20 | top: 0;
21 | left: 0;
22 | width: 100%;
23 | height: 100%;
24 | background:
25 | radial-gradient(circle at 20% 50%, var(--accent-glow) 0%, transparent 50%),
26 | radial-gradient(circle at 80% 20%, rgba(147, 197, 253, 0.1) 0%, transparent 40%),
27 | radial-gradient(circle at 40% 80%, var(--accent-glow) 0%, transparent 50%),
28 | linear-gradient(135deg, rgba(1, 3, 16, 0.9), rgba(8, 12, 23, 0.95));
29 | z-index: -1;
30 | animation: backgroundShift 20s ease-in-out infinite;
31 | }
32 |
33 |
34 | .container {
35 | max-width: 1200px;
36 | margin: 0 auto;
37 | padding: 80px 20px;
38 | }
39 |
40 | .header {
41 | text-align: center;
42 | margin-bottom: 50px;
43 | margin-top: -30px;
44 | }
45 |
46 | .title {
47 | font-size: clamp(1rem, 4vw, 4rem);
48 | font-weight: 800;
49 | background: linear-gradient(135deg, #ffffff, var(--accent-light), var(--accent));
50 | -webkit-background-clip: text;
51 | -webkit-text-fill-color: transparent;
52 | background-clip: text;
53 | margin-bottom: 24px;
54 | letter-spacing: -0.03em;
55 | text-shadow: 0 0 40px var(--accent-glow);
56 | position: relative;
57 | }
58 |
59 | .title::after {
60 | content: '';
61 | position: absolute;
62 | bottom: -10px;
63 | left: 50%;
64 | transform: translateX(-50%);
65 | width: 80px;
66 | height: 4px;
67 | background: linear-gradient(90deg, var(--accent), var(--accent-light));
68 | border-radius: 2px;
69 | animation: titleGlow 2s ease-in-out infinite alternate;
70 | }
71 |
72 | @keyframes titleGlow {
73 | from {
74 | box-shadow: 0 0 10px var(--accent-glow);
75 | }
76 |
77 | to {
78 | box-shadow: 0 0 20px var(--accent-glow), 0 0 30px var(--accent-glow);
79 | }
80 | }
81 |
82 | .subtitle {
83 | font-size: 1.3rem;
84 | color: var(--text-muted);
85 | font-weight: 300;
86 | max-width: 700px;
87 | margin: 0 auto;
88 | line-height: 1.7;
89 | opacity: 0.9;
90 | text-align: center;
91 | padding: 0 20px;
92 | }
93 |
94 | .plans-grid {
95 | display: grid;
96 | grid-template-columns: repeat(auto-fit, minmax(320px, 1fr));
97 | gap: 30px;
98 | max-width: 1000px;
99 | margin: 0 auto;
100 | }
101 |
102 | .plan-card {
103 | background: rgba(10, 14, 26, 0.8);
104 | border: 1px solid var(--border);
105 | border-radius: 24px;
106 | background: linear-gradient(135deg, var(--primary-dark) 25%, var(--accent));
107 | padding: 40px 30px;
108 | cursor:pointer;
109 | position: relative;
110 | transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1);
111 | backdrop-filter: blur(20px);
112 | box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3);
113 | }
114 |
115 | .plan-card::before {
116 | content: '';
117 | position: absolute;
118 | inset: 0;
119 | padding: 1px;
120 | border-radius: 24px;
121 | opacity: 0;
122 | transition: opacity 0.3s ease;
123 | }
124 |
125 | .plan-card:hover {
126 | transform: translateY(-8px);
127 | box-shadow: 0 20px 40px var(--accent-glow);
128 | }
129 |
130 | .plan-card:hover::before {
131 | opacity: 1;
132 | }
133 |
134 | .plan-card.featured {
135 | border: 1px solid var(--accent);
136 | background: linear-gradient(135deg, var(--secondary) 0%, var(--primary-dark) 30%, var(--primary) 50%, var(--accent-glow) 75%);
137 | transform: scale(1.02);
138 | }
139 |
140 | .plan-card.featured::before {
141 | opacity: 1;
142 | }
143 |
144 | .plan-badge {
145 | position: absolute;
146 | top: -12px;
147 | left: 50%;
148 | transform: translateX(-50%);
149 | background: var(--accent);
150 | color: white;
151 | padding: 8px 20px;
152 | border-radius: 20px;
153 | font-size: 0.85rem;
154 | font-weight: 600;
155 | text-transform: uppercase;
156 | letter-spacing: 0.5px;
157 | }
158 |
159 | .plan-header {
160 | text-align: center;
161 | margin-bottom: 40px;
162 | }
163 |
164 | .plan-name {
165 | font-size: 1.8rem;
166 | font-weight: 700;
167 | margin-bottom: 12px;
168 | color: var(--text);
169 | }
170 |
171 | .plan-description {
172 | color: var(--text-muted);
173 | font-size: 0.95rem;
174 | line-height: 1.5;
175 | height:10px;
176 | }
177 |
178 | .plan-price {
179 | text-align: center;
180 | margin-bottom: 40px;
181 | }
182 |
183 | .price-container {
184 | display: flex;
185 | align-items: baseline;
186 | justify-content: center;
187 | margin-bottom: 8px;
188 | }
189 |
190 | .price-currency {
191 | font-size: 1.5rem;
192 | font-weight: 600;
193 | color: var(--accent);
194 | }
195 |
196 | .price-amount {
197 | font-size: 3.5rem;
198 | font-weight: 800;
199 | color: var(--text);
200 | margin: 0 4px;
201 | letter-spacing: -0.02em;
202 | }
203 |
204 | .price-period {
205 | font-size: 1rem;
206 | color: var(--text-muted);
207 | font-weight: 500;
208 | }
209 |
210 | .price-note {
211 | font-size: 0.9rem;
212 | color: var(--text-muted);
213 | opacity: 0.8;
214 | }
215 |
216 | .features-list {
217 | list-style: none;
218 | height:145px;
219 | margin-bottom: 40px;
220 | }
221 |
222 | .feature-item {
223 | display: flex;
224 | align-items: center;
225 | margin-bottom: 16px;
226 | font-size: 0.95rem;
227 | color: var(--text-muted);
228 | }
229 |
230 | .feature-icon {
231 | width: 20px;
232 | height: 20px;
233 | margin-right: 16px;
234 | background: var(--accent);
235 | border-radius: 50%;
236 | display: flex;
237 | align-items: center;
238 | justify-content: center;
239 | flex-shrink: 0;
240 | font-size: 12px;
241 | color: white;
242 | font-weight: 600;
243 | }
244 |
245 | .cta-button {
246 | width: 100%;
247 | padding: 16px 24px;
248 | background: linear-gradient(135deg, var(--accent), var(--accent-light));
249 | border: none;
250 | border-radius: 16px;
251 | color: white;
252 | font-family: 'Poppins', sans-serif;
253 | font-size: 1rem;
254 | font-weight: 600;
255 | cursor: pointer;
256 | transition: all 0.3s ease;
257 | text-transform: uppercase;
258 | letter-spacing: 0.5px;
259 | }
260 |
261 | .cta-button:hover {
262 | transform: translateY(-2px);
263 | box-shadow: 0 12px 24px var(--accent-glow);
264 | }
265 |
266 | .plan-card.featured .cta-button {
267 | background: linear-gradient(135deg, var(--text), var(--accent-light));
268 | color: var(--primary);
269 | }
270 |
271 | .plan-card {
272 | animation: fadeInUp 0.6s ease-out forwards;
273 | opacity: 0;
274 | }
275 |
276 | .plan-card:nth-child(1) {
277 | animation-delay: 0.1s;
278 | }
279 |
280 | .plan-card:nth-child(2) {
281 | animation-delay: 0.2s;
282 | }
283 |
284 | .plan-card:nth-child(3) {
285 | animation-delay: 0.3s;
286 | }
287 |
288 | @keyframes fadeInUp {
289 | from {
290 | opacity: 0;
291 | transform: translateY(30px);
292 | }
293 |
294 | to {
295 | opacity: 1;
296 | transform: translateY(0);
297 | }
298 | }
299 |
300 | .plan-card.featured {
301 | animation-delay: 0.2s;
302 | }
303 |
304 | @media (max-width: 768px) {
305 | .container {
306 | padding: 40px 20px;
307 | }
308 |
309 | .header {
310 | margin-bottom: 60px;
311 | }
312 |
313 | .plans-grid {
314 | gap: 24px;
315 | grid-template-columns: 1fr;
316 | }
317 |
318 | .plan-card {
319 | padding: 32px 24px;
320 | }
321 |
322 | .plan-card.featured {
323 | transform: none;
324 | }
325 | }
326 |
327 | @media (max-width: 480px) {
328 | .plan-card {
329 | padding: 24px 20px;
330 | }
331 |
332 | .price-amount {
333 | font-size: 3rem;
334 | }
335 | }
--------------------------------------------------------------------------------
/public/css/themes.css:
--------------------------------------------------------------------------------
1 | :root {
2 | --background: linear-gradient(145deg, #010310, #080c17);
3 | --home-bg: url("/assets/imgs/backgrounds/home/blue.jpg");
4 | --primary: #01031d;
5 | --primary-dark: #0c0015;
6 | --secondary: #0a0e1a;
7 | --accent: #3b82f6;
8 | --accent-light: #93c5fd;
9 | --accent-glow: rgba(59, 130, 246, 0.2);
10 | --border: rgba(59, 130, 246, 0.3);
11 | --text: #e0f2fe;
12 | --text-muted: #bae6fd;
13 | --hover-danger: #f87171;
14 | --active-tab: linear-gradient(145deg, #1e3a8a, var(--secondary));
15 | }
16 |
17 | :root.shadow {
18 | --background: linear-gradient(145deg, #0b0013, #15001f);
19 | --home-bg: url("/assets/imgs/backgrounds/home/purple.jpg");
20 | --primary: #12001f;
21 | --primary-dark: #0a0015;
22 | --secondary: #1a0a2b;
23 | --accent: #a855f7;
24 | --accent-light: #d8b4fe;
25 | --accent-glow: rgba(168, 85, 247, 0.2);
26 | --border: rgba(168, 85, 247, 0.3);
27 | --text: #f3e8ff;
28 | --text-muted: #e9d5ff;
29 | --hover-danger: #fb7185;
30 | --active-tab: linear-gradient(145deg, #6b21a8, var(--secondary));
31 | }
32 |
33 | :root.redsunset {
34 | --background: linear-gradient(145deg, #2b001b, #490000);
35 | --home-bg: url("/assets/imgs/backgrounds/home/red.jpg");
36 | --primary: #2e020e;
37 | --primary-dark: #1f0101;
38 | --secondary: #450202;
39 | --accent: #ef4444;
40 | --accent-light: #f87171;
41 | --accent-glow: rgba(239, 68, 68, 0.2);
42 | --border: rgba(239, 68, 68, 0.3);
43 | --text: #fef2f2;
44 | --text-muted: #fee2e2;
45 | --hover-danger: #9b1c1c;
46 | --active-tab: linear-gradient(145deg, #ef4444, var(--secondary));
47 | }
48 |
49 | :root.matrix {
50 | --background: linear-gradient(145deg, #000c05, #001a0f);
51 | --home-bg: url("/assets/imgs/backgrounds/home/green.jpg");
52 | --primary: #001a0f;
53 | --primary-dark: #000d07;
54 | --secondary: #003b22;
55 | --accent: #39ff14;
56 | --accent-light: #66ff66;
57 | --accent-glow: rgba(57, 255, 20, 0.25);
58 | --border: rgba(57, 255, 20, 0.3);
59 | --text: #eaffea;
60 | --text-muted: #c6fdd1;
61 | --hover-danger: #16a34a;
62 | --active-tab: linear-gradient(145deg, #39ff14, var(--secondary));
63 | }
64 |
65 | :root.operagx {
66 | --background: linear-gradient(145deg, #0d0606, #040000);
67 | --home-bg: url("/assets/imgs/backgrounds/home/operagx.jpg");
68 | --primary: #090505;
69 | --primary-dark: #0a0b10;
70 | --secondary: #000;
71 | --accent: #ff0050;
72 | --accent-light: #ff4f87;
73 | --accent-glow: rgba(255, 0, 80, 0.15);
74 | --border: rgba(255, 0, 80, 0.25);
75 | --text: #f0f0f5;
76 | --text-muted: #a0a0b2;
77 | --hover-danger: #ff4f87;
78 | --active-tab: linear-gradient(145deg, #ff0050, #1a1b22);
79 | }
80 |
81 |
82 | :root.midnight {
83 | --background: #0a0a0f;
84 | --home-bg: url("/assets/imgs/backgrounds/home/indigo.png");
85 | --primary: #111322;
86 | --primary-dark: #0c0e1a;
87 | --secondary: #1a1c2b;
88 | --accent: #4f46e5;
89 | --accent-light: #818cf8;
90 | --accent-glow: rgba(99, 102, 241, 0.2);
91 | --border: rgba(129, 140, 248, 0.25);
92 | --text: #f1f5f9;
93 | --text-muted: #cbd5e1;
94 | --hover-danger: #ef4444;
95 | --active-tab: linear-gradient(145deg, #4338ca, var(--secondary));
96 | }
97 |
--------------------------------------------------------------------------------
/public/extensions/discovery.html:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ShadowDevLabs/ShadowV3/b0877288e596419f09eecf31ed0d638b2563e64c/public/extensions/discovery.html
--------------------------------------------------------------------------------
/public/extensions/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
13 | Extensions
14 |
16 |
17 |
18 |
19 |
20 | EXTENSIONS COMING SOON
21 |
22 |
23 |
42 |
43 |
44 |
Welcome to Shadow Extensions
45 |
Supercharge your browsing experience with extensions for Shadow.
46 |
47 |
49 |
51 |
53 |
55 |
57 |
58 |
Browse Extensions
59 |
60 |
61 |
75 |
76 |
79 |
80 |
81 |
89 |
90 |
--------------------------------------------------------------------------------
/public/extensions/manage/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
12 | Extensions
13 |
15 |
16 |
17 |
18 |
19 |
20 |
22 |
Extensions
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
All Extensions
31 |
32 |
33 |
34 |
35 |
36 |
Shadow Defaults
37 |
38 | Default extension for Shadow, feel free to configure it!
39 |
40 |
41 |
42 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
71 |
72 |
--------------------------------------------------------------------------------
/public/extensions/manage/script.js:
--------------------------------------------------------------------------------
1 | /*
2 | extension = {
3 | enabled: true,
4 | name: extName,
5 | icon: extIcon,
6 | description: extDesc,
7 | code: extCode
8 | }
9 | */
10 |
11 | class ExtensionsLoader {
12 | constructor() {
13 | this.extensions = async () => { return await settings.get("extensions") };
14 | this.container = document.getElementsByClassName("extensions-list")[0];
15 | this.default = document.getElementsByClassName("extension")[0];
16 | Object.keys(this.extensions).forEach((i) => {
17 | this.load(i);
18 | });
19 | }
20 |
21 | load(ext) {
22 | let i = this.default.cloneNode(true);
23 | i.setAttribute("__ext-id", ext);
24 | let img = i.getElementsByClassName("__ext-img")[0];
25 | let name = i.getElementsByClassName("__ext-name")[0];
26 | let desc = i.getElementsByClassName("__ext-desc")[0];
27 | ext = this.extensions[ext];
28 | if (!ext.enabled) {
29 | i.getElementsByClassName("switch")[1].checked = false;
30 | }
31 | img.src = ext.icon;
32 | name.innerText = ext.name;
33 | desc.innerText = ext.description;
34 | let x = this.container.children.length;
35 | i.setAttribute("num", x);
36 | i.getElementsByClassName("slider")[0].setAttribute(
37 | "for",
38 | `toggleSwitch${x}`,
39 | );
40 | i
41 | .getElementsByClassName("switch")[0]
42 | .getElementsByClassName("switch")[0].id = `toggleSwitch${x}`;
43 | this.container.appendChild(i);
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/public/extensions/manage/style.css:
--------------------------------------------------------------------------------
1 | body {
2 | margin: 0;
3 | font-family: "Open Sans", sans-serif;
4 | background-color: #f8f8f8;
5 | }
6 |
7 | header {
8 | display: flex;
9 | justify-content: space-between;
10 | align-items: center;
11 | padding: 15px;
12 | color: #333;
13 | background-color: #ffffff;
14 | box-shadow: 0px 2px 5px rgba(0, 0, 0, 0.1);
15 | }
16 |
17 | .logo {
18 | display: flex;
19 | align-items: center;
20 | }
21 |
22 | .logo img {
23 | height: 40px;
24 | margin-right: 10px;
25 | border-radius: 50%;
26 | }
27 |
28 | h1 {
29 | margin: 0;
30 | font-size: 1.5rem;
31 | font-family: "Poppins", sans-serif;
32 | color: #333;
33 | }
34 |
35 | .search-container {
36 | position: absolute;
37 | right: 50%;
38 | transform: translateX(50%);
39 | width: 45%;
40 | padding: 5px;
41 | display: flex;
42 | border-color: black;
43 | border-style: solid;
44 | border-width: 1px;
45 | align-items: center;
46 | border-radius: 25px;
47 | box-shadow: 0px 2px 5px rgba(0, 0, 0, 0.1);
48 | }
49 |
50 | .search-input {
51 | flex-grow: 1;
52 | width: 40%;
53 | padding: 5px;
54 | border: none;
55 | margin: 0;
56 | background-color: transparent;
57 | border-radius: 25px;
58 | font-family: "Montserrat", sans-serif;
59 | box-shadow: none;
60 | transition: none;
61 | outline: none;
62 | }
63 |
64 | .search-icon {
65 | color: #555;
66 | margin-left: 10px;
67 | cursor: pointer;
68 | }
69 |
70 | .search-icon:hover {
71 | color: #333;
72 | }
73 |
74 | .extensions {
75 | margin: 20px;
76 | }
77 |
78 | .extensions h4 {
79 | margin-bottom: 10px;
80 | }
81 |
82 | .extensions-list {
83 | display: flex;
84 | flex-wrap: wrap;
85 | }
86 |
87 | .extension {
88 | margin: 10px;
89 | padding: 10px;
90 | border: 1px solid #ddd;
91 | border-radius: 8px;
92 | width: 200px;
93 | position: relative;
94 | }
95 |
96 | .extension img {
97 | width: 100%;
98 | border-radius: 4px;
99 | }
100 |
101 | .extension-sidebyimg {
102 | margin-top: 10px;
103 | width: 100%;
104 | box-sizing: border-box;
105 | }
106 |
107 | .extension-sidebyimg p {
108 | margin-top: -15px;
109 | }
110 |
111 | .switch {
112 | position: relative;
113 | display: inline-block;
114 | width: 40px;
115 | height: 20px;
116 | }
117 |
118 | .switch input {
119 | opacity: 0;
120 | width: 0;
121 | height: 0;
122 | }
123 |
124 | .slider {
125 | position: absolute;
126 | cursor: pointer;
127 | top: 0;
128 | right: 0;
129 | bottom: 0;
130 | background-color: #ccc;
131 | border-radius: 20px;
132 | transition: 0.4s;
133 | width: 40px;
134 | height: 20px;
135 | }
136 |
137 | .slider:before {
138 | position: absolute;
139 | content: "";
140 | height: 16px;
141 | width: 16px;
142 | left: 2px;
143 | top: 2px;
144 | background-color: white;
145 | border-radius: 50%;
146 | transition: 0.4s;
147 | }
148 |
149 | input:checked+.slider {
150 | background-color: #2196f3;
151 | }
152 |
153 | input:checked+.slider:before {
154 | transform: translateX(20px);
155 | }
--------------------------------------------------------------------------------
/public/extensions/script.js:
--------------------------------------------------------------------------------
1 | import { SettingsManager } from "../assets/js/settings_manager.js";
2 |
3 | const settings = new SettingsManager();
4 |
5 | async function clear() {
6 | await settings.get.removeItem("extensions");
7 | }
8 |
9 | function makeExtensionObj(
10 | extName = document.getElementsByName("extName")[0].value,
11 | extIcon = document.getElementsByName("extIcon")[0].value,
12 | extDesc = document.getElementsByName("extDesc")[0].value,
13 | extCode = document.getElementsByName("extCode")[0].value,
14 | ) {
15 | let extension = {
16 | enabled: true,
17 | name: extName,
18 | icon: extIcon,
19 | description: extDesc,
20 | code: extCode,
21 | };
22 | return extension;
23 | }
24 |
25 | function generateId(length) {
26 | let result = "_";
27 | const characters = "abcdefghijklmnopqrstuvwxyz";
28 | const charactersLength = characters.length;
29 | for (let i = 0; i < length; i++) {
30 | result += characters.charAt(Math.floor(Math.random() * charactersLength));
31 | }
32 | return result;
33 | }
34 |
35 | async function addExtension(id = generateId(32), extObj = makeExtensionObj()) {
36 | let extensions = await settings.get("extensions") || "{}";
37 | console.log(`[EXT] Adding extension with ID ${id}`);
38 | extensions[id] = extObj;
39 | console.log("[EXT] New extensions Obj is:");
40 | console.log("[EXT]"+extensions);
41 | await settings.set("extensions", extensions);
42 | }
43 |
44 | let save = addExtension();
45 |
46 | function exportExtension() {
47 | let extString = btoa(JSON.stringify(makeExtensionObj()));
48 | console.log("[EXT]"+extString);
49 | navigator.clipboard.writeText(extString);
50 | }
51 |
52 | function importExtension(i) {
53 | addExtension(generateId(32), JSON.parse(atob(i)));
54 | }
55 |
--------------------------------------------------------------------------------
/public/extensions/style.css:
--------------------------------------------------------------------------------
1 | * {
2 | outline: none;
3 | }
4 |
5 | body {
6 | margin: 0;
7 | font-family: "Montserrat", sans-serif;
8 | background: white;
9 | }
10 |
11 | header {
12 | color: #333;
13 | padding: 10px 20px;
14 | transition: padding 0.3s ease;
15 | }
16 |
17 | .header-content {
18 | display: flex;
19 | justify-content: space-between;
20 | align-items: center;
21 | }
22 |
23 | .logo-container {
24 | display: flex;
25 | align-items: center;
26 | }
27 |
28 | .logo i {
29 | margin-right: 10px;
30 | }
31 |
32 | .logo h1 {
33 | margin: 0;
34 | color: #444;
35 | }
36 |
37 | nav ul {
38 | list-style: none;
39 | display: flex;
40 | margin-left: 20px;
41 | }
42 |
43 | nav a {
44 | text-decoration: none;
45 | color: #333;
46 | margin: 0 15px;
47 | position: relative;
48 | transition:
49 | color 0.3s ease,
50 | border-bottom-color 0.3s ease;
51 | }
52 |
53 | nav a:hover {
54 | color: #3498db;
55 | }
56 |
57 | nav a:active {
58 | color: #3498db;
59 | border-bottom-color: #3498db;
60 | }
61 |
62 | .search-container {
63 | display: flex;
64 | background-color: #f0f0f0;
65 | align-items: center;
66 | border-radius: 5px;
67 | padding: 2px;
68 | transition: box-shadow 0.3s ease;
69 | }
70 |
71 | .search-input {
72 | padding: 10px;
73 | border: none;
74 | width: 250px;
75 | background-color: #f0f0f0;
76 | border-radius: 5px;
77 | font-family: "Montserrat", sans-serif;
78 | box-shadow: none;
79 | transition: none;
80 | outline: none;
81 | }
82 |
83 | .search-input:focus+.search-icon,
84 | .search-container:focus-within {
85 | box-shadow: 0 0 5px rgba(52, 152, 219, 0.7);
86 | }
87 |
88 | .search-icon {
89 | color: #333;
90 | margin-left: 10px;
91 | cursor: pointer;
92 | }
93 |
94 | .hero-section {
95 | background: url("https://ssl.gstatic.com/chrome/webstore/images/promo/marquee_blue_patterned.png") center/cover no-repeat;
96 | /* Updated background property */
97 | color: #000;
98 | width: 80vw;
99 | border-radius: 15px;
100 | margin: 2vw auto;
101 | min-height: 250px;
102 | padding: 60px 20px;
103 | text-align: center;
104 | }
105 |
106 | .hero-content {
107 | max-width: 600px;
108 | margin: -3vw auto;
109 | /* Adjusted margin */
110 | padding: 0;
111 | text-align: center;
112 | }
113 |
114 | .hero-content h2 {
115 | font-size: 28px;
116 | margin-top: 3vw;
117 | }
118 |
119 | .hero-content p {
120 | margin-top: -1vw;
121 | }
122 |
123 | .extensions {
124 | display: flex;
125 | justify-content: center;
126 | margin-top: 20px;
127 | }
128 |
129 | .extensions img {
130 | width: 80px;
131 | height: 80px;
132 | border-radius: 50%;
133 | transition:
134 | transform 0.3s ease,
135 | filter 0.3s ease;
136 | }
137 |
138 | .extensions img:hover {
139 | transform: scale(1.2);
140 | filter: brightness(1.2);
141 | }
142 |
143 | button {
144 | padding: 10px 20px;
145 | margin-top: 1vw;
146 | border: 1px solid black;
147 | background: transparent;
148 | color: black;
149 | border-radius: 20px;
150 | font-family: "Roboto", sans-serif;
151 | }
152 |
153 | .extension-add {
154 | margin-top: 20px;
155 | width: 80vw;
156 | margin: 0 auto;
157 | background-color: #aed6f1;
158 | padding: 20px;
159 | border-radius: 10px;
160 | }
161 |
162 | .extension-add h2 {
163 | text-align: center;
164 | font-size: 24px;
165 | }
166 |
167 | .row {
168 | display: flex;
169 | justify-content: space-between;
170 | align-items: center;
171 | margin-bottom: 15px;
172 | }
173 |
174 | .row input,
175 | textarea {
176 | padding: 10px;
177 | width: 45%;
178 | margin: 5px;
179 | margin-bottom: 10px;
180 | border: 1px solid #ccc;
181 | border-radius: 5px;
182 | box-sizing: border-box;
183 | }
184 |
185 | textarea {
186 | width: 100%;
187 | }
188 |
189 | button {
190 | padding: 10px 20px;
191 | background-color: #3498db;
192 | color: #fff;
193 | border: none;
194 | border-radius: 5px;
195 | cursor: pointer;
196 | transition: background-color 0.3s ease;
197 | }
198 |
199 | button:hover {
200 | background-color: #2980b9;
201 | }
202 |
203 | .extension-add button {
204 | padding: 10px 20px;
205 | background-color: #27ae60;
206 | margin: 0 auto;
207 | color: #fff;
208 | border: none;
209 | border-radius: 5px;
210 | cursor: pointer;
211 | transition: background-color 0.3s ease;
212 | }
213 |
214 | .extension-add button:hover {
215 | background-color: #219d3e;
216 | }
217 |
218 | .btns {
219 | display: flex;
220 | justify-content: center;
221 | width: 10vw;
222 | margin: 0 auto;
223 | }
224 |
225 | footer {
226 | text-align: center;
227 | padding: 10px;
228 | }
229 |
230 | footer p {
231 | margin: 0;
232 | }
233 |
234 | footer a {
235 | color: #3498db;
236 | text-decoration: none;
237 | transition: color 0.3s ease;
238 | }
239 |
240 | footer a:hover {
241 | color: #2980b9;
242 | }
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ShadowDevLabs/ShadowV3/b0877288e596419f09eecf31ed0d638b2563e64c/public/favicon.ico
--------------------------------------------------------------------------------
/public/history.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | History
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
27 |
34 |
35 |
36 |
37 |
--------------------------------------------------------------------------------
/public/home/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 | Home
12 |
13 |
14 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 | Shadow
29 | discord.gg/goshadow
30 |
31 |
32 |
--------------------------------------------------------------------------------
/public/home/quotes.json:
--------------------------------------------------------------------------------
1 | [
2 | "I don't play the odds, I play the man",
3 | "When you're backed against the wall, break the goddamn thing down",
4 | "Don't raise your voice, improve your argument",
5 | "Anyone can do my job, but no one can be me",
6 | "You do what they say or they shoot you, right? Wrong! You take the gun. You pull out a bigger gun or you call their bluff, or you do one of another 146 other things",
7 | "Life is like this, I like this",
8 | "Shadow ~ my greatest creation -NC",
9 | "A students become workers, C students run companies, and F students run the world",
10 | "LA >>>>>>>>",
11 | "🦅🦅🦅🦅 RAHHHHH",
12 | "They think you care, they'll walk all over you",
13 | "Loyalty is a two-way street",
14 | "Winners don't make excuses",
15 | "That’s the difference between us, you want to lose small, I want to win big",
16 | "I don’t have dreams, I have goals",
17 | "Sometimes good guys gotta do bad things to make the bad guys pay",
18 | "I’m not here to make friends, I’m here to win",
19 | "The only time success comes before work is in the dictionary",
20 | "I don’t get lucky. I make my own luck",
21 | "You don’t send a puppy to clean up its own mess",
22 | "I’m not a charity case, I’m a goddamn lawyer",
23 | "You want to change your life? Change the way you think",
24 | "I don’t lose. I might not win yet, but I don’t lose",
25 | "People don’t pay me to make the right decision, they pay me to make their decision right",
26 | "I’m not afraid of the truth, I’m afraid of what happens when it comes out",
27 | "You don’t have to have it all figured out to move forward",
28 | "The best way to predict the future is to create it",
29 | "I’m not the guy who folds when the going gets tough",
30 | "You wanna play dirty? Fine. Just know I play dirtier",
31 | "I don’t run from a fight, I bring the fight to you",
32 | "You’re not a king because you sit on a throne, you’re a king because people bow",
33 | "I don’t care about the law, I care about winning",
34 | "There’s no such thing as an unwinnable case, just unprepared lawyers",
35 | "I’m not just a pretty face, I’m the whole damn package",
36 | "You don’t get what you deserve, you get what you take"
37 | ]
--------------------------------------------------------------------------------
/public/home/script.js:
--------------------------------------------------------------------------------
1 | const getStarted = document.getElementsByClassName("getStarted");
2 |
3 | getStarted[0].addEventListener("click", function () {
4 | parent.createTab("shadow://new");
5 | });
6 |
7 |
--------------------------------------------------------------------------------
/public/legacy_uv/uv.config.js:
--------------------------------------------------------------------------------
1 | self.__uv$config = {
2 | prefix: "/legacy_uv/service/",
3 | bare: "/bare/",
4 | encodeUrl: Ultraviolet.codec.xor.encode,
5 | decodeUrl: Ultraviolet.codec.xor.decode,
6 | handler: "/uv/uv.handler.js",
7 | client: "/uv/uv.client.js",
8 | bundle: "/uv/uv.bundle.js",
9 | config: "/uv/uv.config.js",
10 | sw: "/uv/uv.sw.js",
11 | };
12 |
13 | self._open = self.open;
14 | self.open = (url, title, _) => parent.tabs.createTab(url, title);
15 |
16 | self.__shadow = {
17 | erudaState: false,
18 | eruda: null
19 | }
20 |
21 | self.addEventListener("message", (e) => {
22 | if (e.data === "__shadow$toggleEruda") {
23 | if (__shadow.eruda._devTools && !__shadow.eruda._devTools._isShow) {
24 | __shadow.erudaState = true;
25 | __shadow.eruda.show();
26 | } else if (__shadow.erudaState) {
27 | __shadow.erudaState = false;
28 | __shadow.eruda.destroy();
29 | } else {
30 | __shadow.erudaState = true;
31 | __shadow.eruda.init();
32 | __shadow.eruda.show();
33 | }
34 | }
35 | })
36 |
37 | self.onload = () => {
38 | if (typeof window === "object" && self.constructor === Window) {
39 | const script = document.createElement("script");
40 | script.src = "https://cdn.jsdelivr.net/npm/eruda";
41 | script.onload = () => {
42 | self.__shadow.eruda = eruda;
43 | }
44 | document.head.append(script);
45 | document.onclick = (e) => { if(e.target.id !== "__shadow-search-bar") parent.postMessage("hide-suggestions"); }
46 | try {
47 | parent.updateOmni();
48 | parent.setTab();
49 | //Update history on everything EXCEPT for shadow:// urls
50 | if (__uv) parent.tabs.updateHistory(__uv.location.href, document.title, `https://www.google.com/s2/favassets/imgs/icons?domain=${__uv.location.href}&sz=24`);
51 | } catch (e) {
52 | console.log(`[LOAD] Error in initializing tab: ${e}`);
53 | }
54 | }
55 | }
--------------------------------------------------------------------------------
/public/login.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 | Login
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
29 |
30 |
70 |
71 |
72 |
Don't have an account? Sign up
73 |
74 |
75 |
76 |
77 |
78 |
--------------------------------------------------------------------------------
/public/new.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 | New Tab
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
Shadow
26 |
27 |
28 |
29 |
31 |
32 |
33 |
37 |
38 |
45 |
46 |
47 |
48 | ×
49 |
Add a Shortcut
50 |
57 |
58 |
59 |
60 |
61 | ×
62 |
Edit Shortcut
63 |
70 |
71 |
72 |
86 |
90 |
103 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
125 |
126 |
127 |
--------------------------------------------------------------------------------
/public/pages/ai.html:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/pages/books.html:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/pages/chat.html:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/pages/credits.html:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/pages/extensions.html:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/pages/extensions/manage.html:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/pages/games.html:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/pages/history.html:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/pages/home.html:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/pages/login.html:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/pages/new.html:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/pages/privacy.html:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/pages/settings.html:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/privacy.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Privacy Policy - Shadow Browser
7 |
41 |
42 |
43 |
44 |
Privacy Policy
45 |
Effective Date: 11/23/2024
46 |
47 |
48 |
1. Information We Collect
49 |
Temporary Viewing of IP Addresses
50 |
51 | To ensure functionality and resolve technical issues, we may temporarily view IP addresses in live logs:
52 |
53 |
54 | Purpose: Used to identify and troubleshoot errors, manage service disruptions, and enhance security.
55 | Retention: IP addresses are not saved or stored. They are visible only in live logs and discarded immediately after use.
56 |
57 |
Client-Side Browsing History
58 |
59 | Client-Side Storage Only: Browsing history is stored locally on your device.
60 | No Server-Side Access: We do not collect, store, or have access to your browsing history or activity.
61 |
62 |
63 |
64 |
65 |
2. Third-Party Services
66 |
We partner with third-party services to enhance your experience:
67 |
Google Analytics
68 |
69 | We use Google Analytics to analyze website usage and improve our platform. Google may collect data such as device information, IP addresses, and usage patterns.
70 |
71 |
72 | More Information: Please review Google’s Privacy Policy .
73 |
74 |
Third Party Ads
75 |
76 | We work with third party companies to display relevant advertisements. These companies may collect information to tailor ads to you.
77 |
78 |
79 | More Information: Please review Their Privacy Policy .
80 |
81 |
82 |
83 |
84 |
3. Children’s Privacy
85 |
86 | Shadow Browser does not knowingly allow individuals under the age of 13 to use our services.
87 |
88 |
89 | Prohibited Use: If you are under 13, please do not use Shadow Browser.
90 | Actions Taken: If we are alerted that a user under 13 has used our service, we will take immediate steps to remove any information related to that user from our systems.
91 |
92 |
93 |
94 |
95 |
4. How We Protect Your Information
96 |
97 | We implement the following measures to safeguard your privacy:
98 |
99 |
100 | No Data Logging: Shadow Browser does not log or store browsing history, IP addresses, or personal information on our servers.
101 | Encrypted Communication: All data transmitted through Shadow Browser is encrypted for security.
102 |
103 |
104 |
105 |
106 |
5. Your Privacy Rights
107 |
108 | Shadow Browser is designed to prioritize your privacy:
109 |
110 |
111 | Control Over Data: Browsing history is stored locally on your device and never shared with us.
112 | Transparency: Data collected by third-party services, such as Google Analytics and Adsterra, is subject to their privacy policies.
113 |
114 |
115 |
116 |
117 |
6. Changes to This Privacy Policy
118 |
119 | We may update this Privacy Policy periodically to reflect changes in our practices. Any updates will be posted on this page with the "Effective Date" noted at the top.
120 |
121 |
122 |
123 |
124 |
7. Contact Us
125 |
If you have any questions or concerns about this Privacy Policy or our practices, please contact us:
126 |
127 | Email: admin@phantom.lol
128 | Discord: discord.gg/goshadow
129 |
130 |
131 |
132 |
Shadow Browser is committed to maintaining a safe, private, and secure browsing experience for all users. Thank you for trusting us.
133 |
134 |
135 |
136 |
--------------------------------------------------------------------------------
/public/settings/script.js:
--------------------------------------------------------------------------------
1 | fetch('themes.json')
2 | .then(response => response.json())
3 | .then(themes => {
4 | const themesContainer = document.getElementById('themes-container');
5 |
6 | themes.forEach(theme => {
7 | const themeDiv = document.createElement('div');
8 | themeDiv.classList.add('theme');
9 | themeDiv.onclick = () => {
10 | changeTheme(theme.value);
11 | };
12 | themeDiv.style.backgroundColor = theme.css['primary'];
13 | themeDiv.style.color = theme.css['text'];
14 | themeDiv.style.borderColor = theme.css['border'];
15 |
16 | const colorDiv = document.createElement('div');
17 | colorDiv.classList.add('color');
18 | colorDiv.style.background = `linear-gradient(135deg, ${theme.css['primary']}, ${theme.css['secondary']}, ${theme.css['accent']}, ${theme.css['accent-light']})`;
19 | colorDiv.style.borderColor = theme.css['accent-light'];
20 |
21 | const afterDiv = document.createElement('div');
22 | afterDiv.classList.add('theme-after');
23 | afterDiv.style.cssText = `height: 0.5px; width: 100%; position: absolute; bottom: 0; left: 0; background: linear-gradient(to right, ${theme.css['accent']}, ${theme.css['accent-light']}); transform: scaleX(0); transform-origin: bottom right; transition: transform 0.3s ease;`;
24 | themeDiv.appendChild(afterDiv);
25 |
26 | themeDiv.addEventListener('mouseover', () => {
27 | themeDiv.style.background = theme.css['active-tab'];
28 | themeDiv.style.boxShadow = `0 12px 24px ${theme.css['accent-glow']}`;
29 | themeDiv.style.borderColor = theme.css['accent'];
30 | afterDiv.style.transform = 'scaleX(1)';
31 | afterDiv.style.transformOrigin = 'bottom left';
32 | });
33 |
34 | themeDiv.addEventListener('mouseleave', () => {
35 | themeDiv.style.background = theme.css['secondary'];
36 | themeDiv.style.boxShadow = `0 4px 6px rgba(0, 0, 0, 0.1)`;
37 | themeDiv.style.borderColor = theme.css['border'];
38 | afterDiv.style.transform = 'scaleX(0)';
39 | afterDiv.style.transformOrigin = 'bottom right';
40 | });
41 |
42 | themeDiv.appendChild(colorDiv);
43 | themeDiv.appendChild(document.createTextNode(theme.name));
44 |
45 | themesContainer.appendChild(themeDiv);
46 | });
47 | })
48 | .catch(error => console.error('Error loading themes:', error));
49 |
50 |
51 | function abtblank() {
52 | const url = window.location.href;
53 | const width = window.innerWidth;
54 | const height = window.innerHeight;
55 |
56 | let inFrame;
57 |
58 | try {
59 | inFrame = window !== top;
60 | } catch (e) {
61 | inFrame = true;
62 | }
63 |
64 | if (!inFrame && !navigator.userAgent.includes("Firefox")) {
65 | const popup = window.open("about:blank", `width=${width},height=${height}`);
66 |
67 | if (!popup || popup.closed) {
68 | alert(
69 | "Allow popups and redirects to hide this from showing up in your history.",
70 | );
71 | } else {
72 | const doc = popup.document;
73 | const iframe = doc.createElement("iframe");
74 | const style = iframe.style;
75 | const link = doc.createElement("link");
76 |
77 | iframe.src = url;
78 | style.position = "fixed";
79 | style.top = style.bottom = style.left = style.right = 0;
80 | style.border = style.outline = "none";
81 | style.width = style.height = "100%";
82 |
83 | doc.head.appendChild(link);
84 | doc.body.appendChild(iframe);
85 | window.location.replace("https://google.com");
86 | }
87 | }
88 | }
89 |
90 | async function wispURLOption() {
91 | const wispSelection = document.getElementById('wispSelection');
92 | const customWispURL = document.getElementById('wispURL');
93 | const setWispUrlBtn = document.getElementById('wispBtn');
94 |
95 | if (wispSelection.value === 'custom') {
96 | customWispURL.value = await window.settings.get("server");
97 | customWispURL.style.display = 'block';
98 | setWispUrlBtn.style.display = 'flex';
99 | } else {
100 | customWispURL.style.display = 'none';
101 | setWispUrlBtn.style.display = 'none';
102 | await window.settings.set("server", `wss://${location.host}/wisp/`);
103 | top.tabs.setTransport();
104 | }
105 | }
--------------------------------------------------------------------------------
/public/settings/themes.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "name": "Default",
4 | "value": "default",
5 | "css": {
6 | "background": "linear-gradient(145deg, #010310, #080c17)",
7 | "home-bg": "url('/assets/imgs/backgrounds/home/blue.jpg')",
8 | "primary": "#01031d",
9 | "primary-dark": "#0c0015",
10 | "secondary": "#0a0e1a",
11 | "accent": "#3b82f6",
12 | "accent-light": "#93c5fd",
13 | "accent-glow": "rgba(59, 130, 246, 0.2)",
14 | "border": "rgba(59, 130, 246, 0.3)",
15 | "text": "#e0f2fe",
16 | "text-muted": "#bae6fd",
17 | "hover-danger": "#f87171",
18 | "active-tab": "linear-gradient(145deg, #1e3a8a, var(secondary))"
19 | }
20 | },
21 | {
22 | "name": "Shadow",
23 | "value": "shadow",
24 | "css": {
25 | "background": "linear-gradient(145deg, #0b0013, #15001f)",
26 | "home-bg": "url('/assets/imgs/backgrounds/home/purple.jpg')",
27 | "primary": "#12001f",
28 | "primary-dark": "#0a0015",
29 | "secondary": "#1a0a2b",
30 | "accent": "#a855f7",
31 | "accent-light": "#d8b4fe",
32 | "accent-glow": "rgba(168, 85, 247, 0.2)",
33 | "border": "rgba(168, 85, 247, 0.3)",
34 | "text": "#f3e8ff",
35 | "text-muted": "#e9d5ff",
36 | "hover-danger": "#fb7185",
37 | "active-tab": "linear-gradient(145deg, #6b21a8, var(secondary))"
38 | }
39 | },
40 | {
41 | "name": "Midnight City",
42 | "value": "midnight",
43 | "css": {
44 | "background": "#0a0a0f",
45 | "home-bg": "url('/assets/imgs/backgrounds/home/indigo.png')",
46 | "primary": "#111322",
47 | "primary-dark": "#0c0e1a",
48 | "secondary": "#1a1c2b",
49 | "accent": "#4f46e5",
50 | "accent-light": "#818cf8",
51 | "accent-glow": "rgba(99, 102, 241, 0.2)",
52 | "border": "rgba(129, 140, 248, 0.25)",
53 | "text": "#f1f5f9",
54 | "text-muted": "#cbd5e1",
55 | "hover-danger": "#ef4444",
56 | "active-tab": "linear-gradient(145deg, #4338ca, var(--secondary))"
57 | }
58 | },
59 | {
60 | "name": "Red Sunset",
61 | "value": "redsunset",
62 | "css": {
63 | "background": "linear-gradient(145deg, #2b001b, #490000)",
64 | "home-bg": "url('/assets/imgs/backgrounds/home/red.jpg')",
65 | "primary": "#2e020e",
66 | "primary-dark": "#1f0101",
67 | "secondary": "#450202",
68 | "accent": "#ef4444",
69 | "accent-light": "#f87171",
70 | "accent-glow": "rgba(239, 68, 68, 0.2)",
71 | "border": "rgba(239, 68, 68, 0.3)",
72 | "text": "#fef2f2",
73 | "text-muted": "#fee2e2",
74 | "hover-danger": "#9b1c1c",
75 | "active-tab": "linear-gradient(145deg, #ef4444, var(--secondary))"
76 | }
77 | },
78 | {
79 | "name": "Matrix",
80 | "value": "matrix",
81 | "css": {
82 | "background": "linear-gradient(145deg, #000c05, #001a0f)",
83 | "home-bg": "url('/assets/imgs/backgrounds/home/green.jpg')",
84 | "primary": "#001a0f",
85 | "primary-dark": "#000d07",
86 | "secondary": "#003b22",
87 | "accent": "#39ff14",
88 | "accent-light": "#66ff66",
89 | "accent-glow": "rgba(57, 255, 20, 0.25)",
90 | "border": "rgba(57, 255, 20, 0.3)",
91 | "text": "#eaffea",
92 | "text-muted": "#c6fdd1",
93 | "hover-danger": "#16a34a",
94 | "active-tab": "linear-gradient(145deg, #39ff14, var(--secondary))"
95 | }
96 | },
97 | {
98 | "name": "Opera GX",
99 | "value": "operagx",
100 | "css": {
101 | "background": "linear-gradient(145deg, #0d0606, #040000)",
102 | "home-bg": "url('/assets/imgs/backgrounds/home/operagx.jpg')",
103 | "primary": "#090505",
104 | "primary-dark": "#0a0b10",
105 | "secondary": "#000",
106 | "accent": "#ff0050",
107 | "accent-light": "#ff4f87",
108 | "accent-glow": "rgba(255, 0, 80, 0.15)",
109 | "border": "rgba(255, 0, 80, 0.25)",
110 | "text": "#f0f0f5",
111 | "text-muted": "#a0a0b2",
112 | "hover-danger": "#ff4f87",
113 | "active-tab": "linear-gradient(145deg, #ff0050, #1a1b22)"
114 | }
115 | }
116 | ]
117 |
--------------------------------------------------------------------------------
/public/subscriptions.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Shadow Plans - Choose Your Power
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
17 |
18 |
19 |
20 |
21 |
25 |
26 |
27 |
28 | $
29 | 1.99
30 | /month
31 |
32 |
Billed monthly
33 |
34 |
35 |
36 |
37 | ✓
38 | No Ads
39 |
40 |
41 | ✓
42 | Priority Support
43 |
44 |
45 | ✓
46 | 2 Week AdvDispenser Cooldown
47 |
48 |
49 |
50 |
Get Started
51 |
52 |
53 |
54 |
55 |
Most Popular
56 |
57 |
61 |
62 |
63 |
64 | $
65 | 3.99
66 | /month
67 |
68 |
Billed monthly
69 |
70 |
71 |
72 |
73 | ✓
74 | No Ads
75 |
76 |
77 | ✓
78 | Priority Support
79 |
80 |
81 | ✓
82 | Beta Access
83 |
84 |
85 | ✓
86 | 24h AdvDispenser Cooldown
87 |
88 |
89 |
90 |
Choose Core
91 |
92 |
93 |
94 |
--------------------------------------------------------------------------------
/public/sw.js:
--------------------------------------------------------------------------------
1 | importScripts("/uv/uv.bundle.js");
2 | importScripts("/uv/uv.config.js");
3 | importScripts("/uv/uv.client.js");
4 | importScripts(__uv$config.sw || "/uv/uv.sw.js");
5 | importScripts("/assets/js/settings_manager_sw.js")
6 | importScripts("/assets/js/history_helper_sw.js")
7 | self.settings = new SettingsManager();
8 | self.history = new HistoryHelper();
9 |
10 |
11 | const uv = new UVServiceWorker();
12 | uv.on("request", async (event) => {
13 | event.data.headers["user-agent"] = await self.settings.get("user-agent") ?? event.data.headers["user-agent"]
14 | });
15 |
16 | self.addEventListener("message", (event) => {
17 | const { reason, data } = event.data
18 | if(reason === "save-open-tabs") {
19 | self.history.setOpen(data);
20 | }
21 | })
22 |
23 | self.addEventListener("fetch", async (event) => {
24 | if (event.request.url.startsWith(location.origin + __uv$config.prefix)) {
25 | event.respondWith((async () => await uv.fetch(event))());
26 | }
27 | else event.respondWith((async () => await fetch(event.request))());
28 | });
--------------------------------------------------------------------------------
/public/uv/uv.config.js:
--------------------------------------------------------------------------------
1 | self.__uv$config = {
2 | prefix: "/uv/service/",
3 | bare: "/bare/",
4 | encodeUrl: Ultraviolet.codec.xor.encode,
5 | decodeUrl: Ultraviolet.codec.xor.decode,
6 | handler: "/uv/uv.handler.js",
7 | client: "/uv/uv.client.js",
8 | bundle: "/uv/uv.bundle.js",
9 | config: "/uv/uv.config.js",
10 | sw: "/uv/uv.sw.js",
11 | };
12 |
13 | if (self.location.pathname != '/') {
14 | self._open = self.open;
15 | self.open = (url, title, _) => { parent.tabs.createTab(url, title); console.warn(`Replaced open correctly and opening new tab with src ${url}`) }
16 | }
17 |
18 | self.__shadow = {
19 | erudaState: false,
20 | eruda: null
21 | }
22 |
23 | self.addEventListener("message", (e) => {
24 | if (e.data === "__shadow$toggleEruda") {
25 | if (__shadow.eruda._devTools && !__shadow.eruda._devTools._isShow) {
26 | __shadow.erudaState = true;
27 | __shadow.eruda.show();
28 | } else if (__shadow.erudaState) {
29 | __shadow.erudaState = false;
30 | __shadow.eruda.destroy();
31 | } else {
32 | __shadow.erudaState = true;
33 | __shadow.eruda.init();
34 | __shadow.eruda.show();
35 | }
36 | }
37 | })
38 |
39 | self.onload = () => {
40 | if (typeof window === "object" && self.constructor === Window) {
41 | const script = document.createElement("script");
42 | script.src = "https://cdn.jsdelivr.net/npm/eruda";
43 | script.onload = () => {
44 | self.__shadow.eruda = eruda;
45 | }
46 | document.head.append(script);
47 | document.onclick = (e) => { if (e.target.id !== "__shadow-search-bar") parent.postMessage("hide-suggestions"); }
48 | try {
49 | parent.updateOmni();
50 | parent.setTab();
51 | //Update history on everything EXCEPT for shadow:// urls
52 | if (__uv) parent.tabs.updateHistory(__uv.location.href, document.title, `https://www.google.com/s2/favicons/imgs/icons?domain=${__uv.location.href}&sz=24`);
53 | } catch (e) {
54 | console.log(`[LOAD] Error in initializing tab: ${e}`);
55 | }
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/render.yaml:
--------------------------------------------------------------------------------
1 | services:
2 | - type: web
3 | name: learn-astrology
4 | env: node
5 | plan: free
6 | startCommand: npm install && npm start
7 |
--------------------------------------------------------------------------------
/useragents.js:
--------------------------------------------------------------------------------
1 | import * as cheerio from "cheerio";
2 | import http from "http";
3 | const port = 8000;
4 |
5 | async function getRecentUA() {
6 | let text = await fetch("https://useragents.me/");
7 | text = await text.text();
8 | const $ = cheerio.load(text);
9 | return $(
10 | "#most-common-desktop-useragents-json-csv > div:eq(0) > textarea",
11 | ).val();
12 | }
13 |
14 | const requestListener = async function (req, res) {
15 | res.end(await getRecentUA());
16 | };
17 |
18 | const server = http.createServer(requestListener);
19 | server.listen(port, () => {
20 | console.log(`Server is running on port ${port}`);
21 | });
22 |
--------------------------------------------------------------------------------
/vercel.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": 2,
3 | "builds": [
4 | {
5 | "src": "./index.js",
6 | "use": "@vercel/node"
7 | }
8 | ],
9 | "routes": [
10 | {
11 | "src": "/(.*)",
12 | "dest": "/"
13 | }
14 | ]
15 | }
16 |
--------------------------------------------------------------------------------