File size: 34,013 Bytes
48478e0
b3be9b0
48478e0
 
b3be9b0
 
8530bdb
b3be9b0
48478e0
6ef0f4d
b3be9b0
 
 
 
8530bdb
 
48478e0
b3be9b0
 
 
48478e0
9bf7433
 
 
48478e0
 
 
b3be9b0
9bf7433
b3be9b0
 
 
 
48478e0
 
b3be9b0
 
 
 
 
48478e0
 
b3be9b0
 
 
 
 
48478e0
b3be9b0
 
48478e0
 
b3be9b0
 
 
 
 
48478e0
 
8530bdb
48478e0
b3be9b0
 
 
 
9bf7433
b3be9b0
 
 
 
 
9bf7433
b3be9b0
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
48478e0
386f330
b3be9b0
 
 
 
48478e0
b3be9b0
 
 
 
48478e0
 
b3be9b0
 
 
 
 
 
 
 
 
 
 
9bf7433
b3be9b0
 
 
48478e0
8530bdb
b3be9b0
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
9bf7433
b3be9b0
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
9bf7433
b3be9b0
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
9bf7433
b3be9b0
 
 
48478e0
 
b3be9b0
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
48478e0
c24fb91
 
 
 
 
 
 
 
 
 
28f7baa
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
48478e0
 
 
9bf7433
b3be9b0
 
 
 
 
 
 
ff96d22
b3be9b0
 
 
 
 
 
6ef0f4d
4f84572
 
b3be9b0
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
c24fb91
28f7baa
 
 
 
 
b3be9b0
 
48478e0
 
 
9bf7433
b3be9b0
 
53024ba
 
 
 
b3be9b0
 
 
 
 
 
 
6ef0f4d
b3be9b0
 
 
 
 
c24fb91
b3be9b0
53024ba
b3be9b0
 
 
 
53024ba
b3be9b0
 
c24fb91
53024ba
b3be9b0
 
53024ba
 
 
b3be9b0
53024ba
 
 
b3be9b0
53024ba
b3be9b0
53024ba
 
 
 
 
 
 
 
 
 
6f2b20f
 
53024ba
 
 
6f2b20f
 
 
 
53024ba
 
 
 
 
 
 
 
 
b3be9b0
53024ba
 
b3be9b0
 
 
6ef0f4d
9bf7433
b3be9b0
 
 
9bf7433
 
 
b3be9b0
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
9bf7433
8530bdb
b3be9b0
9bf7433
b3be9b0
1ae5008
b3be9b0
 
9bf7433
b3be9b0
 
9bf7433
b3be9b0
9bf7433
b3be9b0
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
48478e0
8530bdb
28f7baa
6ef0f4d
 
 
6f2b20f
 
6ef0f4d
 
6f2b20f
 
6ef0f4d
 
6f2b20f
 
 
 
 
 
 
 
6ef0f4d
 
 
 
 
e5beffa
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
c24fb91
b3be9b0
 
 
 
 
 
 
 
53024ba
 
b3be9b0
 
 
6ef0f4d
 
b3be9b0
 
 
 
 
 
 
 
 
c24fb91
28f7baa
 
b3be9b0
 
 
 
 
 
 
 
8530bdb
b3be9b0
c24fb91
53024ba
c24fb91
 
6ef0f4d
6f2b20f
20bc739
78d26b9
28f7baa
 
 
 
028328e
e5beffa
63f4126
e5beffa
63f4126
c24fb91
63f4126
28f7baa
63f4126
e5beffa
63f4126
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
c24fb91
028328e
 
e5beffa
6ef0f4d
 
e5beffa
6ef0f4d
 
 
 
 
 
 
 
 
 
b3be9b0
 
76549b4
b3be9b0
 
 
 
 
 
6ef0f4d
b3be9b0
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
9bf7433
b3be9b0
 
 
 
 
 
 
 
 
 
 
 
20bc739
b3be9b0
6ef0f4d
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
408f16a
 
 
1ae5008
408f16a
 
 
 
e5beffa
 
 
 
 
 
 
1ae5008
e5beffa
 
 
 
 
 
 
 
 
 
 
28f7baa
76549b4
e5beffa
 
 
28f7baa
76549b4
 
 
408f16a
 
76549b4
408f16a
 
 
78d26b9
28f7baa
78d26b9
28f7baa
 
e5beffa
28f7baa
408f16a
 
6ef0f4d
 
 
 
 
 
 
 
b3be9b0
 
6ef0f4d
b3be9b0
 
 
 
 
 
 
 
 
48478e0
6ef0f4d
b3be9b0
 
 
 
 
9bf7433
48478e0
 
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
<!DOCTYPE html>
<html lang="zh-TW">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>單牌占卜法 - One Card Tarot</title>
    <script src="https://unpkg.com/mqtt/dist/mqtt.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
    <style>
        /* --- 樣式保持不變 --- */
        * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
        }

        body {
            font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
            background: linear-gradient(135deg, #1e3c72 0%, #2a5298 100%);
            min-height: 100vh;
            display: flex;
            justify-content: center;
            align-items: center;
            padding: 20px;
        }

        .container {
            background: rgba(255, 255, 255, 0.95);
            border-radius: 20px;
            padding: 40px;
            max-width: 800px;
            width: 100%;
            box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3);
        }

        h1 {
            text-align: center;
            color: #2a5298;
            margin-bottom: 10px;
            font-size: 2.5em;
        }

        .subtitle {
            text-align: center;
            color: #666;
            margin-bottom: 30px;
        }

        .input-section {
            margin-bottom: 30px;
        }

        label {
            display: block;
            margin-bottom: 10px;
            color: #333;
            font-weight: 600;
        }

        textarea {
            width: 100%;
            padding: 15px;
            border: 2px solid #ddd;
            border-radius: 10px;
            font-size: 16px;
            resize: vertical;
            min-height: 100px;
            transition: border-color 0.3s;
        }

        textarea:focus {
            outline: none;
            border-color: #2a5298;
        }

        .btn {
            width: 100%;
            padding: 15px;
            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
            color: white;
            border: none;
            border-radius: 10px;
            font-size: 18px;
            font-weight: 600;
            cursor: pointer;
            transition: transform 0.2s, box-shadow 0.2s;
        }

        .btn:hover {
            transform: translateY(-2px);
            box-shadow: 0 10px 20px rgba(102, 126, 234, 0.4);
        }

        .btn:active {
            transform: translateY(0);
        }

        .btn:disabled {
            background: #ccc;
            cursor: not-allowed;
            transform: none;
        }

        .result-section {
            margin-top: 30px;
            display: none;
        }

        .result-section.show {
            display: block;
            animation: fadeIn 0.5s;
        }

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

        .card-display {
            display: flex;
            flex-direction: column;
            align-items: center;
            margin-bottom: 30px;
        }

        .card-container {
            position: relative;
            width: 300px;
            height: 500px;
            margin-bottom: 20px;
        }

        .card {
            width: 100%;
            height: 100%;
            border-radius: 15px;
            box-shadow: 0 10px 30px rgba(0, 0, 0, 0.3);
            overflow: hidden;
            transition: transform 0.6s;
            transform-style: preserve-3d;
            background: #000;
        }

        .card.reversed {
            transform: rotate(180deg);
        }

        .card img {
            width: 100%;
            height: 100%;
            object-fit: cover;
        }

        .card-info {
            text-align: center;
            padding: 20px;
            background: #f8f9fa;
            border-radius: 10px;
            width: 100%;
        }

        .card-name {
            font-size: 24px;
            color: #2a5298;
            font-weight: bold;
            margin-bottom: 10px;
        }

        .card-position {
            font-size: 18px;
            color: #666;
            font-style: italic;
        }

        .interpretation {
            background: #f8f9fa;
            padding: 25px;
            border-radius: 10px;
            border-left: 5px solid #667eea;
        }

        .interpretation h3 {
            color: #2a5298;
            margin-bottom: 15px;
            font-size: 20px;
        }

        /* Markdown 渲染樣式 */
        .markdown-body {
            line-height: 1.8;
            color: #333;
            word-wrap: break-word;
        }

        .markdown-body h1,
        .markdown-body h2,
        .markdown-body h3,
        .markdown-body h4,
        .markdown-body h5,
        .markdown-body h6 {
            margin-top: 20px;
            margin-bottom: 12px;
            font-weight: 600;
            line-height: 1.4;
            color: #2a5298;
        }

        .markdown-body h1 { font-size: 1.8em; border-bottom: 2px solid #eee; padding-bottom: 8px; }
        .markdown-body h2 { font-size: 1.5em; border-bottom: 1px solid #eee; padding-bottom: 6px; }
        .markdown-body h3 { font-size: 1.3em; }
        .markdown-body h4 { font-size: 1.15em; }

        .markdown-body p {
            margin-bottom: 12px;
        }

        .markdown-body ul,
        .markdown-body ol {
            margin-bottom: 12px;
            padding-left: 2em;
        }

        .markdown-body li {
            margin-bottom: 6px;
        }

        .markdown-body strong {
            color: #2a5298;
            font-weight: 700;
        }

        .markdown-body em {
            color: #555;
            font-style: italic;
        }

        .markdown-body code {
            background: #f0f0f0;
            padding: 2px 6px;
            border-radius: 4px;
            font-family: 'Courier New', Courier, monospace;
            font-size: 0.9em;
            color: #c7254e;
        }

        .markdown-body pre {
            background: #282c34;
            color: #abb2bf;
            padding: 15px;
            border-radius: 8px;
            overflow-x: auto;
            margin-bottom: 15px;
        }

        .markdown-body pre code {
            background: transparent;
            color: inherit;
            padding: 0;
            font-size: 0.9em;
        }

        .markdown-body blockquote {
            border-left: 4px solid #667eea;
            padding: 10px 15px;
            margin: 15px 0;
            background: #f9f9ff;
            color: #555;
            border-radius: 0 8px 8px 0;
        }

        .markdown-body a {
            color: #667eea;
            text-decoration: none;
            border-bottom: 1px dashed #667eea;
        }

        .markdown-body a:hover {
            color: #764ba2;
            border-bottom-style: solid;
        }

        .markdown-body hr {
            border: none;
            border-top: 1px solid #ddd;
            margin: 20px 0;
        }

        .markdown-body table {
            border-collapse: collapse;
            width: 100%;
            margin-bottom: 15px;
        }

        .markdown-body table th,
        .markdown-body table td {
            border: 1px solid #ddd;
            padding: 8px 12px;
            text-align: left;
        }

        .markdown-body table th {
            background: #f0f0f0;
            font-weight: 600;
        }

        .markdown-body table tr:nth-child(even) {
            background: #f9f9f9;
        }

        .section-divider {
            border: none;
            border-top: 1px dashed #ccc;
            margin: 20px 0;
        }

        .reasoning-section {
            background: #fff9e6;
            padding: 15px;
            border-radius: 8px;
            border-left: 4px solid #ffc107;
            margin-bottom: 20px;
        }

        .reasoning-section summary {
            cursor: pointer;
            font-weight: 600;
            color: #856404;
            user-select: none;
        }

        .reasoning-section[open] summary {
            margin-bottom: 10px;
        }

        .loading {
            text-align: center;
            padding: 40px;
            display: none;
        }

        .loading.show {
            display: block;
        }

        .spinner {
            border: 4px solid #f3f3f3;
            border-top: 4px solid #667eea;
            border-radius: 50%;
            width: 50px;
            height: 50px;
            animation: spin 1s linear infinite;
            margin: 0 auto 20px;
        }

        @keyframes spin {
            0% { transform: rotate(0deg); }
            100% { transform: rotate(360deg); }
        }

        .error {
            background: #fee;
            color: #c33;
            padding: 15px;
            border-radius: 10px;
            margin-top: 20px;
            display: none;
        }

        .error.show {
            display: block;
        }

        .status {
            text-align: center;
            padding: 10px;
            margin-bottom: 20px;
            border-radius: 5px;
            display: none;
        }

        .status.show {
            display: block;
        }

        .status.connecting {
            background: #fff3cd;
            color: #856404;
        }

        .status.connected {
            background: #d4edda;
            color: #155724;
        }

        .status.error {
            background: #f8d7da;
            color: #721c24;
        }

        .img-debug {
            font-size: 12px;
            color: #999;
            margin-top: 5px;
            word-break: break-all;
        }

        /* 牌義標題樣式 */
        .card-title {
            font-size: 28px;
            font-weight: bold;
            color: #2a5298;
            margin-bottom: 20px;
            padding-bottom: 10px;
            border-bottom: 2px solid #667eea;
        }

        /* 牌義原文區塊樣式 */
        .meaning-original {
            background: #f0f4ff;
            padding: 15px 20px;
            border-radius: 8px;
            border-left: 4px solid #667eea;
            margin-bottom: 20px;
            font-size: 14px;
            color: #444;
            line-height: 1.6;
        }

        .meaning-original strong {
            color: #2a5298;
        }

        .meaning-original .label {
            display: inline-block;
            background: #667eea;
            color: white;
            padding: 2px 10px;
            border-radius: 4px;
            font-size: 12px;
            margin-bottom: 6px;
        }
    </style>
</head>
<body>
    <div class="container">
        <h1>🔮 單牌占卜法</h1>
        <p class="subtitle">One Card Tarot Spread</p>

        <div id="status" class="status">準備連接...</div>

        <div class="input-section">
            <label for="question">請輸入您的問題:</label>
            <textarea id="question" placeholder="例如:我最近工作運勢如何?"></textarea>
        </div>

        <button class="btn" id="drawBtn" onclick="drawCard()">抽取塔羅牌</button>

        <div class="loading" id="loading">
            <div class="spinner"></div>
            <p>正在抽取塔羅牌並進行解讀...</p>
        </div>

        <div class="error" id="error"></div>

        <div class="result-section" id="result">
            <div class="card-display">
                <div class="card-container">
                    <div class="card" id="cardImage">
                        <img src="" alt="Tarot Card" id="cardImg">
                    </div>
                </div>
                <div class="card-info">
                    <div class="card-name" id="cardName"></div>
                    <div class="card-position" id="cardPosition"></div>
                    <div class="img-debug" id="imgDebug"></div>
                </div>
            </div>

            <div class="interpretation">
                <h3>📖 牌義解讀</h3>
                <div id="reasoningContainer"></div>
                <div id="cardTitle" class="card-title"></div>
                <!-- 牌義原文區塊 -->
                <div id="meaningOriginal" class="meaning-original" style="display:none;">
                    <div class="label">📜 card.json 牌義原文</div>
                    <div id="meaningOriginalText"></div>
                </div>
                <div class="markdown-body" id="interpretation"></div>
            </div>
        </div>
    </div>

    <script>
        // 配置 marked.js
        marked.setOptions({
            breaks: true,
            gfm: true,
            headerIds: true,
            mangle: false
        });

        // 配置
        const UNIQUE_PREFIX = 'my_secure_hy3_test_8f7a';
        const MQTT_BROKER = 'wss://broker.emqx.io:8084/mqtt';
        const TAROT_IMAGES_URL = 'https://raw.githubusercontent.com/metabismuth/tarot-json/refs/heads/master/tarot-images.json';
        const CARDS_BASE_URL = 'https://raw.githubusercontent.com/metabismuth/tarot-json/refs/heads/master/cards/';
        const CARD_MEANINGS_URL = './card.json';

        // 全局變量
        let client = null;
        let clientId = null;
        let tarotCards = [];
        let cardMeanings = {};
        let isConnected = false;
        let isDataLoaded = false;

        // 初始化
        window.onload = function() {
            initMQTT();
            loadAllData();
        };

        // 加載所有數據
        async function loadAllData() {
            try {
                console.log('正在加載塔羅牌圖片數據...');
                const imageResponse = await fetch(TAROT_IMAGES_URL);
                if (!imageResponse.ok) {
                    throw new Error(`HTTP error! status: ${imageResponse.status}`);
                }
                const imageData = await imageResponse.json();
                if (imageData.cards && Array.isArray(imageData.cards)) {
                    tarotCards = imageData.cards;
                } else {
                    throw new Error('圖片數據格式不正確');
                }
                console.log('圖片數據加載成功,共', tarotCards.length, '張牌');

                console.log('正在加載牌義數據...');
                const meaningResponse = await fetch(CARD_MEANINGS_URL);
                if (!meaningResponse.ok) {
                    throw new Error(`HTTP error! status: ${meaningResponse.status}`);
                }
                const meaningData = await meaningResponse.json();
                if (meaningData.cards && Array.isArray(meaningData.cards)) {
                    meaningData.cards.forEach(card => {
                        const cardName = card.name.trim();
                        cardMeanings[cardName] = {
                            meaning_up: card.meaning_up || '(無正位解析)',
                            meaning_rev: card.meaning_rev || '(無逆位解析)'
                        };
                        if (cardName.startsWith('The ')) {
                            const shortName = cardName.replace('The ', '');
                            cardMeanings[shortName] = cardMeanings[cardName];
                        }
                    });
                    console.log('牌義數據加載成功,共', Object.keys(cardMeanings).length, '筆');
                } else {
                    throw new Error('牌義數據格式不正確');
                }

                isDataLoaded = true;
                console.log('所有數據加載完成!');

            } catch (error) {
                console.error('數據加載失敗:', error);
                showError('數據加載失敗,請確認 card.json 檔案是否存在:' + error.message);
            }
        }

        // 初始化 MQTT 連接
        function initMQTT() {
            clientId = 'client_' + Date.now() + '_' + Math.random().toString(36).substr(2, 9);
            
            const options = {
                clientId: clientId,
                clean: true,
                connectTimeout: 4000,
                reconnectPeriod: 1000,
            };

            updateStatus('正在連接占卜伺服器...', 'connecting');

            try {
                client = mqtt.connect(MQTT_BROKER, options);

                client.on('connect', function() {
                    console.log('MQTT 連接成功');
                    isConnected = true;
                    updateStatus('已連接占卜伺服器', 'connected');
                    
                    const subscribeTopic = `${UNIQUE_PREFIX}/hy3/response/${clientId}`;
                    client.subscribe(subscribeTopic, function(err) {
                        if (!err) {
                            console.log('訂閱主題成功:', subscribeTopic);
                        } else {
                            console.error('訂閱失敗:', err);
                        }
                    });
                });

                client.on('message', function(topic, message) {
                    try {
                        const data = JSON.parse(message.toString());
                        console.log('收到完整回應:', JSON.stringify(data, null, 2));
                        if (data.client_id === clientId) {
                            handleLLMResponse(data);
                        }
                    } catch (error) {
                        console.error('解析消息失敗:', error);
                    }
                });

                client.on('error', function(err) {
                    console.error('MQTT 錯誤:', err);
                    updateStatus('連接錯誤: ' + err.message, 'error');
                    isConnected = false;
                });

                client.on('close', function() {
                    console.log('MQTT 連接關閉');
                    isConnected = false;
                    updateStatus('連接已關閉', 'error');
                });

            } catch (error) {
                console.error('MQTT 初始化失敗:', error);
                updateStatus('初始化失敗: ' + error.message, 'error');
            }
        }

        function updateStatus(message, type) {
            const statusEl = document.getElementById('status');
            statusEl.textContent = message;
            statusEl.className = 'status show ' + type;
        }

        // 獲取牌義
        function getCardMeaning(cardName, isUpright) {
            const positionKey = isUpright ? 'meaning_up' : 'meaning_rev';
            
            let meaning = cardMeanings[cardName]?.[positionKey];
            if (meaning) return meaning;
            
            const withoutThe = cardName.replace(/^The /, '');
            meaning = cardMeanings[withoutThe]?.[positionKey];
            if (meaning) return meaning;
            
            const withThe = 'The ' + cardName;
            meaning = cardMeanings[withThe]?.[positionKey];
            if (meaning) return meaning;
            
            const lowerName = cardName.toLowerCase();
            for (const key of Object.keys(cardMeanings)) {
                if (key.toLowerCase() === lowerName || key.toLowerCase() === 'the ' + lowerName) {
                    return cardMeanings[key][positionKey];
                }
            }
            
            return '(未找到牌義)';
        }

        // --- 過濾違規關鍵詞(防止 LLM 越界) ---
        function filterForbiddenTerms(text) {
            const forbiddenTerms = [
                '甲狀腺', '自律神經', '纖維肌痛症', 'CBT', 'EMDR', 
                '中醫', '針灸', '診斷', '處方', '用藥',
                'fibromyalgia', 'cognitive behavioral therapy', 'eye movement desensitization',
                '慢性病初期', '心理疾患', '創傷後壓力症候群', 'PTSD'
            ];
            
            let filtered = text;
            forbiddenTerms.forEach(term => {
                if (filtered.includes(term)) {
                    filtered = filtered.replace(new RegExp(term, 'gi'), '【相關症狀】');
                }
            });
            
            return filtered;
        }

        // 抽牌
        async function drawCard() {
            const question = document.getElementById('question').value.trim();
            
            if (!question) {
                alert('請先輸入您的問題');
                return;
            }

            if (!isDataLoaded) {
                alert('牌義數據尚未加載完成,請稍後再試');
                return;
            }

            if (!isConnected) {
                alert('尚未連接到占卜伺服器,請等待連接完成後再試');
                return;
            }

            document.getElementById('loading').classList.add('show');
            document.getElementById('result').classList.remove('show');
            document.getElementById('error').classList.remove('show');
            document.getElementById('drawBtn').disabled = true;
            document.getElementById('reasoningContainer').innerHTML = '';
            document.getElementById('interpretation').innerHTML = '';
            document.getElementById('cardTitle').innerHTML = '';
            document.getElementById('meaningOriginal').style.display = 'none';
            document.getElementById('meaningOriginalText').innerHTML = '';

            try {
                const randomIndex = Math.floor(Math.random() * tarotCards.length);
                const selectedCard = tarotCards[randomIndex];
                const isUpright = Math.random() > 0.5;
                
                console.log('選中的牌:', selectedCard);
                displayCard(selectedCard, isUpright);

                const position = isUpright ? '正位' : '逆位';
                const positionEmoji = isUpright ? '🌿' : '🔄';
                
                document.getElementById('cardTitle').textContent = `${positionEmoji} ${selectedCard.name} ${position}`;
                
                const meaningText = getCardMeaning(selectedCard.name, isUpright);
                console.log('牌義內容:', meaningText);

                // 顯示牌義原文(供使用者對照)
                const meaningOriginalEl = document.getElementById('meaningOriginal');
                const meaningOriginalTextEl = document.getElementById('meaningOriginalText');
                meaningOriginalTextEl.textContent = meaningText;
                meaningOriginalEl.style.display = 'block';

                // --- 最終版 Prompt:結合問題與牌義,使用 Mistral 模型 ---
                const systemPrompt = `你是一位資深的塔羅占卜師,擁有20年的實務經驗,專注於將塔羅智慧轉化為具體、溫暖且實用的生活指引。

---

【你的任務】

使用者提出了一個具體問題,並抽到了一張塔羅牌。請根據牌義,為使用者提供**完整、深刻、有溫度**的解析,幫助他們獲得清晰的視角與可行的方向。

---

【核心原則】

1. **忠於牌義,但靈活應用**:解析內容必須源自 card.json 的牌義原文,但要將牌義轉化為針對使用者問題的具體觀察與建議。不要只是翻譯牌義,而是要「活用」它。

2. **保持開放性**:塔羅占卜是指引,不是預言。避免使用「一定會」、「絕對是」、「注定」等武斷用語。改用「可能意味著」、「這暗示」、「可以考慮」等開放性表達。

3. **不做醫療診斷**:不得指定任何具體疾病名稱、治療方式或藥物建議。若涉及健康問題,請溫和提醒使用者諮詢專業醫師。

4. **不指控他人**:不得將牌義解讀為「他人陰謀」、「刻意隱瞞」或「家庭衝突」等外部指控。聚焦於當事人自身的能量與選擇。

5. **語氣溫暖、同理**:使用者可能正在經歷困惑或焦慮,請用溫和、支持的語氣回應,避免恐嚇或過度嚴肅的語言。

---

【輸出格式要求】

請嚴格按照以下結構輸出,確保解析**完整、有深度、具體可行**:

---

**🔍 核心訊息:牌義的精華提煉**

(約 150-200 字)
- 用 2-3 句話提煉這張牌的核心意涵
- 直接連結到使用者的問題情境
- 點出這個牌義對當事人最重要的提示是什麼

---

**💭 心理與情緒層面:內在世界的探索**

(約 200-250 字)
- 當事人可能正在經歷什麼樣的內在狀態(焦慮、期待、恐懼、矛盾等)?
- 牌義如何呼應或映照這些情緒?
- 這張牌邀請當事人如何看待自己的情緒?

---

**🌱 行動建議:具體可行的方向**

(約 250-300 字)
- 給出 3-4 個具體、可執行的建議
- 每個建議都要有「為什麼這樣做」的牌義依據
- 避免空泛的建議(如「放鬆就好」),要具體(如「每天留 15 分鐘做一個小決定,練習主動感」)

---

**💎 結語:一句溫暖的收尾**

(約 50-80 字)
- 用一句有力量、有溫度的話語總結
- 給予當事人信心與希望
- 不超過 3 句話

---

【格式規範】

- 請使用繁體中文輸出
- 每個面向的標題請使用粗體(**)
- 總字數請控制在 700-1000 字之間
- 直接輸出解析內容,不要加「好的,以下是您的解析」之類的開場白
- 不要在結尾加「如果您需要進一步討論...」等延伸邀請

---

【範例參考】(僅供語氣參考,請勿複製內容)

使用者的問題是:「我最近在工作上感到停滯,不知道該不該換工作?」

核心訊息可以這樣開頭:「這張牌告訴你,停滯不是終點,而是轉折前的寧靜。你感受到的瓶頸,其實是內在正在重新校準方向的訊號...」

心理層面可以這樣展開:「你可能正在兩種聲音之間拉扯——一邊是對穩定的渴望,另一邊是對成長的嚮往。這種矛盾不是弱點,而是你內心正在成長的證明...」

行動建議可以這樣具體化:「第一,為自己設定一個 90 天的觀察期,記錄每天工作中讓你感到充實與空虛的時刻...」

結語可以這樣收尾:「你不需要今天就知道答案,但你可以從今天開始,為自己創造更多選擇的空間。」

---

【現在請開始】

使用者問題:${question}

抽到的牌:${selectedCard.name}${position}

牌義原文:${meaningText}

請根據以上資訊,為這位使用者提供完整、深刻的塔羅解析。`;

                const requestData = {
                    client_id: clientId,
                    message: `【牌名】${selectedCard.name}${position})\n\n【牌義】${meaningText}\n\n【問題】${question}\n\n${systemPrompt}`
                };

                console.log('發送請求給 Mistral 模型(結合問題模式)');

                const publishTopic = `${UNIQUE_PREFIX}/hy3/request`;
                client.publish(publishTopic, JSON.stringify(requestData), { qos: 1 }, function(err) {
                    if (err) {
                        console.error('發布失敗:', err);
                        showError('發送請求失敗,請稍後重試');
                        document.getElementById('loading').classList.remove('show');
                        document.getElementById('drawBtn').disabled = false;
                    }
                });

            } catch (error) {
                console.error('抽牌過程發生錯誤:', error);
                showError('抽牌過程發生錯誤: ' + error.message);
                document.getElementById('loading').classList.remove('show');
                document.getElementById('drawBtn').disabled = false;
            }
        }

        // 顯示牌面
        function displayCard(card, isUpright) {
            const cardImg = document.getElementById('cardImg');
            const cardName = document.getElementById('cardName');
            const cardPosition = document.getElementById('cardPosition');
            const cardElement = document.getElementById('cardImage');
            const imgDebug = document.getElementById('imgDebug');

            const imgFileName = card.img;
            const imgUrl = CARDS_BASE_URL + imgFileName;
            
            cardImg.src = imgUrl;
            cardImg.alt = card.name;
            
            cardImg.onerror = function() {
                console.error('圖片加載失敗:', imgUrl);
                showError('圖片加載失敗,請檢查網絡連接');
            };
            
            cardName.textContent = card.name;
            cardPosition.textContent = isUpright ? '✨ 正位 (Upright)' : '🔄 逆位 (Reversed)';
            imgDebug.textContent = `Image: ${imgFileName}`;
            
            if (!isUpright) {
                cardElement.classList.add('reversed');
            } else {
                cardElement.classList.remove('reversed');
            }
        }

        // 處理 LLM 回應
        function handleLLMResponse(data) {
            document.getElementById('loading').classList.remove('show');
            document.getElementById('drawBtn').disabled = false;

            if (data.status === 'success') {
                const interpretationEl = document.getElementById('interpretation');
                const reasoningContainer = document.getElementById('reasoningContainer');
                
                if (data.reasoning && data.reasoning.trim()) {
                    const reasoningHtml = marked.parse(data.reasoning);
                    reasoningContainer.innerHTML = `
                        <details class="reasoning-section">
                            <summary>💭 查看 AI 思考過程</summary>
                            <div class="markdown-body">${reasoningHtml}</div>
                        </details>
                    `;
                } else {
                    reasoningContainer.innerHTML = '';
                }
                
                let resultContent = data.result || '';
                
                if (Array.isArray(resultContent)) {
                    console.log('檢測到陣列回應,長度:', resultContent.length);
                    resultContent = resultContent[resultContent.length - 1] || '';
                }
                
                if (typeof resultContent === 'string') {
                    // 如果回應包含完整的三個面向,保留全部
                    // 否則嘗試提取有效內容
                    const validPatterns = [
                        /(\*\*🔍 核心訊息\*\*[\s\S]*?)(?=\*\*💭 心理與情緒層面\*\*|$)/,
                        /(\*\*💭 心理與情緒層面\*\*[\s\S]*?)(?=\*\*🌱 行動建議\*\*|$)/,
                        /(\*\*🌱 行動建議\*\*[\s\S]*?)(?=---|$)/
                    ];
                    
                    // 檢查是否包含三個面向
                    const hasAllSections = resultContent.includes('核心訊息') && 
                                          resultContent.includes('心理與情緒') && 
                                          resultContent.includes('行動建議');
                    
                    if (!hasAllSections && resultContent.length > 100) {
                        // 嘗試提取 Markdown 標題內容
                        const mdMatch = resultContent.match(/(\*\*[^*]+\*\*[\s\S]*?)(?=\n\n|$)/);
                        if (mdMatch && mdMatch[0].length > 50) {
                            resultContent = mdMatch[0];
                        }
                    }
                    
                    // 過濾違規關鍵詞
                    resultContent = filterForbiddenTerms(resultContent);
                    
                    resultContent = resultContent.replace(/['"\]\s]+$/, '').trim();
                    resultContent = resultContent.replace(/^['"\s]+/, '').trim();
                    resultContent = resultContent.replace(/\\n/g, '\n');

                    const codeBlockMatch = resultContent.match(/```(?:markdown|md)?\s*([\s\S]*?)```/);
                    if (codeBlockMatch) {
                        resultContent = codeBlockMatch[1].trim();
                    }
                }
                
                console.log('最終解析內容:', resultContent.substring(0, 200) + '...');
                
                // 如果結果為空或太短,顯示牌義原文並附註
                if (!resultContent || resultContent.length < 20) {
                    const originalText = document.getElementById('meaningOriginalText').textContent;
                    resultContent = `**⚠️ 解析生成中**\n\n牌義原文:${originalText}\n\n請根據上述牌義對照實際情況。`;
                }
                
                const resultHtml = marked.parse(resultContent || '(無解析內容)');
                interpretationEl.innerHTML = resultHtml;
                
                document.getElementById('result').classList.add('show');
            } else if (data.status === 'error') {
                showError('解讀失敗: ' + (data.error || '未知錯誤'));
            } else {
                showError('未知回應狀態: ' + data.status);
            }
        }

        // 顯示錯誤
        function showError(message) {
            const errorEl = document.getElementById('error');
            errorEl.textContent = message;
            errorEl.classList.add('show');
            
            setTimeout(() => {
                errorEl.classList.remove('show');
            }, 5000);
        }

        // 頁面卸載時清理
        window.onbeforeunload = function() {
            if (client) {
                client.end();
            }
        };
    </script>
</body>
</html>