studyup 1.0.3 → 1.0.4

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.
Files changed (3) hide show
  1. package/index.html +192 -97
  2. package/package.json +1 -1
  3. package/server.js +0 -50
package/index.html CHANGED
@@ -7,7 +7,6 @@
7
7
  <!-- Material Design / Roboto Typography & Icons -->
8
8
  <link href="https://fonts.googleapis.com/css?family=Roboto:400,500,700&display=swap" rel="stylesheet">
9
9
  <link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
10
-
11
10
  <style>
12
11
  :root {
13
12
  --primary-color: #3f51b5; /* Indigo */
@@ -23,7 +22,6 @@
23
22
  --shadow-elevation-2: 0 3px 1px -2px rgba(0,0,0,0.2), 0 2px 2px 0 rgba(0,0,0,0.14), 0 1px 5px 0 rgba(0,0,0,0.12);
24
23
  --shadow-elevation-4: 0 4px 5px -2px rgba(0,0,0,0.2), 0 7px 10px 1px rgba(0,0,0,0.14), 0 2px 16px 1px rgba(0,0,0,0.12);
25
24
  }
26
-
27
25
  body {
28
26
  font-family: 'Roboto', sans-serif;
29
27
  background-color: var(--background-color);
@@ -35,7 +33,6 @@
35
33
  min-height: 100vh;
36
34
  justify-content: space-between;
37
35
  }
38
-
39
36
  header {
40
37
  background-color: var(--primary-color);
41
38
  color: white;
@@ -45,7 +42,6 @@
45
42
  align-items: center;
46
43
  justify-content: space-between;
47
44
  }
48
-
49
45
  header h1 {
50
46
  margin: 0;
51
47
  font-size: 20px;
@@ -53,13 +49,11 @@
53
49
  letter-spacing: 0.5px;
54
50
  cursor: pointer;
55
51
  }
56
-
57
52
  .header-actions {
58
53
  display: flex;
59
54
  gap: 12px;
60
55
  align-items: center;
61
56
  }
62
-
63
57
  .view {
64
58
  display: none;
65
59
  flex: 1;
@@ -68,7 +62,6 @@
68
62
  .view.active {
69
63
  display: flex;
70
64
  }
71
-
72
65
  .container {
73
66
  max-width: 420px;
74
67
  width: 100%;
@@ -80,7 +73,6 @@
80
73
  align-items: center;
81
74
  justify-content: center;
82
75
  }
83
-
84
76
  .card {
85
77
  background-color: var(--card-background);
86
78
  border-radius: 2px;
@@ -93,7 +85,6 @@
93
85
  width: 100%;
94
86
  box-sizing: border-box;
95
87
  }
96
-
97
88
  .card-icon-wrapper {
98
89
  background-color: rgba(63, 81, 181, 0.1);
99
90
  color: var(--primary-color);
@@ -105,32 +96,27 @@
105
96
  justify-content: center;
106
97
  margin-bottom: 20px;
107
98
  }
108
-
109
99
  .card-icon-wrapper i {
110
100
  font-size: 32px;
111
101
  }
112
-
113
102
  .card-title {
114
103
  font-size: 24px;
115
104
  font-weight: 500;
116
105
  margin: 0 0 8px 0;
117
106
  color: var(--text-primary);
118
107
  }
119
-
120
108
  .card-subtitle {
121
109
  font-size: 14px;
122
110
  color: var(--text-secondary);
123
111
  margin: 0 0 24px 0;
124
112
  line-height: 1.4;
125
113
  }
126
-
127
114
  .input-container {
128
115
  position: relative;
129
116
  margin-bottom: 24px;
130
117
  width: 100%;
131
118
  text-align: left;
132
119
  }
133
-
134
120
  .input-container input {
135
121
  width: 100%;
136
122
  padding: 12px;
@@ -143,13 +129,11 @@
143
129
  background: transparent;
144
130
  transition: border-color 0.2s;
145
131
  }
146
-
147
132
  .input-container input:focus {
148
133
  border-color: var(--primary-color);
149
134
  border-width: 2px;
150
135
  padding: 11px;
151
136
  }
152
-
153
137
  .input-container label {
154
138
  position: absolute;
155
139
  left: 12px;
@@ -161,7 +145,6 @@
161
145
  background: white;
162
146
  padding: 0 4px;
163
147
  }
164
-
165
148
  .input-container input:focus ~ label,
166
149
  .input-container input:not(:placeholder-shown) ~ label {
167
150
  top: -10px;
@@ -169,7 +152,6 @@
169
152
  color: var(--primary-color);
170
153
  font-weight: 500;
171
154
  }
172
-
173
155
  #error-msg {
174
156
  color: var(--error-color);
175
157
  font-size: 13px;
@@ -177,7 +159,6 @@
177
159
  display: none;
178
160
  font-weight: 500;
179
161
  }
180
-
181
162
  .btn-raised {
182
163
  background-color: var(--primary-color);
183
164
  color: white;
@@ -194,26 +175,22 @@
194
175
  transition: background-color 0.2s, box-shadow 0.2s;
195
176
  letter-spacing: 0.5px;
196
177
  }
197
-
198
178
  .btn-raised:hover {
199
179
  background-color: var(--primary-dark);
200
180
  box-shadow: var(--shadow-elevation-4);
201
181
  }
202
-
203
182
  .btn-danger {
204
183
  background-color: var(--error-color);
205
184
  }
206
185
  .btn-danger:hover {
207
186
  background-color: #b71c1c;
208
187
  }
209
-
210
188
  .btn-warning {
211
189
  background-color: var(--warning-color);
212
190
  }
213
191
  .btn-warning:hover {
214
192
  background-color: #e65100;
215
193
  }
216
-
217
194
  .portal-container {
218
195
  max-width: 1000px;
219
196
  width: 100%;
@@ -225,7 +202,6 @@
225
202
  gap: 24px;
226
203
  flex: 1;
227
204
  }
228
-
229
205
  .portal-card {
230
206
  background-color: var(--card-background);
231
207
  border-radius: 2px;
@@ -235,11 +211,9 @@
235
211
  flex-direction: column;
236
212
  overflow: hidden;
237
213
  }
238
-
239
214
  .portal-card:hover {
240
215
  box-shadow: var(--shadow-elevation-4);
241
216
  }
242
-
243
217
  .card-media {
244
218
  height: 120px;
245
219
  display: flex;
@@ -247,33 +221,28 @@
247
221
  justify-content: center;
248
222
  color: white;
249
223
  }
250
-
251
224
  .card-content {
252
225
  padding: 24px;
253
226
  flex: 1;
254
227
  }
255
-
256
228
  .portal-card-title {
257
229
  font-size: 20px;
258
230
  font-weight: 500;
259
231
  margin: 0 0 8px 0;
260
232
  color: var(--text-primary);
261
233
  }
262
-
263
234
  .card-text {
264
235
  font-size: 14px;
265
236
  color: var(--text-secondary);
266
237
  line-height: 1.4;
267
238
  margin: 0;
268
239
  }
269
-
270
240
  .card-actions {
271
241
  padding: 8px 16px 16px 16px;
272
242
  display: flex;
273
243
  justify-content: flex-end;
274
244
  gap: 8px;
275
245
  }
276
-
277
246
  .btn-flat {
278
247
  background: transparent;
279
248
  border: none;
@@ -289,11 +258,9 @@
289
258
  transition: background-color 0.2s;
290
259
  letter-spacing: 0.5px;
291
260
  }
292
-
293
261
  .btn-flat:hover {
294
262
  background-color: rgba(63, 81, 181, 0.08);
295
263
  }
296
-
297
264
  .admin-section {
298
265
  background: white;
299
266
  padding: 24px;
@@ -301,7 +268,6 @@
301
268
  box-shadow: var(--shadow-elevation-2);
302
269
  margin-bottom: 24px;
303
270
  }
304
-
305
271
  .admin-section h2 {
306
272
  margin-top: 0;
307
273
  font-size: 18px;
@@ -309,7 +275,6 @@
309
275
  border-bottom: 2px solid var(--background-color);
310
276
  padding-bottom: 8px;
311
277
  }
312
-
313
278
  .code-list-item {
314
279
  display: flex;
315
280
  flex-direction: column;
@@ -320,7 +285,6 @@
320
285
  border: 1px solid #e0e0e0;
321
286
  gap: 12px;
322
287
  }
323
-
324
288
  @media(min-width: 600px) {
325
289
  .code-list-item {
326
290
  flex-direction: row;
@@ -328,7 +292,6 @@
328
292
  justify-content: space-between;
329
293
  }
330
294
  }
331
-
332
295
  .checkbox-group {
333
296
  display: flex;
334
297
  flex-wrap: wrap;
@@ -336,14 +299,12 @@
336
299
  font-size: 14px;
337
300
  align-items: center;
338
301
  }
339
-
340
302
  .checkbox-group label {
341
303
  display: flex;
342
304
  align-items: center;
343
305
  gap: 4px;
344
306
  cursor: pointer;
345
307
  }
346
-
347
308
  #game-modal {
348
309
  display: none;
349
310
  position: fixed;
@@ -357,7 +318,6 @@
357
318
  align-items: center;
358
319
  justify-content: center;
359
320
  }
360
-
361
321
  .modal-content {
362
322
  width: 90vw;
363
323
  height: 85vh;
@@ -368,7 +328,6 @@
368
328
  flex-direction: column;
369
329
  overflow: hidden;
370
330
  }
371
-
372
331
  .modal-header {
373
332
  background-color: #212121;
374
333
  color: white;
@@ -377,19 +336,16 @@
377
336
  align-items: center;
378
337
  justify-content: space-between;
379
338
  }
380
-
381
339
  .modal-title {
382
340
  margin: 0;
383
341
  font-size: 16px;
384
342
  font-weight: 500;
385
343
  }
386
-
387
344
  .modal-header-actions {
388
345
  display: flex;
389
346
  align-items: center;
390
347
  gap: 8px;
391
348
  }
392
-
393
349
  .modal-btn {
394
350
  background: transparent;
395
351
  border: none;
@@ -403,33 +359,56 @@
403
359
  border-radius: 50%;
404
360
  transition: background-color 0.2s;
405
361
  }
406
-
407
362
  .modal-btn:hover {
408
363
  background-color: rgba(255, 255, 255, 0.1);
409
364
  }
410
-
411
365
  .modal-body {
412
366
  flex: 1;
413
367
  width: 100%;
414
368
  background: #fff;
415
369
  }
416
-
417
370
  .modal-body iframe {
418
371
  width: 100%;
419
372
  height: 100%;
420
373
  border: none;
421
374
  }
422
-
423
375
  footer {
424
376
  text-align: center;
425
377
  padding: 16px;
426
378
  color: var(--text-secondary);
427
379
  font-size: 12px;
428
380
  }
381
+
382
+ /* Custom modal notification box */
383
+ #notification-modal {
384
+ display: none;
385
+ position: fixed;
386
+ top: 0;
387
+ left: 0;
388
+ width: 100vw;
389
+ height: 100vh;
390
+ background-color: rgba(0, 0, 0, 0.5);
391
+ z-index: 2000;
392
+ align-items: center;
393
+ justify-content: center;
394
+ }
395
+ .notification-box {
396
+ background: white;
397
+ padding: 24px;
398
+ border-radius: 4px;
399
+ box-shadow: var(--shadow-elevation-4);
400
+ max-width: 350px;
401
+ width: 90%;
402
+ text-align: center;
403
+ }
404
+ .notification-message {
405
+ margin-bottom: 20px;
406
+ font-size: 15px;
407
+ color: var(--text-primary);
408
+ }
429
409
  </style>
430
410
  </head>
431
411
  <body>
432
-
433
412
  <header>
434
413
  <h1 onclick="logout()">StudyUp</h1>
435
414
  <div class="header-actions">
@@ -454,7 +433,6 @@
454
433
  </div>
455
434
 
456
435
  <div id="error-msg">Invalid class code or system currently locked.</div>
457
-
458
436
  <button class="btn-raised" onclick="verifyCode()">Enter Code</button>
459
437
  </div>
460
438
  </main>
@@ -527,16 +505,24 @@
527
505
  </div>
528
506
  </div>
529
507
 
508
+ <!-- Custom Notification Modal -->
509
+ <div id="notification-modal">
510
+ <div class="notification-box">
511
+ <div id="notification-text" class="notification-message">Message here</div>
512
+ <button class="btn-raised" onclick="closeNotification()">OK</button>
513
+ </div>
514
+ </div>
515
+
530
516
  <footer>
531
517
  StudyUp &bull; Student Resource Portal &bull; 2026
532
518
  </footer>
533
519
 
534
520
  <script>
535
521
  const AVAILABLE_GAMES = [
536
- { id: 'Sandboxels', name: 'Sandboxels', desc: 'A falling sand and physics sandbox game where you can mix elements and trigger chemical reactions.', url: 'sandboxels-package/sandboxels-1.13.2/index.html', color: '#00897b', icon: 'science' },
537
- { id: 'HexGL', name: 'HexGL', desc: 'A futuristic 3D WebGL racing game built with HTML5 where you pilot a high-speed craft through complex tracks.', url: 'hexgl-package/HexGL-master/index.html', color: '#e53935', icon: 'speed' },
538
- { id: 'ClassiCube', name: 'ClassiCube', desc: 'A lightweight multiplayer-compatible classic block building sandbox game for spatial visualization.', url: 'classicube-package/index.html', color: '#00acc1', icon: 'terrain' },
539
- { id: '2048', name: '2048', desc: 'A classic sliding tile puzzle game where you combine matching numbers to reach the 2048 tile.', url: '2048-package/index.html', color: '#ffb300', icon: 'grid_on' }
522
+ { id: 'Sandboxels', name: 'Sandboxels', desc: 'A falling sand and physics sandbox game where you can mix elements and trigger chemical reactions.', url: 'https://cdn.jsdelivr.net/gh/RuneScape/sandboxels@master/index.html', color: '#00897b', icon: 'science' },
523
+ { id: 'HexGL', name: 'HexGL', desc: 'A futuristic 3D WebGL racing game built with HTML5 where you pilot a high-speed craft through complex tracks.', url: 'https://hexgl.bkcore.com/play/', color: '#e53935', icon: 'speed' },
524
+ { id: 'ClassiCube', name: 'ClassiCube', desc: 'A lightweight multiplayer-compatible classic block building sandbox game for spatial visualization.', url: 'https://www.classicube.net/play/', color: '#00acc1', icon: 'terrain' },
525
+ { id: '2048', name: '2048', desc: 'A classic sliding tile puzzle game where you combine matching numbers to reach the 2048 tile.', url: 'https://play2048.co/', color: '#ffb300', icon: 'grid_on' }
540
526
  ];
541
527
 
542
528
  const ADMIN_APP = {
@@ -552,45 +538,151 @@
552
538
  }
553
539
  };
554
540
 
555
- // Fetch config from Express server backend (/api/config)
556
- async function getConfig() {
541
+ const STORAGE_KEY = 'studyup_portal_config_v2';
542
+ const BROADCAST_CHANNEL_NAME = 'studyup_sync_channel';
543
+
544
+ // Session state tracking for current browser tab/window
545
+ let currentSession = {
546
+ isLoggedIn: false,
547
+ code: null, // null, '814212', or student class code
548
+ isAdmin: false
549
+ };
550
+
551
+ let broadcastChannel = null;
552
+ try {
553
+ if (window.BroadcastChannel) {
554
+ broadcastChannel = new BroadcastChannel(BROADCAST_CHANNEL_NAME);
555
+ broadcastChannel.onmessage = (event) => {
556
+ handleSyncEvent(event.data);
557
+ };
558
+ }
559
+ } catch (e) {
560
+ console.warn("BroadcastChannel not supported in this environment.", e);
561
+ }
562
+
563
+ // Listen to storage events across tabs/devices
564
+ window.addEventListener('storage', (event) => {
565
+ if (event.key === STORAGE_KEY || event.key === STORAGE_KEY + '_timestamp') {
566
+ const config = getConfig();
567
+ evaluateCurrentSession(config);
568
+ }
569
+ });
570
+
571
+ // Periodic polling interval to guarantee instant updates across all open tabs/devices
572
+ setInterval(() => {
573
+ const config = getConfig();
574
+ evaluateCurrentSession(config);
575
+ }, 1000);
576
+
577
+ function showNotification(msg) {
578
+ document.getElementById('notification-text').innerText = msg;
579
+ document.getElementById('notification-modal').style.display = 'flex';
580
+ }
581
+
582
+ function closeNotification() {
583
+ document.getElementById('notification-modal').style.display = 'none';
584
+ }
585
+
586
+ function getConfig() {
557
587
  try {
558
- const response = await fetch('/api/config');
559
- const data = await response.json();
560
- return data;
588
+ const stored = localStorage.getItem(STORAGE_KEY);
589
+ if (stored) {
590
+ return JSON.parse(stored);
591
+ }
561
592
  } catch (err) {
562
- console.error("Failed to fetch configuration from server:", err);
563
- return {
564
- globalBlock: false,
565
- codes: { "123456": { games: ["Sandboxels", "HexGL", "ClassiCube", "2048"] } }
566
- };
593
+ console.error("Failed to read from localStorage:", err);
567
594
  }
595
+
596
+ const defaultConfig = {
597
+ globalBlock: false,
598
+ timestamp: Date.now(),
599
+ codes: {
600
+ "123456": { games: ["Sandboxels", "HexGL", "ClassiCube", "2048"] }
601
+ }
602
+ };
603
+ saveConfig(defaultConfig, false);
604
+ return defaultConfig;
568
605
  }
569
606
 
570
- // Save config changes to Express server backend (/api/config)
571
- async function saveConfig(configObj) {
607
+ function saveConfig(configObj, broadcast = true) {
572
608
  try {
573
- await fetch('/api/config', {
574
- method: 'POST',
575
- headers: { 'Content-Type': 'application/json' },
576
- body: JSON.stringify(configObj)
577
- });
609
+ configObj.timestamp = Date.now();
610
+ localStorage.setItem(STORAGE_KEY, JSON.stringify(configObj));
611
+ localStorage.setItem(STORAGE_KEY + '_timestamp', configObj.timestamp.toString());
612
+
613
+ if (broadcast && broadcastChannel) {
614
+ broadcastChannel.postMessage({ type: 'CONFIG_UPDATED', config: configObj });
615
+ }
578
616
  } catch (err) {
579
- console.error("Failed to save changes to server:", err);
580
- alert("Error saving update to server.");
617
+ console.error("Failed to save to localStorage:", err);
618
+ showNotification("Error saving update to local storage.");
619
+ }
620
+ }
621
+
622
+ function handleSyncEvent(data) {
623
+ if (data && data.type === 'CONFIG_UPDATED') {
624
+ evaluateCurrentSession(data.config);
581
625
  }
582
626
  }
583
627
 
628
+ // Evaluate whether the logged-in user is still valid based on latest config
629
+ function evaluateCurrentSession(config) {
630
+ const loginViewActive = document.getElementById('login-view').classList.contains('active');
631
+ if (!currentSession.isLoggedIn || loginViewActive) {
632
+ return;
633
+ }
634
+
635
+ // Master Admin is never automatically kicked out unless global lock happens and they choose to log out,
636
+ // but let's check if regular student code is valid or globally blocked.
637
+ if (config.globalBlock && !currentSession.isAdmin) {
638
+ forceLogout("System is temporarily locked by administrator.");
639
+ return;
640
+ }
641
+
642
+ if (currentSession.isAdmin) {
643
+ // Admin dashboard might be open, update dashboard if view is active
644
+ if (document.getElementById('admin-view').classList.contains('active')) {
645
+ loadAdminDashboard();
646
+ }
647
+ return;
648
+ }
649
+
650
+ // Student session validation
651
+ const code = currentSession.code;
652
+ if (!config.codes || !config.codes[code]) {
653
+ forceLogout("Your class code has been modified or revoked.");
654
+ return;
655
+ }
656
+
657
+ // Update student dashboard games dynamically in real time
658
+ const allowedGames = config.codes[code].games || [];
659
+ if (document.getElementById('portal-view').classList.contains('active')) {
660
+ loadStudentDashboard(allowedGames, false);
661
+ }
662
+ }
663
+
664
+ function forceLogout(reason) {
665
+ currentSession = { isLoggedIn: false, code: null, isAdmin: false };
666
+ closeGame();
667
+ switchView('login-view');
668
+ document.getElementById('current-user-badge').innerText = '';
669
+ const errorMsg = document.getElementById('error-msg');
670
+ errorMsg.innerText = reason;
671
+ errorMsg.style.display = 'block';
672
+ document.getElementById('class-code-input').value = '';
673
+ document.getElementById('class-code-input').focus();
674
+ }
675
+
584
676
  document.getElementById('class-code-input').addEventListener('keypress', function (e) {
585
677
  if (e.key === 'Enter') {
586
678
  verifyCode();
587
679
  }
588
680
  });
589
681
 
590
- async function verifyCode() {
682
+ function verifyCode() {
591
683
  const inputVal = document.getElementById('class-code-input').value.trim();
592
684
  const errorMsg = document.getElementById('error-msg');
593
- const config = await getConfig();
685
+ const config = getConfig();
594
686
 
595
687
  if (config.globalBlock && inputVal !== '814212') {
596
688
  errorMsg.innerText = "System is temporarily locked by administrator.";
@@ -602,14 +694,16 @@
602
694
  if (inputVal === '814212') {
603
695
  errorMsg.style.display = 'none';
604
696
  document.getElementById('class-code-input').value = '';
697
+ currentSession = { isLoggedIn: true, code: '814212', isAdmin: true };
605
698
  switchView('portal-view');
606
699
  document.getElementById('current-user-badge').innerText = 'Master Admin Mode';
607
-
700
+
608
701
  const allGameIds = AVAILABLE_GAMES.map(g => g.id);
609
702
  loadStudentDashboard(allGameIds, true);
610
703
  } else if (config.codes && config.codes[inputVal]) {
611
704
  errorMsg.style.display = 'none';
612
705
  document.getElementById('class-code-input').value = '';
706
+ currentSession = { isLoggedIn: true, code: inputVal, isAdmin: false };
613
707
  switchView('portal-view');
614
708
  document.getElementById('current-user-badge').innerText = `Class Code: ${inputVal}`;
615
709
  loadStudentDashboard(config.codes[inputVal].games || [], false);
@@ -626,6 +720,7 @@
626
720
  }
627
721
 
628
722
  function logout() {
723
+ currentSession = { isLoggedIn: false, code: null, isAdmin: false };
629
724
  closeGame();
630
725
  switchView('login-view');
631
726
  document.getElementById('current-user-badge').innerText = '';
@@ -653,7 +748,7 @@
653
748
  displayCards.forEach(item => {
654
749
  const card = document.createElement('div');
655
750
  card.className = 'portal-card';
656
-
751
+
657
752
  if (item.isCustomAction) {
658
753
  card.innerHTML = `
659
754
  <div class="card-media" style="background-color: ${item.color};">
@@ -686,10 +781,10 @@
686
781
  });
687
782
  }
688
783
 
689
- async function loadAdminDashboard() {
690
- const config = await getConfig();
784
+ function loadAdminDashboard() {
785
+ const config = getConfig();
691
786
  const isBlocked = config.globalBlock;
692
-
787
+
693
788
  const statusText = document.getElementById('global-status-text');
694
789
  const toggleBtn = document.getElementById('global-toggle-btn');
695
790
  if (isBlocked) {
@@ -720,7 +815,7 @@
720
815
 
721
816
  const item = document.createElement('div');
722
817
  item.className = 'code-list-item';
723
-
818
+
724
819
  let checkboxesHtml = AVAILABLE_GAMES.map(g => {
725
820
  const checked = assignedGames.includes(g.id) ? 'checked' : '';
726
821
  return `<label><input type="checkbox" value="${g.id}" ${checked} onchange="updateCodePermission('${code}', '${g.id}', this.checked)"> ${g.name}</label>`;
@@ -742,14 +837,14 @@
742
837
  });
743
838
  }
744
839
 
745
- async function toggleGlobalBlock() {
746
- const config = await getConfig();
840
+ function toggleGlobalBlock() {
841
+ const config = getConfig();
747
842
  config.globalBlock = !config.globalBlock;
748
- await saveConfig(config);
843
+ saveConfig(config);
749
844
  loadAdminDashboard();
750
845
  }
751
846
 
752
- async function createNewCode() {
847
+ function createNewCode() {
753
848
  const input = document.getElementById('new-code-input');
754
849
  const err = document.getElementById('admin-error-msg');
755
850
  const val = input.value.trim();
@@ -765,7 +860,7 @@
765
860
  return;
766
861
  }
767
862
 
768
- const config = await getConfig();
863
+ const config = getConfig();
769
864
  if (!config.codes) config.codes = {};
770
865
 
771
866
  if (config.codes[val]) {
@@ -776,22 +871,22 @@
776
871
 
777
872
  err.style.display = 'none';
778
873
  config.codes[val] = { games: AVAILABLE_GAMES.map(g => g.id) };
779
- await saveConfig(config);
874
+ saveConfig(config);
780
875
  input.value = '';
781
876
  loadAdminDashboard();
782
877
  }
783
878
 
784
- async function deleteCode(code) {
879
+ function deleteCode(code) {
785
880
  if (confirm(`Are you sure you want to delete code ${code}?`)) {
786
- const config = await getConfig();
881
+ const config = getConfig();
787
882
  delete config.codes[code];
788
- await saveConfig(config);
883
+ saveConfig(config);
789
884
  loadAdminDashboard();
790
885
  }
791
886
  }
792
887
 
793
- async function updateCodePermission(code, gameId, isChecked) {
794
- const config = await getConfig();
888
+ function updateCodePermission(code, gameId, isChecked) {
889
+ const config = getConfig();
795
890
  if (!config.codes[code].games) config.codes[code].games = [];
796
891
 
797
892
  if (isChecked) {
@@ -801,13 +896,13 @@
801
896
  } else {
802
897
  config.codes[code].games = config.codes[code].games.filter(id => id !== gameId);
803
898
  }
804
- await saveConfig(config);
899
+ saveConfig(config);
805
900
  }
806
901
 
807
- async function allowAllForCode(code) {
808
- const config = await getConfig();
902
+ function allowAllForCode(code) {
903
+ const config = getConfig();
809
904
  config.codes[code].games = AVAILABLE_GAMES.map(g => g.id);
810
- await saveConfig(config);
905
+ saveConfig(config);
811
906
  loadAdminDashboard();
812
907
  }
813
908
 
@@ -831,7 +926,7 @@
831
926
 
832
927
  if (!document.fullscreenElement) {
833
928
  modalBox.requestFullscreen().catch(err => {
834
- alert(`Error attempting to enable fullscreen: ${err.message}`);
929
+ showNotification(`Error attempting to enable fullscreen: ${err.message}`);
835
930
  });
836
931
  icon.innerText = 'fullscreen_exit';
837
932
  } else {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "studyup",
3
- "version": "1.0.3",
3
+ "version": "1.0.4",
4
4
  "description": "StudyUp Game Portal",
5
5
  "main": "index.html",
6
6
  "scripts": {
package/server.js DELETED
@@ -1,50 +0,0 @@
1
- const express = require('express');
2
- const fs = require('fs');
3
- const path = require('path');
4
-
5
- const app = express();
6
- const PORT = process.env.PORT || 3000;
7
- const DATA_FILE = path.join(__dirname, 'data.json');
8
-
9
- app.use(express.json());
10
- // Serve static files from the same root folder where server.js lives
11
- app.use(express.static(__dirname));
12
-
13
- // Helper to read data
14
- function getData() {
15
- if (!fs.existsSync(DATA_FILE)) {
16
- const defaultData = {
17
- globalBlock: false,
18
- codes: {
19
- "123456": { games: ["Sandboxels", "HexGL", "ClassiCube", "2048"] }
20
- }
21
- };
22
- fs.writeFileSync(DATA_FILE, JSON.stringify(defaultData, null, 2));
23
- return defaultData;
24
- }
25
- return JSON.parse(fs.readFileSync(DATA_FILE, 'utf8'));
26
- }
27
-
28
- // Helper to write data
29
- function saveData(data) {
30
- fs.writeFileSync(DATA_FILE, JSON.stringify(data, null, 2));
31
- }
32
-
33
- // API: Get current config
34
- app.get('/api/config', (req, res) => {
35
- res.json(getData());
36
- });
37
-
38
- // API: Save/Update config (Admin actions)
39
- app.post('/api/config', (req, res) => {
40
- try {
41
- saveData(req.body);
42
- res.json({ success: true });
43
- } catch (err) {
44
- res.status(500).json({ success: false, error: err.message });
45
- }
46
- });
47
-
48
- app.listen(PORT, () => {
49
- console.log(`StudyUp server running on port ${PORT}`);
50
- });