stem-lab-toolkit 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/admin.html +131 -0
- package/ads.txt +880 -0
- package/api.js +79 -0
- package/assets/logo.png +0 -0
- package/browse.html +59 -0
- package/css/style.css +602 -0
- package/game.html +208 -0
- package/games.json +25310 -0
- package/index.html +359 -0
- package/js/admin.js +177 -0
- package/js/games.js +113 -0
- package/js/main.js +301 -0
- package/package.json +17 -0
package/api.js
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
const http = require('http');
|
|
2
|
+
const fs = require('fs');
|
|
3
|
+
const path = require('path');
|
|
4
|
+
|
|
5
|
+
const GAMES_FILE = path.join(__dirname, 'games.json');
|
|
6
|
+
const ADMIN_PIN = '1234';
|
|
7
|
+
const PORT = 3001;
|
|
8
|
+
|
|
9
|
+
function readGames() {
|
|
10
|
+
try { return JSON.parse(fs.readFileSync(GAMES_FILE, 'utf8')); }
|
|
11
|
+
catch { return []; }
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function writeGames(games) {
|
|
15
|
+
fs.writeFileSync(GAMES_FILE, JSON.stringify(games, null, 2));
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function json(res, status, obj) {
|
|
19
|
+
res.writeHead(status, {
|
|
20
|
+
'Content-Type': 'application/json',
|
|
21
|
+
'Access-Control-Allow-Origin': '*',
|
|
22
|
+
});
|
|
23
|
+
res.end(JSON.stringify(obj));
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function parseBody(req, cb) {
|
|
27
|
+
let body = '';
|
|
28
|
+
req.on('data', d => { body += d; if (body.length > 1e6) req.destroy(); });
|
|
29
|
+
req.on('end', () => { try { cb(JSON.parse(body)); } catch { cb(null); } });
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const server = http.createServer((req, res) => {
|
|
33
|
+
if (req.method === 'OPTIONS') {
|
|
34
|
+
res.writeHead(204, {
|
|
35
|
+
'Access-Control-Allow-Origin': '*',
|
|
36
|
+
'Access-Control-Allow-Methods': 'GET, POST, OPTIONS',
|
|
37
|
+
'Access-Control-Allow-Headers': 'Content-Type',
|
|
38
|
+
});
|
|
39
|
+
return res.end();
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// GET /api/games — visible games for browse page
|
|
43
|
+
if (req.method === 'GET' && req.url === '/api/games') {
|
|
44
|
+
return json(res, 200, readGames().filter(g => g.visible));
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// GET /api/games/all — all games for admin
|
|
48
|
+
if (req.method === 'GET' && req.url === '/api/games/all') {
|
|
49
|
+
return json(res, 200, readGames());
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// POST /api/games — full save (admin only)
|
|
53
|
+
if (req.method === 'POST' && req.url === '/api/games') {
|
|
54
|
+
return parseBody(req, data => {
|
|
55
|
+
if (!data || data.adminPin !== ADMIN_PIN)
|
|
56
|
+
return json(res, 403, { error: 'Unauthorized.' });
|
|
57
|
+
if (!Array.isArray(data.games))
|
|
58
|
+
return json(res, 400, { error: 'Invalid payload.' });
|
|
59
|
+
writeGames(data.games);
|
|
60
|
+
json(res, 200, { ok: true });
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// POST /api/pin — validate PIN, return role
|
|
65
|
+
if (req.method === 'POST' && req.url === '/api/pin') {
|
|
66
|
+
return parseBody(req, data => {
|
|
67
|
+
if (!data) return json(res, 400, { error: 'Bad request.' });
|
|
68
|
+
if (data.pin === ADMIN_PIN) return json(res, 200, { role: 'admin' });
|
|
69
|
+
if (data.pin === '5555') return json(res, 200, { role: 'user' });
|
|
70
|
+
return json(res, 403, { role: 'none' });
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
json(res, 404, { error: 'Not found.' });
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
server.listen(PORT, '127.0.0.1', () => {
|
|
78
|
+
console.log(`API listening on 127.0.0.1:${PORT}`);
|
|
79
|
+
});
|
package/assets/logo.png
ADDED
|
File without changes
|
package/browse.html
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
+
<title>MM Games</title>
|
|
7
|
+
<link rel="stylesheet" href="./css/style.css">
|
|
8
|
+
</head>
|
|
9
|
+
<body>
|
|
10
|
+
|
|
11
|
+
<header class="site-header">
|
|
12
|
+
<div class="header-inner">
|
|
13
|
+
<a href="./browse.html" class="logo-link">
|
|
14
|
+
<img src="./assets/logo.png" alt="" class="logo-img" onerror="this.style.display='none'">
|
|
15
|
+
<span>MM Games</span>
|
|
16
|
+
</a>
|
|
17
|
+
<nav class="site-nav">
|
|
18
|
+
<a href="./browse.html" class="active">Browse</a>
|
|
19
|
+
</nav>
|
|
20
|
+
</div>
|
|
21
|
+
</header>
|
|
22
|
+
|
|
23
|
+
<main style="padding: 32px 0 64px;">
|
|
24
|
+
<div class="container">
|
|
25
|
+
|
|
26
|
+
<!-- Search + filters -->
|
|
27
|
+
<div style="display:flex; flex-wrap:wrap; align-items:center; gap:16px; margin-bottom:28px;">
|
|
28
|
+
<div class="search-wrap" style="flex:1; min-width:200px;">
|
|
29
|
+
<svg width="16" height="16" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24">
|
|
30
|
+
<circle cx="11" cy="11" r="8"/><line x1="21" y1="21" x2="16.65" y2="16.65"/>
|
|
31
|
+
</svg>
|
|
32
|
+
<input class="search-input" id="search" type="search" placeholder="Search games…" autocomplete="off">
|
|
33
|
+
</div>
|
|
34
|
+
<div class="category-filters" id="cat-filters">
|
|
35
|
+
<button class="cat-btn active" data-cat="All">All</button>
|
|
36
|
+
<button class="cat-btn" data-cat="Action">Action</button>
|
|
37
|
+
<button class="cat-btn" data-cat="Adventure">Adventure</button>
|
|
38
|
+
<button class="cat-btn" data-cat="Sports">Sports</button>
|
|
39
|
+
<button class="cat-btn" data-cat="Puzzle">Puzzle</button>
|
|
40
|
+
<button class="cat-btn" data-cat="Strategy">Strategy</button>
|
|
41
|
+
<button class="cat-btn" data-cat="Classic">Classic</button>
|
|
42
|
+
</div>
|
|
43
|
+
</div>
|
|
44
|
+
|
|
45
|
+
<!-- Game of the Day -->
|
|
46
|
+
<div class="section" id="gotd-section"></div>
|
|
47
|
+
|
|
48
|
+
<!-- Game grid -->
|
|
49
|
+
<div class="section">
|
|
50
|
+
<div class="section-title" id="grid-label">All Games</div>
|
|
51
|
+
<div class="game-grid" id="game-grid"></div>
|
|
52
|
+
</div>
|
|
53
|
+
|
|
54
|
+
</div>
|
|
55
|
+
</main>
|
|
56
|
+
|
|
57
|
+
<script src="./js/games.js"></script>
|
|
58
|
+
</body>
|
|
59
|
+
</html>
|