├── .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 | Deploy on Render 27 | 28 | 29 | Deploy on Cyclic 30 | 31 | 32 | Deploy with Vercel 33 | 34 | 35 | Deploy to Koyeb 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 |
17 |

ShadowAssistant

18 |

powered and sponsored by ShuttleAI

19 |
20 |
21 |
22 |
23 | 24 | 31 |
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 |
2 | 70 |
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 | favicon 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 | 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 |
24 |
25 |
26 | 29 | 35 |
36 |
37 | 38 | 39 |
40 |
41 |
42 |
43 |
44 |

Welcome to Shadow Extensions

45 |

Supercharge your browsing experience with extensions for Shadow.

46 |
47 | 49 | 51 | 53 | 55 | 57 |
58 | 59 |
60 |
61 |
62 |

Add an Extension

63 |
64 | 65 | 67 |
68 |
69 | 70 |
71 |
72 | 73 |
74 |
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 | 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 |
18 |
19 |

History

20 | 21 | 22 |
23 | 24 | 25 |
26 |
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 | Shadow Logo 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 |
21 | 26 |

Welcome Back

27 |

Please enter your credentials to continue

28 |
29 | 30 | 70 | 71 | 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 |
34 | 35 | 36 |
37 |
38 |
39 | 40 | AddShortcut 42 |

Add

43 |
44 |
45 |
46 | 59 | 72 |
73 |
  • 74 | 75 | 77 | Edit 78 |
  • 79 |
  • 80 | 81 | 83 | Delete 84 |
  • 85 |
    86 |
    87 |

    88 |
    
     89 |   
    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 | 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 | 92 |
    93 | 94 |
    95 |

    4. How We Protect Your Information

    96 |

    97 | We implement the following measures to safeguard your privacy: 98 |

    99 | 103 |
    104 | 105 |
    106 |

    5. Your Privacy Rights

    107 |

    108 | Shadow Browser is designed to prioritize your privacy: 109 |

    110 | 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 | 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 |
    15 |

    Shadow Premium Plans

    16 |
    17 | 18 |
    19 | 20 |
    21 |
    22 |

    ShadowLite

    23 |

    Perfect for those who just don't want ads

    24 |
    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 | 51 |
    52 | 53 | 54 | 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 | --------------------------------------------------------------------------------