File size: 70,253 Bytes
c4dff99
 
 
 
 
32cc7b0
c4dff99
 
 
 
32cc7b0
c4dff99
 
32cc7b0
 
 
 
 
 
 
 
c4dff99
 
 
 
 
32cc7b0
 
c4dff99
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
32cc7b0
 
c4dff99
 
 
 
 
 
 
32cc7b0
c4dff99
32cc7b0
c4dff99
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
32cc7b0
 
c4dff99
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
32cc7b0
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
c4dff99
32cc7b0
 
c4dff99
 
32cc7b0
c4dff99
 
 
 
32cc7b0
c4dff99
 
 
32cc7b0
 
 
c4dff99
 
 
32cc7b0
 
 
 
 
 
c4dff99
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
32cc7b0
 
c4dff99
 
 
 
 
 
 
 
 
 
 
 
9ab20fc
c4dff99
 
 
 
 
9ab20fc
 
 
 
 
 
 
 
c4dff99
 
 
32cc7b0
 
 
 
 
 
 
 
 
 
 
 
c4dff99
 
32cc7b0
 
 
c4dff99
32cc7b0
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
9ab20fc
c4dff99
 
 
 
 
 
 
 
 
 
 
32cc7b0
c4dff99
acf954f
 
 
 
32cc7b0
 
 
c4dff99
 
32cc7b0
 
9ab20fc
 
b72f799
 
 
 
9ab20fc
 
 
 
 
 
 
 
 
b72f799
 
9ab20fc
 
b72f799
 
 
32cc7b0
 
 
adc7e5a
 
 
32cc7b0
 
 
 
 
 
 
 
 
c4dff99
 
 
 
adc7e5a
c4dff99
32cc7b0
 
c4dff99
adc7e5a
 
 
 
 
 
 
 
c4dff99
 
 
 
 
 
 
32cc7b0
c4dff99
 
 
 
 
32cc7b0
 
c4dff99
 
 
 
32cc7b0
 
 
 
 
 
 
 
c4dff99
 
 
32cc7b0
adc7e5a
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
32cc7b0
 
 
 
 
 
 
 
 
c4dff99
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
32cc7b0
c4dff99
 
 
 
 
 
 
 
 
 
 
 
 
 
32cc7b0
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
b72f799
 
 
 
 
 
 
 
 
 
9ab20fc
 
 
 
 
 
 
 
 
 
 
 
 
 
acf954f
 
 
 
 
 
 
 
 
 
 
9ab20fc
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3825049
 
 
9ab20fc
 
3825049
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
9ab20fc
 
 
3825049
9ab20fc
3825049
9ab20fc
3825049
9ab20fc
 
 
 
 
 
 
b72f799
9ab20fc
b72f799
 
 
 
 
9ab20fc
 
b72f799
 
 
9ab20fc
b72f799
 
 
9ab20fc
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
b72f799
9ab20fc
b72f799
9ab20fc
b72f799
9ab20fc
b72f799
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
9ab20fc
 
 
b72f799
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
9ab20fc
b72f799
 
 
 
 
 
 
32cc7b0
 
 
adc7e5a
32cc7b0
 
 
 
 
 
 
 
 
c4dff99
32cc7b0
c4dff99
 
 
32cc7b0
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
c4dff99
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
32cc7b0
 
 
 
 
 
 
c4dff99
 
32cc7b0
 
 
 
 
 
 
 
 
 
adc7e5a
 
 
 
32cc7b0
adc7e5a
 
c4dff99
 
 
 
 
 
 
 
 
32cc7b0
 
c4dff99
 
 
32cc7b0
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
c4dff99
 
 
 
 
 
 
 
 
32cc7b0
 
 
c4dff99
 
 
 
 
 
 
 
 
 
 
32cc7b0
 
 
 
 
 
 
 
 
c4dff99
 
 
 
 
 
 
 
 
 
32cc7b0
c4dff99
 
 
 
32cc7b0
 
 
 
 
 
 
 
 
 
c4dff99
382cf91
 
 
 
c4dff99
 
 
 
 
 
 
 
32cc7b0
 
 
 
 
c4dff99
32cc7b0
 
c4dff99
 
 
32cc7b0
 
c4dff99
 
32cc7b0
 
 
c4dff99
 
32cc7b0
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
c4dff99
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2f7b383
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
c4dff99
2f7b383
c4dff99
 
 
2f7b383
c4dff99
 
 
32cc7b0
 
 
 
 
 
 
 
 
 
 
c4dff99
 
 
 
2f7b383
c4dff99
 
 
 
 
 
 
 
 
 
32cc7b0
 
 
 
 
 
 
 
 
 
b72f799
32cc7b0
 
b4dce81
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
32cc7b0
 
 
b4dce81
32cc7b0
 
 
 
 
c4dff99
 
 
32cc7b0
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
c4dff99
32cc7b0
c4dff99
adc7e5a
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
b72f799
32cc7b0
c4dff99
32cc7b0
c4dff99
 
 
 
 
 
 
 
 
 
 
32cc7b0
c4dff99
32cc7b0
 
 
 
c4dff99
 
 
 
 
 
 
 
 
 
 
 
 
32cc7b0
 
c4dff99
 
 
 
adc7e5a
c4dff99
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3825049
 
 
 
 
14dc0c4
 
 
 
 
 
3825049
 
14dc0c4
3825049
 
14dc0c4
 
 
3825049
 
14dc0c4
3825049
 
 
14dc0c4
 
3825049
14dc0c4
3825049
 
 
14dc0c4
3825049
 
14dc0c4
c4dff99
c64d2e4
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
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
111
112
113
114
115
116
117
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
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
176
177
178
179
180
181
182
183
184
185
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
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
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
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340
1341
1342
1343
1344
1345
1346
1347
1348
<!DOCTYPE html>
<html lang="zh-TW">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>圓周率之神的眷顧 - 老師儀表板</title>
    <!-- 引入外部套件 -->
    <script src="https://cdnjs.cloudflare.com/ajax/libs/qrcodejs/1.0.0/qrcode.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/canvas-confetti@1.6.0/dist/confetti.browser.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/xlsx/0.18.5/xlsx.full.min.js"></script>
    <style>
        :root {
            /* 根據 pigod.png 的神秘金黃色澤進行主色彩調配 */
            --primary: #d4af37;        /* 金色 */
            --primary-hover: #b49126;  /* 稍暗金 */
            --bg-overlay: rgba(20, 15, 10, 0.85); /* 黑色疊加層以確保文字清晰 */
            --panel: rgba(45, 35, 25, 0.65);      /* 牛皮紙質感的半透明版面 */
            --border: rgba(212, 175, 55, 0.3);    /* 金色邊框 */
            --text: #fdf6e3;           /* 羊皮紙白 */
            --accent: #fcd34d;
        }
        body {
            margin: 0;
            padding: 0;
            font-family: 'Inter', system-ui, -apple-system, sans-serif;
            background: var(--bg-overlay) url('pigod.png') center center / cover fixed;
            background-blend-mode: overlay;
            color: var(--text);
            min-height: 100vh;
        }
        .container { padding: 2rem; max-width: 1400px; margin: 0 auto; }
        
        /* 玻璃面板 */
        .glass-panel {
            background: var(--panel);
            backdrop-filter: blur(16px);
            border: 1px solid var(--border);
            border-radius: 1rem;
            padding: 2rem;
            box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.5);
            margin-bottom: 2rem;
        }

        /* 步驟 1:建立房間 */
        #setupScreen {
            max-width: 500px;
            margin: 15vh auto;
            text-align: center;
        }
        input.room-input {
            width: 100%;
            padding: 1rem;
            font-size: 1.5rem;
            text-align: center;
            background: rgba(0,0,0,0.3);
            border: 2px solid rgba(255,255,255,0.2);
            border-radius: 0.75rem;
            color: white;
            margin-bottom: 1.5rem;
            text-transform: uppercase;
            letter-spacing: 2px;
            box-sizing: border-box;
        }
        button.btn {
            background: linear-gradient(135deg, var(--primary), var(--primary-hover));
            color: #222; /* 金底黑字更有質感 */
            border: 1px solid rgba(255,255,255,0.2);
            padding: 1rem 2rem;
            font-size: 1.25rem;
            border-radius: 0.75rem;
            cursor: pointer;
            font-weight: bold;
            transition: all 0.2s;
            width: 100%;
            box-shadow: 0 4px 6px rgba(0,0,0,0.5);
        }
        button.btn:hover { transform: translateY(-2px); box-shadow: 0 10px 15px -3px var(--border); }
        button.btn:disabled { background: #6b7280; transform: none; cursor: not-allowed; }

        /* 步驟 2:儀表板 */
        #dashboardScreen { display: none; }
        .header {
            display: flex;
            justify-content: space-between;
            align-items: flex-start;
            flex-wrap: wrap;
            gap: 2rem;
        }
        .header-left { display: flex; align-items: center; gap: 2rem; }
        #qrcodeArea {
            background: white;
            padding: 1rem;
            border-radius: 1rem;
            box-shadow: 0 10px 25px rgba(0,0,0,0.5);
            transition: transform 0.3s;
        }
        #qrcodeArea:hover { transform: scale(1.05); }
        .room-info h1 { margin: 0 0 0.5rem 0; font-size: 2.5rem; color: var(--accent); text-shadow: 0 2px 4px rgba(0,0,0,0.5); }
        .room-info p { margin: 0 0 0.5rem 0; color: #d1d5db; font-size: 1.2rem; }
        .controls select {
            padding: 0.75rem 1rem;
            background: rgba(0,0,0,0.5);
            border: 1px solid var(--border);
            color: white;
            border-radius: 0.5rem;
            font-size: 1.1rem;
            cursor: pointer;
        }

        /* 學生標籤網格 (瀑布流卡片) */
        .students-grid {
            display: flex;
            flex-wrap: wrap;
            gap: 1.25rem;
            min-height: 200px;
        }
        .student-chip {
            background: rgba(255, 255, 255, 0.05);
            border: 1px solid var(--border);
            padding: 1.25rem;
            border-radius: 1rem;
            display: flex;
            flex-direction: column;
            align-items: center;
            gap: 0.5rem;
            animation: fadeIn 0.5s cubic-bezier(0.16, 1, 0.3, 1) forwards;
            transition: transform 0.3s, box-shadow 0.3s, background-color 0.3s;
            min-width: 140px;
            box-shadow: 0 4px 6px rgba(0,0,0,0.2);
            position: relative;
        }
        .student-chip .delete-btn {
            position: absolute;
            top: -10px;
            right: -10px;
            width: 28px;
            height: 28px;
            background: #ef4444;
            color: white;
            border-radius: 50%;
            border: 2px solid var(--bg);
            display: flex;
            justify-content: center;
            align-items: center;
            cursor: pointer;
            font-size: 14px;
            font-weight: bold;
            opacity: 0;
            transition: opacity 0.2s, transform 0.2s;
            z-index: 10;
        }
        .student-chip:hover .delete-btn { opacity: 1; }
        .student-chip .delete-btn:hover { transform: scale(1.1); background: #dc2626; }
        .student-chip:hover { transform: translateY(-5px); box-shadow: 0 15px 20px rgba(0,0,0,0.4); }
        .student-chip .name { font-weight: bold; font-size: 1.3rem; }
        .student-chip .number { color: var(--accent); font-family: monospace; font-size: 1.5rem; font-weight: bold; letter-spacing: 1px; text-shadow: 0 2px 2px rgba(0,0,0,0.5);}
        .student-chip .result {
            display: none; width: 100%; text-align: center;
            margin-top: 0.5rem; padding-top: 0.5rem;
            border-top: 1px dashed rgba(255,255,255,0.2);
            font-size: 1rem; font-weight: bold; color: #2dd4bf; text-shadow: 0 1px 2px rgba(0,0,0,0.5);
        }

        /* 狀態顏色 */
        .student-chip.searching { background: rgba(59, 130, 246, 0.25); border-color: #60a5fa; }
        .student-chip.done { background: rgba(16, 185, 129, 0.25); border-color: #34d399; }
        .student-chip.error { background: rgba(239, 68, 68, 0.25); border-color: #f87171; }

        @keyframes fadeIn { from { opacity: 0; transform: scale(0.8) translateY(20px); } to { opacity: 1; transform: scale(1) translateY(0); } }

        /* Intro 動畫 */
        @keyframes slowFadeIn { to { opacity: 1; transform: translateY(0); } from { opacity: 0; transform: translateY(20px); } }
        /* 文字容器等待 5 秒後浮現 */
        @keyframes delayedFadeIn { from { opacity: 0; } to { opacity: 1; } }
        @keyframes blink { 0%, 100% { opacity: 1; text-shadow: 0 0 10px rgba(255,255,255,0.8); } 50% { opacity: 0.3; text-shadow: none; } }

        /* Loading 動畫 */
        .spinner {
            width: 24px; height: 24px; margin: 0 auto;
            border: 3px solid rgba(255,255,255,0.2); border-top: 3px solid white;
            border-radius: 50%; animation: spin 1s linear infinite;
        }
        @keyframes spin { 100% { transform: rotate(360deg); } }

        /* 步驟 3:長條圖與頒獎台 */
        #resultsArea { display: none; }
        .podium-container {
            display: flex; justify-content: center; align-items: flex-end;
            margin: 3rem 0; height: 300px; gap: 1rem;
        }
        .podium {
            display: flex; flex-direction: column; align-items: center; justify-content: flex-end;
            width: 140px; opacity: 0; transform: translateY(50px);
        }
        .podium-info { text-align: center; margin-bottom: 0.5rem; }
        .podium-name { font-weight: bold; font-size: 1.4rem; color: #fff; text-shadow: 0 2px 6px rgba(0,0,0,0.8); }
        .podium-val { color: var(--accent); font-size: 1rem; font-weight: bold; text-shadow: 0 1px 3px rgba(0,0,0,0.8); }
        
        .podium-step {
            width: 100%; display: flex; align-items: center; justify-content: center;
            font-weight: bold; color: #111; font-size: 2rem;
            border-top-left-radius: 1rem; border-top-right-radius: 1rem;
            box-shadow: inset 0 -10px 20px rgba(0,0,0,0.2), 0 10px 15px -3px rgba(0,0,0,0.5);
        }
        /* 頒獎台動畫與高度設定 */
        .podium.show { animation: slideUp 0.8s cubic-bezier(0.16, 1, 0.3, 1) forwards; }
        .podium.first { animation-delay: 0.6s; }
        .podium.second { animation-delay: 0.3s; }
        .podium.third { animation-delay: 0s; }
        /* 一般頒獎台顏色 */
        .podium.first .podium-step { height: 220px; background: linear-gradient(135deg, #fef08a, #ca8a04); border: 2px solid #fef08a; }
        .podium.second .podium-step { height: 160px; background: linear-gradient(135deg, #f3f4f6, #9ca3af); border: 2px solid #fff; }
        .podium.third .podium-step { height: 110px; background: linear-gradient(135deg, #fed7aa, #c2410c); border: 2px solid #fed7aa; }
        @keyframes slideUp { to { opacity: 1; transform: translateY(0); } }

        /* Hall of Fame Podium Specific Overrides */
        .hof-podium-container {
            display: flex; justify-content: center; align-items: flex-end;
            margin: 3rem 0; height: 350px; gap: 1rem;
        }
        .podium.bronze-shared .podium-step { height: 110px; background: linear-gradient(135deg, #fed7aa, #c2410c); border: 2px solid #fed7aa; }
        .podium.silver-shared .podium-step { height: 160px; background: linear-gradient(135deg, #f3f4f6, #9ca3af); border: 2px solid #fff; }

    </style>
</head>
<body>
    <!-- Intro 閃爍進場畫面 (保留 5 秒背景圖無濾鏡,第 5 秒後濾鏡與文字淡入) -->
    <div id="introScreen" style="position: fixed; top: 0; left: 0; width: 100vw; height: 100vh; display: flex; flex-direction: column; justify-content: center; align-items: center; z-index: 10000; cursor: pointer; transition: opacity 1s; background: url('pigod.png') center center / cover fixed;">
        <!-- 黑色漸變遮罩 -->
        <div style="position: absolute; top:0; left:0; width:100%; height:100%; background: var(--bg-overlay); animation: delayedFadeIn 2s ease-in 5s forwards; opacity: 0; pointer-events: none;"></div>
        
        <!-- 文字區 -->
        <div style="position: relative; z-index: 2; display: flex; flex-direction: column; justify-content: center; align-items: center; opacity: 0; animation: slowFadeIn 2s cubic-bezier(0.16, 1, 0.3, 1) 5s forwards;">
            <h1 style="color: var(--accent); font-size: clamp(3rem, 8vw, 6rem); text-shadow: 0 4px 15px rgba(0,0,0,0.9); margin: 0; text-align: center;">圓周率之神的眷顧</h1>
            <p style="color: #fff; font-size: 2rem; margin-top: 3rem; animation: blink 2.5s infinite; letter-spacing: 2px;">- Click to Start -</p>
        </div>
    </div>

    <div class="container">
        <!-- 畫面 1:設定房間 -->
        <div id="setupScreen" class="glass-panel" style="display: none; opacity: 0; transition: opacity 0.8s;">
            <h1 style="color: var(--accent); font-size: 3rem; margin-top: 0; text-shadow: 0 4px 10px rgba(0,0,0,0.6);">圓周率之神的眷顧</h1>
            <p style="color: #d1d5db; margin-bottom: 2rem;">請為信徒們建立一個獨一無二的儀式代碼</p>
            <input type="text" id="roomInput" class="room-input" placeholder="例如: 805, MATH-1" autocomplete="off">
            <div class="controls" style="margin-bottom: 2rem; text-align: left;">
                <label style="display: block; margin-bottom: 0.5rem; color: #fbbf24; font-weight: bold; font-size: 1.1rem; padding-left: 0.5rem;">⚙️ 請選擇要讓學生尋找幾位數?</label>
                <select id="digitSelect" class="room-input" style="max-width: 100%; margin-bottom: 1rem; cursor: pointer;">
                    <option value="4">4 位數 (100% 機率找到)</option>
                    <option value="5">5 位數 (100% 機率找到)</option>
                    <option value="6">6 位數 (100% 機率找到)</option>
                    <option value="7" selected>7 位數 (100% 機率找到)</option>
                    <option value="8">8 位數 (86% 機率出現在2億位內)</option>
                    <option value="9">9 位數 (機率偏低,可能無法找到)</option>
                    <option value="10">10 位數 (極高機率查無此字串)</option>
                </select>
            </div>
            <div id="historyRooms" style="margin-bottom: 1.5rem; text-align: left; display: none;">
                <p style="color: #9ca3af; font-size: 0.95rem; margin: 0 0 0.5rem 0.5rem;">🕰️ 近期輸入過的教室 (點擊可快速返回避免斷線掉位):</p>
                <div id="historyChips" style="display: flex; gap: 0.5rem; flex-wrap: wrap;"></div>
            </div>
            
            <button class="btn" id="startRoomBtn">進入教室</button>

            <div id="systemWarning" style="color: #ef4444; background: rgba(239, 68, 68, 0.1); padding: 1rem; border-radius: 0.5rem; margin-top: 1.5rem; display: none; text-align: left;">
                ⚠️ <strong>警告:無法讀取網路 IP</strong><br>您目前似乎是直接點擊檔案 (file://) 開啟。<br>這會導致 QRCode 網址出錯,學生手機將無法連線掃描。<br>👉 <strong>解決方法</strong>:請使用 VScode 的 Live Server 或 Python 本機伺服器開啟。
            </div>
        </div>

        <!-- 畫面 2:主儀表板 -->
        <div id="dashboardScreen">
            <div class="header glass-panel">
                <div class="header-left">
                    <div id="qrcodeArea" style="display: none;"></div>
                    <div class="room-info">
                        <h1>教室代碼:<span id="displayRoomCode" style="padding: 0.2rem 1rem; background: rgba(0,0,0,0.3); border-radius: 0.5rem; border: 1px solid rgba(255,255,255,0.2);"></span>
                            <button class="btn" id="openHofBtn" style="width: auto; background: rgba(30, 25, 15, 0.8); border: 1px solid rgba(252, 211, 77, 0.6); color: #fcd34d; padding: 0.3rem 0.6rem; font-size: 1rem; border-radius: 0.3rem; margin-left: 1rem; cursor: pointer; vertical-align: middle; box-shadow: none;">👑 名人堂</button>
                            <button class="btn" id="muteBtn" style="width: auto; background: rgba(75, 85, 99, 0.8); border: 1px solid rgba(156, 163, 175, 0.5); color: white; padding: 0.3rem 0.6rem; font-size: 1rem; border-radius: 0.3rem; margin-left: 0.5rem; cursor: pointer; vertical-align: middle; box-shadow: none;">🔊 靜音</button>
                        </h1>
                        <p>這間教室已設定為尋找 <strong id="displayDigitLength" style="color: #34d399;">X</strong> 位數字。</p>
                        <p id="qrHintText" style="color: #fbbf24; display: none;">👉 請讓學生用平板或手機掃描左側條碼加入</p>
                        <p style="font-size: 0.9rem; color: #6b7280; display: none;" id="urlPContainer">連線網址:<span id="studentUrlDisplay" style="font-family: monospace;"></span></p>
                    </div>
                </div>
            </div>

            <!-- 名人堂面板 (新增,預設隱藏) -->
            <div class="glass-panel" id="hallOfFamePanel" style="margin-bottom: 2rem; background: rgba(30, 25, 15, 0.6); border: 1px solid rgba(252, 211, 77, 0.4); display: none;">
                <div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 1rem; flex-wrap: wrap; gap: 1rem;">
                    <h2 style="color: #fcd34d; margin-top: 0; margin-bottom: 0; font-size: 1.8rem; text-shadow: 0 2px 4px rgba(0,0,0,0.5); display: flex; align-items: center; gap: 0.5rem;">
                        👑 歷屆神諭名人堂
                    </h2>
                    <div style="display: flex; gap: 0.5rem;">
                        <input type="file" id="champExcelFile" accept=".xlsx, .xls" style="display: none;" />
                        <button class="btn" id="importChampBtn" style="width: auto; padding: 0.5rem 1rem; font-size: 0.9rem; background: rgba(212, 175, 55, 0.2); border-color: rgba(212, 175, 55, 0.5); color: #fdf6e3; box-shadow: none;" title="匯入過去的名人堂 Excel 選單">
                            📥 匯入歷史榜單
                        </button>
                        <button class="btn" id="resetHofBtn" style="width: auto; padding: 0.5rem 1rem; font-size: 0.9rem; background: rgba(239, 68, 68, 0.2); border-color: rgba(239, 68, 68, 0.5); color: #f87171; box-shadow: none;" title="清除所有紀錄,恢復預設">
                            清空紀錄
                        </button>
                    </div>
                </div>
                <p style="color: #d1d5db; margin-bottom: 1.5rem; margin-top: 0;">記錄歷年來獲得圓周率之神最高眷顧 (尋找深度最深) 的傳奇信徒。本次儀式若有突破,將會即時發佈「NEW!」洗榜!</p>
                <div id="hofPodiumArea" class="hof-podium-container">
                    <!-- 名單將以頒獎台方式生成 -->
                </div>
            </div>

            <!-- Excel 匯入面板 -->
            <div class="glass-panel" style="margin-bottom: 2rem; background: rgba(0,0,0,0.4); border: 1px dashed var(--primary);">
                <h2 style="color: var(--accent); margin-top: 0; font-size: 1.8rem; text-shadow: 0 2px 4px rgba(0,0,0,0.5);">📁 賜下預先名冊 (Excel 匯入)</h2>
                <p style="color: #d1d5db; margin-bottom: 1rem;">可先從 Excel 匯入檔案自動尋找第一名。檔案需至少兩欄:第一欄姓名、第二欄數字(不含標頭第一行)。
                    <a href="https://docs.google.com/spreadsheets/d/11hXMTLU7rPeu9h6B-BNGF6esFWEXJSGo/edit?usp=drive_link&ouid=115947803934724851078&rtpof=true&sd=true" target="_blank" style="color: var(--accent); text-decoration: underline; margin-left: 0.5rem; white-space: nowrap;">📥 下載範例檔案</a>
                </p>
                <input type="file" id="excelFile" accept=".xlsx, .xls" style="color: white; margin-bottom: 1rem; width: 100%; padding: 0.5rem; border: 1px solid rgba(255,255,255,0.2); border-radius: 0.5rem; background: rgba(0,0,0,0.3);"/>
                <div style="display: flex; gap: 1rem; align-items: center; flex-wrap: wrap;">
                    <button class="btn" id="importBtn" style="width: auto; padding: 0.75rem 1.5rem; font-size: 1.2rem;">匯入並預先請求神諭</button>
                    <span id="importStatus" style="color: #34d399; font-size: 1.1rem; font-weight: bold;"></span>
                </div>
                <div style="margin-top: 1.5rem; padding-top: 1rem; border-top: 1px solid rgba(255,255,255,0.1);">
                    <label style="color: white; cursor: pointer; display: flex; align-items: center; gap: 0.5rem; font-size: 1.1rem; font-weight: bold;">
                        <input type="checkbox" id="mergeDataCheck" checked style="transform: scale(1.5);"> 當現場學生活動結束時,將現場新資料與 Excel 名冊「合併」結算 (若未勾選則現場資料將獨立覆蓋結果)
                    </label>
                </div>
            </div>

            <div class="glass-panel" style="margin-bottom: 2rem;">
                <div style="display: flex; justify-content: space-between; align-items: flex-end; margin-bottom: 1.5rem; border-bottom: 1px solid rgba(255,255,255,0.1); padding-bottom: 1rem; flex-wrap: wrap; gap: 1rem;">
                    <div>
                        <h2 style="margin: 0; font-size: 1.8rem; text-shadow: 0 2px 4px rgba(0,0,0,0.5);">👨‍🎓 信徒等待區</h2>
                        <p style="margin: 0.5rem 0 0 0; color: #d1d5db;">目前已收到 <strong id="studentCount" style="color: white; font-size: 1.2rem;">0</strong> 份幸運數字</p>
                    </div>
                    <div style="display: flex; gap: 1rem;">
                        <button class="btn" id="restartMusicBtn" style="width: auto; padding: 1rem 1.5rem; font-size: 1.2rem; background: rgba(59, 130, 246, 0.8); border-color: rgba(96, 165, 250, 0.5); color: white;" title="返回等候階段,重新播放背景音樂">
                            🎵 重啟等候音樂
                        </button>
                        <button class="btn" id="searchApiBtn" style="width: auto; padding: 1rem 3rem; font-size: 1.5rem;">
                            ✨ 祈求眷顧!開始搜尋
                        </button>
                    </div>
                </div>
                <!-- 卡片列表動態產生於此 -->
                <div class="students-grid" id="studentsGrid"></div>
            </div>

            <!-- 畫面 3:搜尋結果與圖表 -->
            <div id="resultsArea" class="glass-panel">
                <h1 style="text-align: center; color: var(--accent); font-size: 2.5rem; margin-top: 0; text-shadow: 0 4px 10px rgba(0,0,0,0.8);">🏆 獲得神之眷顧的贏家 🏆</h1>
                
                <div class="podium-container" id="podiumArea">
                    <!-- 頒獎台動態產生於此 -->
                </div>
                
                <div style="background: rgba(0,0,0,0.4); border-radius: 1rem; padding: 2rem; margin-top: 3rem; border: 1px solid var(--border);">
                    <h2 style="margin-top: 0; border-left: 4px solid var(--primary); padding-left: 1rem;">📊 詳細神諭結果 (位數越深排名越高)</h2>
                    <canvas id="resultChart"></canvas>
                </div>
            </div>

    <!-- QR Code 放大顯示遮罩 -->
    <div id="qrModal" style="display: none; position: fixed; top: 0; left: 0; width: 100vw; height: 100vh; background: rgba(0,0,0,0.95); z-index: 9999; flex-direction: column; justify-content: center; align-items: center; cursor: pointer; backdrop-filter: blur(5px);">
        <div id="qrModalContent" style="background: white; padding: 2rem; border-radius: 1rem; box-shadow: 0 25px 50px -12px rgba(0,0,0,0.5);"></div>
        <div style="margin-top: 1.5rem; text-align: center;">
            <p style="color: #9ca3af; font-size: 1.2rem; margin-top: 1rem;">(點選畫面任意處即可關閉)</p>
        </div>
    </div>

    <!-- Firebase 邏輯 -->
    <script type="module">
        import { initializeApp } from "https://www.gstatic.com/firebasejs/10.8.1/firebase-app.js";
        import { getFirestore, doc, collection, onSnapshot, query, orderBy, getDocs, limit, setDoc, deleteDoc, updateDoc, where } from "https://www.gstatic.com/firebasejs/10.8.1/firebase-firestore.js";

        // --- 音樂資源設定 ---
        const audioDrift = new Audio('Singularity Drift.mp3');
        audioDrift.loop = true;
        audioDrift.volume = 0;

        const audioDawn = new Audio('Sacred Dawn Ascending.mp3');
        audioDawn.loop = true;
        audioDawn.volume = 0;

        let fadeInterval = null;

        function playDrift() {
            clearInterval(fadeInterval);
            audioDrift.play().catch(e => console.warn("Audio play error:", e));
            
            fadeInterval = setInterval(() => {
                let driftVol = audioDrift.volume;
                let dawnVol = audioDawn.volume;
                let done = true;
                
                if (dawnVol > 0.05) {
                    audioDawn.volume = Math.max(0, dawnVol - 0.05);
                    done = false;
                } else {
                    audioDawn.volume = 0;
                    if(!audioDawn.paused) audioDawn.pause();
                }
                
                if (driftVol < 0.95) {
                    audioDrift.volume = Math.min(1, driftVol + 0.05);
                    done = false;
                } else {
                    audioDrift.volume = 1;
                }
                
                if (done) clearInterval(fadeInterval);
            }, 100);
        }

        function playDawn() {
            clearInterval(fadeInterval);
            audioDawn.play().catch(e => console.warn("Audio play error:", e));
            
            fadeInterval = setInterval(() => {
                let driftVol = audioDrift.volume;
                let dawnVol = audioDawn.volume;
                let done = true;
                
                if (driftVol > 0.05) {
                    audioDrift.volume = Math.max(0, driftVol - 0.05);
                    done = false;
                } else {
                    audioDrift.volume = 0;
                    if(!audioDrift.paused) audioDrift.pause();
                }
                
                if (dawnVol < 0.95) {
                    audioDawn.volume = Math.min(1, dawnVol + 0.05);
                    done = false;
                } else {
                    audioDawn.volume = 1;
                }
                
                if (done) clearInterval(fadeInterval);
            }, 100);
        }
        
        const firebaseConfig = {
            apiKey: "AIzaSyA2fxxaGAdWSR_QOH2Hm92kttGZmLDH8-w",
            authDomain: "pi-search-89a08.firebaseapp.com",
            projectId: "pi-search-89a08",
            storageBucket: "pi-search-89a08.firebasestorage.app",
            messagingSenderId: "1003005654079",
            appId: "1:1003005654079:web:636235436f432376d8748a"
        };

        let app, db;
        let isTestMode = false;

        try {
            if(firebaseConfig.apiKey === "YOUR_API_KEY") {
                isTestMode = true;
                console.warn("⚠️ Firebase 金鑰未設定,將以測試模式執行 (產生虛擬學生)。");
            } else {
                app = initializeApp(firebaseConfig);
                db = getFirestore(app);
            }
        } catch(e) { console.error("Firebase 初始化失敗:", e); }

        let currentRoom = "";
        let studentsData = []; // [{ id, name, number, paddedNumber, resultVal, status }]
        let excelStudentsData = []; // [{ id, name, number, paddedNumber, resultVal, status }]

        // UI 元件
        const startRoomBtn = document.getElementById('startRoomBtn');
        const searchApiBtn = document.getElementById('searchApiBtn');
        const setupScreen = document.getElementById('setupScreen');
        const dashboardScreen = document.getElementById('dashboardScreen');
        const studentsGrid = document.getElementById('studentsGrid');
        const resultsArea = document.getElementById('resultsArea');

        // 警告 file:// 使用者
        if (window.location.protocol === 'file:') {
            document.getElementById('systemWarning').style.display = 'block';
        }

        // --- 讀取本地端教室歷史紀錄 (LocalStorage) ---
        function loadRoomHistory() {
            try {
                const history = JSON.parse(localStorage.getItem('pi_room_history') || '[]');
                const historyDiv = document.getElementById('historyRooms');
                const chipsDiv = document.getElementById('historyChips');
                if (history.length > 0) {
                    historyDiv.style.display = 'block';
                    chipsDiv.innerHTML = '';
                    history.forEach(room => {
                        const chip = document.createElement('span');
                        chip.innerText = room;
                        chip.style.cssText = 'background: rgba(245,158,11,0.2); border: 1px solid #fbbf24; color: #fbbf24; padding: 0.5rem 1rem; border-radius: 2rem; cursor: pointer; font-weight: bold; transition: all 0.2s;';
                        chip.onmouseover = () => { chip.style.background = 'rgba(245,158,11,0.4)'; chip.style.transform = 'translateY(-2px)'; };
                        chip.onmouseout = () => { chip.style.background = 'rgba(245,158,11,0.2)'; chip.style.transform = 'none'; };
                        chip.onclick = () => {
                            document.getElementById('roomInput').value = room;
                            startRoomBtn.click();
                        };
                        chipsDiv.appendChild(chip);
                    });
                }
            } catch(e) {}
        }
        function saveRoomHistory(room) {
            try {
                let history = JSON.parse(localStorage.getItem('pi_room_history') || '[]');
                history = history.filter(r => r !== room);      // 把存在過的先拔除
                history.unshift(room);                          // 插入到最前面
                history = history.slice(0, 5);                  // 最多保留 5 個避免太壅擠
                localStorage.setItem('pi_room_history', JSON.stringify(history));
                loadRoomHistory();                              // 更新 UI
            } catch(e) {}
        }
        loadRoomHistory();

        // --- 名人堂資料設定與渲染 ---
        const defaultHof = [
            { name: "歷屆傳奇學姊", paddedNumber: "3141592", resultVal: 200000, year: 2023, isNew: false },
            { name: "歷屆幸運學長", paddedNumber: "1234567", resultVal: 150000, year: 2024, isNew: false },
            { name: "歷屆尋道者", paddedNumber: "7777777", resultVal: 50000, year: 2025, isNew: false },
            { name: "歷屆虔信者", paddedNumber: "8888888", resultVal: 12000, year: 2022, isNew: false }
        ];

        let hallOfFame = JSON.parse(localStorage.getItem('pi_hall_of_fame')) || defaultHof.map(h => ({...h}));

        // 隱藏/展開名人堂面版
        let hofVisible = false;
        document.getElementById('openHofBtn').addEventListener('click', () => {
            hofVisible = !hofVisible;
            const panel = document.getElementById('hallOfFamePanel');
            if (hofVisible) {
                panel.style.display = 'block';
                // Trigger a render sequence to rerun animations
                renderHallOfFame();
            } else {
                panel.style.display = 'none';
            }
        });

        // 靜音按鈕
        let isMuted = false;
        document.getElementById('muteBtn').addEventListener('click', (e) => {
            isMuted = !isMuted;
            audioDrift.muted = isMuted;
            audioDawn.muted = isMuted;
            e.target.innerText = isMuted ? '🔇 取消靜音' : '🔊 靜音';
            e.target.style.background = isMuted ? 'rgba(239, 68, 68, 0.8)' : 'rgba(75, 85, 99, 0.8)';
            e.target.style.borderColor = isMuted ? 'rgba(248, 113, 113, 0.5)' : 'rgba(156, 163, 175, 0.5)';
        });

        // 匯入 champ.xlsx 名單
        const importChampBtn = document.getElementById('importChampBtn');
        const champExcelFile = document.getElementById('champExcelFile');

        importChampBtn.addEventListener('click', () => {
            champExcelFile.click();
        });

        champExcelFile.addEventListener('change', async (e) => {
            const file = e.target.files[0];
            if(!file) return;

            const reader = new FileReader();
            reader.onload = async (e) => {
                const data = new Uint8Array(e.target.result);
                const workbook = XLSX.read(data, {type: 'array'});
                const firstSheet = workbook.Sheets[workbook.SheetNames[0]];
                const rows = XLSX.utils.sheet_to_json(firstSheet);
                
                let newCount = 0;
                let updateCount = 0;

                for(let row of rows) {
                    if (row['姓名'] && row['圓周率中的位置']) {
                        const cleanName = String(row['姓名']).trim();
                        const numStr = String(row['所選號碼'] || '');
                        const resultV = Number(row['圓周率中的位置']);
                        const yearV = Number(row['參加年度'] || new Date().getFullYear());

                        const existIdx = hallOfFame.findIndex(h => h.name === cleanName && h.year === yearV);
                        
                        if (existIdx > -1) {
                            // 若已存在,且有更高的分數則更新
                            if (resultV > hallOfFame[existIdx].resultVal) {
                                hallOfFame[existIdx].resultVal = resultV;
                                hallOfFame[existIdx].paddedNumber = numStr;
                                hallOfFame[existIdx].number = numStr;
                                updateCount++;
                            }
                        } else {
                            hallOfFame.push({
                                name: cleanName,
                                paddedNumber: numStr,
                                number: numStr,
                                resultVal: resultV,
                                year: yearV,
                                isNew: false 
                            });
                            newCount++;
                        }
                    }
                }

                if (newCount > 0 || updateCount > 0) {
                    renderHallOfFame();
                    alert(`成功匯入榜單!(新增 ${newCount} 筆,更新 ${updateCount} 筆資料)`);
                } else {
                    alert("檔案皆為重複資料,或格式可能有誤未能讀取。");
                }
            }
            reader.readAsArrayBuffer(file);
            champExcelFile.value = ''; // reset so it can trigger again
        });


        function renderHallOfFame() {
            const list = document.getElementById('hofPodiumArea');
            if(!list) return;
            list.innerHTML = '';
            
            // 根據深度(數字越大)進行降冪排序
            hallOfFame.sort((a, b) => b.resultVal - a.resultVal);
            // 只保留前 5 名
            hallOfFame = hallOfFame.slice(0, 5);
            localStorage.setItem('pi_hall_of_fame', JSON.stringify(hallOfFame));

            if(hallOfFame.length === 0) {
                list.innerHTML = '<p style="color: #9ca3af; text-align: center; padding: 1rem; width: 100%;">尚無名人堂紀錄</p>';
                return;
            }

            // 排列頒獎台順序: [第5名(銅), 第4名(銅), 第2名(銀), 第1名(金), 第3名(銀)] -> 視覺上讓最高的中間
            /* 
               由於要呈現5筆資料:
               1名: 金色中間 (高度 220)
               2名、3名: 銀色兩側 (高度 160)
               4名、5名: 銅色最外圍 (高度 110)
               視覺排列我們使用 order 重新組合 : 4, 2, 1, 3, 5
            */
            const visualOrder = [];
            if(hallOfFame[3]) visualOrder.push({ data: hallOfFame[3], rankClass: 'bronze-shared',  stepContent: '🥉<br>第4名', animationDelay: '0s' });
            if(hallOfFame[1]) visualOrder.push({ data: hallOfFame[1], rankClass: 'silver-shared',  stepContent: '🥈<br>第2名', animationDelay: '0.3s' });
            if(hallOfFame[0]) visualOrder.push({ data: hallOfFame[0], rankClass: 'first',          stepContent: '🥇<br>第1名', animationDelay: '0.6s' });
            if(hallOfFame[2]) visualOrder.push({ data: hallOfFame[2], rankClass: 'silver-shared',  stepContent: '🥈<br>第3名', animationDelay: '0.2s' });
            if(hallOfFame[4]) visualOrder.push({ data: hallOfFame[4], rankClass: 'bronze-shared',  stepContent: '🥉<br>第5名', animationDelay: '0s' });

            visualOrder.forEach(item => {
                const record = item.data;
                const div = document.createElement('div');
                div.className = `podium ${item.rankClass} show`;
                div.style.animationDelay = item.animationDelay;
                div.innerHTML = `
                    <div class="podium-info" style="margin-bottom: 0.8rem; transform: scale(0.9);">
                        <div style="font-size: 0.9rem; color: #d1d5db; margin-bottom: 0.2rem;">${record.year}</div>
                        <div class="podium-name">${record.name}
                            ${record.isNew ? '<br><span style="color: #ef4444; font-size: 0.8rem; font-weight: bold; animation: blink 1.5s infinite; text-shadow: 0 0 5px rgba(239,68,68,0.5);">NEW!</span>' : ''}
                        </div>
                        <div class="podium-val" style="margin-top: 0.3rem;">深度 ${record.resultVal.toLocaleString()}</div>
                    </div>
                    <div class="podium-step" style="font-size: 1.2rem; line-height: 1.2; text-align: center;">${item.stepContent}</div>
                `;
                list.appendChild(div);
            });
        }

        document.getElementById('resetHofBtn').addEventListener('click', () => {
            if(confirm('確定要清空名人堂紀錄並恢復預設嗎?')) {
                localStorage.removeItem('pi_hall_of_fame');
                hallOfFame = defaultHof.map(h => ({...h}));
                renderHallOfFame();
            }
        });

        function updateHallOfFame(validData) {
            if(!validData || validData.length === 0) return;
            
            const currentYear = new Date().getFullYear();
            let hasNewRecord = false;
            
            // 先清除上一次因為 "isNew" 呈現的狀態
            hallOfFame.forEach(h => h.isNew = false);
            
            validData.forEach(student => {
                const cleanName = student.name.replace(" (名冊)", "");
                const numStr = String(student.paddedNumber || student.number);
                
                // 檢查是否已存在
                const existingIdx = hallOfFame.findIndex(h => h.name === cleanName && (h.paddedNumber === numStr || h.number === numStr) && h.year === currentYear);
                
                if(existingIdx === -1) {
                    hallOfFame.push({
                        name: cleanName,
                        paddedNumber: numStr,
                        number: student.number,
                        resultVal: student.resultVal,
                        year: currentYear,
                        isNew: true
                    });
                    hasNewRecord = true;
                } else {
                    // 若存在,看結果是否更新?(原則上定值,但預防萬一)
                    if (student.resultVal > hallOfFame[existingIdx].resultVal) {
                         hallOfFame[existingIdx].resultVal = student.resultVal;
                         hallOfFame[existingIdx].isNew = true;
                         hasNewRecord = true;
                    }
                }
            });
            
            if (hasNewRecord || validData.length > 0) {
                renderHallOfFame();
            }
        }
        
        // 初次渲染
        renderHallOfFame();

        // --- 0. 開場動畫點擊 ---
        const introScreen = document.getElementById('introScreen');
        introScreen.addEventListener('click', () => {
            playDrift(); // 🎵 開始播放背景音樂
            introScreen.style.opacity = '0';
            setTimeout(() => {
                introScreen.style.display = 'none';
                setupScreen.style.display = 'block';
                // 稍微延遲讓 display block 作用後加上 opacity 動畫
                setTimeout(() => { setupScreen.style.opacity = '1'; }, 50);
            }, 800);
        });

        // --- 1. 建立房間 ---
        startRoomBtn.addEventListener('click', async () => {
            currentRoom = document.getElementById('roomInput').value.trim().toUpperCase();
            if(!currentRoom) { alert("請輸入教室代碼!"); return; }
            
            // 取得選擇的位數
            const selectedLength = parseInt(document.getElementById('digitSelect').value);
            
            // 防呆與檢查是否資料庫已有該教室
            startRoomBtn.disabled = true;
            startRoomBtn.innerText = "正在設定教室與產生 QRCode...";
            
            try {
                if (!isTestMode) {
                    // 初始化或更新該房間設定 (包含指定的位數,讓學生端能讀取)
                    await setDoc(doc(db, "rooms", currentRoom), {
                        roomCode: currentRoom,
                        digitLength: selectedLength,
                        isSearching: false,
                        updatedAt: new Date()
                    }, { merge: true });

                    const qCheck = query(collection(db, "rooms", currentRoom, "students"), limit(1));
                    const snapshot = await getDocs(qCheck);
                    if (!snapshot.empty) {
                        const wantToContinue = confirm(`代碼「${currentRoom}」已經存在且包含先前的信徒資料!\n\n・點擊【確定】: 返回原有儀式,延續先前的記錄。\n・點擊【取消】: 取消進入,系統將自動為您建立新代碼 (加上序號)。`);
                        if (!wantToContinue) {
                            let suffix = 1;
                            let newRoom = `${currentRoom}-${String(suffix).padStart(2, '0')}`;
                            startRoomBtn.innerText = "正在自動尋找新房間空位...";
                            // 尋找尚未被使用的房號
                            while(true) {
                                const checkNew = await getDocs(query(collection(db, "rooms", newRoom, "students"), limit(1)));
                                if (checkNew.empty) break;
                                suffix++;
                                newRoom = `${currentRoom}-${String(suffix).padStart(2, '0')}`;
                            }
                            currentRoom = newRoom;
                            
                            // 更新為全新的房間標籤
                            await setDoc(doc(db, "rooms", currentRoom), {
                                roomCode: currentRoom,
                                digitLength: selectedLength,
                                isSearching: false,
                                updatedAt: new Date()
                            }, { merge: true });
                        }
                    }
                }
            } catch(e) { console.warn("檢查教室狀態遇到錯誤:", e); }

            startRoomBtn.disabled = false;
            startRoomBtn.innerText = "進入教室";
            
            saveRoomHistory(currentRoom);

            document.getElementById('displayDigitLength').innerText = selectedLength;
            
            // 介面轉換
            setupScreen.style.display = 'none';
            dashboardScreen.style.display = 'block';
            document.getElementById('displayRoomCode').innerText = currentRoom;

            // 產生 QRCode (指向同一網路位置的 student.html)
            const currentUrl = new URL(window.location.href);
            // 確保網址是把 index.html 替換成 student.html,並加上 room 參數
            let studentPath = currentUrl.pathname.replace('index.html', 'student.html');
            if (studentPath === currentUrl.pathname) { // 如果網址隱藏了 index.html (如 Hugging Face)
                if (!studentPath.endsWith('/')) {
                    studentPath += '/';
                }
                studentPath += 'student.html';
            }
            const studentUrl = `${currentUrl.origin}${studentPath}?room=${encodeURIComponent(currentRoom)}`;
            document.getElementById('studentUrlDisplay').innerText = studentUrl;
            
            const qrBox = document.getElementById('qrcodeArea');
            qrBox.innerHTML = ''; // 清空可能舊的QRcode
            qrBox.style.display = 'block';
            document.getElementById('qrHintText').style.display = 'block';
            document.getElementById('urlPContainer').style.display = 'block';

            new QRCode(qrBox, {
                text: studentUrl, width: 150, height: 150, colorDark : "#000000", colorLight : "#ffffff", correctLevel : QRCode.CorrectLevel.L
            });
            // 加上手指游標提示與 Title
            qrBox.style.cursor = 'pointer';
            qrBox.title = '點擊方塊以全螢幕放大 QRCode';
            
            // 繪製放大的 QRCode 到遮罩中並綁定點擊開關
            document.getElementById('qrModalContent').innerHTML = '';
            new QRCode(document.getElementById("qrModalContent"), {
                text: studentUrl, width: 300, height: 300, colorDark : "#000000", colorLight : "#ffffff", correctLevel : QRCode.CorrectLevel.L
            });
            
            document.getElementById('qrcodeArea').onclick = () => {
                document.getElementById('qrModal').style.display = 'flex';
                playDrift(); // 自動重新播放等候音樂 (第二階段掃碼時)
            };
            document.getElementById('qrModal').onclick = () => document.getElementById('qrModal').style.display = 'none';
            // 手動按鈕重新播放音樂
            document.getElementById('restartMusicBtn').onclick = () => playDrift();

            // 啟動資料庫監聽
            if(isTestMode) {
                alert("目前為【測試模式】(未設定Firebase)\n系統即將自動產生三個虛擬同學加入。");
                setTimeout(() => mockJoin("王小明", "1234"), 1500);
                setTimeout(() => mockJoin("陳大華", "7777777"), 3000);
                setTimeout(() => mockJoin("林老師", "1314"), 4500);
            } else {
                startListening(currentRoom);
                // 於背景自動清理過期房間 (超過 30 天無活動)
                cleanupOldRooms();
            }
        });

        async function cleanupOldRooms() {
            try {
                // 取得 30 天前的 Timestamp
                const thirtyDaysAgo = new Date(Date.now() - 30 * 24 * 60 * 60 * 1000);
                const oldRoomsQuery = query(collection(db, "rooms"), where("updatedAt", "<", thirtyDaysAgo));
                const oldSnapshot = await getDocs(oldRoomsQuery);
                
                oldSnapshot.forEach(async (roomDoc) => {
                    try {
                        // Firebase Firestore 必須手動遞迴刪除 Subcollection
                        const studentsSnap = await getDocs(collection(db, "rooms", roomDoc.id, "students"));
                        studentsSnap.forEach(async (s) => await deleteDoc(s.ref));
                        await deleteDoc(roomDoc.ref);
                        console.log(`已清理過期房間: ${roomDoc.id}`);
                    } catch(e) {}
                });
            } catch(e) { console.warn("清除舊資料失敗", e); }
        }

        // --- Firebase 監聽器 ---
        function startListening(room) {
            const q = query(collection(db, "rooms", room, "students"), orderBy("timestamp", "asc"));
            onSnapshot(q, (snapshot) => {
                snapshot.docChanges().forEach((change) => {
                    if (change.type === "added") {
                        const data = change.doc.data();
                        addStudentChip(change.doc.id, data.name, data.number);
                    }
                    if (change.type === "removed") {
                        removeStudentChip(change.doc.id);
                    }
                });
            }, (error) => {
                console.error("監聽 Firebase 失敗:", error);
                alert("無法連線到資料庫,請檢查網路或金鑰權限設定!");
            });
        }

        // 虛擬測試用
        let mockId = 0;
        function mockJoin(name, num) { addStudentChip(`mock_${mockId++}`, name, num); }

        // --- 2. 顯示與刪除學生卡片 ---
        function removeStudentChip(id) {
            const index = studentsData.findIndex(s => s.id === id);
            if(index > -1) studentsData.splice(index, 1);
            const chip = document.getElementById(`chip_${id}`);
            if(chip) chip.remove();
            document.getElementById('studentCount').innerText = studentsData.length;
        }

        function addStudentChip(id, name, number) {
            if(studentsData.find(s => s.id === id)) return; // 防呆:重複加入
            
            studentsData.push({ id, name, number, resultVal: null });
            document.getElementById('studentCount').innerText = studentsData.length;

            const div = document.createElement('div');
            div.className = 'student-chip';
            div.id = `chip_${id}`;
            div.innerHTML = `
                <button class="delete-btn" title="踢除此學生">✕</button>
                <div class="name">${name}</div>
                <div class="number">${number}</div>
                <div class="result" id="res_${id}"></div>
            `;
            
            // 綁定剔除按鈕事件
            const delBtn = div.querySelector('.delete-btn');
            delBtn.onclick = async () => {
                if(confirm(`確定要剔除 ${name} (${number}) 嗎?`)) {
                    if(!isTestMode) await deleteDoc(doc(db, "rooms", currentRoom, "students", id));
                    else removeStudentChip(id); // 測試模式本地刪除
                }
            };

            studentsGrid.appendChild(div);
            // 當新同學加入時捲動到最底 (若已顯示結果則不捲動,避免影響觀看開獎畫面)
            if (document.getElementById('resultsArea').style.display !== 'block') {
                window.scrollTo({ top: document.body.scrollHeight, behavior: 'smooth' });
            }
        }

        // --- 3. 開始環遊圓周率 (API 批量處理) ---
        searchApiBtn.addEventListener('click', async () => {
            if(studentsData.length === 0) { alert("尚未有學生加入!"); return; }
            searchApiBtn.disabled = true;
            searchApiBtn.innerText = "⏳ 瘋狂搜尋圓周率中...";
            
            // 告訴學生端「已經起飛」
            if(!isTestMode) await updateDoc(doc(db, "rooms", currentRoom), { isSearching: true });

            // 取得老師設定的位數 (抓取之前保存在UI上的值)
            const selectedLength = parseInt(document.getElementById('displayDigitLength').innerText);
            
            // Promise.allSettled 並以批次執行,防止一口氣發送 400 條請求被瀏覽器擋下或被 Pi/Proxy API 阻斷
            const batchSize = 15; // 提升至每批 15 人同時運算,加快速度
            for (let i = 0; i < studentsData.length; i += batchSize) {
                const batch = studentsData.slice(i, i + batchSize);
                await Promise.allSettled(batch.map(student => fetchPiForStudent(student, selectedLength)));
                // 加入 100 毫秒極短延遲,讓瀏覽器能在此空檔渲染畫面 (呈現一個個翻牌的酷炫效果)
                await new Promise(r => setTimeout(r, 100)); 
            }

            searchApiBtn.disabled = false;
            searchApiBtn.innerText = "✨ 祈求眷顧!開始搜尋";
            showCombinedResults();
        });

        // ======================================
        // EXCEL 匯入與獨立搜尋邏輯
        // ======================================
        document.getElementById('importBtn').addEventListener('click', async () => {
            const file = document.getElementById('excelFile').files[0];
            if(!file) { alert("請先選擇 Excel 檔案!"); return; }
            const selectedLength = parseInt(document.getElementById('displayDigitLength').innerText);
            if(isNaN(selectedLength)) { alert("請先設定教室並進入儀式後,再匯入名冊。"); return; }

            document.getElementById('importBtn').disabled = true;
            document.getElementById('importStatus').innerText = "⏳ 正在讀取名冊...";

            const reader = new FileReader();
            reader.onload = async (e) => {
                const data = new Uint8Array(e.target.result);
                // 透過 XLSX 函式庫解析二進位陣列
                const workbook = XLSX.read(data, {type: 'array'});
                const firstSheet = workbook.Sheets[workbook.SheetNames[0]];
                const rows = XLSX.utils.sheet_to_json(firstSheet, {header: 1});
                
                excelStudentsData = [];
                for(let i=1; i<rows.length; i++) { // 從第二行開始,預設跳過表頭
                    const row = rows[i];
                    if(row.length >= 2 && row[0] !== undefined && row[1] !== undefined) {
                         let rawNumber = String(row[1]).trim().replace(/\.0$/, ''); // 防呆: Excel 可能把純數字轉浮點
                         excelStudentsData.push({
                             id: `excel_${i}`,
                             name: String(row[0]).trim(),
                             number: rawNumber,
                             resultVal: null,
                             paddedNumber: ''
                         });
                    }
                }
                
                if(excelStudentsData.length === 0) {
                    document.getElementById('importStatus').innerText = "檔案內沒有有效資料。";
                    document.getElementById('importBtn').disabled = false;
                    return;
                }

                document.getElementById('importStatus').innerText = `已讀取 ${excelStudentsData.length} 筆資料,正向神殿祈求神諭中...`;
                
                const batchSize = 15;
                for (let i = 0; i < excelStudentsData.length; i += batchSize) {
                    const batch = excelStudentsData.slice(i, i + batchSize);
                    await Promise.allSettled(batch.map(student => fetchPiForExcelStudent(student, selectedLength)));
                    await new Promise(r => setTimeout(r, 100)); 
                }

                document.getElementById('importBtn').disabled = false;
                showExcelOnlyResults();
            };
            reader.readAsArrayBuffer(file);
        });

        async function fetchPiForExcelStudent(student, selectedLength) {
            let luckyNum = String(student.number);
            if (luckyNum.length > selectedLength) {
                student.finalOutput = "字數過長不符規定";
                return;
            } 
            luckyNum = luckyNum.padStart(selectedLength, "0");
            student.paddedNumber = luckyNum;

            try {
                const targetUrl = `https://www.angio.net/newpi/piquery?q=${luckyNum}`;
                let data = null;
                try {
                    const res = await fetch(`https://corsproxy.io/?${encodeURIComponent(targetUrl)}`);
                    if(!res.ok) throw new Error("px1 fail");
                    data = await res.json();
                } catch(e1) {
                    try {
                        const res = await fetch(`https://api.codetabs.com/v1/proxy/?quest=${targetUrl}`);
                        if(!res.ok) throw new Error("px2 fail");
                        data = await res.json();
                    } catch(e2) {
                        const res = await fetch(`https://api.allorigins.win/raw?url=${encodeURIComponent(targetUrl)}`);
                        if(!res.ok) throw new Error("px3 fail");
                        data = await res.json();
                    }
                }

                if (data && data.status === 'OK' && data.r && data.r.length > 0) {
                    const resultData = data.r[0];
                    if (resultData.status === 'found') {
                        student.resultVal = resultData.p;
                        student.finalOutput = `在第 ${student.resultVal} 位`;
                    } else {
                        student.finalOutput = "未找到";
                    }
                }
            } catch (e) {
                student.finalOutput = "查詢失敗";
            }
        }

        // ======================================
        // 共用 API 查詢 (現場卡片繪圖邏輯)
        // ======================================
        async function fetchPiForStudent(student, selectedLength) {
            const chip = document.getElementById(`chip_${student.id}`);
            const resDiv = document.getElementById(`res_${student.id}`);
            
            // 變更卡片 UI 為「搜尋中」
            chip.classList.add('searching');
            resDiv.style.display = 'block';
            resDiv.innerHTML = '<div class="spinner"></div>'; // 顯示旋轉圈圈

            let luckyNum = String(student.number);
            let finalOutput = null; // 最終顯示文字
            let finalVal = null;    // 最終數字結果

            if (luckyNum.length > selectedLength) {
                finalOutput = "字數過長不符規定";
            } else {
                luckyNum = luckyNum.padStart(selectedLength, "0");
                student.paddedNumber = luckyNum;
                // 更新 UI 將數字補零呈現
                chip.querySelector('.number').innerText = luckyNum;

                try {
                    // 目標 API 網址
                    const targetUrl = `https://www.angio.net/newpi/piquery?q=${luckyNum}`;
                    let data = null;

                    // 實作多層 Proxy 備援機制,防止單一 Proxy 被阻礙或學校網路擋住
                    try {
                        // 優先使用 corsproxy (最快)
                        const res = await fetch(`https://corsproxy.io/?${encodeURIComponent(targetUrl)}`);
                        if (!res.ok) throw new Error("Proxy 1 Fail");
                        data = await res.json();
                    } catch (e1) {
                        try {
                            // 備用 1: codetabs
                            const res = await fetch(`https://api.codetabs.com/v1/proxy/?quest=${targetUrl}`);
                            if (!res.ok) throw new Error("Proxy 2 Fail");
                            data = await res.json();
                        } catch (e2) {
                            // 備用 2: allorigins
                            const res = await fetch(`https://api.allorigins.win/raw?url=${encodeURIComponent(targetUrl)}`);
                            if (!res.ok) throw new Error("Proxy 3 Fail");
                            data = await res.json();
                        }
                    }

                    if (data && data.status === 'OK' && data.r && data.r.length > 0) {
                        const resultData = data.r[0];
                        if (resultData.status === 'found') {
                            finalVal = resultData.p;
                            finalOutput = `在第 ${finalVal} 位`;
                        } else {
                            finalOutput = "在圓周率中未找到";
                        }
                    } else {
                        throw new Error("Invalid API Response");
                    }
                } catch (e) {
                    console.error("API Error", e);
                    finalOutput = "查詢失敗 (網路/CORS/無回應)";
                }
            }

            // 更新結果 (推播給 Firebase,讓該學生手機端也能看到)
            if(!isTestMode) {
                try {
                    await updateDoc(doc(db, "rooms", currentRoom, "students", student.id), {
                        resultVal: finalVal,
                        finalOutput: finalOutput,
                        status: 'done'
                    });
                } catch(e) { console.error("Update Student Error", e); }
            }

            student.resultVal = finalVal;
            chip.classList.remove('searching');
            resDiv.innerHTML = "";
            
            if (typeof finalVal === 'number') {
                chip.classList.add('done');
                resDiv.innerText = finalOutput;
            } else {
                chip.classList.add('error');
                resDiv.innerText = finalOutput;
            }
        }

        // --- 4. 繪圖與頒獎 ---
        let chartInstance = null;
        
        // 專門為 Excel 單獨查完後率先呼出圖表顯示第一名
        function showExcelOnlyResults() {
            const validData = excelStudentsData.filter(s => typeof s.resultVal === 'number');
            validData.sort((a,b) => b.resultVal - a.resultVal);
            
            resultsArea.style.display = 'block';
            resultsArea.scrollIntoView({ behavior: 'smooth' });
            
            if(validData.length > 0) {
                updateHallOfFame(validData);
                document.getElementById('importStatus').innerHTML = `✅ 預先查詢完畢!目前名冊榜首為:<strong style="color:var(--accent); font-size:1.3rem; margin-left: 0.5rem; text-shadow:0 0 5px rgba(252,211,77,0.5);">${validData[0].name}</strong> (隱藏深度 ${validData[0].resultVal})`;
                drawPodium(validData);

                // 發射紙花特效
                setTimeout(() => {
                    confetti({ particleCount: 200, spread: 100, origin: { y: 0.6 }, colors: ['#fde047', '#34d399', '#60a5fa'] });
                }, 500);

                // 將 Excel 資料預先繪製長條圖
                const ctx = document.getElementById('resultChart').getContext('2d');
                if (chartInstance) chartInstance.destroy();
                chartInstance = new Chart(ctx, {
                    type: 'bar',
                    data: {
                        labels: validData.map(s => `${s.name} (${s.paddedNumber})`),
                        datasets: [{
                            label: '神之軌跡隱藏深度 (名冊)',
                            data: validData.map(s => s.resultVal),
                            backgroundColor: 'rgba(212, 175, 55, 0.7)',
                            borderColor: '#fcd34d',
                            borderWidth: 2,
                            borderRadius: 6
                        }]
                    },
                    options: {
                        responsive: true,
                        plugins: { legend: { labels: { color: '#f9fafb', font: {size: 14} } } },
                        scales: {
                            y: { ticks: { color: '#9ca3af' }, grid: { color: 'rgba(255,255,255,0.05)' } },
                            x: { ticks: { color: '#d1d5db', maxRotation: 45, minRotation: 45 }, grid: { display: false } }
                        }
                    }
                });
            } else {
                document.getElementById('importStatus').innerHTML = `✅ 查詢完畢!可惜匯入名冊中無人獲得神之眷顧。`;
                drawPodium([]);
                if(chartInstance) { chartInstance.destroy(); chartInstance = null; }
            }
        }

        // 現場查詢後合併邏輯顯示結果
        function showCombinedResults() {
            resultsArea.style.display = 'block';
            resultsArea.scrollIntoView({ behavior: 'smooth' });

            const isMerge = document.getElementById('mergeDataCheck').checked;
            
            // 基礎池:現場有找到的學生
            let finalPool = studentsData.filter(s => typeof s.resultVal === 'number');

            // 如果勾選合併 且 Excel內確實有資料
            if (isMerge && excelStudentsData.length > 0) {
                const excelValid = excelStudentsData.filter(s => typeof s.resultVal === 'number');
                // 為名冊學生的名字上個 (匯入) 標記以免與現場撞名
                const wrappedExcel = excelValid.map(s => ({
                    ...s,
                    name: s.name + " (名冊)"
                }));
                // 陣列合併
                finalPool = finalPool.concat(wrappedExcel);
            }

            // 按數字大小從大排到小 (越深的人排名越高)
            finalPool.sort((a, b) => b.resultVal - a.resultVal);

            // 排列名次並將最終總排名推播至 Firebase 讓學生端更新
            let currentRank = 1;
            for (let i = 0; i < finalPool.length; i++) {
                // 若分數與前一名不同,名次改為目前人數順序 (並列名次處理)
                if (i > 0 && finalPool[i].resultVal < finalPool[i-1].resultVal) {
                    currentRank = i + 1;
                }
                finalPool[i].rank = currentRank;

                // 若非 Excel 資料 (即現場真實手機連線資料),回寫 Firebase
                if (!finalPool[i].id.startsWith('excel_') && !isTestMode) {
                    try {
                        updateDoc(doc(db, "rooms", currentRoom, "students", finalPool[i].id), {
                            rank: currentRank
                        });
                    } catch(e) {}
                }
            }

            updateHallOfFame(finalPool);
            drawPodium(finalPool);

            if(finalPool.length > 0) {
                // 發射紙花特效 (Premium UI 微互動) 🎉
                setTimeout(() => {
                    confetti({ particleCount: 200, spread: 100, origin: { y: 0.6 }, colors: ['#fde047', '#34d399', '#60a5fa'] });
                }, 500);
                
                // 長條圖繪製
                const ctx = document.getElementById('resultChart').getContext('2d');
                if (chartInstance) chartInstance.destroy();
                chartInstance = new Chart(ctx, {
                    type: 'bar',
                    data: {
                        labels: finalPool.map(s => `${s.name} (${s.paddedNumber})`),
                        datasets: [{
                            label: '神之軌跡隱藏深度',
                            data: finalPool.map(s => s.resultVal),
                            backgroundColor: 'rgba(212, 175, 55, 0.7)',
                            borderColor: '#fcd34d',
                            borderWidth: 2,
                            borderRadius: 6
                        }]
                    },
                    options: {
                        responsive: true,
                        plugins: { legend: { labels: { color: '#f9fafb', font: {size: 14} } } },
                        scales: {
                            y: { ticks: { color: '#9ca3af' }, grid: { color: 'rgba(255,255,255,0.05)' } },
                            x: { ticks: { color: '#d1d5db', maxRotation: 45, minRotation: 45 }, grid: { display: false } }
                        }
                    }
                });
            } else {
               if(chartInstance) { chartInstance.destroy(); chartInstance = null; }
            }
        }

        function drawPodium(validData) {
            playDawn(); // 🎵 結果出爐,切換為 Sacred Dawn Ascending.mp3 音樂
            const podiumArea = document.getElementById('podiumArea');
            podiumArea.innerHTML = "";

            if(validData.length === 0) {
                podiumArea.innerHTML = "<h3 style='color:#fbbf24'>沒有人找到號碼,無頒獎台 QQ</h3>"; return;
            }

            const top3 = validData.slice(0, 3);
            
            // 順序: 2 -> 1 -> 3
            const order = [];
            if(top3.length >= 2) order.push({ data: top3[1], rank: 'second', num: 2 });
            if(top3.length >= 1) order.push({ data: top3[0], rank: 'first', num: 1 });
            if(top3.length >= 3) order.push({ data: top3[2], rank: 'third', num: 3 });

            order.forEach(item => {
                const div = document.createElement('div');
                div.className = `podium ${item.rank} show`;
                div.innerHTML = `
                    <div class="podium-info">
                        <div class="podium-name">${item.data.name}</div>
                        <div class="podium-val">深度 ${item.data.resultVal}</div>
                    </div>
                    <div class="podium-step">${item.num}</div>
                `;
                podiumArea.appendChild(div);
            });
        }
    </script>

    <!-- 頁尾宣告資訊區 -->
    <style>
        .footer-credit {
            text-align: right;
            color: rgba(255, 255, 255, 0.3);
            font-size: 0.85rem;
            padding: 2rem 0;
            margin-top: 2rem;
            border-top: 1px solid rgba(255, 255, 255, 0.1);
            transition: color 0.3s;
        }
        .footer-credit:hover {
            color: rgba(255, 255, 255, 0.7);
        }
        .footer-link {
            color: inherit;
            text-decoration: underline;
            transition: color 0.2s;
        }
        .footer-link:hover {
            color: #fcd34d;
        }
    </style>
    <div class="footer-credit">
        <div style="margin-bottom: 4px;">
            程式設計:新竹縣精華國中 藍星宇
        </div>
        <div style="margin-bottom: 4px;">
            教育社群:<a href="https://www.facebook.com/groups/1554372228718393" target="_blank" class="footer-link">萬物皆數</a>
        </div>
        <div>
            Pi-search 使用資料:<a href="https://www.angio.net/pi/" target="_blank" class="footer-link">angio.net/pi/</a>
        </div>
    </div>
</div>
</body>
</html>