mm-math 0.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 +145 -0
- package/ads.txt +880 -0
- package/api.js +179 -0
- package/assets/favicon.png +0 -0
- package/assets/logo.png +0 -0
- package/browse.html +178 -0
- package/change-password.html +168 -0
- package/control-panel.html +885 -0
- package/css/style.css +615 -0
- package/index.html +577 -0
- package/js/admin.js +285 -0
- package/js/games.js +192 -0
- package/js/main.js +191 -0
- package/js/pin-modal.js +13 -0
- package/js/theme.js +20 -0
- package/package.json +36 -0
- package/tool.html +227 -0
- package/version.txt +1 -0
package/js/theme.js
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
const moonSVG = `<svg width="16" height="16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" viewBox="0 0 24 24"><path d="M21 12.79A9 9 0 1111.21 3 7 7 0 0021 12.79z"/></svg>`;
|
|
2
|
+
const sunSVG = `<svg width="16" height="16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" viewBox="0 0 24 24"><circle cx="12" cy="12" r="5"/><line x1="12" y1="1" x2="12" y2="3"/><line x1="12" y1="21" x2="12" y2="23"/><line x1="4.22" y1="4.22" x2="5.64" y2="5.64"/><line x1="18.36" y1="18.36" x2="19.78" y2="19.78"/><line x1="1" y1="12" x2="3" y2="12"/><line x1="21" y1="12" x2="23" y2="12"/><line x1="4.22" y1="19.78" x2="5.64" y2="18.36"/><line x1="18.36" y1="5.64" x2="19.78" y2="4.22"/></svg>`;
|
|
3
|
+
|
|
4
|
+
function toggleTheme() {
|
|
5
|
+
const isDark = document.documentElement.getAttribute('data-theme') === 'dark';
|
|
6
|
+
const next = isDark ? 'light' : 'dark';
|
|
7
|
+
document.documentElement.setAttribute('data-theme', next);
|
|
8
|
+
localStorage.setItem('mm_theme', next);
|
|
9
|
+
updateIcon();
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function updateIcon() {
|
|
13
|
+
const btn = document.getElementById('theme-toggle');
|
|
14
|
+
if (!btn) return;
|
|
15
|
+
const isDark = document.documentElement.getAttribute('data-theme') === 'dark';
|
|
16
|
+
btn.innerHTML = isDark ? sunSVG : moonSVG;
|
|
17
|
+
btn.title = isDark ? 'Light mode' : 'Dark mode';
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
updateIcon();
|
package/package.json
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "mm-math",
|
|
3
|
+
"version": "0.0.0",
|
|
4
|
+
"description": "STEM educational toolkit",
|
|
5
|
+
"main": "api.js",
|
|
6
|
+
"files": [
|
|
7
|
+
"api.js",
|
|
8
|
+
"index.html",
|
|
9
|
+
"browse.html",
|
|
10
|
+
"admin.html",
|
|
11
|
+
"control-panel.html",
|
|
12
|
+
"change-password.html",
|
|
13
|
+
"tool.html",
|
|
14
|
+
"ads.txt",
|
|
15
|
+
"version.txt",
|
|
16
|
+
"assets/",
|
|
17
|
+
"css/",
|
|
18
|
+
"js/"
|
|
19
|
+
],
|
|
20
|
+
"scripts": {
|
|
21
|
+
"start": "node api.js"
|
|
22
|
+
},
|
|
23
|
+
"keywords": [
|
|
24
|
+
"education",
|
|
25
|
+
"stem",
|
|
26
|
+
"toolkit",
|
|
27
|
+
"math"
|
|
28
|
+
],
|
|
29
|
+
"license": "MIT",
|
|
30
|
+
"author": "",
|
|
31
|
+
"dependencies": {
|
|
32
|
+
"bcrypt": "^6.0.0",
|
|
33
|
+
"express": "^5.2.1",
|
|
34
|
+
"express-rate-limit": "^8.5.1"
|
|
35
|
+
}
|
|
36
|
+
}
|
package/tool.html
ADDED
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<script>(function(){var n=performance.getEntriesByType('navigation')[0];if(n&&n.type==='reload'){sessionStorage.clear();window.location.replace('./index.html');return;}if(sessionStorage.getItem('mm_auth')!=='true'){window.location.replace('./index.html');return;}if(sessionStorage.getItem('mm_force_change')==='true'){window.location.replace('./change-password.html');return;}var r=sessionStorage.getItem('mm_rank');if(!r){window.location.replace('./index.html');}})();</script>
|
|
6
|
+
<link rel="icon" type="image/png" href="/assets/favicon.png">
|
|
7
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
8
|
+
<title>MM Games</title>
|
|
9
|
+
<link rel="stylesheet" href="./css/style.css">
|
|
10
|
+
<style>
|
|
11
|
+
body { background: #0a0a0a; color: #fff; }
|
|
12
|
+
|
|
13
|
+
.site-header { background: #111; border-bottom-color: #222; }
|
|
14
|
+
.site-header .logo-link { color: #fff; }
|
|
15
|
+
.site-nav a { color: rgba(255,255,255,.5); }
|
|
16
|
+
.site-nav a:hover { background: #222; color: #fff; }
|
|
17
|
+
|
|
18
|
+
.game-wrap {
|
|
19
|
+
display: flex;
|
|
20
|
+
flex-direction: column;
|
|
21
|
+
align-items: center;
|
|
22
|
+
padding: 20px 16px 40px;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
.game-title-bar {
|
|
26
|
+
width: 100%;
|
|
27
|
+
max-width: 960px;
|
|
28
|
+
display: flex;
|
|
29
|
+
align-items: center;
|
|
30
|
+
justify-content: space-between;
|
|
31
|
+
margin-bottom: 12px;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
.game-name {
|
|
35
|
+
font-size: 1.1rem;
|
|
36
|
+
font-weight: 600;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
.game-frame-wrap {
|
|
40
|
+
width: 100%;
|
|
41
|
+
max-width: 960px;
|
|
42
|
+
position: relative;
|
|
43
|
+
background: #000;
|
|
44
|
+
border-radius: 10px;
|
|
45
|
+
overflow: hidden;
|
|
46
|
+
box-shadow: 0 8px 40px rgba(0,0,0,.6);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
.game-frame-ratio {
|
|
50
|
+
padding-bottom: 56.25%;
|
|
51
|
+
position: relative;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
.game-iframe {
|
|
55
|
+
position: absolute;
|
|
56
|
+
inset: 0;
|
|
57
|
+
width: 100%;
|
|
58
|
+
height: 100%;
|
|
59
|
+
border: none;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
.game-actions {
|
|
63
|
+
width: 100%;
|
|
64
|
+
max-width: 960px;
|
|
65
|
+
display: flex;
|
|
66
|
+
gap: 10px;
|
|
67
|
+
margin-top: 14px;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
.fullscreen-btn {
|
|
71
|
+
display: flex;
|
|
72
|
+
align-items: center;
|
|
73
|
+
gap: 8px;
|
|
74
|
+
padding: 10px 22px;
|
|
75
|
+
background: #ff9f0a;
|
|
76
|
+
color: #fff;
|
|
77
|
+
border: none;
|
|
78
|
+
border-radius: 8px;
|
|
79
|
+
font-size: 0.9rem;
|
|
80
|
+
font-weight: 600;
|
|
81
|
+
cursor: pointer;
|
|
82
|
+
transition: background .15s;
|
|
83
|
+
font-family: inherit;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
.fullscreen-btn:hover { background: #e68e00; }
|
|
87
|
+
|
|
88
|
+
.back-btn {
|
|
89
|
+
display: flex;
|
|
90
|
+
align-items: center;
|
|
91
|
+
gap: 6px;
|
|
92
|
+
padding: 10px 18px;
|
|
93
|
+
background: rgba(255,255,255,.08);
|
|
94
|
+
color: rgba(255,255,255,.7);
|
|
95
|
+
border: none;
|
|
96
|
+
border-radius: 8px;
|
|
97
|
+
font-size: 0.9rem;
|
|
98
|
+
font-weight: 500;
|
|
99
|
+
cursor: pointer;
|
|
100
|
+
font-family: inherit;
|
|
101
|
+
transition: background .15s;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
.back-btn:hover { background: rgba(255,255,255,.14); }
|
|
105
|
+
|
|
106
|
+
.error-state {
|
|
107
|
+
text-align: center;
|
|
108
|
+
padding: 80px 20px;
|
|
109
|
+
color: rgba(255,255,255,.5);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
.error-state h2 { font-size: 1.2rem; margin-bottom: 8px; color: #fff; }
|
|
113
|
+
</style>
|
|
114
|
+
</head>
|
|
115
|
+
<body>
|
|
116
|
+
|
|
117
|
+
<header class="site-header">
|
|
118
|
+
<div class="header-inner">
|
|
119
|
+
<a href="./browse.html" class="logo-link">
|
|
120
|
+
<img src="./assets/logo.png?v=new" alt="" class="logo-img" onerror="this.style.display='none'">
|
|
121
|
+
<span>MM Games</span>
|
|
122
|
+
|
|
123
|
+
</a>
|
|
124
|
+
<nav class="site-nav">
|
|
125
|
+
<a href="./browse.html">Browse</a>
|
|
126
|
+
<a href="./admin.html">Admin</a>
|
|
127
|
+
<a href="./index.html" class="nav-calc-btn" title="Calculator"><svg width="18" height="18" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" viewBox="0 0 24 24"><rect x="4" y="2" width="16" height="20" rx="2"/><line x1="8" y1="6" x2="16" y2="6"/><line x1="8" y1="10" x2="10" y2="10"/><line x1="8" y1="14" x2="10" y2="14"/><line x1="8" y1="18" x2="10" y2="18"/><line x1="14" y1="10" x2="16" y2="10"/><line x1="14" y1="14" x2="16" y2="14"/><line x1="14" y1="18" x2="16" y2="18"/></svg></a>
|
|
128
|
+
</nav>
|
|
129
|
+
</div>
|
|
130
|
+
</header>
|
|
131
|
+
|
|
132
|
+
<div class="game-wrap" id="game-wrap">
|
|
133
|
+
<div class="error-state"><h2>Loading…</h2></div>
|
|
134
|
+
</div>
|
|
135
|
+
|
|
136
|
+
<script>
|
|
137
|
+
(async () => {
|
|
138
|
+
const params = new URLSearchParams(window.location.search);
|
|
139
|
+
const slug = params.get('slug');
|
|
140
|
+
const wrap = document.getElementById('game-wrap');
|
|
141
|
+
|
|
142
|
+
function error(msg) {
|
|
143
|
+
wrap.innerHTML = `<div class="error-state"><h2>Game not found</h2><p>${msg}</p><br><a href="./browse.html" style="color:#ff9f0a;">← Back to games</a></div>`;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
if (!slug) return error('No game specified.');
|
|
147
|
+
|
|
148
|
+
let games;
|
|
149
|
+
try {
|
|
150
|
+
const res = await fetch('./api/games');
|
|
151
|
+
games = res.ok ? await res.json() : null;
|
|
152
|
+
} catch { games = null; }
|
|
153
|
+
|
|
154
|
+
if (!games) {
|
|
155
|
+
try {
|
|
156
|
+
const res = await fetch('./games.json');
|
|
157
|
+
const all = await res.json();
|
|
158
|
+
games = all.filter(g => g.visible);
|
|
159
|
+
} catch { return error('Could not load game data.'); }
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
const game = games.find(g => g.slug === slug);
|
|
163
|
+
if (!game) return error('Game not found.');
|
|
164
|
+
|
|
165
|
+
document.title = game.name + ' — MM Games';
|
|
166
|
+
|
|
167
|
+
const SITE = 'https://calc.moshelab.com';
|
|
168
|
+
const isLocal = game.embedType === 'local';
|
|
169
|
+
const src = isLocal ? '' :
|
|
170
|
+
game.embedType === 'gamepix' ? game.embedUrl :
|
|
171
|
+
`https://html5.gamedistribution.com/${game.gameId}/?gd_sdk_referrer_url=${SITE}/games/${game.slug}`;
|
|
172
|
+
|
|
173
|
+
wrap.innerHTML = `
|
|
174
|
+
<div class="game-title-bar">
|
|
175
|
+
<div class="game-name">${game.name}</div>
|
|
176
|
+
<span class="cat-badge ${game.category}">${game.category}</span>
|
|
177
|
+
</div>
|
|
178
|
+
<div class="game-frame-wrap" id="frame-wrap" style="${isLocal ? 'height:80vh;' : ''}">
|
|
179
|
+
<div class="${isLocal ? '' : 'game-frame-ratio'}" style="${isLocal ? 'height:100%;' : ''}">
|
|
180
|
+
<iframe
|
|
181
|
+
class="game-iframe"
|
|
182
|
+
id="game-iframe"
|
|
183
|
+
${src ? `src="${src}"` : ''}
|
|
184
|
+
allowfullscreen
|
|
185
|
+
allow="fullscreen; autoplay; encrypted-media"
|
|
186
|
+
scrolling="${isLocal ? 'yes' : 'no'}"
|
|
187
|
+
style="${isLocal ? 'position:static;height:100%;' : ''}"
|
|
188
|
+
></iframe>
|
|
189
|
+
</div>
|
|
190
|
+
</div>
|
|
191
|
+
<div class="game-actions">
|
|
192
|
+
<button class="fullscreen-btn" id="fs-btn">
|
|
193
|
+
<svg width="16" height="16" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24">
|
|
194
|
+
<path d="M8 3H5a2 2 0 00-2 2v0.0.1m18 0V5a2 2 0 00-2-2h-3m0 18h3a2 2 0 002-2v-3M3 16v0.0.1a2 2 0 002 2h3"/>
|
|
195
|
+
</svg>
|
|
196
|
+
Fullscreen
|
|
197
|
+
</button>
|
|
198
|
+
<button class="back-btn" onclick="history.length > 1 ? history.back() : window.location='./browse.html'">
|
|
199
|
+
← Back
|
|
200
|
+
</button>
|
|
201
|
+
</div>
|
|
202
|
+
`;
|
|
203
|
+
|
|
204
|
+
if (isLocal) {
|
|
205
|
+
try {
|
|
206
|
+
const localPath = `./local-games/${game.localFile || game.slug + '.html'}`;
|
|
207
|
+
const res = await fetch(localPath);
|
|
208
|
+
const html = await res.text();
|
|
209
|
+
document.getElementById('game-iframe').srcdoc = html;
|
|
210
|
+
} catch {
|
|
211
|
+
error('Could not load game file.');
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
document.getElementById('fs-btn').addEventListener('click', () => {
|
|
216
|
+
const el = document.getElementById('frame-wrap');
|
|
217
|
+
if (el.requestFullscreen) el.requestFullscreen();
|
|
218
|
+
else if (el.webkitRequestFullscreen) el.webkitRequestFullscreen();
|
|
219
|
+
});
|
|
220
|
+
})();
|
|
221
|
+
</script>
|
|
222
|
+
<script src="./js/pin-modal.js?v=0.0.1c"></script>
|
|
223
|
+
|
|
224
|
+
<a href="https://forms.gle/59RMvCkURByui1747" target="_blank" rel="noopener" style="position:fixed;bottom:20px;right:20px;background:rgba(255,255,255,.08);color:rgba(255,255,255,.4);font-size:0.72rem;font-weight:500;padding:6px 14px;border-radius:999px;text-decoration:none;z-index:199;border:1px solid rgba(255,255,255,.1);transition:all .15s;" onmouseover="this.style.background='rgba(255,255,255,.16)';this.style.color='rgba(255,255,255,.8)'" onmouseout="this.style.background='rgba(255,255,255,.08)';this.style.color='rgba(255,255,255,.4)'">Feedback</a>
|
|
225
|
+
|
|
226
|
+
</body>
|
|
227
|
+
</html>
|
package/version.txt
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
v0.0.1
|