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/index.html
ADDED
|
@@ -0,0 +1,577 @@
|
|
|
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">
|
|
6
|
+
<title>Calculator</title>
|
|
7
|
+
<link rel="icon" type="image/png" href="/assets/favicon.png">
|
|
8
|
+
<style>
|
|
9
|
+
*,*::before,*::after{box-sizing:border-box;margin:0;padding:0;}
|
|
10
|
+
|
|
11
|
+
body {
|
|
12
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
|
|
13
|
+
background: #080e1c;
|
|
14
|
+
min-height: 100vh;
|
|
15
|
+
display: flex;
|
|
16
|
+
align-items: center;
|
|
17
|
+
justify-content: center;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/* ── Logo ── */
|
|
21
|
+
#logo {
|
|
22
|
+
position: fixed;
|
|
23
|
+
top: 14px;
|
|
24
|
+
left: 14px;
|
|
25
|
+
width: 36px;
|
|
26
|
+
height: 36px;
|
|
27
|
+
border-radius: 8px;
|
|
28
|
+
overflow: hidden;
|
|
29
|
+
cursor: pointer;
|
|
30
|
+
opacity: 0.7;
|
|
31
|
+
transition: opacity .2s, transform .2s;
|
|
32
|
+
user-select: none;
|
|
33
|
+
-webkit-user-select: none;
|
|
34
|
+
}
|
|
35
|
+
#logo:hover { opacity: 1; transform: scale(1.05); }
|
|
36
|
+
#logo img { width: 100%; height: 100%; object-fit: cover; display: block; }
|
|
37
|
+
|
|
38
|
+
/* ── Calculator shell ── */
|
|
39
|
+
.calc {
|
|
40
|
+
background: linear-gradient(160deg, #16213e 0%, #0f172a 100%);
|
|
41
|
+
border: 1px solid #1e3a5c;
|
|
42
|
+
border-radius: 24px;
|
|
43
|
+
padding: 22px;
|
|
44
|
+
width: 330px;
|
|
45
|
+
box-shadow: 0 30px 80px rgba(0,0,0,.7), 0 0 0 1px rgba(255,255,255,.04) inset;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/* ── Display ── */
|
|
49
|
+
.display {
|
|
50
|
+
background: #050c1a;
|
|
51
|
+
border: 1px solid #1a3252;
|
|
52
|
+
border-radius: 14px;
|
|
53
|
+
padding: 14px 18px 16px;
|
|
54
|
+
margin-bottom: 18px;
|
|
55
|
+
min-height: 90px;
|
|
56
|
+
display: flex;
|
|
57
|
+
flex-direction: column;
|
|
58
|
+
justify-content: flex-end;
|
|
59
|
+
position: relative;
|
|
60
|
+
overflow: hidden;
|
|
61
|
+
}
|
|
62
|
+
.display-mode {
|
|
63
|
+
position: absolute;
|
|
64
|
+
top: 10px;
|
|
65
|
+
left: 14px;
|
|
66
|
+
font-size: 10px;
|
|
67
|
+
font-weight: 700;
|
|
68
|
+
letter-spacing: .1em;
|
|
69
|
+
color: #2563eb;
|
|
70
|
+
text-transform: uppercase;
|
|
71
|
+
}
|
|
72
|
+
.display-expr {
|
|
73
|
+
font-size: 12px;
|
|
74
|
+
color: #3d5a80;
|
|
75
|
+
text-align: right;
|
|
76
|
+
min-height: 18px;
|
|
77
|
+
overflow: hidden;
|
|
78
|
+
text-overflow: ellipsis;
|
|
79
|
+
white-space: nowrap;
|
|
80
|
+
margin-bottom: 4px;
|
|
81
|
+
font-family: 'SF Mono', 'Fira Code', 'Courier New', monospace;
|
|
82
|
+
}
|
|
83
|
+
.display-val {
|
|
84
|
+
font-size: 38px;
|
|
85
|
+
font-weight: 300;
|
|
86
|
+
color: #e2eaf8;
|
|
87
|
+
text-align: right;
|
|
88
|
+
overflow: hidden;
|
|
89
|
+
text-overflow: ellipsis;
|
|
90
|
+
white-space: nowrap;
|
|
91
|
+
letter-spacing: -0.02em;
|
|
92
|
+
line-height: 1;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/* ── Button grid ── */
|
|
96
|
+
.grid {
|
|
97
|
+
display: grid;
|
|
98
|
+
grid-template-columns: repeat(5, 1fr);
|
|
99
|
+
gap: 7px;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
button {
|
|
103
|
+
border: none;
|
|
104
|
+
border-radius: 10px;
|
|
105
|
+
height: 50px;
|
|
106
|
+
font-size: 14px;
|
|
107
|
+
font-weight: 500;
|
|
108
|
+
cursor: pointer;
|
|
109
|
+
transition: filter .08s, transform .06s, background .08s;
|
|
110
|
+
color: #c8d8ee;
|
|
111
|
+
font-family: inherit;
|
|
112
|
+
outline: none;
|
|
113
|
+
}
|
|
114
|
+
button:active {
|
|
115
|
+
transform: scale(0.93);
|
|
116
|
+
filter: brightness(1.15);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
.n { background: #1e3352; }
|
|
120
|
+
.n:hover { background: #253e65; }
|
|
121
|
+
|
|
122
|
+
.op { background: #1d47a3; color: #fff; font-size: 18px; }
|
|
123
|
+
.op:hover { background: #2254c0; }
|
|
124
|
+
|
|
125
|
+
.sci { background: #102240; font-size: 12px; color: #8aaecc; }
|
|
126
|
+
.sci:hover { background: #173260; }
|
|
127
|
+
|
|
128
|
+
.util { background: #162a46; font-size: 12px; color: #5c85b0; }
|
|
129
|
+
.util:hover { background: #1e3a5c; }
|
|
130
|
+
|
|
131
|
+
.clr { background: #4a0f0f; color: #fca5a5; }
|
|
132
|
+
.clr:hover { background: #5f1414; }
|
|
133
|
+
|
|
134
|
+
.eq { background: linear-gradient(135deg, #1a56db, #2563eb); color: #fff; font-size: 20px; font-weight: 400; }
|
|
135
|
+
.eq:hover { background: linear-gradient(135deg, #1d4ed8, #3b82f6); }
|
|
136
|
+
|
|
137
|
+
/* ── Modal ── */
|
|
138
|
+
.overlay {
|
|
139
|
+
display: none;
|
|
140
|
+
position: fixed;
|
|
141
|
+
inset: 0;
|
|
142
|
+
background: rgba(0,0,0,.75);
|
|
143
|
+
backdrop-filter: blur(6px);
|
|
144
|
+
z-index: 999;
|
|
145
|
+
align-items: center;
|
|
146
|
+
justify-content: center;
|
|
147
|
+
}
|
|
148
|
+
.overlay.open { display: flex; }
|
|
149
|
+
|
|
150
|
+
.modal {
|
|
151
|
+
background: linear-gradient(160deg, #16213e, #0f172a);
|
|
152
|
+
border: 1px solid #1e3a5c;
|
|
153
|
+
border-radius: 18px;
|
|
154
|
+
padding: 30px 28px 24px;
|
|
155
|
+
width: 310px;
|
|
156
|
+
box-shadow: 0 24px 70px rgba(0,0,0,.7);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
.modal-head {
|
|
160
|
+
display: flex;
|
|
161
|
+
align-items: center;
|
|
162
|
+
gap: 10px;
|
|
163
|
+
margin-bottom: 24px;
|
|
164
|
+
}
|
|
165
|
+
.modal-head img {
|
|
166
|
+
width: 26px;
|
|
167
|
+
height: 26px;
|
|
168
|
+
border-radius: 6px;
|
|
169
|
+
}
|
|
170
|
+
.modal-head h2 {
|
|
171
|
+
font-size: 17px;
|
|
172
|
+
font-weight: 600;
|
|
173
|
+
color: #e2eaf8;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
.field { margin-bottom: 14px; }
|
|
177
|
+
.field label {
|
|
178
|
+
display: block;
|
|
179
|
+
font-size: 11px;
|
|
180
|
+
font-weight: 600;
|
|
181
|
+
letter-spacing: .07em;
|
|
182
|
+
text-transform: uppercase;
|
|
183
|
+
color: #3d5a80;
|
|
184
|
+
margin-bottom: 6px;
|
|
185
|
+
}
|
|
186
|
+
.field input {
|
|
187
|
+
width: 100%;
|
|
188
|
+
background: #050c1a;
|
|
189
|
+
border: 1px solid #1a3252;
|
|
190
|
+
border-radius: 9px;
|
|
191
|
+
padding: 10px 13px;
|
|
192
|
+
color: #e2eaf8;
|
|
193
|
+
font-size: 14px;
|
|
194
|
+
font-family: inherit;
|
|
195
|
+
outline: none;
|
|
196
|
+
transition: border-color .18s;
|
|
197
|
+
}
|
|
198
|
+
.field input:focus { border-color: #2563eb; }
|
|
199
|
+
|
|
200
|
+
.modal-err {
|
|
201
|
+
font-size: 12px;
|
|
202
|
+
color: #f87171;
|
|
203
|
+
min-height: 16px;
|
|
204
|
+
margin-bottom: 12px;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
.btn-signin {
|
|
208
|
+
width: 100%;
|
|
209
|
+
background: #2563eb;
|
|
210
|
+
color: #fff;
|
|
211
|
+
border: none;
|
|
212
|
+
border-radius: 9px;
|
|
213
|
+
padding: 11px;
|
|
214
|
+
font-size: 14px;
|
|
215
|
+
font-weight: 600;
|
|
216
|
+
cursor: pointer;
|
|
217
|
+
font-family: inherit;
|
|
218
|
+
transition: background .15s;
|
|
219
|
+
}
|
|
220
|
+
.btn-signin:hover { background: #1d4ed8; }
|
|
221
|
+
.btn-signin:disabled { opacity: .55; cursor: default; }
|
|
222
|
+
|
|
223
|
+
.btn-cancel {
|
|
224
|
+
width: 100%;
|
|
225
|
+
background: none;
|
|
226
|
+
border: none;
|
|
227
|
+
color: #3d5a80;
|
|
228
|
+
font-size: 13px;
|
|
229
|
+
font-family: inherit;
|
|
230
|
+
padding: 9px;
|
|
231
|
+
margin-top: 6px;
|
|
232
|
+
border-radius: 9px;
|
|
233
|
+
cursor: pointer;
|
|
234
|
+
transition: color .15s, background .15s;
|
|
235
|
+
}
|
|
236
|
+
.btn-cancel:hover { color: #6b8fb5; background: rgba(255,255,255,.04); }
|
|
237
|
+
</style>
|
|
238
|
+
</head>
|
|
239
|
+
<body>
|
|
240
|
+
|
|
241
|
+
<div id="logo" title="">
|
|
242
|
+
<img src="assets/logo.png" alt="">
|
|
243
|
+
</div>
|
|
244
|
+
|
|
245
|
+
<div class="calc">
|
|
246
|
+
<div class="display">
|
|
247
|
+
<div class="display-mode" id="modeLabel">DEG</div>
|
|
248
|
+
<div class="display-expr" id="dExpr"></div>
|
|
249
|
+
<div class="display-val" id="dVal">0</div>
|
|
250
|
+
</div>
|
|
251
|
+
|
|
252
|
+
<div class="grid">
|
|
253
|
+
<!-- row 1 -->
|
|
254
|
+
<button class="util" onclick="toggleMode()" id="modeBtn">DEG</button>
|
|
255
|
+
<button class="sci" onclick="inp('sin(')">sin</button>
|
|
256
|
+
<button class="sci" onclick="inp('cos(')">cos</button>
|
|
257
|
+
<button class="sci" onclick="inp('tan(')">tan</button>
|
|
258
|
+
<button class="clr" onclick="clearAll()">C</button>
|
|
259
|
+
|
|
260
|
+
<!-- row 2 -->
|
|
261
|
+
<button class="util" onclick="inp('π')">π</button>
|
|
262
|
+
<button class="sci" onclick="inp('asin(')">sin⁻¹</button>
|
|
263
|
+
<button class="sci" onclick="inp('acos(')">cos⁻¹</button>
|
|
264
|
+
<button class="sci" onclick="inp('atan(')">tan⁻¹</button>
|
|
265
|
+
<button class="util" onclick="backspace()">⌫</button>
|
|
266
|
+
|
|
267
|
+
<!-- row 3 -->
|
|
268
|
+
<button class="util" onclick="inp('e')">e</button>
|
|
269
|
+
<button class="sci" onclick="inp('log(')">log</button>
|
|
270
|
+
<button class="sci" onclick="inp('ln(')">ln</button>
|
|
271
|
+
<button class="sci" onclick="inp('√(')">√</button>
|
|
272
|
+
<button class="sci" onclick="inp('%')">%</button>
|
|
273
|
+
|
|
274
|
+
<!-- row 4 -->
|
|
275
|
+
<button class="sci" onclick="doSquare()">x²</button>
|
|
276
|
+
<button class="sci" onclick="inp('^')">xʸ</button>
|
|
277
|
+
<button class="sci" onclick="inp('1/(')">1/x</button>
|
|
278
|
+
<button class="n" onclick="inp('(')"> ( </button>
|
|
279
|
+
<button class="n" onclick="inp(')')"> ) </button>
|
|
280
|
+
|
|
281
|
+
<!-- row 5 -->
|
|
282
|
+
<button class="n" onclick="inp('7')">7</button>
|
|
283
|
+
<button class="n" onclick="inp('8')">8</button>
|
|
284
|
+
<button class="n" onclick="inp('9')">9</button>
|
|
285
|
+
<button class="op" onclick="inp('÷')">÷</button>
|
|
286
|
+
<button class="sci" onclick="doFactorial()">n!</button>
|
|
287
|
+
|
|
288
|
+
<!-- row 6 -->
|
|
289
|
+
<button class="n" onclick="inp('4')">4</button>
|
|
290
|
+
<button class="n" onclick="inp('5')">5</button>
|
|
291
|
+
<button class="n" onclick="inp('6')">6</button>
|
|
292
|
+
<button class="op" onclick="inp('×')">×</button>
|
|
293
|
+
<button class="sci" onclick="inp('10^(')">10ˣ</button>
|
|
294
|
+
|
|
295
|
+
<!-- row 7 -->
|
|
296
|
+
<button class="n" onclick="inp('1')">1</button>
|
|
297
|
+
<button class="n" onclick="inp('2')">2</button>
|
|
298
|
+
<button class="n" onclick="inp('3')">3</button>
|
|
299
|
+
<button class="op" onclick="inp('−')">−</button>
|
|
300
|
+
<button class="sci" onclick="inp('e^(')">eˣ</button>
|
|
301
|
+
|
|
302
|
+
<!-- row 8 -->
|
|
303
|
+
<button class="util" onclick="toggleSign()">±</button>
|
|
304
|
+
<button class="n" onclick="inp('0')">0</button>
|
|
305
|
+
<button class="n" onclick="inp('.')">.</button>
|
|
306
|
+
<button class="op" onclick="inp('+')">+</button>
|
|
307
|
+
<button class="eq" onclick="calculate()">=</button>
|
|
308
|
+
</div>
|
|
309
|
+
</div>
|
|
310
|
+
|
|
311
|
+
<!-- Login modal -->
|
|
312
|
+
<div class="overlay" id="overlay">
|
|
313
|
+
<div class="modal">
|
|
314
|
+
<div class="modal-head">
|
|
315
|
+
<img src="assets/logo.png" alt="">
|
|
316
|
+
<h2>Sign In</h2>
|
|
317
|
+
</div>
|
|
318
|
+
<div class="field">
|
|
319
|
+
<label>Username</label>
|
|
320
|
+
<input type="text" id="fUser" autocomplete="username" placeholder="Username">
|
|
321
|
+
</div>
|
|
322
|
+
<div class="field">
|
|
323
|
+
<label>Password</label>
|
|
324
|
+
<input type="password" id="fPass" autocomplete="current-password" placeholder="Password">
|
|
325
|
+
</div>
|
|
326
|
+
<div class="modal-err" id="fErr"></div>
|
|
327
|
+
<button class="btn-signin" id="fBtn" onclick="doLogin()">Sign In</button>
|
|
328
|
+
<button class="btn-cancel" onclick="closeModal()">Cancel</button>
|
|
329
|
+
</div>
|
|
330
|
+
</div>
|
|
331
|
+
|
|
332
|
+
<script>
|
|
333
|
+
// ── State ─────────────────────────────────────────────────────────────────────
|
|
334
|
+
let expr = '';
|
|
335
|
+
let degMode = true;
|
|
336
|
+
let justCalc = false;
|
|
337
|
+
|
|
338
|
+
const dExpr = document.getElementById('dExpr');
|
|
339
|
+
const dVal = document.getElementById('dVal');
|
|
340
|
+
const modeLabel = document.getElementById('modeLabel');
|
|
341
|
+
const modeBtn = document.getElementById('modeBtn');
|
|
342
|
+
|
|
343
|
+
function render(result) {
|
|
344
|
+
dExpr.textContent = expr;
|
|
345
|
+
dVal.textContent = result !== undefined ? result : (expr || '0');
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
// ── Input ─────────────────────────────────────────────────────────────────────
|
|
349
|
+
function inp(ch) {
|
|
350
|
+
if (justCalc && /^[\d(πe√]/.test(ch)) expr = '';
|
|
351
|
+
justCalc = false;
|
|
352
|
+
expr += ch;
|
|
353
|
+
render();
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
function clearAll() {
|
|
357
|
+
expr = '';
|
|
358
|
+
justCalc = false;
|
|
359
|
+
dExpr.textContent = '';
|
|
360
|
+
dVal.textContent = '0';
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
function backspace() {
|
|
364
|
+
if (justCalc) { clearAll(); return; }
|
|
365
|
+
// remove last token (multi-char like 'sin(' or single char)
|
|
366
|
+
const multiChar = expr.match(/(asin\(|acos\(|atan\(|sin\(|cos\(|tan\(|log\(|ln\(|√\(|10\^\(|e\^\(|1\/\()$/);
|
|
367
|
+
expr = multiChar ? expr.slice(0, -multiChar[0].length) : expr.slice(0, -1);
|
|
368
|
+
render();
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
function toggleMode() {
|
|
372
|
+
degMode = !degMode;
|
|
373
|
+
const label = degMode ? 'DEG' : 'RAD';
|
|
374
|
+
modeLabel.textContent = label;
|
|
375
|
+
modeBtn.textContent = label;
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
function doSquare() {
|
|
379
|
+
if (!expr) return;
|
|
380
|
+
expr = '(' + expr + ')^2';
|
|
381
|
+
render();
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
function doFactorial() {
|
|
385
|
+
const m = expr.match(/(\d+)$/);
|
|
386
|
+
if (!m) return;
|
|
387
|
+
expr = expr.slice(0, -m[1].length) + fact(parseInt(m[1]));
|
|
388
|
+
render();
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
function fact(n) {
|
|
392
|
+
if (!Number.isInteger(n) || n < 0) return NaN;
|
|
393
|
+
if (n > 170) return Infinity;
|
|
394
|
+
let r = 1;
|
|
395
|
+
for (let i = 2; i <= n; i++) r *= i;
|
|
396
|
+
return r;
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
function toggleSign() {
|
|
400
|
+
if (!expr) return;
|
|
401
|
+
expr = expr.startsWith('−') ? expr.slice(1) : '−' + expr;
|
|
402
|
+
render();
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
// ── Evaluate ──────────────────────────────────────────────────────────────────
|
|
406
|
+
function calculate() {
|
|
407
|
+
if (!expr) return;
|
|
408
|
+
try {
|
|
409
|
+
const result = evalExpr(expr);
|
|
410
|
+
if (typeof result !== 'number') throw new Error();
|
|
411
|
+
dExpr.textContent = expr + ' =';
|
|
412
|
+
const formatted = Number.isFinite(result)
|
|
413
|
+
? +parseFloat(result.toPrecision(12)) + ''
|
|
414
|
+
: result === Infinity ? '∞' : result === -Infinity ? '-∞' : 'Error';
|
|
415
|
+
dVal.textContent = formatted;
|
|
416
|
+
expr = Number.isFinite(result) ? formatted : '';
|
|
417
|
+
justCalc = true;
|
|
418
|
+
} catch {
|
|
419
|
+
dVal.textContent = 'Error';
|
|
420
|
+
justCalc = true;
|
|
421
|
+
setTimeout(() => { if (justCalc) { render(); } }, 1400);
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
function evalExpr(raw) {
|
|
426
|
+
const toRad = degMode ? (x => x * Math.PI / 180) : (x => x);
|
|
427
|
+
const fromRad = degMode ? (x => x * 180 / Math.PI) : (x => x);
|
|
428
|
+
|
|
429
|
+
const _sin = x => Math.sin(toRad(x));
|
|
430
|
+
const _cos = x => Math.cos(toRad(x));
|
|
431
|
+
const _tan = x => Math.tan(toRad(x));
|
|
432
|
+
const _asin = x => fromRad(Math.asin(x));
|
|
433
|
+
const _acos = x => fromRad(Math.acos(x));
|
|
434
|
+
const _atan = x => fromRad(Math.atan(x));
|
|
435
|
+
const _log = x => Math.log10(x);
|
|
436
|
+
const _ln = x => Math.log(x);
|
|
437
|
+
const _sqrt = x => Math.sqrt(x);
|
|
438
|
+
|
|
439
|
+
let e = raw
|
|
440
|
+
// order matters: longer patterns first
|
|
441
|
+
.replace(/asin\(/g, '_asin(')
|
|
442
|
+
.replace(/acos\(/g, '_acos(')
|
|
443
|
+
.replace(/atan\(/g, '_atan(')
|
|
444
|
+
.replace(/sin\(/g, '_sin(')
|
|
445
|
+
.replace(/cos\(/g, '_cos(')
|
|
446
|
+
.replace(/tan\(/g, '_tan(')
|
|
447
|
+
.replace(/log\(/g, '_log(')
|
|
448
|
+
.replace(/ln\(/g, '_ln(')
|
|
449
|
+
.replace(/√\(/g, '_sqrt(')
|
|
450
|
+
.replace(/π/g, '(Math.PI)')
|
|
451
|
+
.replace(/\be\b/g, '(Math.E)')
|
|
452
|
+
.replace(/÷/g, '/')
|
|
453
|
+
.replace(/×/g, '*')
|
|
454
|
+
.replace(/−/g, '-')
|
|
455
|
+
.replace(/\^/g, '**')
|
|
456
|
+
.replace(/%/g, '/100');
|
|
457
|
+
|
|
458
|
+
// eslint-disable-next-line no-new-func
|
|
459
|
+
return new Function(
|
|
460
|
+
'_sin','_cos','_tan','_asin','_acos','_atan','_log','_ln','_sqrt',
|
|
461
|
+
'"use strict"; return (' + e + ');'
|
|
462
|
+
)(_sin, _cos, _tan, _asin, _acos, _atan, _log, _ln, _sqrt);
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
// ── Keyboard ──────────────────────────────────────────────────────────────────
|
|
466
|
+
document.addEventListener('keydown', e => {
|
|
467
|
+
if (document.getElementById('overlay').classList.contains('open')) return;
|
|
468
|
+
const k = e.key;
|
|
469
|
+
if (k >= '0' && k <= '9') inp(k);
|
|
470
|
+
else if (k === '.') inp('.');
|
|
471
|
+
else if (k === '+') inp('+');
|
|
472
|
+
else if (k === '-') inp('−');
|
|
473
|
+
else if (k === '*') inp('×');
|
|
474
|
+
else if (k === '/') { e.preventDefault(); inp('÷'); }
|
|
475
|
+
else if (k === '^') inp('^');
|
|
476
|
+
else if (k === '(') inp('(');
|
|
477
|
+
else if (k === ')') inp(')');
|
|
478
|
+
else if (k === '%') inp('%');
|
|
479
|
+
else if (k === 'Enter' || k === '=') calculate();
|
|
480
|
+
else if (k === 'Backspace') backspace();
|
|
481
|
+
else if (k === 'Escape') clearAll();
|
|
482
|
+
});
|
|
483
|
+
|
|
484
|
+
// ── Triple-click logo → modal ─────────────────────────────────────────────────
|
|
485
|
+
let clickCount = 0, clickTimer = null;
|
|
486
|
+
document.getElementById('logo').addEventListener('click', () => {
|
|
487
|
+
clickCount++;
|
|
488
|
+
if (clickTimer) clearTimeout(clickTimer);
|
|
489
|
+
clickTimer = setTimeout(() => { clickCount = 0; }, 500);
|
|
490
|
+
if (clickCount >= 3) {
|
|
491
|
+
clickCount = 0;
|
|
492
|
+
clearTimeout(clickTimer);
|
|
493
|
+
openModal();
|
|
494
|
+
}
|
|
495
|
+
});
|
|
496
|
+
|
|
497
|
+
function openModal() {
|
|
498
|
+
document.getElementById('fUser').value = '';
|
|
499
|
+
document.getElementById('fPass').value = '';
|
|
500
|
+
document.getElementById('fErr').textContent = '';
|
|
501
|
+
document.getElementById('fBtn').disabled = false;
|
|
502
|
+
document.getElementById('overlay').classList.add('open');
|
|
503
|
+
setTimeout(() => document.getElementById('fUser').focus(), 60);
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
function closeModal() {
|
|
507
|
+
document.getElementById('overlay').classList.remove('open');
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
document.getElementById('overlay').addEventListener('click', e => {
|
|
511
|
+
if (e.target === document.getElementById('overlay')) closeModal();
|
|
512
|
+
});
|
|
513
|
+
|
|
514
|
+
document.getElementById('fPass').addEventListener('keydown', e => {
|
|
515
|
+
if (e.key === 'Enter') doLogin();
|
|
516
|
+
});
|
|
517
|
+
document.getElementById('fUser').addEventListener('keydown', e => {
|
|
518
|
+
if (e.key === 'Enter') document.getElementById('fPass').focus();
|
|
519
|
+
});
|
|
520
|
+
|
|
521
|
+
// ── Login ─────────────────────────────────────────────────────────────────────
|
|
522
|
+
async function doLogin() {
|
|
523
|
+
const username = document.getElementById('fUser').value.trim();
|
|
524
|
+
const password = document.getElementById('fPass').value;
|
|
525
|
+
const errEl = document.getElementById('fErr');
|
|
526
|
+
const btn = document.getElementById('fBtn');
|
|
527
|
+
|
|
528
|
+
if (!username || !password) {
|
|
529
|
+
errEl.textContent = 'Enter username and password.';
|
|
530
|
+
return;
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
btn.disabled = true;
|
|
534
|
+
btn.textContent = 'Signing in…';
|
|
535
|
+
errEl.textContent = '';
|
|
536
|
+
|
|
537
|
+
try {
|
|
538
|
+
const res = await fetch('/api/login', {
|
|
539
|
+
method: 'POST',
|
|
540
|
+
headers: { 'Content-Type': 'application/json' },
|
|
541
|
+
body: JSON.stringify({ username, password }),
|
|
542
|
+
});
|
|
543
|
+
const data = await res.json();
|
|
544
|
+
|
|
545
|
+
if (!res.ok || !data.success) {
|
|
546
|
+
errEl.textContent = res.status === 429
|
|
547
|
+
? 'Too many attempts. Wait 60 seconds.'
|
|
548
|
+
: 'Invalid username or password.';
|
|
549
|
+
btn.disabled = false;
|
|
550
|
+
btn.textContent = 'Sign In';
|
|
551
|
+
return;
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
sessionStorage.setItem('mm_auth', 'true');
|
|
555
|
+
sessionStorage.setItem('mm_token', data.token);
|
|
556
|
+
sessionStorage.setItem('mm_rank', data.rank);
|
|
557
|
+
sessionStorage.setItem('mm_user', username);
|
|
558
|
+
sessionStorage.setItem('mm_force_change', data.forcePasswordChange ? 'true' : 'false');
|
|
559
|
+
|
|
560
|
+
if (data.forcePasswordChange) {
|
|
561
|
+
window.location.href = './change-password.html';
|
|
562
|
+
} else if (data.rank === 'administrator') {
|
|
563
|
+
window.location.href = './admin.html';
|
|
564
|
+
} else if (data.rank === 'admin') {
|
|
565
|
+
window.location.href = './control-panel.html';
|
|
566
|
+
} else {
|
|
567
|
+
window.location.href = './browse.html';
|
|
568
|
+
}
|
|
569
|
+
} catch {
|
|
570
|
+
errEl.textContent = 'Network error. Try again.';
|
|
571
|
+
btn.disabled = false;
|
|
572
|
+
btn.textContent = 'Sign In';
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
</script>
|
|
576
|
+
</body>
|
|
577
|
+
</html>
|