studyup 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/2048-package/README.md +29 -0
- package/2048-package/assets/Screenshots-2048-Game.jpg +0 -0
- package/2048-package/css/style.css +69 -0
- package/2048-package/index.html +23 -0
- package/2048-package/src/script.js +270 -0
- package/classicube-package/README.md +5 -0
- package/classicube-package/css/style.css +7 -0
- package/classicube-package/index.html +72 -0
- package/classicube-package/js/ClassiCube.js +63288 -0
- package/classicube-package/js/jquery.js +2 -0
- package/classicube-package/static/default.zip +0 -0
- package/index.html +852 -517
- package/package.json +1 -1
- package/server.js +50 -0
package/index.html
CHANGED
|
@@ -1,518 +1,853 @@
|
|
|
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.0">
|
|
6
|
-
<title>StudyUp - Student Portal</title>
|
|
7
|
-
<!-- Material Design
|
|
8
|
-
<link href="https://fonts.googleapis.com/css?family=Roboto:400,500,700&display=swap" rel="stylesheet">
|
|
9
|
-
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
|
|
10
|
-
|
|
11
|
-
<style>
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
--primary-
|
|
15
|
-
--
|
|
16
|
-
--
|
|
17
|
-
--background
|
|
18
|
-
--
|
|
19
|
-
--text-
|
|
20
|
-
--
|
|
21
|
-
--
|
|
22
|
-
--
|
|
23
|
-
--shadow-elevation-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
color: var(--
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
header {
|
|
40
|
-
background-color: var(--primary-color);
|
|
41
|
-
color: white;
|
|
42
|
-
padding: 16px 24px;
|
|
43
|
-
box-shadow: var(--shadow-elevation-2);
|
|
44
|
-
display: flex;
|
|
45
|
-
align-items: center;
|
|
46
|
-
justify-content: space-between;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
header h1 {
|
|
50
|
-
margin: 0;
|
|
51
|
-
font-size: 20px;
|
|
52
|
-
font-weight: 500;
|
|
53
|
-
letter-spacing: 0.5px;
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
.
|
|
58
|
-
display:
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
font-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
color: var(--text-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
border-color
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
font-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
font-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
.btn-flat
|
|
278
|
-
background
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
background-color
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
border-radius:
|
|
301
|
-
box-shadow:
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
padding:
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
margin:
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
}
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
</
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
</
|
|
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.0">
|
|
6
|
+
<title>StudyUp - Student Portal</title>
|
|
7
|
+
<!-- Material Design / Roboto Typography & Icons -->
|
|
8
|
+
<link href="https://fonts.googleapis.com/css?family=Roboto:400,500,700&display=swap" rel="stylesheet">
|
|
9
|
+
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
|
|
10
|
+
|
|
11
|
+
<style>
|
|
12
|
+
:root {
|
|
13
|
+
--primary-color: #3f51b5; /* Indigo */
|
|
14
|
+
--primary-dark: #303f9f;
|
|
15
|
+
--accent-color: #ff4081;
|
|
16
|
+
--background-color: #eeeeee;
|
|
17
|
+
--card-background: #ffffff;
|
|
18
|
+
--text-primary: rgba(0, 0, 0, 0.87);
|
|
19
|
+
--text-secondary: rgba(0, 0, 0, 0.54);
|
|
20
|
+
--error-color: #d32f2f;
|
|
21
|
+
--success-color: #388e3c;
|
|
22
|
+
--warning-color: #f57c00;
|
|
23
|
+
--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
|
+
--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
|
+
}
|
|
26
|
+
|
|
27
|
+
body {
|
|
28
|
+
font-family: 'Roboto', sans-serif;
|
|
29
|
+
background-color: var(--background-color);
|
|
30
|
+
color: var(--text-primary);
|
|
31
|
+
margin: 0;
|
|
32
|
+
padding: 0;
|
|
33
|
+
display: flex;
|
|
34
|
+
flex-direction: column;
|
|
35
|
+
min-height: 100vh;
|
|
36
|
+
justify-content: space-between;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
header {
|
|
40
|
+
background-color: var(--primary-color);
|
|
41
|
+
color: white;
|
|
42
|
+
padding: 16px 24px;
|
|
43
|
+
box-shadow: var(--shadow-elevation-2);
|
|
44
|
+
display: flex;
|
|
45
|
+
align-items: center;
|
|
46
|
+
justify-content: space-between;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
header h1 {
|
|
50
|
+
margin: 0;
|
|
51
|
+
font-size: 20px;
|
|
52
|
+
font-weight: 500;
|
|
53
|
+
letter-spacing: 0.5px;
|
|
54
|
+
cursor: pointer;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
.header-actions {
|
|
58
|
+
display: flex;
|
|
59
|
+
gap: 12px;
|
|
60
|
+
align-items: center;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
.view {
|
|
64
|
+
display: none;
|
|
65
|
+
flex: 1;
|
|
66
|
+
flex-direction: column;
|
|
67
|
+
}
|
|
68
|
+
.view.active {
|
|
69
|
+
display: flex;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
.container {
|
|
73
|
+
max-width: 420px;
|
|
74
|
+
width: 100%;
|
|
75
|
+
margin: auto;
|
|
76
|
+
padding: 16px;
|
|
77
|
+
box-sizing: border-box;
|
|
78
|
+
flex: 1;
|
|
79
|
+
display: flex;
|
|
80
|
+
align-items: center;
|
|
81
|
+
justify-content: center;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
.card {
|
|
85
|
+
background-color: var(--card-background);
|
|
86
|
+
border-radius: 2px;
|
|
87
|
+
box-shadow: var(--shadow-elevation-4);
|
|
88
|
+
padding: 32px 24px;
|
|
89
|
+
display: flex;
|
|
90
|
+
flex-direction: column;
|
|
91
|
+
align-items: center;
|
|
92
|
+
text-align: center;
|
|
93
|
+
width: 100%;
|
|
94
|
+
box-sizing: border-box;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
.card-icon-wrapper {
|
|
98
|
+
background-color: rgba(63, 81, 181, 0.1);
|
|
99
|
+
color: var(--primary-color);
|
|
100
|
+
width: 64px;
|
|
101
|
+
height: 64px;
|
|
102
|
+
border-radius: 50%;
|
|
103
|
+
display: flex;
|
|
104
|
+
align-items: center;
|
|
105
|
+
justify-content: center;
|
|
106
|
+
margin-bottom: 20px;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
.card-icon-wrapper i {
|
|
110
|
+
font-size: 32px;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
.card-title {
|
|
114
|
+
font-size: 24px;
|
|
115
|
+
font-weight: 500;
|
|
116
|
+
margin: 0 0 8px 0;
|
|
117
|
+
color: var(--text-primary);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
.card-subtitle {
|
|
121
|
+
font-size: 14px;
|
|
122
|
+
color: var(--text-secondary);
|
|
123
|
+
margin: 0 0 24px 0;
|
|
124
|
+
line-height: 1.4;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
.input-container {
|
|
128
|
+
position: relative;
|
|
129
|
+
margin-bottom: 24px;
|
|
130
|
+
width: 100%;
|
|
131
|
+
text-align: left;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
.input-container input {
|
|
135
|
+
width: 100%;
|
|
136
|
+
padding: 12px;
|
|
137
|
+
font-size: 16px;
|
|
138
|
+
font-family: 'Roboto', sans-serif;
|
|
139
|
+
border: 1px solid rgba(0, 0, 0, 0.2);
|
|
140
|
+
border-radius: 2px;
|
|
141
|
+
outline: none;
|
|
142
|
+
box-sizing: border-box;
|
|
143
|
+
background: transparent;
|
|
144
|
+
transition: border-color 0.2s;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
.input-container input:focus {
|
|
148
|
+
border-color: var(--primary-color);
|
|
149
|
+
border-width: 2px;
|
|
150
|
+
padding: 11px;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
.input-container label {
|
|
154
|
+
position: absolute;
|
|
155
|
+
left: 12px;
|
|
156
|
+
top: 14px;
|
|
157
|
+
color: var(--text-secondary);
|
|
158
|
+
font-size: 16px;
|
|
159
|
+
pointer-events: none;
|
|
160
|
+
transition: 0.2s ease all;
|
|
161
|
+
background: white;
|
|
162
|
+
padding: 0 4px;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
.input-container input:focus ~ label,
|
|
166
|
+
.input-container input:not(:placeholder-shown) ~ label {
|
|
167
|
+
top: -10px;
|
|
168
|
+
font-size: 12px;
|
|
169
|
+
color: var(--primary-color);
|
|
170
|
+
font-weight: 500;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
#error-msg {
|
|
174
|
+
color: var(--error-color);
|
|
175
|
+
font-size: 13px;
|
|
176
|
+
margin-bottom: 16px;
|
|
177
|
+
display: none;
|
|
178
|
+
font-weight: 500;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
.btn-raised {
|
|
182
|
+
background-color: var(--primary-color);
|
|
183
|
+
color: white;
|
|
184
|
+
border: none;
|
|
185
|
+
font-family: 'Roboto', sans-serif;
|
|
186
|
+
font-weight: 500;
|
|
187
|
+
font-size: 14px;
|
|
188
|
+
text-transform: uppercase;
|
|
189
|
+
padding: 12px 24px;
|
|
190
|
+
border-radius: 2px;
|
|
191
|
+
cursor: pointer;
|
|
192
|
+
width: 100%;
|
|
193
|
+
box-shadow: var(--shadow-elevation-2);
|
|
194
|
+
transition: background-color 0.2s, box-shadow 0.2s;
|
|
195
|
+
letter-spacing: 0.5px;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
.btn-raised:hover {
|
|
199
|
+
background-color: var(--primary-dark);
|
|
200
|
+
box-shadow: var(--shadow-elevation-4);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
.btn-danger {
|
|
204
|
+
background-color: var(--error-color);
|
|
205
|
+
}
|
|
206
|
+
.btn-danger:hover {
|
|
207
|
+
background-color: #b71c1c;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
.btn-warning {
|
|
211
|
+
background-color: var(--warning-color);
|
|
212
|
+
}
|
|
213
|
+
.btn-warning:hover {
|
|
214
|
+
background-color: #e65100;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
.portal-container {
|
|
218
|
+
max-width: 1000px;
|
|
219
|
+
width: 100%;
|
|
220
|
+
margin: 40px auto;
|
|
221
|
+
padding: 0 16px;
|
|
222
|
+
box-sizing: border-box;
|
|
223
|
+
display: grid;
|
|
224
|
+
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
|
|
225
|
+
gap: 24px;
|
|
226
|
+
flex: 1;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
.portal-card {
|
|
230
|
+
background-color: var(--card-background);
|
|
231
|
+
border-radius: 2px;
|
|
232
|
+
box-shadow: var(--shadow-elevation-2);
|
|
233
|
+
transition: box-shadow 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
|
234
|
+
display: flex;
|
|
235
|
+
flex-direction: column;
|
|
236
|
+
overflow: hidden;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
.portal-card:hover {
|
|
240
|
+
box-shadow: var(--shadow-elevation-4);
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
.card-media {
|
|
244
|
+
height: 120px;
|
|
245
|
+
display: flex;
|
|
246
|
+
align-items: center;
|
|
247
|
+
justify-content: center;
|
|
248
|
+
color: white;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
.card-content {
|
|
252
|
+
padding: 24px;
|
|
253
|
+
flex: 1;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
.portal-card-title {
|
|
257
|
+
font-size: 20px;
|
|
258
|
+
font-weight: 500;
|
|
259
|
+
margin: 0 0 8px 0;
|
|
260
|
+
color: var(--text-primary);
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
.card-text {
|
|
264
|
+
font-size: 14px;
|
|
265
|
+
color: var(--text-secondary);
|
|
266
|
+
line-height: 1.4;
|
|
267
|
+
margin: 0;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
.card-actions {
|
|
271
|
+
padding: 8px 16px 16px 16px;
|
|
272
|
+
display: flex;
|
|
273
|
+
justify-content: flex-end;
|
|
274
|
+
gap: 8px;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
.btn-flat {
|
|
278
|
+
background: transparent;
|
|
279
|
+
border: none;
|
|
280
|
+
color: var(--primary-color);
|
|
281
|
+
font-family: 'Roboto', sans-serif;
|
|
282
|
+
font-weight: 500;
|
|
283
|
+
font-size: 14px;
|
|
284
|
+
text-transform: uppercase;
|
|
285
|
+
padding: 8px 12px;
|
|
286
|
+
border-radius: 2px;
|
|
287
|
+
cursor: pointer;
|
|
288
|
+
text-decoration: none;
|
|
289
|
+
transition: background-color 0.2s;
|
|
290
|
+
letter-spacing: 0.5px;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
.btn-flat:hover {
|
|
294
|
+
background-color: rgba(63, 81, 181, 0.08);
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
.admin-section {
|
|
298
|
+
background: white;
|
|
299
|
+
padding: 24px;
|
|
300
|
+
border-radius: 2px;
|
|
301
|
+
box-shadow: var(--shadow-elevation-2);
|
|
302
|
+
margin-bottom: 24px;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
.admin-section h2 {
|
|
306
|
+
margin-top: 0;
|
|
307
|
+
font-size: 18px;
|
|
308
|
+
color: var(--primary-color);
|
|
309
|
+
border-bottom: 2px solid var(--background-color);
|
|
310
|
+
padding-bottom: 8px;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
.code-list-item {
|
|
314
|
+
display: flex;
|
|
315
|
+
flex-direction: column;
|
|
316
|
+
background: #fafafa;
|
|
317
|
+
padding: 16px;
|
|
318
|
+
margin-bottom: 12px;
|
|
319
|
+
border-radius: 4px;
|
|
320
|
+
border: 1px solid #e0e0e0;
|
|
321
|
+
gap: 12px;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
@media(min-width: 600px) {
|
|
325
|
+
.code-list-item {
|
|
326
|
+
flex-direction: row;
|
|
327
|
+
align-items: center;
|
|
328
|
+
justify-content: space-between;
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
.checkbox-group {
|
|
333
|
+
display: flex;
|
|
334
|
+
flex-wrap: wrap;
|
|
335
|
+
gap: 12px;
|
|
336
|
+
font-size: 14px;
|
|
337
|
+
align-items: center;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
.checkbox-group label {
|
|
341
|
+
display: flex;
|
|
342
|
+
align-items: center;
|
|
343
|
+
gap: 4px;
|
|
344
|
+
cursor: pointer;
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
#game-modal {
|
|
348
|
+
display: none;
|
|
349
|
+
position: fixed;
|
|
350
|
+
top: 0;
|
|
351
|
+
left: 0;
|
|
352
|
+
width: 100vw;
|
|
353
|
+
height: 100vh;
|
|
354
|
+
background-color: rgba(0, 0, 0, 0.7);
|
|
355
|
+
z-index: 1000;
|
|
356
|
+
flex-direction: column;
|
|
357
|
+
align-items: center;
|
|
358
|
+
justify-content: center;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
.modal-content {
|
|
362
|
+
width: 90vw;
|
|
363
|
+
height: 85vh;
|
|
364
|
+
background-color: #000;
|
|
365
|
+
border-radius: 4px;
|
|
366
|
+
box-shadow: 0 8px 10px 1px rgba(0,0,0,0.14), 0 3px 14px 2px rgba(0,0,0,0.12), 0 5px 5px -3px rgba(0,0,0,0.2);
|
|
367
|
+
display: flex;
|
|
368
|
+
flex-direction: column;
|
|
369
|
+
overflow: hidden;
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
.modal-header {
|
|
373
|
+
background-color: #212121;
|
|
374
|
+
color: white;
|
|
375
|
+
padding: 12px 20px;
|
|
376
|
+
display: flex;
|
|
377
|
+
align-items: center;
|
|
378
|
+
justify-content: space-between;
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
.modal-title {
|
|
382
|
+
margin: 0;
|
|
383
|
+
font-size: 16px;
|
|
384
|
+
font-weight: 500;
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
.modal-header-actions {
|
|
388
|
+
display: flex;
|
|
389
|
+
align-items: center;
|
|
390
|
+
gap: 8px;
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
.modal-btn {
|
|
394
|
+
background: transparent;
|
|
395
|
+
border: none;
|
|
396
|
+
color: white;
|
|
397
|
+
cursor: pointer;
|
|
398
|
+
font-size: 20px;
|
|
399
|
+
display: flex;
|
|
400
|
+
align-items: center;
|
|
401
|
+
justify-content: center;
|
|
402
|
+
padding: 6px;
|
|
403
|
+
border-radius: 50%;
|
|
404
|
+
transition: background-color 0.2s;
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
.modal-btn:hover {
|
|
408
|
+
background-color: rgba(255, 255, 255, 0.1);
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
.modal-body {
|
|
412
|
+
flex: 1;
|
|
413
|
+
width: 100%;
|
|
414
|
+
background: #fff;
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
.modal-body iframe {
|
|
418
|
+
width: 100%;
|
|
419
|
+
height: 100%;
|
|
420
|
+
border: none;
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
footer {
|
|
424
|
+
text-align: center;
|
|
425
|
+
padding: 16px;
|
|
426
|
+
color: var(--text-secondary);
|
|
427
|
+
font-size: 12px;
|
|
428
|
+
}
|
|
429
|
+
</style>
|
|
430
|
+
</head>
|
|
431
|
+
<body>
|
|
432
|
+
|
|
433
|
+
<header>
|
|
434
|
+
<h1 onclick="logout()">StudyUp</h1>
|
|
435
|
+
<div class="header-actions">
|
|
436
|
+
<span id="current-user-badge" style="font-size: 13px; font-weight: 500;"></span>
|
|
437
|
+
<i class="material-icons" style="cursor:pointer;" onclick="logout()">exit_to_app</i>
|
|
438
|
+
</div>
|
|
439
|
+
</header>
|
|
440
|
+
|
|
441
|
+
<!-- VIEW 1: Login Screen -->
|
|
442
|
+
<div id="login-view" class="view active">
|
|
443
|
+
<main class="container">
|
|
444
|
+
<div class="card">
|
|
445
|
+
<div class="card-icon-wrapper">
|
|
446
|
+
<i class="material-icons">lock_outline</i>
|
|
447
|
+
</div>
|
|
448
|
+
<h2 class="card-title">Welcome Student</h2>
|
|
449
|
+
<p class="card-subtitle">Please enter your class code to access your study materials and interactive guides.</p>
|
|
450
|
+
|
|
451
|
+
<div class="input-container">
|
|
452
|
+
<input type="text" id="class-code-input" placeholder="e.g. 123456" maxlength="6" autofocus>
|
|
453
|
+
<label for="class-code-input">Class Code</label>
|
|
454
|
+
</div>
|
|
455
|
+
|
|
456
|
+
<div id="error-msg">Invalid class code or system currently locked.</div>
|
|
457
|
+
|
|
458
|
+
<button class="btn-raised" onclick="verifyCode()">Enter Code</button>
|
|
459
|
+
</div>
|
|
460
|
+
</main>
|
|
461
|
+
</div>
|
|
462
|
+
|
|
463
|
+
<!-- VIEW 2: Portal Dashboard -->
|
|
464
|
+
<div id="portal-view" class="view">
|
|
465
|
+
<div class="portal-container" id="student-games-grid"></div>
|
|
466
|
+
</div>
|
|
467
|
+
|
|
468
|
+
<!-- VIEW 3: Admin Management Panel View -->
|
|
469
|
+
<div id="admin-view" class="view">
|
|
470
|
+
<div style="max-width: 1000px; width: 100%; margin: 30px auto; padding: 0 16px; box-sizing: border-box; flex:1;">
|
|
471
|
+
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px;">
|
|
472
|
+
<h2 style="margin: 0; color: var(--primary-color);">Admin Dashboard</h2>
|
|
473
|
+
<button class="btn-raised" style="width: auto; padding: 8px 16px;" onclick="switchView('portal-view')">← Back to Portal</button>
|
|
474
|
+
</div>
|
|
475
|
+
|
|
476
|
+
<!-- Global Control Section -->
|
|
477
|
+
<div class="admin-section">
|
|
478
|
+
<h2>Global System Controls</h2>
|
|
479
|
+
<p style="font-size: 14px; color: var(--text-secondary);">Manage global availability for all student class codes.</p>
|
|
480
|
+
<div style="display: flex; align-items: center; justify-content: space-between; flex-wrap: wrap; gap: 16px;">
|
|
481
|
+
<div>
|
|
482
|
+
<strong>Maintenance / Lockdown Mode:</strong>
|
|
483
|
+
<div id="global-status-text" style="font-size: 13px; color: var(--success-color); font-weight: 500;">All student codes are currently ACTIVE.</div>
|
|
484
|
+
</div>
|
|
485
|
+
<button id="global-toggle-btn" class="btn-raised btn-warning" onclick="toggleGlobalBlock()">Temporarily Block All Codes</button>
|
|
486
|
+
</div>
|
|
487
|
+
</div>
|
|
488
|
+
|
|
489
|
+
<!-- Create Code Section -->
|
|
490
|
+
<div class="admin-section">
|
|
491
|
+
<h2>Create New Class Code</h2>
|
|
492
|
+
<div style="display: flex; gap: 12px; flex-wrap: wrap; align-items: flex-end;">
|
|
493
|
+
<div class="input-container" style="flex: 1; min-width: 200px; margin-bottom: 0;">
|
|
494
|
+
<input type="text" id="new-code-input" placeholder="e.g. 654321" maxlength="6">
|
|
495
|
+
<label for="new-code-input">New Code (Digits)</label>
|
|
496
|
+
</div>
|
|
497
|
+
<button class="btn-raised" onclick="createNewCode()" style="height: 48px; width: auto; padding: 0 24px;">Create Code</button>
|
|
498
|
+
</div>
|
|
499
|
+
<div id="admin-error-msg" style="color: var(--error-color); font-size: 13px; margin-top: 8px; display: none;"></div>
|
|
500
|
+
</div>
|
|
501
|
+
|
|
502
|
+
<!-- Manage Codes Section -->
|
|
503
|
+
<div class="admin-section">
|
|
504
|
+
<h2>Manage Existing Codes & Game Permissions</h2>
|
|
505
|
+
<div id="codes-management-list"></div>
|
|
506
|
+
</div>
|
|
507
|
+
</div>
|
|
508
|
+
</div>
|
|
509
|
+
|
|
510
|
+
<!-- Game Modal Window Overlay -->
|
|
511
|
+
<div id="game-modal">
|
|
512
|
+
<div class="modal-content" id="modal-box">
|
|
513
|
+
<div class="modal-header">
|
|
514
|
+
<h3 id="modal-game-title" class="modal-title">Game Window</h3>
|
|
515
|
+
<div class="modal-header-actions">
|
|
516
|
+
<button class="modal-btn" onclick="toggleFullScreen()" title="Toggle Fullscreen">
|
|
517
|
+
<i class="material-icons" id="fullscreen-icon">fullscreen</i>
|
|
518
|
+
</button>
|
|
519
|
+
<button class="modal-btn" onclick="closeGame()" title="Close Window">
|
|
520
|
+
<i class="material-icons">close</i>
|
|
521
|
+
</button>
|
|
522
|
+
</div>
|
|
523
|
+
</div>
|
|
524
|
+
<div class="modal-body">
|
|
525
|
+
<iframe id="game-iframe" src="" title="Game View" sandbox="allow-scripts allow-same-origin allow-modals allow-pointer-lock allow-downloads"></iframe>
|
|
526
|
+
</div>
|
|
527
|
+
</div>
|
|
528
|
+
</div>
|
|
529
|
+
|
|
530
|
+
<footer>
|
|
531
|
+
StudyUp • Student Resource Portal • 2026
|
|
532
|
+
</footer>
|
|
533
|
+
|
|
534
|
+
<script>
|
|
535
|
+
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' }
|
|
540
|
+
];
|
|
541
|
+
|
|
542
|
+
const ADMIN_APP = {
|
|
543
|
+
id: 'AdminPanel',
|
|
544
|
+
name: 'Admin Panel',
|
|
545
|
+
desc: 'Manage global locks, create or delete class codes, and control individual student app permissions.',
|
|
546
|
+
color: '#3f51b5',
|
|
547
|
+
icon: 'admin_panel_settings',
|
|
548
|
+
isCustomAction: true,
|
|
549
|
+
action: function() {
|
|
550
|
+
switchView('admin-view');
|
|
551
|
+
loadAdminDashboard();
|
|
552
|
+
}
|
|
553
|
+
};
|
|
554
|
+
|
|
555
|
+
// Fetch config from Express server backend (/api/config)
|
|
556
|
+
async function getConfig() {
|
|
557
|
+
try {
|
|
558
|
+
const response = await fetch('/api/config');
|
|
559
|
+
const data = await response.json();
|
|
560
|
+
return data;
|
|
561
|
+
} 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
|
+
};
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
// Save config changes to Express server backend (/api/config)
|
|
571
|
+
async function saveConfig(configObj) {
|
|
572
|
+
try {
|
|
573
|
+
await fetch('/api/config', {
|
|
574
|
+
method: 'POST',
|
|
575
|
+
headers: { 'Content-Type': 'application/json' },
|
|
576
|
+
body: JSON.stringify(configObj)
|
|
577
|
+
});
|
|
578
|
+
} catch (err) {
|
|
579
|
+
console.error("Failed to save changes to server:", err);
|
|
580
|
+
alert("Error saving update to server.");
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
document.getElementById('class-code-input').addEventListener('keypress', function (e) {
|
|
585
|
+
if (e.key === 'Enter') {
|
|
586
|
+
verifyCode();
|
|
587
|
+
}
|
|
588
|
+
});
|
|
589
|
+
|
|
590
|
+
async function verifyCode() {
|
|
591
|
+
const inputVal = document.getElementById('class-code-input').value.trim();
|
|
592
|
+
const errorMsg = document.getElementById('error-msg');
|
|
593
|
+
const config = await getConfig();
|
|
594
|
+
|
|
595
|
+
if (config.globalBlock && inputVal !== '814212') {
|
|
596
|
+
errorMsg.innerText = "System is temporarily locked by administrator.";
|
|
597
|
+
errorMsg.style.display = 'block';
|
|
598
|
+
document.getElementById('class-code-input').focus();
|
|
599
|
+
return;
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
if (inputVal === '814212') {
|
|
603
|
+
errorMsg.style.display = 'none';
|
|
604
|
+
document.getElementById('class-code-input').value = '';
|
|
605
|
+
switchView('portal-view');
|
|
606
|
+
document.getElementById('current-user-badge').innerText = 'Master Admin Mode';
|
|
607
|
+
|
|
608
|
+
const allGameIds = AVAILABLE_GAMES.map(g => g.id);
|
|
609
|
+
loadStudentDashboard(allGameIds, true);
|
|
610
|
+
} else if (config.codes && config.codes[inputVal]) {
|
|
611
|
+
errorMsg.style.display = 'none';
|
|
612
|
+
document.getElementById('class-code-input').value = '';
|
|
613
|
+
switchView('portal-view');
|
|
614
|
+
document.getElementById('current-user-badge').innerText = `Class Code: ${inputVal}`;
|
|
615
|
+
loadStudentDashboard(config.codes[inputVal].games || [], false);
|
|
616
|
+
} else {
|
|
617
|
+
errorMsg.innerText = "Invalid class code. Please check with your teacher or instructor.";
|
|
618
|
+
errorMsg.style.display = 'block';
|
|
619
|
+
document.getElementById('class-code-input').focus();
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
function switchView(viewId) {
|
|
624
|
+
document.querySelectorAll('.view').forEach(v => v.classList.remove('active'));
|
|
625
|
+
document.getElementById(viewId).classList.add('active');
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
function logout() {
|
|
629
|
+
closeGame();
|
|
630
|
+
switchView('login-view');
|
|
631
|
+
document.getElementById('current-user-badge').innerText = '';
|
|
632
|
+
document.getElementById('class-code-input').focus();
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
function loadStudentDashboard(allowedGameIds, isAdminUser) {
|
|
636
|
+
const grid = document.getElementById('student-games-grid');
|
|
637
|
+
grid.innerHTML = '';
|
|
638
|
+
|
|
639
|
+
const permittedGames = AVAILABLE_GAMES.filter(g => allowedGameIds.includes(g.id));
|
|
640
|
+
const displayCards = [];
|
|
641
|
+
if (isAdminUser) displayCards.push(ADMIN_APP);
|
|
642
|
+
permittedGames.forEach(g => displayCards.push(g));
|
|
643
|
+
|
|
644
|
+
if (displayCards.length === 0) {
|
|
645
|
+
grid.innerHTML = `<div style="grid-column: 1/-1; text-align: center; padding: 40px; background: white; border-radius: 4px; box-shadow: var(--shadow-elevation-2);">
|
|
646
|
+
<i class="material-icons" style="font-size: 48px; color: var(--text-secondary);">block</i>
|
|
647
|
+
<h3 style="margin: 12px 0 4px 0;">No Apps Available</h3>
|
|
648
|
+
<p style="color: var(--text-secondary); margin: 0;">There are currently no apps assigned or permitted for this class code.</p>
|
|
649
|
+
</div>`;
|
|
650
|
+
return;
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
displayCards.forEach(item => {
|
|
654
|
+
const card = document.createElement('div');
|
|
655
|
+
card.className = 'portal-card';
|
|
656
|
+
|
|
657
|
+
if (item.isCustomAction) {
|
|
658
|
+
card.innerHTML = `
|
|
659
|
+
<div class="card-media" style="background-color: ${item.color};">
|
|
660
|
+
<i class="material-icons">${item.icon}</i>
|
|
661
|
+
</div>
|
|
662
|
+
<div class="card-content">
|
|
663
|
+
<h2 class="portal-card-title">${item.name}</h2>
|
|
664
|
+
<p class="card-text">${item.desc}</p>
|
|
665
|
+
</div>
|
|
666
|
+
<div class="card-actions">
|
|
667
|
+
<button class="btn-flat" id="admin-panel-launch-btn">Open Panel</button>
|
|
668
|
+
</div>
|
|
669
|
+
`;
|
|
670
|
+
card.querySelector('#admin-panel-launch-btn').addEventListener('click', item.action);
|
|
671
|
+
} else {
|
|
672
|
+
card.innerHTML = `
|
|
673
|
+
<div class="card-media" style="background-color: ${item.color};">
|
|
674
|
+
<i class="material-icons">${item.icon}</i>
|
|
675
|
+
</div>
|
|
676
|
+
<div class="card-content">
|
|
677
|
+
<h2 class="portal-card-title">${item.name}</h2>
|
|
678
|
+
<p class="card-text">${item.desc}</p>
|
|
679
|
+
</div>
|
|
680
|
+
<div class="card-actions">
|
|
681
|
+
<button class="btn-flat" onclick="openGame('${item.name}', '${item.url}')">Launch App</button>
|
|
682
|
+
</div>
|
|
683
|
+
`;
|
|
684
|
+
}
|
|
685
|
+
grid.appendChild(card);
|
|
686
|
+
});
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
async function loadAdminDashboard() {
|
|
690
|
+
const config = await getConfig();
|
|
691
|
+
const isBlocked = config.globalBlock;
|
|
692
|
+
|
|
693
|
+
const statusText = document.getElementById('global-status-text');
|
|
694
|
+
const toggleBtn = document.getElementById('global-toggle-btn');
|
|
695
|
+
if (isBlocked) {
|
|
696
|
+
statusText.innerText = "System is LOCKED. All student codes are temporarily blocked.";
|
|
697
|
+
statusText.style.color = "var(--error-color)";
|
|
698
|
+
toggleBtn.innerText = "Unblock All Codes";
|
|
699
|
+
toggleBtn.className = "btn-raised";
|
|
700
|
+
} else {
|
|
701
|
+
statusText.innerText = "All student codes are currently ACTIVE.";
|
|
702
|
+
statusText.style.color = "var(--success-color)";
|
|
703
|
+
toggleBtn.innerText = "Temporarily Block All Codes";
|
|
704
|
+
toggleBtn.className = "btn-raised btn-warning";
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
const listContainer = document.getElementById('codes-management-list');
|
|
708
|
+
listContainer.innerHTML = '';
|
|
709
|
+
|
|
710
|
+
const studentCodes = Object.keys(config.codes || {});
|
|
711
|
+
|
|
712
|
+
if (studentCodes.length === 0) {
|
|
713
|
+
listContainer.innerHTML = `<p style="color: var(--text-secondary); font-style: italic;">No active student codes found. Create one above.</p>`;
|
|
714
|
+
return;
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
studentCodes.forEach(code => {
|
|
718
|
+
const codeObj = config.codes[code];
|
|
719
|
+
const assignedGames = codeObj.games || [];
|
|
720
|
+
|
|
721
|
+
const item = document.createElement('div');
|
|
722
|
+
item.className = 'code-list-item';
|
|
723
|
+
|
|
724
|
+
let checkboxesHtml = AVAILABLE_GAMES.map(g => {
|
|
725
|
+
const checked = assignedGames.includes(g.id) ? 'checked' : '';
|
|
726
|
+
return `<label><input type="checkbox" value="${g.id}" ${checked} onchange="updateCodePermission('${code}', '${g.id}', this.checked)"> ${g.name}</label>`;
|
|
727
|
+
}).join('');
|
|
728
|
+
|
|
729
|
+
item.innerHTML = `
|
|
730
|
+
<div>
|
|
731
|
+
<strong style="font-size: 16px; color: var(--primary-color);">Code: ${code}</strong>
|
|
732
|
+
</div>
|
|
733
|
+
<div class="checkbox-group">
|
|
734
|
+
${checkboxesHtml}
|
|
735
|
+
</div>
|
|
736
|
+
<div style="display: flex; gap: 8px;">
|
|
737
|
+
<button class="btn-raised" style="padding: 6px 12px; font-size: 12px;" onclick="allowAllForCode('${code}')">Allow All</button>
|
|
738
|
+
<button class="btn-raised btn-danger" style="padding: 6px 12px; font-size: 12px;" onclick="deleteCode('${code}')">Delete</button>
|
|
739
|
+
</div>
|
|
740
|
+
`;
|
|
741
|
+
listContainer.appendChild(item);
|
|
742
|
+
});
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
async function toggleGlobalBlock() {
|
|
746
|
+
const config = await getConfig();
|
|
747
|
+
config.globalBlock = !config.globalBlock;
|
|
748
|
+
await saveConfig(config);
|
|
749
|
+
loadAdminDashboard();
|
|
750
|
+
}
|
|
751
|
+
|
|
752
|
+
async function createNewCode() {
|
|
753
|
+
const input = document.getElementById('new-code-input');
|
|
754
|
+
const err = document.getElementById('admin-error-msg');
|
|
755
|
+
const val = input.value.trim();
|
|
756
|
+
|
|
757
|
+
if (!/^\d{6}$/.test(val)) {
|
|
758
|
+
err.innerText = "Code must be exactly 6 digits.";
|
|
759
|
+
err.style.display = 'block';
|
|
760
|
+
return;
|
|
761
|
+
}
|
|
762
|
+
if (val === '814212') {
|
|
763
|
+
err.innerText = "814212 is reserved as the Master Admin code.";
|
|
764
|
+
err.style.display = 'block';
|
|
765
|
+
return;
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
const config = await getConfig();
|
|
769
|
+
if (!config.codes) config.codes = {};
|
|
770
|
+
|
|
771
|
+
if (config.codes[val]) {
|
|
772
|
+
err.innerText = "This code already exists.";
|
|
773
|
+
err.style.display = 'block';
|
|
774
|
+
return;
|
|
775
|
+
}
|
|
776
|
+
|
|
777
|
+
err.style.display = 'none';
|
|
778
|
+
config.codes[val] = { games: AVAILABLE_GAMES.map(g => g.id) };
|
|
779
|
+
await saveConfig(config);
|
|
780
|
+
input.value = '';
|
|
781
|
+
loadAdminDashboard();
|
|
782
|
+
}
|
|
783
|
+
|
|
784
|
+
async function deleteCode(code) {
|
|
785
|
+
if (confirm(`Are you sure you want to delete code ${code}?`)) {
|
|
786
|
+
const config = await getConfig();
|
|
787
|
+
delete config.codes[code];
|
|
788
|
+
await saveConfig(config);
|
|
789
|
+
loadAdminDashboard();
|
|
790
|
+
}
|
|
791
|
+
}
|
|
792
|
+
|
|
793
|
+
async function updateCodePermission(code, gameId, isChecked) {
|
|
794
|
+
const config = await getConfig();
|
|
795
|
+
if (!config.codes[code].games) config.codes[code].games = [];
|
|
796
|
+
|
|
797
|
+
if (isChecked) {
|
|
798
|
+
if (!config.codes[code].games.includes(gameId)) {
|
|
799
|
+
config.codes[code].games.push(gameId);
|
|
800
|
+
}
|
|
801
|
+
} else {
|
|
802
|
+
config.codes[code].games = config.codes[code].games.filter(id => id !== gameId);
|
|
803
|
+
}
|
|
804
|
+
await saveConfig(config);
|
|
805
|
+
}
|
|
806
|
+
|
|
807
|
+
async function allowAllForCode(code) {
|
|
808
|
+
const config = await getConfig();
|
|
809
|
+
config.codes[code].games = AVAILABLE_GAMES.map(g => g.id);
|
|
810
|
+
await saveConfig(config);
|
|
811
|
+
loadAdminDashboard();
|
|
812
|
+
}
|
|
813
|
+
|
|
814
|
+
function openGame(gameName, gameUrl) {
|
|
815
|
+
document.getElementById('modal-game-title').innerText = gameName;
|
|
816
|
+
document.getElementById('game-iframe').src = gameUrl;
|
|
817
|
+
document.getElementById('game-modal').style.display = 'flex';
|
|
818
|
+
}
|
|
819
|
+
|
|
820
|
+
function closeGame() {
|
|
821
|
+
if (document.fullscreenElement) {
|
|
822
|
+
document.exitFullscreen();
|
|
823
|
+
}
|
|
824
|
+
document.getElementById('game-modal').style.display = 'none';
|
|
825
|
+
document.getElementById('game-iframe').src = '';
|
|
826
|
+
}
|
|
827
|
+
|
|
828
|
+
function toggleFullScreen() {
|
|
829
|
+
const modalBox = document.getElementById('modal-box');
|
|
830
|
+
const icon = document.getElementById('fullscreen-icon');
|
|
831
|
+
|
|
832
|
+
if (!document.fullscreenElement) {
|
|
833
|
+
modalBox.requestFullscreen().catch(err => {
|
|
834
|
+
alert(`Error attempting to enable fullscreen: ${err.message}`);
|
|
835
|
+
});
|
|
836
|
+
icon.innerText = 'fullscreen_exit';
|
|
837
|
+
} else {
|
|
838
|
+
document.exitFullscreen();
|
|
839
|
+
icon.innerText = 'fullscreen';
|
|
840
|
+
}
|
|
841
|
+
}
|
|
842
|
+
|
|
843
|
+
document.addEventListener('fullscreenchange', () => {
|
|
844
|
+
const icon = document.getElementById('fullscreen-icon');
|
|
845
|
+
if (!document.fullscreenElement) {
|
|
846
|
+
icon.innerText = 'fullscreen';
|
|
847
|
+
} else {
|
|
848
|
+
icon.innerText = 'fullscreen_exit';
|
|
849
|
+
}
|
|
850
|
+
});
|
|
851
|
+
</script>
|
|
852
|
+
</body>
|
|
518
853
|
</html>
|