educational variant
This commit is contained in:
145
edu/index.html
Normal file
145
edu/index.html
Normal file
@ -0,0 +1,145 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Open Learning Hub</title>
|
||||
<link rel="stylesheet" href="style.css">
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
<div class="container">
|
||||
<h1>Open Learning Hub</h1>
|
||||
<p>Your gateway to free educational resources across the web.</p>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<main class="container">
|
||||
<section id="tip-section">
|
||||
<h2>💡 Tip of the Day</h2>
|
||||
<p id="daily-tip">Loading tip...</p>
|
||||
<button id="new-tip-btn">Get Another Tip</button>
|
||||
</section>
|
||||
|
||||
<section id="resources">
|
||||
<h2>Educational Resources</h2>
|
||||
<div class="filter-controls">
|
||||
<button class="filter-btn active" data-filter="all">All</button>
|
||||
<button class="filter-btn" data-filter="coding">Coding</button>
|
||||
<button class="filter-btn" data-filter="science">Science</button>
|
||||
<button class="filter-btn" data-filter="math">Math</button>
|
||||
<button class="filter-btn" data-filter="humanities">Humanities</button>
|
||||
<button class="filter-btn" data-filter="languages">Languages</button>
|
||||
<button class="filter-btn" data-filter="courses">Online Courses</button>
|
||||
</div>
|
||||
|
||||
<div class="resource-grid">
|
||||
<!-- Coding -->
|
||||
<article class="card" data-category="coding">
|
||||
<h3>freeCodeCamp</h3>
|
||||
<p>Learn to code for free with interactive lessons and projects.</p>
|
||||
<a href="https://www.freecodecamp.org" target="_blank">Visit Site</a>
|
||||
</article>
|
||||
<article class="card" data-category="coding">
|
||||
<h3>MDN Web Docs</h3>
|
||||
<p>Resources for developers, by developers. The bible of web dev.</p>
|
||||
<a href="https://developer.mozilla.org" target="_blank">Visit Site</a>
|
||||
</article>
|
||||
<article class="card" data-category="coding">
|
||||
<h3>GitHub</h3>
|
||||
<p>The world's largest platform for software development and version control.</p>
|
||||
<a href="https://github.com" target="_blank">Visit Site</a>
|
||||
</article>
|
||||
|
||||
<!-- Science -->
|
||||
<article class="card" data-category="science">
|
||||
<h3>NASA</h3>
|
||||
<p>Explore the universe with images, videos, and news from NASA.</p>
|
||||
<a href="https://www.nasa.gov" target="_blank">Visit Site</a>
|
||||
</article>
|
||||
<article class="card" data-category="science">
|
||||
<h3>National Geographic</h3>
|
||||
<p>World leader in geography, cartography and exploration.</p>
|
||||
<a href="https://www.nationalgeographic.com" target="_blank">Visit Site</a>
|
||||
</article>
|
||||
<article class="card" data-category="science">
|
||||
<h3>Nature</h3>
|
||||
<p>Leading international weekly journal of science.</p>
|
||||
<a href="https://www.nature.com" target="_blank">Visit Site</a>
|
||||
</article>
|
||||
|
||||
<!-- Math -->
|
||||
<article class="card" data-category="math">
|
||||
<h3>Khan Academy</h3>
|
||||
<p>Free, world-class education for anyone, anywhere.</p>
|
||||
<a href="https://www.khanacademy.org" target="_blank">Visit Site</a>
|
||||
</article>
|
||||
<article class="card" data-category="math">
|
||||
<h3>Wolfram MathWorld</h3>
|
||||
<p>The web's most extensive mathematics resource.</p>
|
||||
<a href="https://mathworld.wolfram.com" target="_blank">Visit Site</a>
|
||||
</article>
|
||||
<article class="card" data-category="math">
|
||||
<h3>Desmos</h3>
|
||||
<p>Beautiful, free online graphing calculator.</p>
|
||||
<a href="https://www.desmos.com" target="_blank">Visit Site</a>
|
||||
</article>
|
||||
|
||||
<!-- Humanities -->
|
||||
<article class="card" data-category="humanities">
|
||||
<h3>Project Gutenberg</h3>
|
||||
<p>A library of over 60,000 free eBooks.</p>
|
||||
<a href="https://www.gutenberg.org" target="_blank">Visit Site</a>
|
||||
</article>
|
||||
<article class="card" data-category="humanities">
|
||||
<h3>Smithsonian</h3>
|
||||
<p>Official website of the Smithsonian Institution.</p>
|
||||
<a href="https://www.si.edu" target="_blank">Visit Site</a>
|
||||
</article>
|
||||
<article class="card" data-category="humanities">
|
||||
<h3>Internet Archive</h3>
|
||||
<p>Non-profit library of millions of free books, movies, software, and music.</p>
|
||||
<a href="https://archive.org" target="_blank">Visit Site</a>
|
||||
</article>
|
||||
|
||||
<!-- Languages -->
|
||||
<article class="card" data-category="languages">
|
||||
<h3>Duolingo</h3>
|
||||
<p>The world's most popular way to learn a language. It's 100% free.</p>
|
||||
<a href="https://www.duolingo.com" target="_blank">Visit Site</a>
|
||||
</article>
|
||||
<article class="card" data-category="languages">
|
||||
<h3>BBC Languages</h3>
|
||||
<p>Archived but excellent resources for learning 40 languages.</p>
|
||||
<a href="https://www.bbc.co.uk/languages/" target="_blank">Visit Site</a>
|
||||
</article>
|
||||
|
||||
<!-- Online Courses -->
|
||||
<article class="card" data-category="courses">
|
||||
<h3>Coursera</h3>
|
||||
<p>Build skills with courses, certificates, and degrees online from world-class universities.</p>
|
||||
<a href="https://www.coursera.org" target="_blank">Visit Site</a>
|
||||
</article>
|
||||
<article class="card" data-category="courses">
|
||||
<h3>edX</h3>
|
||||
<p>Access 2000+ free online courses from 140 leading institutions worldwide.</p>
|
||||
<a href="https://www.edx.org" target="_blank">Visit Site</a>
|
||||
</article>
|
||||
<article class="card" data-category="courses">
|
||||
<h3>MIT OpenCourseWare</h3>
|
||||
<p>A web-based publication of virtually all MIT course content.</p>
|
||||
<a href="https://ocw.mit.edu" target="_blank">Visit Site</a>
|
||||
</article>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
<footer>
|
||||
<div class="container">
|
||||
<p>© 2025 Open Learning Hub.</p>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
<script src="script.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
65
edu/script.js
Normal file
65
edu/script.js
Normal file
@ -0,0 +1,65 @@
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
// --- Tip of the Day Logic ---
|
||||
const tips = [
|
||||
"Spaced repetition is a learning technique that incorporates increasing intervals of time between subsequent review of previously learned material.",
|
||||
"The Pomodoro Technique uses a timer to break down work into intervals, traditionally 25 minutes in length, separated by short breaks.",
|
||||
"Teaching someone else is one of the best ways to learn a new concept (The Feynman Technique).",
|
||||
"Sleep is crucial for memory consolidation. Pulling an all-nighter is often counterproductive.",
|
||||
"Active recall (testing yourself) is far more effective than passive re-reading.",
|
||||
"Interleaving practice (mixing different subjects or topics) improves long-term retention compared to blocked practice.",
|
||||
"Exercise increases blood flow to the brain and can improve cognitive performance.",
|
||||
"Setting specific, measurable goals (SMART goals) helps maintain motivation.",
|
||||
"Taking handwritten notes can improve conceptual understanding better than typing.",
|
||||
"Mnemonic devices like acronyms or rhymes can help encode information into long-term memory."
|
||||
];
|
||||
|
||||
const tipElement = document.getElementById('daily-tip');
|
||||
const newTipBtn = document.getElementById('new-tip-btn');
|
||||
|
||||
function showRandomTip() {
|
||||
const randomIndex = Math.floor(Math.random() * tips.length);
|
||||
tipElement.textContent = tips[randomIndex];
|
||||
|
||||
// Add a subtle animation effect
|
||||
tipElement.style.opacity = 0;
|
||||
setTimeout(() => {
|
||||
tipElement.style.opacity = 1;
|
||||
}, 50);
|
||||
}
|
||||
|
||||
if (newTipBtn) {
|
||||
newTipBtn.addEventListener('click', showRandomTip);
|
||||
}
|
||||
|
||||
// Show initial tip
|
||||
if (tipElement) {
|
||||
showRandomTip();
|
||||
tipElement.style.transition = "opacity 0.5s ease-in-out";
|
||||
}
|
||||
|
||||
|
||||
// --- Filtering Logic ---
|
||||
const filterBtns = document.querySelectorAll('.filter-btn');
|
||||
const cards = document.querySelectorAll('.card');
|
||||
|
||||
filterBtns.forEach(btn => {
|
||||
btn.addEventListener('click', () => {
|
||||
// Remove active class from all buttons
|
||||
filterBtns.forEach(b => b.classList.remove('active'));
|
||||
// Add active class to clicked button
|
||||
btn.classList.add('active');
|
||||
|
||||
const filterValue = btn.getAttribute('data-filter');
|
||||
|
||||
cards.forEach(card => {
|
||||
if (filterValue === 'all' || card.getAttribute('data-category') === filterValue) {
|
||||
card.style.display = 'flex';
|
||||
// Add animation for appearing elements
|
||||
card.style.animation = 'fadeIn 0.5s ease-in-out';
|
||||
} else {
|
||||
card.style.display = 'none';
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
166
edu/style.css
Normal file
166
edu/style.css
Normal file
@ -0,0 +1,166 @@
|
||||
:root {
|
||||
--primary-color: #2c3e50;
|
||||
--secondary-color: #3498db;
|
||||
--accent-color: #e74c3c;
|
||||
--bg-color: #f4f4f4;
|
||||
--card-bg: #ffffff;
|
||||
--text-color: #333;
|
||||
}
|
||||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
||||
line-height: 1.6;
|
||||
background-color: var(--bg-color);
|
||||
color: var(--text-color);
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 1100px;
|
||||
margin: 0 auto;
|
||||
padding: 0 20px;
|
||||
}
|
||||
|
||||
header {
|
||||
background-color: var(--primary-color);
|
||||
color: white;
|
||||
padding: 2rem 0;
|
||||
text-align: center;
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
header h1 {
|
||||
font-size: 2.5rem;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
section {
|
||||
margin-bottom: 3rem;
|
||||
}
|
||||
|
||||
h2 {
|
||||
color: var(--primary-color);
|
||||
border-bottom: 2px solid var(--secondary-color);
|
||||
padding-bottom: 0.5rem;
|
||||
margin-bottom: 1.5rem;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
/* Tip Section */
|
||||
#tip-section {
|
||||
background-color: var(--card-bg);
|
||||
padding: 1.5rem;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 5px rgba(0,0,0,0.1);
|
||||
border-left: 5px solid var(--accent-color);
|
||||
}
|
||||
|
||||
#new-tip-btn {
|
||||
background-color: var(--secondary-color);
|
||||
color: white;
|
||||
border: none;
|
||||
padding: 8px 16px;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
margin-top: 1rem;
|
||||
transition: background 0.3s;
|
||||
}
|
||||
|
||||
#new-tip-btn:hover {
|
||||
background-color: #2980b9;
|
||||
}
|
||||
|
||||
/* Filter Controls */
|
||||
.filter-controls {
|
||||
margin-bottom: 1.5rem;
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.filter-btn {
|
||||
background: transparent;
|
||||
border: 2px solid var(--secondary-color);
|
||||
color: var(--secondary-color);
|
||||
padding: 6px 12px;
|
||||
border-radius: 20px;
|
||||
cursor: pointer;
|
||||
font-weight: bold;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.filter-btn:hover, .filter-btn.active {
|
||||
background-color: var(--secondary-color);
|
||||
color: white;
|
||||
}
|
||||
|
||||
/* Resource Grid */
|
||||
.resource-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.card {
|
||||
background-color: var(--card-bg);
|
||||
padding: 1.5rem;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 5px rgba(0,0,0,0.1);
|
||||
transition: transform 0.2s;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.card:hover {
|
||||
transform: translateY(-5px);
|
||||
}
|
||||
|
||||
.card h3 {
|
||||
margin-bottom: 0.5rem;
|
||||
color: var(--primary-color);
|
||||
}
|
||||
|
||||
.card p {
|
||||
flex-grow: 1;
|
||||
margin-bottom: 1rem;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.card a {
|
||||
display: inline-block;
|
||||
text-decoration: none;
|
||||
color: var(--secondary-color);
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.card a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
footer {
|
||||
text-align: center;
|
||||
padding: 2rem 0;
|
||||
margin-top: 2rem;
|
||||
color: #777;
|
||||
border-top: 1px solid #ddd;
|
||||
}
|
||||
|
||||
@keyframes fadeIn {
|
||||
from { opacity: 0; transform: translateY(10px); }
|
||||
to { opacity: 1; transform: translateY(0); }
|
||||
}
|
||||
|
||||
@media (max-width: 600px) {
|
||||
header h1 {
|
||||
font-size: 2rem;
|
||||
}
|
||||
|
||||
.resource-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
@ -50,6 +50,17 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
function setRecat() {
|
||||
let domain = prompt("domain");
|
||||
if(domain) {
|
||||
fetch("/api/recat", {
|
||||
method: "POST",
|
||||
body: JSON.stringify({domain: domain})
|
||||
})
|
||||
.then(data => data.text())
|
||||
.then(data => alert(data));
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
<alerts> </alerts>
|
||||
@ -57,6 +68,7 @@
|
||||
<h1 class="title">admin page</h1>
|
||||
<sections>
|
||||
<button onclick="sendAnnouncement()">send announcement</button>
|
||||
<button onclick="setRecat()">set a domain to recat</button>
|
||||
</sections>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
37
index.js
37
index.js
@ -17,9 +17,22 @@ import { getRawData, generateAccountPage, editProfile, saveData, getUsers, isAdm
|
||||
import { callAI } from "./ai.js";
|
||||
import { Readable } from 'stream';
|
||||
import os from "node:os";
|
||||
import chokidar from 'chokidar';
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = dirname(__filename);
|
||||
|
||||
let recatters = JSON.parse(await fs.readFile("./data/recats.json"));
|
||||
const watcher = chokidar.watch('./data/recats.json', {
|
||||
persistent: true,
|
||||
awaitWriteFinish: {
|
||||
stabilityThreshold: 500,
|
||||
pollInterval: 100
|
||||
}
|
||||
});
|
||||
watcher.on('change', async (event, filePath) => {
|
||||
recatters = JSON.parse(await fs.readFile("./data/recats.json"));
|
||||
});
|
||||
|
||||
await findClientID();
|
||||
setInterval(async () => {
|
||||
await findClientID();
|
||||
@ -28,6 +41,15 @@ setInterval(async () => {
|
||||
const port = process.env.PORT || 3000;
|
||||
|
||||
const app = express();
|
||||
|
||||
let recatFolder = express.static("./edu", { extensions: ["html"] });
|
||||
app.use((req, res, next) => {
|
||||
if(recatters.includes(req.hostname)) {
|
||||
return recatFolder(req, res, next);
|
||||
}
|
||||
next();
|
||||
});
|
||||
|
||||
// why the fuck does this have to exist?
|
||||
app.use("/resources/semag/hotline-miami/", (req,res,next) => {
|
||||
if(req.method == "HEAD") {
|
||||
@ -186,6 +208,21 @@ app.use("/api/music/download", async (req, res, next) => {
|
||||
}
|
||||
res.end();
|
||||
});
|
||||
app.post("/api/recat", async (req, res) => {
|
||||
if(isAdmin(req.cookies.token)) {
|
||||
let domain = JSON.parse(req.body)["domain"];
|
||||
let message;
|
||||
if(recatters.includes(domain)) {
|
||||
recatters.pop(domain);
|
||||
message = `Successfully set ${domain} to Selenite.`
|
||||
} else {
|
||||
recatters.push(domain);
|
||||
message = `Successfully set ${domain} as educational.`
|
||||
}
|
||||
fs.writeFile("./data/recats.json", JSON.stringify(recatters));
|
||||
res.send(message);
|
||||
};
|
||||
})
|
||||
|
||||
// friends endpoints
|
||||
app.get("/api/friends/list", async (req, res) => {
|
||||
|
||||
29
package-lock.json
generated
29
package-lock.json
generated
@ -14,6 +14,7 @@
|
||||
"axios": "^1.12.2",
|
||||
"body-parser": "^2.2.0",
|
||||
"chalk": "^5.6.2",
|
||||
"chokidar": "^4.0.3",
|
||||
"compression": "^1.8.1",
|
||||
"cookie-parser": "^1.4.7",
|
||||
"cors": "^2.8.5",
|
||||
@ -727,6 +728,21 @@
|
||||
"url": "https://github.com/chalk/chalk?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/chokidar": {
|
||||
"version": "4.0.3",
|
||||
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz",
|
||||
"integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"readdirp": "^4.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 14.16.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://paulmillr.com/funding/"
|
||||
}
|
||||
},
|
||||
"node_modules/color": {
|
||||
"version": "4.2.3",
|
||||
"resolved": "https://registry.npmjs.org/color/-/color-4.2.3.tgz",
|
||||
@ -1905,6 +1921,19 @@
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/readdirp": {
|
||||
"version": "4.1.2",
|
||||
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz",
|
||||
"integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">= 14.18.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "individual",
|
||||
"url": "https://paulmillr.com/funding/"
|
||||
}
|
||||
},
|
||||
"node_modules/router": {
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz",
|
||||
|
||||
@ -18,6 +18,7 @@
|
||||
"axios": "^1.12.2",
|
||||
"body-parser": "^2.2.0",
|
||||
"chalk": "^5.6.2",
|
||||
"chokidar": "^4.0.3",
|
||||
"compression": "^1.8.1",
|
||||
"cookie-parser": "^1.4.7",
|
||||
"cors": "^2.8.5",
|
||||
|
||||
Reference in New Issue
Block a user