stem-lab-toolkit 1.0.1 → 1.0.3
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 +60 -13
- package/api.js +163 -85
- package/assets/favicon.png +0 -0
- package/assets/logo.png +0 -0
- package/browse.html +121 -5
- package/bump.sh +5 -1
- package/change-password.html +168 -0
- package/control-panel.html +885 -0
- package/css/style.css +3 -1
- package/index.html +247 -98
- package/js/admin.js +144 -29
- package/js/games.js +2 -2
- package/js/main.js +14 -95
- package/js/pin-modal.js +10 -95
- package/js/theme.js +20 -0
- package/package.json +7 -2
- package/{game.html → tool.html} +31 -18
- package/users.json +62 -0
- package/version.txt +1 -1
- package/pins.json +0 -1
package/css/style.css
CHANGED
|
@@ -50,8 +50,9 @@ a { color: inherit; text-decoration: none; }
|
|
|
50
50
|
}
|
|
51
51
|
|
|
52
52
|
.logo-img {
|
|
53
|
-
height:
|
|
53
|
+
height: 40px;
|
|
54
54
|
width: auto;
|
|
55
|
+
border-radius: 8px;
|
|
55
56
|
}
|
|
56
57
|
|
|
57
58
|
.site-nav {
|
|
@@ -611,3 +612,4 @@ a { color: inherit; text-decoration: none; }
|
|
|
611
612
|
transition: background .15s, color .15s;
|
|
612
613
|
}
|
|
613
614
|
.nav-calc-btn:hover { background: var(--surface); color: var(--text); }
|
|
615
|
+
|
package/index.html
CHANGED
|
@@ -25,7 +25,6 @@
|
|
|
25
25
|
user-select: none;
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
-
/* Header */
|
|
29
28
|
.calc-header {
|
|
30
29
|
display: flex;
|
|
31
30
|
align-items: center;
|
|
@@ -64,7 +63,6 @@
|
|
|
64
63
|
letter-spacing: .05em;
|
|
65
64
|
}
|
|
66
65
|
|
|
67
|
-
/* Display */
|
|
68
66
|
.calc-display {
|
|
69
67
|
padding: 12px 24px 20px;
|
|
70
68
|
text-align: right;
|
|
@@ -95,7 +93,6 @@
|
|
|
95
93
|
transition: font-size .1s;
|
|
96
94
|
}
|
|
97
95
|
|
|
98
|
-
/* Scientific row */
|
|
99
96
|
.sci-rows {
|
|
100
97
|
background: #111;
|
|
101
98
|
border-bottom: 1px solid rgba(255,255,255,.06);
|
|
@@ -127,7 +124,6 @@
|
|
|
127
124
|
.btn-sci:active { background: rgba(255,255,255,.1); }
|
|
128
125
|
.btn-sci.inv-active { color: #ff9f0a; }
|
|
129
126
|
|
|
130
|
-
/* Main buttons */
|
|
131
127
|
.calc-buttons {
|
|
132
128
|
display: grid;
|
|
133
129
|
grid-template-columns: repeat(4, 1fr);
|
|
@@ -156,8 +152,8 @@
|
|
|
156
152
|
.btn-zero { grid-column: span 2; justify-content: flex-start; padding-left: 30px; }
|
|
157
153
|
.btn-eq { background: #ff9f0a; color: #fff; }
|
|
158
154
|
|
|
159
|
-
/*
|
|
160
|
-
.
|
|
155
|
+
/* Login overlay */
|
|
156
|
+
.login-overlay {
|
|
161
157
|
position: fixed;
|
|
162
158
|
inset: 0;
|
|
163
159
|
background: rgba(0,0,0,.75);
|
|
@@ -167,97 +163,84 @@
|
|
|
167
163
|
z-index: 999;
|
|
168
164
|
backdrop-filter: blur(8px);
|
|
169
165
|
}
|
|
166
|
+
.login-overlay.hidden { display: none; }
|
|
170
167
|
|
|
171
|
-
.
|
|
172
|
-
|
|
173
|
-
.pin-box {
|
|
168
|
+
.login-box {
|
|
174
169
|
background: #1c1c1e;
|
|
175
170
|
border-radius: 20px;
|
|
176
|
-
padding: 32px 28px
|
|
171
|
+
padding: 32px 28px 28px;
|
|
177
172
|
width: 300px;
|
|
178
173
|
box-shadow: 0 32px 80px rgba(0,0,0,.6);
|
|
179
174
|
border: 1px solid rgba(255,255,255,.08);
|
|
180
175
|
text-align: center;
|
|
181
176
|
}
|
|
182
177
|
|
|
183
|
-
.
|
|
178
|
+
.login-box h2 {
|
|
184
179
|
font-size: 1rem;
|
|
185
180
|
font-weight: 600;
|
|
186
181
|
color: #fff;
|
|
187
182
|
margin-bottom: 4px;
|
|
188
183
|
}
|
|
189
184
|
|
|
190
|
-
.
|
|
185
|
+
.login-box p {
|
|
191
186
|
font-size: 0.8rem;
|
|
192
187
|
color: rgba(255,255,255,.4);
|
|
193
188
|
margin-bottom: 24px;
|
|
194
189
|
}
|
|
195
190
|
|
|
196
|
-
.
|
|
197
|
-
|
|
198
|
-
justify-content: center;
|
|
199
|
-
gap: 14px;
|
|
200
|
-
margin-bottom: 28px;
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
.pin-dot {
|
|
204
|
-
width: 13px;
|
|
205
|
-
height: 13px;
|
|
206
|
-
border-radius: 50%;
|
|
207
|
-
border: 2px solid rgba(255,255,255,.2);
|
|
208
|
-
background: transparent;
|
|
209
|
-
transition: all .15s;
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
.pin-dot.filled { background: #ff9f0a; border-color: #ff9f0a; }
|
|
213
|
-
.pin-dot.error { background: #ff3b30; border-color: #ff3b30; }
|
|
214
|
-
|
|
215
|
-
.pin-keypad {
|
|
216
|
-
display: grid;
|
|
217
|
-
grid-template-columns: repeat(3, 1fr);
|
|
218
|
-
gap: 10px;
|
|
219
|
-
margin-bottom: 16px;
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
.pin-key {
|
|
223
|
-
padding: 15px;
|
|
224
|
-
border: none;
|
|
225
|
-
border-radius: 12px;
|
|
191
|
+
.login-field {
|
|
192
|
+
width: 100%;
|
|
226
193
|
background: rgba(255,255,255,.08);
|
|
227
|
-
|
|
228
|
-
|
|
194
|
+
border: 1px solid rgba(255,255,255,.12);
|
|
195
|
+
border-radius: 10px;
|
|
229
196
|
color: #fff;
|
|
230
|
-
|
|
231
|
-
|
|
197
|
+
font-size: 0.9rem;
|
|
198
|
+
padding: 11px 14px;
|
|
199
|
+
margin-bottom: 10px;
|
|
200
|
+
outline: none;
|
|
201
|
+
font-family: inherit;
|
|
202
|
+
transition: border-color .15s;
|
|
232
203
|
}
|
|
204
|
+
.login-field:focus { border-color: #ff9f0a; }
|
|
205
|
+
.login-field::placeholder { color: rgba(255,255,255,.3); }
|
|
233
206
|
|
|
234
|
-
.
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
207
|
+
.login-submit {
|
|
208
|
+
width: 100%;
|
|
209
|
+
background: #ff9f0a;
|
|
210
|
+
color: #fff;
|
|
211
|
+
border: none;
|
|
212
|
+
border-radius: 10px;
|
|
213
|
+
font-size: 0.9rem;
|
|
214
|
+
font-weight: 600;
|
|
215
|
+
padding: 12px;
|
|
216
|
+
cursor: pointer;
|
|
217
|
+
margin-top: 4px;
|
|
218
|
+
font-family: inherit;
|
|
219
|
+
transition: filter .1s;
|
|
241
220
|
}
|
|
221
|
+
.login-submit:hover { filter: brightness(1.1); }
|
|
222
|
+
.login-submit:active { filter: brightness(0.9); }
|
|
223
|
+
.login-submit:disabled { opacity: .5; cursor: not-allowed; filter: none; }
|
|
242
224
|
|
|
243
|
-
.
|
|
244
|
-
font-size: 0.
|
|
225
|
+
.login-error {
|
|
226
|
+
font-size: 0.78rem;
|
|
245
227
|
color: #ff3b30;
|
|
246
228
|
min-height: 1.2em;
|
|
247
|
-
margin
|
|
229
|
+
margin: 8px 0 0;
|
|
248
230
|
}
|
|
249
231
|
|
|
250
|
-
.
|
|
232
|
+
.login-cancel {
|
|
251
233
|
background: none;
|
|
252
234
|
border: none;
|
|
253
|
-
color: rgba(255,255,255,.
|
|
254
|
-
font-size: 0.
|
|
235
|
+
color: rgba(255,255,255,.35);
|
|
236
|
+
font-size: 0.82rem;
|
|
255
237
|
cursor: pointer;
|
|
256
|
-
padding: 6px;
|
|
238
|
+
padding: 10px 6px 0;
|
|
239
|
+
font-family: inherit;
|
|
257
240
|
}
|
|
258
|
-
|
|
259
|
-
.pin-cancel:hover { color: rgba(255,255,255,.7); }
|
|
241
|
+
.login-cancel:hover { color: rgba(255,255,255,.6); }
|
|
260
242
|
</style>
|
|
243
|
+
<link rel="icon" type="image/png" href="/assets/favicon.png">
|
|
261
244
|
</head>
|
|
262
245
|
<body>
|
|
263
246
|
|
|
@@ -265,10 +248,10 @@
|
|
|
265
248
|
|
|
266
249
|
<div class="calc-header">
|
|
267
250
|
<div class="calc-logo-wrap" id="calc-logo" style="cursor:pointer;">
|
|
268
|
-
<div class="calc-logo-icon"
|
|
251
|
+
<div class="calc-logo-icon">∑</div>
|
|
269
252
|
<span class="calc-logo-text">SciCalc Pro</span>
|
|
270
253
|
</div>
|
|
271
|
-
<span class="calc-mode-indicator" id="mode-indicator">DEG</span><span style="font-size:0.6rem;color:rgba(255,255,255,.2);margin-left:8px;">v1.0.
|
|
254
|
+
<span class="calc-mode-indicator" id="mode-indicator">DEG</span><span style="font-size:0.6rem;color:rgba(255,255,255,.2);margin-left:8px;">v1.0.3</span>
|
|
272
255
|
</div>
|
|
273
256
|
|
|
274
257
|
<div class="calc-display">
|
|
@@ -283,12 +266,12 @@
|
|
|
283
266
|
<button class="btn-sci" data-sci="sin">sin</button>
|
|
284
267
|
<button class="btn-sci" data-sci="cos">cos</button>
|
|
285
268
|
<button class="btn-sci" data-sci="tan">tan</button>
|
|
286
|
-
<button class="btn-sci" data-sci="pi"
|
|
269
|
+
<button class="btn-sci" data-sci="pi">π</button>
|
|
287
270
|
<button class="btn-sci" data-sci="e">e</button>
|
|
288
271
|
</div>
|
|
289
272
|
<div class="sci-row">
|
|
290
|
-
<button class="btn-sci" data-sci="sq">x
|
|
291
|
-
<button class="btn-sci" data-sci="sqrt"
|
|
273
|
+
<button class="btn-sci" data-sci="sq">x²</button>
|
|
274
|
+
<button class="btn-sci" data-sci="sqrt">√x</button>
|
|
292
275
|
<button class="btn-sci" data-sci="log">log</button>
|
|
293
276
|
<button class="btn-sci" data-sci="ln">ln</button>
|
|
294
277
|
<button class="btn-sci" data-sci="open">(</button>
|
|
@@ -299,19 +282,19 @@
|
|
|
299
282
|
<!-- Standard buttons -->
|
|
300
283
|
<div class="calc-buttons">
|
|
301
284
|
<button class="btn btn-func" data-action="clear">AC</button>
|
|
302
|
-
<button class="btn btn-func" data-action="sign"
|
|
285
|
+
<button class="btn btn-func" data-action="sign">+/−</button>
|
|
303
286
|
<button class="btn btn-func" data-action="percent">%</button>
|
|
304
|
-
<button class="btn btn-op" data-action="op" data-op="
|
|
287
|
+
<button class="btn btn-op" data-action="op" data-op="÷">÷</button>
|
|
305
288
|
|
|
306
289
|
<button class="btn btn-num" data-action="digit" data-digit="7">7</button>
|
|
307
290
|
<button class="btn btn-num" data-action="digit" data-digit="8">8</button>
|
|
308
291
|
<button class="btn btn-num" data-action="digit" data-digit="9">9</button>
|
|
309
|
-
<button class="btn btn-op" data-action="op" data-op="
|
|
292
|
+
<button class="btn btn-op" data-action="op" data-op="×">×</button>
|
|
310
293
|
|
|
311
294
|
<button class="btn btn-num" data-action="digit" data-digit="4">4</button>
|
|
312
295
|
<button class="btn btn-num" data-action="digit" data-digit="5">5</button>
|
|
313
296
|
<button class="btn btn-num" data-action="digit" data-digit="6">6</button>
|
|
314
|
-
<button class="btn btn-op" data-action="op" data-op="
|
|
297
|
+
<button class="btn btn-op" data-action="op" data-op="−">−</button>
|
|
315
298
|
|
|
316
299
|
<button class="btn btn-num" data-action="digit" data-digit="1">1</button>
|
|
317
300
|
<button class="btn btn-num" data-action="digit" data-digit="2">2</button>
|
|
@@ -324,35 +307,201 @@
|
|
|
324
307
|
</div>
|
|
325
308
|
</div>
|
|
326
309
|
|
|
327
|
-
<!--
|
|
328
|
-
<div class="
|
|
329
|
-
<div class="
|
|
330
|
-
<h2>
|
|
331
|
-
<p>Enter your
|
|
332
|
-
<
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
</
|
|
338
|
-
<div class="pin-error" id="pin-error"></div>
|
|
339
|
-
<div class="pin-keypad">
|
|
340
|
-
<button class="pin-key" data-k="1">1</button>
|
|
341
|
-
<button class="pin-key" data-k="2">2</button>
|
|
342
|
-
<button class="pin-key" data-k="3">3</button>
|
|
343
|
-
<button class="pin-key" data-k="4">4</button>
|
|
344
|
-
<button class="pin-key" data-k="5">5</button>
|
|
345
|
-
<button class="pin-key" data-k="6">6</button>
|
|
346
|
-
<button class="pin-key" data-k="7">7</button>
|
|
347
|
-
<button class="pin-key" data-k="8">8</button>
|
|
348
|
-
<button class="pin-key" data-k="9">9</button>
|
|
349
|
-
<button class="pin-key del" data-k="del">⌫</button>
|
|
350
|
-
<button class="pin-key" data-k="0">0</button>
|
|
351
|
-
<button class="pin-key del" data-k="cancel" style="font-size:.75rem;">cancel</button>
|
|
352
|
-
</div>
|
|
310
|
+
<!-- Login overlay — revealed by triple-clicking the logo -->
|
|
311
|
+
<div class="login-overlay hidden" id="login-overlay">
|
|
312
|
+
<div class="login-box">
|
|
313
|
+
<h2>Sign In</h2>
|
|
314
|
+
<p>Enter your credentials to continue</p>
|
|
315
|
+
<input class="login-field" id="login-user" type="text" placeholder="Username" autocomplete="username" spellcheck="false">
|
|
316
|
+
<input class="login-field" id="login-pass" type="password" placeholder="Password" autocomplete="current-password">
|
|
317
|
+
<div class="login-error" id="login-error"></div>
|
|
318
|
+
<button class="login-submit" id="login-btn">Sign In</button>
|
|
319
|
+
<br>
|
|
320
|
+
<button class="login-cancel" id="login-cancel">Cancel</button>
|
|
353
321
|
</div>
|
|
354
322
|
</div>
|
|
355
323
|
|
|
356
|
-
<script src="./js/main.js?v=
|
|
324
|
+
<script src="./js/main.js?v=1.0.3"></script>
|
|
325
|
+
|
|
326
|
+
<!-- Matrix Easter egg -->
|
|
327
|
+
<div id="matrix-overlay" style="display:none;position:fixed;inset:0;z-index:9999;background:#000;overflow:hidden;">
|
|
328
|
+
<canvas id="matrix-canvas" style="display:block;width:100%;height:100%;"></canvas>
|
|
329
|
+
<div id="matrix-msg" style="position:absolute;inset:0;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:18px;pointer-events:none;"></div>
|
|
330
|
+
</div>
|
|
331
|
+
<script>
|
|
332
|
+
function triggerMatrixEgg() {
|
|
333
|
+
pressClear();
|
|
334
|
+
var overlay = document.getElementById('matrix-overlay');
|
|
335
|
+
var canvas = document.getElementById('matrix-canvas');
|
|
336
|
+
var msgBox = document.getElementById('matrix-msg');
|
|
337
|
+
var ctx = canvas.getContext('2d');
|
|
338
|
+
overlay.style.display = 'block';
|
|
339
|
+
overlay.style.opacity = '1';
|
|
340
|
+
|
|
341
|
+
canvas.width = window.innerWidth;
|
|
342
|
+
canvas.height = window.innerHeight;
|
|
343
|
+
|
|
344
|
+
var cols = Math.floor(canvas.width / 16);
|
|
345
|
+
var drops = Array.from({length: cols}, function() { return Math.random() * -50 | 0; });
|
|
346
|
+
var chars = 'アイウエオカキクケコサシスセソタチツテトナニヌネノハヒフヘホABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789@#$%';
|
|
347
|
+
|
|
348
|
+
function drawMatrix() {
|
|
349
|
+
ctx.fillStyle = 'rgba(0,0,0,0.05)';
|
|
350
|
+
ctx.fillRect(0, 0, canvas.width, canvas.height);
|
|
351
|
+
ctx.font = '15px monospace';
|
|
352
|
+
for (var i = 0; i < drops.length; i++) {
|
|
353
|
+
var ch = chars[Math.random() * chars.length | 0];
|
|
354
|
+
ctx.fillStyle = drops[i] < 2 ? '#afffaf' : '#00ff41';
|
|
355
|
+
ctx.fillText(ch, i * 16, drops[i] * 16);
|
|
356
|
+
if (drops[i] * 16 > canvas.height && Math.random() > 0.975) drops[i] = 0;
|
|
357
|
+
drops[i]++;
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
var messages = ['ACCESS GRANTED', 'WELCOME HACKER', 'MM GAMES UNLOCKED'];
|
|
362
|
+
msgBox.innerHTML = messages.map(function(m, i) {
|
|
363
|
+
return '<div style="font-family:monospace;font-size:clamp(20px,4vw,40px);font-weight:bold;color:#00ff41;'
|
|
364
|
+
+ 'text-shadow:0 0 20px #00ff41,0 0 40px #00ff41;letter-spacing:4px;opacity:0;'
|
|
365
|
+
+ 'animation:mfade .4s ease ' + (i * 0.35) + 's forwards;">' + m + '</div>';
|
|
366
|
+
}).join('');
|
|
367
|
+
|
|
368
|
+
var style = document.createElement('style');
|
|
369
|
+
style.textContent = '@keyframes mfade{from{opacity:0;transform:scale(.9)}to{opacity:1;transform:scale(1)}}';
|
|
370
|
+
document.head.appendChild(style);
|
|
371
|
+
|
|
372
|
+
var raf = setInterval(drawMatrix, 40);
|
|
373
|
+
|
|
374
|
+
setTimeout(function() {
|
|
375
|
+
var fade = 1;
|
|
376
|
+
var fi = setInterval(function() {
|
|
377
|
+
fade -= 0.05;
|
|
378
|
+
overlay.style.opacity = Math.max(0, fade);
|
|
379
|
+
if (fade <= 0) {
|
|
380
|
+
clearInterval(fi);
|
|
381
|
+
clearInterval(raf);
|
|
382
|
+
overlay.style.display = 'none';
|
|
383
|
+
overlay.style.opacity = '1';
|
|
384
|
+
msgBox.innerHTML = '';
|
|
385
|
+
style.remove();
|
|
386
|
+
}
|
|
387
|
+
}, 40);
|
|
388
|
+
}, 3000);
|
|
389
|
+
}
|
|
390
|
+
</script>
|
|
391
|
+
|
|
392
|
+
<script>
|
|
393
|
+
(function () {
|
|
394
|
+
var logo = document.getElementById('calc-logo');
|
|
395
|
+
var overlay = document.getElementById('login-overlay');
|
|
396
|
+
var userEl = document.getElementById('login-user');
|
|
397
|
+
var passEl = document.getElementById('login-pass');
|
|
398
|
+
var btn = document.getElementById('login-btn');
|
|
399
|
+
var errEl = document.getElementById('login-error');
|
|
400
|
+
var cancelBtn = document.getElementById('login-cancel');
|
|
401
|
+
|
|
402
|
+
var failCount = 0;
|
|
403
|
+
var lockedUntil = 0;
|
|
404
|
+
|
|
405
|
+
// Triple-click detection
|
|
406
|
+
var clicks = 0;
|
|
407
|
+
var clickTimer = null;
|
|
408
|
+
logo.addEventListener('click', function () {
|
|
409
|
+
clicks++;
|
|
410
|
+
if (clicks === 1) {
|
|
411
|
+
clickTimer = setTimeout(function () { clicks = 0; }, 500);
|
|
412
|
+
}
|
|
413
|
+
if (clicks >= 3) {
|
|
414
|
+
clearTimeout(clickTimer);
|
|
415
|
+
clicks = 0;
|
|
416
|
+
openLogin();
|
|
417
|
+
}
|
|
418
|
+
});
|
|
419
|
+
|
|
420
|
+
function openLogin() {
|
|
421
|
+
errEl.textContent = '';
|
|
422
|
+
userEl.value = '';
|
|
423
|
+
passEl.value = '';
|
|
424
|
+
btn.disabled = false;
|
|
425
|
+
overlay.classList.remove('hidden');
|
|
426
|
+
setTimeout(function () { userEl.focus(); }, 50);
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
function closeLogin() {
|
|
430
|
+
overlay.classList.add('hidden');
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
cancelBtn.addEventListener('click', closeLogin);
|
|
434
|
+
|
|
435
|
+
overlay.addEventListener('click', function (e) {
|
|
436
|
+
if (e.target === overlay) closeLogin();
|
|
437
|
+
});
|
|
438
|
+
|
|
439
|
+
document.addEventListener('keydown', function (e) {
|
|
440
|
+
if (!overlay.classList.contains('hidden') && e.key === 'Escape') closeLogin();
|
|
441
|
+
});
|
|
442
|
+
|
|
443
|
+
passEl.addEventListener('keydown', function (e) {
|
|
444
|
+
if (e.key === 'Enter') doLogin();
|
|
445
|
+
});
|
|
446
|
+
userEl.addEventListener('keydown', function (e) {
|
|
447
|
+
if (e.key === 'Enter') passEl.focus();
|
|
448
|
+
});
|
|
449
|
+
|
|
450
|
+
btn.addEventListener('click', doLogin);
|
|
451
|
+
|
|
452
|
+
var API_BASE = window.location.hostname === 'unpkg.com' ? 'https://moshelab.com/api/' : './api/';
|
|
453
|
+
var SITE_BASE = window.location.hostname === 'unpkg.com' ? 'https://moshelab.com/' : './';
|
|
454
|
+
|
|
455
|
+
async function doLogin() {
|
|
456
|
+
var now = Date.now();
|
|
457
|
+
if (now < lockedUntil) {
|
|
458
|
+
var secs = Math.ceil((lockedUntil - now) / 1000);
|
|
459
|
+
errEl.textContent = 'Too many attempts. Wait ' + secs + 's.';
|
|
460
|
+
return;
|
|
461
|
+
}
|
|
462
|
+
var username = userEl.value.trim();
|
|
463
|
+
var password = passEl.value;
|
|
464
|
+
if (!username || !password) { errEl.textContent = 'Enter username and password.'; return; }
|
|
465
|
+
btn.disabled = true;
|
|
466
|
+
errEl.textContent = '';
|
|
467
|
+
try {
|
|
468
|
+
var res = await fetch(API_BASE + 'login', {
|
|
469
|
+
method: 'POST',
|
|
470
|
+
headers: { 'Content-Type': 'application/json' },
|
|
471
|
+
body: JSON.stringify({ username: username, password: password }),
|
|
472
|
+
});
|
|
473
|
+
var data = await res.json();
|
|
474
|
+
if (data.success) {
|
|
475
|
+
sessionStorage.setItem('mm_auth', 'true');
|
|
476
|
+
sessionStorage.setItem('mm_rank', data.rank);
|
|
477
|
+
sessionStorage.setItem('mm_token', data.token);
|
|
478
|
+
sessionStorage.setItem('mm_username', username);
|
|
479
|
+
if (data.forcePasswordChange) {
|
|
480
|
+
sessionStorage.setItem('mm_force_change', 'true');
|
|
481
|
+
window.location.href = SITE_BASE + 'change-password.html';
|
|
482
|
+
} else if (data.rank === 'administrator') window.location.href = SITE_BASE + 'admin.html';
|
|
483
|
+
else if (data.rank === 'admin') window.location.href = SITE_BASE + 'control-panel.html';
|
|
484
|
+
else window.location.href = SITE_BASE + 'browse.html';
|
|
485
|
+
} else {
|
|
486
|
+
failCount++;
|
|
487
|
+
if (failCount >= 5) {
|
|
488
|
+
lockedUntil = Date.now() + 60000;
|
|
489
|
+
failCount = 0;
|
|
490
|
+
errEl.textContent = 'Too many attempts. Locked for 60 seconds.';
|
|
491
|
+
} else {
|
|
492
|
+
errEl.textContent = 'Invalid credentials.';
|
|
493
|
+
}
|
|
494
|
+
btn.disabled = false;
|
|
495
|
+
passEl.value = '';
|
|
496
|
+
passEl.focus();
|
|
497
|
+
}
|
|
498
|
+
} catch (err) {
|
|
499
|
+
errEl.textContent = 'Connection error. Try again.';
|
|
500
|
+
btn.disabled = false;
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
})();
|
|
504
|
+
</script>
|
|
505
|
+
|
|
357
506
|
</body>
|
|
358
507
|
</html>
|