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/css/style.css CHANGED
@@ -50,8 +50,9 @@ a { color: inherit; text-decoration: none; }
50
50
  }
51
51
 
52
52
  .logo-img {
53
- height: 28px;
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
- /* PIN overlay */
160
- .pin-overlay {
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
- .pin-overlay.hidden { display: none; }
172
-
173
- .pin-box {
168
+ .login-box {
174
169
  background: #1c1c1e;
175
170
  border-radius: 20px;
176
- padding: 32px 28px 24px;
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
- .pin-box h2 {
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
- .pin-box p {
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
- .pin-dots {
197
- display: flex;
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
- font-size: 1.1rem;
228
- font-weight: 500;
194
+ border: 1px solid rgba(255,255,255,.12);
195
+ border-radius: 10px;
229
196
  color: #fff;
230
- cursor: pointer;
231
- transition: background .1s;
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
- .pin-key:hover { background: rgba(255,255,255,.14); }
235
- .pin-key:active { background: rgba(255,255,255,.2); }
236
-
237
- .pin-key.del {
238
- font-size: 0.85rem;
239
- background: transparent;
240
- color: rgba(255,255,255,.5);
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
- .pin-error {
244
- font-size: 0.8rem;
225
+ .login-error {
226
+ font-size: 0.78rem;
245
227
  color: #ff3b30;
246
228
  min-height: 1.2em;
247
- margin-bottom: 8px;
229
+ margin: 8px 0 0;
248
230
  }
249
231
 
250
- .pin-cancel {
232
+ .login-cancel {
251
233
  background: none;
252
234
  border: none;
253
- color: rgba(255,255,255,.4);
254
- font-size: 0.85rem;
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">∑</div>
251
+ <div class="calc-logo-icon">&#8721;</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.0</span>
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">π</button>
269
+ <button class="btn-sci" data-sci="pi">&#960;</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²</button>
291
- <button class="btn-sci" data-sci="sqrt">√x</button>
273
+ <button class="btn-sci" data-sci="sq">x&#178;</button>
274
+ <button class="btn-sci" data-sci="sqrt">&#8730;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">+/−</button>
285
+ <button class="btn btn-func" data-action="sign">+/&#8722;</button>
303
286
  <button class="btn btn-func" data-action="percent">%</button>
304
- <button class="btn btn-op" data-action="op" data-op="÷">÷</button>
287
+ <button class="btn btn-op" data-action="op" data-op="&#247;">&#247;</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="×">×</button>
292
+ <button class="btn btn-op" data-action="op" data-op="&#215;">&#215;</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="">−</button>
297
+ <button class="btn btn-op" data-action="op" data-op="&#8722;">&#8722;</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
- <!-- PIN overlay -->
328
- <div class="pin-overlay hidden" id="pin-overlay">
329
- <div class="pin-box">
330
- <h2>Enter PIN</h2>
331
- <p>Enter your access code</p>
332
- <div class="pin-dots" id="pin-dots">
333
- <div class="pin-dot" id="d0"></div>
334
- <div class="pin-dot" id="d1"></div>
335
- <div class="pin-dot" id="d2"></div>
336
- <div class="pin-dot" id="d3"></div>
337
- </div>
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=2"></script>
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>