@aiyiran/myclaw 1.1.83 → 1.1.85

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (19) hide show
  1. package/a/1.html +842 -0
  2. package/a/2.html +1531 -0
  3. package/assets/myclaw-artifacts.js +2 -2
  4. package/index.js +53 -0
  5. package/package.json +1 -1
  6. package/skills/yiran-course-template-pipeline/template-index.json +109 -1
  7. package/skills/yiran-course-template-pipeline/template-index.md +36 -0
  8. package//346/210/221/347/232/204/345/217/254/345/224/244/345/233/276/351/211/264/357/274/232/346/215/242/344/270/273/351/242/230/346/215/242/345/215/241/346/261/240/__demo__.html +1531 -0
  9. package//346/210/221/347/232/204/345/217/254/345/224/244/345/233/276/351/211/264/357/274/232/346/215/242/344/270/273/351/242/230/346/215/242/345/215/241/346/261/240/__student-view__.html +77 -0
  10. package//346/210/221/347/232/204/345/217/254/345/224/244/345/233/276/351/211/264/357/274/232/346/215/242/344/270/273/351/242/230/346/215/242/345/215/241/346/261/240/__student__.json +60 -0
  11. package//346/210/221/347/232/204/345/217/254/345/224/244/345/233/276/351/211/264/357/274/232/346/215/242/344/270/273/351/242/230/346/215/242/345/215/241/346/261/240/__teacher-view__.html +52 -0
  12. package//346/210/221/347/232/204/345/217/254/345/224/244/345/233/276/351/211/264/357/274/232/346/215/242/344/270/273/351/242/230/346/215/242/345/215/241/346/261/240/__teacher__.json +45 -0
  13. package//346/210/221/347/232/204/345/217/254/345/224/244/345/233/276/351/211/264/357/274/232/346/215/242/344/270/273/351/242/230/346/215/242/345/215/241/346/261/240/index.html +150 -0
  14. package//351/200/203/345/207/272/350/277/267/345/256/253/357/274/232/346/210/221/346/235/245/345/256/232/350/247/204/345/210/231/__demo__.html +842 -0
  15. package//351/200/203/345/207/272/350/277/267/345/256/253/357/274/232/346/210/221/346/235/245/345/256/232/350/247/204/345/210/231/__student-view__.html +77 -0
  16. package//351/200/203/345/207/272/350/277/267/345/256/253/357/274/232/346/210/221/346/235/245/345/256/232/350/247/204/345/210/231/__student__.json +60 -0
  17. package//351/200/203/345/207/272/350/277/267/345/256/253/357/274/232/346/210/221/346/235/245/345/256/232/350/247/204/345/210/231/__teacher-view__.html +52 -0
  18. package//351/200/203/345/207/272/350/277/267/345/256/253/357/274/232/346/210/221/346/235/245/345/256/232/350/247/204/345/210/231/__teacher__.json +45 -0
  19. package//351/200/203/345/207/272/350/277/267/345/256/253/357/274/232/346/210/221/346/235/245/345/256/232/350/247/204/345/210/231/index.html +150 -0
package/a/2.html ADDED
@@ -0,0 +1,1531 @@
1
+ <!DOCTYPE html>
2
+ <html lang="zh-CN">
3
+
4
+ <head>
5
+ <meta charset="UTF-8">
6
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
7
+ <title>恶龙宝可梦 · 图签系统</title>
8
+ <style>
9
+ :root {
10
+ --bg: #08081a;
11
+ --bg2: #11112a;
12
+ --bg3: #181838;
13
+ --purple: #8b5cf6;
14
+ --purple2: #6d28d9;
15
+ --gold: #f59e0b;
16
+ --gold2: #b45309;
17
+ --text: #e2e8f0;
18
+ --muted: #94a3b8;
19
+ --border: rgba(139, 92, 246, .25);
20
+ --c-common: #6b7280;
21
+ --c-rare: #3b82f6;
22
+ --c-epic: #a855f7;
23
+ --c-legend: #f59e0b;
24
+ }
25
+
26
+ * {
27
+ margin: 0;
28
+ padding: 0;
29
+ box-sizing: border-box
30
+ }
31
+
32
+ body {
33
+ background: var(--bg);
34
+ color: var(--text);
35
+ font-family: 'Segoe UI', system-ui, -apple-system, sans-serif;
36
+ min-height: 100vh;
37
+ overflow-x: hidden;
38
+ }
39
+
40
+ /* Ambient background */
41
+ body::before {
42
+ content: '';
43
+ position: fixed;
44
+ inset: 0;
45
+ pointer-events: none;
46
+ z-index: 0;
47
+ background:
48
+ radial-gradient(ellipse 60% 40% at 15% 15%, rgba(109, 40, 217, .18) 0%, transparent 70%),
49
+ radial-gradient(ellipse 50% 35% at 85% 85%, rgba(245, 158, 11, .12) 0%, transparent 70%),
50
+ radial-gradient(ellipse 40% 30% at 50% 50%, rgba(139, 92, 246, .06) 0%, transparent 60%);
51
+ }
52
+
53
+ /* ── HEADER ─────────────────────────────── */
54
+ header {
55
+ position: sticky;
56
+ top: 0;
57
+ z-index: 100;
58
+ background: rgba(8, 8, 26, .88);
59
+ border-bottom: 1px solid var(--border);
60
+ backdrop-filter: blur(14px);
61
+ -webkit-backdrop-filter: blur(14px);
62
+ padding: 1.1rem 2rem;
63
+ }
64
+
65
+ .hdr {
66
+ max-width: 1400px;
67
+ margin: 0 auto;
68
+ display: flex;
69
+ align-items: center;
70
+ justify-content: space-between;
71
+ flex-wrap: wrap;
72
+ gap: 1rem;
73
+ }
74
+
75
+ .hdr-title h1 {
76
+ font-size: 1.7rem;
77
+ font-weight: 900;
78
+ letter-spacing: .04em;
79
+ background: linear-gradient(130deg, var(--purple) 0%, var(--gold) 100%);
80
+ -webkit-background-clip: text;
81
+ -webkit-text-fill-color: transparent;
82
+ background-clip: text;
83
+ }
84
+
85
+ .hdr-title p {
86
+ font-size: .75rem;
87
+ color: var(--muted);
88
+ margin-top: .15rem;
89
+ }
90
+
91
+ .hdr-stats {
92
+ display: flex;
93
+ align-items: center;
94
+ gap: 1.5rem;
95
+ }
96
+
97
+ .stat {
98
+ text-align: center;
99
+ }
100
+
101
+ .stat-val {
102
+ font-size: 1.4rem;
103
+ font-weight: 700;
104
+ color: var(--gold);
105
+ line-height: 1;
106
+ }
107
+
108
+ .stat-lbl {
109
+ font-size: .65rem;
110
+ color: var(--muted);
111
+ text-transform: uppercase;
112
+ letter-spacing: .1em;
113
+ margin-top: .2rem;
114
+ }
115
+
116
+ .progress-wrap {
117
+ min-width: 160px;
118
+ }
119
+
120
+ .progress-top {
121
+ display: flex;
122
+ justify-content: space-between;
123
+ font-size: .72rem;
124
+ color: var(--muted);
125
+ margin-bottom: .35rem;
126
+ }
127
+
128
+ .prog-bar {
129
+ height: 6px;
130
+ background: rgba(255, 255, 255, .08);
131
+ border-radius: 3px;
132
+ overflow: hidden;
133
+ }
134
+
135
+ .prog-fill {
136
+ height: 100%;
137
+ border-radius: 3px;
138
+ background: linear-gradient(90deg, var(--purple), var(--gold));
139
+ transition: width .6s cubic-bezier(.4, 0, .2, 1);
140
+ }
141
+
142
+ /* ── CONTROLS ────────────────────────────── */
143
+ .controls {
144
+ max-width: 1400px;
145
+ margin: 1.25rem auto;
146
+ padding: 0 2rem;
147
+ display: flex;
148
+ align-items: flex-start;
149
+ justify-content: space-between;
150
+ flex-wrap: wrap;
151
+ gap: 1rem;
152
+ position: relative;
153
+ z-index: 5;
154
+ }
155
+
156
+ .filter-col {
157
+ display: flex;
158
+ flex-direction: column;
159
+ gap: .6rem;
160
+ }
161
+
162
+ .filter-row {
163
+ display: flex;
164
+ align-items: center;
165
+ gap: .4rem;
166
+ flex-wrap: wrap;
167
+ }
168
+
169
+ .filter-lbl {
170
+ font-size: .72rem;
171
+ color: var(--muted);
172
+ white-space: nowrap;
173
+ }
174
+
175
+ .fb {
176
+ padding: .3rem .8rem;
177
+ border-radius: 20px;
178
+ border: 1px solid var(--border);
179
+ background: rgba(139, 92, 246, .08);
180
+ color: var(--muted);
181
+ cursor: pointer;
182
+ font-size: .78rem;
183
+ transition: all .18s;
184
+ white-space: nowrap;
185
+ }
186
+
187
+ .fb:hover {
188
+ background: rgba(139, 92, 246, .18);
189
+ color: var(--text);
190
+ }
191
+
192
+ .fb.active {
193
+ background: var(--purple);
194
+ color: #fff;
195
+ border-color: var(--purple);
196
+ }
197
+
198
+ .fb.legend-btn {
199
+ border-color: rgba(245, 158, 11, .4);
200
+ color: var(--gold);
201
+ }
202
+
203
+ .fb.legend-btn.active {
204
+ background: linear-gradient(135deg, var(--gold2), var(--gold));
205
+ color: #1a0a00;
206
+ border-color: var(--gold);
207
+ }
208
+
209
+ .action-col {
210
+ display: flex;
211
+ flex-direction: column;
212
+ gap: .6rem;
213
+ align-items: flex-end;
214
+ }
215
+
216
+ .view-toggle {
217
+ display: flex;
218
+ overflow: hidden;
219
+ border: 1px solid var(--border);
220
+ border-radius: 8px;
221
+ }
222
+
223
+ .vb {
224
+ padding: .35rem .9rem;
225
+ border: none;
226
+ background: transparent;
227
+ color: var(--muted);
228
+ cursor: pointer;
229
+ font-size: .78rem;
230
+ transition: all .18s;
231
+ }
232
+
233
+ .vb.active {
234
+ background: var(--purple);
235
+ color: #fff;
236
+ }
237
+
238
+ .draw-btns {
239
+ display: flex;
240
+ gap: .6rem;
241
+ }
242
+
243
+ .dbtn {
244
+ padding: .6rem 1.4rem;
245
+ border: none;
246
+ border-radius: 8px;
247
+ font-size: .9rem;
248
+ font-weight: 700;
249
+ cursor: pointer;
250
+ position: relative;
251
+ overflow: hidden;
252
+ transition: all .2s;
253
+ letter-spacing: .02em;
254
+ }
255
+
256
+ .dbtn-single {
257
+ background: linear-gradient(135deg, var(--purple2), var(--purple));
258
+ color: #fff;
259
+ }
260
+
261
+ .dbtn-ten {
262
+ background: linear-gradient(135deg, var(--gold2), var(--gold));
263
+ color: #1a0a00;
264
+ }
265
+
266
+ .dbtn:hover {
267
+ transform: translateY(-2px);
268
+ }
269
+
270
+ .dbtn-single:hover {
271
+ box-shadow: 0 6px 24px rgba(109, 40, 217, .5);
272
+ }
273
+
274
+ .dbtn-ten:hover {
275
+ box-shadow: 0 6px 24px rgba(245, 158, 11, .5);
276
+ }
277
+
278
+ .dbtn::before {
279
+ content: '';
280
+ position: absolute;
281
+ inset: 0;
282
+ background: linear-gradient(105deg, transparent 30%, rgba(255, 255, 255, .2) 50%, transparent 70%);
283
+ transform: translateX(-100%);
284
+ transition: transform .4s;
285
+ }
286
+
287
+ .dbtn:hover::before {
288
+ transform: translateX(100%);
289
+ }
290
+
291
+ .dbtn-reset {
292
+ background: rgba(100, 100, 120, .25);
293
+ border: 1px solid rgba(255, 255, 255, .15);
294
+ color: var(--muted);
295
+ font-size: .85rem;
296
+ }
297
+
298
+ .dbtn-reset:hover {
299
+ background: rgba(239, 68, 68, .3);
300
+ border-color: rgba(239, 68, 68, .5);
301
+ color: #fca5a5;
302
+ box-shadow: 0 6px 24px rgba(239, 68, 68, .25);
303
+ transform: translateY(-2px);
304
+ }
305
+
306
+ #confirm-modal {
307
+ display: none;
308
+ position: fixed;
309
+ inset: 0;
310
+ z-index: 800;
311
+ background: rgba(0, 0, 0, .85);
312
+ align-items: center;
313
+ justify-content: center;
314
+ }
315
+
316
+ #confirm-modal.show {
317
+ display: flex;
318
+ }
319
+
320
+ .confirm-box {
321
+ background: var(--bg2);
322
+ border: 1px solid rgba(239, 68, 68, .5);
323
+ border-radius: 14px;
324
+ padding: 2rem 2.5rem;
325
+ text-align: center;
326
+ max-width: 340px;
327
+ }
328
+
329
+ .confirm-box h3 {
330
+ font-size: 1.1rem;
331
+ margin-bottom: .6rem;
332
+ color: #fca5a5;
333
+ }
334
+
335
+ .confirm-box p {
336
+ font-size: .82rem;
337
+ color: var(--muted);
338
+ line-height: 1.5;
339
+ margin-bottom: 1.4rem;
340
+ }
341
+
342
+ .confirm-btns {
343
+ display: flex;
344
+ gap: .8rem;
345
+ justify-content: center;
346
+ }
347
+
348
+ .cbtn {
349
+ padding: .55rem 1.6rem;
350
+ border: none;
351
+ border-radius: 8px;
352
+ font-size: .88rem;
353
+ font-weight: 700;
354
+ cursor: pointer;
355
+ transition: all .2s;
356
+ }
357
+
358
+ .cbtn-cancel {
359
+ background: rgba(255, 255, 255, .08);
360
+ color: var(--text);
361
+ }
362
+
363
+ .cbtn-cancel:hover {
364
+ background: rgba(255, 255, 255, .15);
365
+ }
366
+
367
+ .cbtn-ok {
368
+ background: rgba(239, 68, 68, .7);
369
+ color: #fff;
370
+ }
371
+
372
+ .cbtn-ok:hover {
373
+ background: rgba(239, 68, 68, .9);
374
+ box-shadow: 0 4px 16px rgba(239, 68, 68, .4);
375
+ }
376
+
377
+ /* ── CARD GRID ───────────────────────────── */
378
+ .grid {
379
+ max-width: 1400px;
380
+ margin: 0 auto;
381
+ padding: 0 2rem 4rem;
382
+ display: grid;
383
+ grid-template-columns: repeat(auto-fill, minmax(192px, 1fr));
384
+ gap: 1.1rem;
385
+ position: relative;
386
+ z-index: 5;
387
+ }
388
+
389
+ .card-wrap {
390
+ perspective: 1200px;
391
+ }
392
+
393
+ .card {
394
+ position: relative;
395
+ border-radius: 14px;
396
+ height: 320px;
397
+ cursor: pointer;
398
+ transition: transform .3s ease, filter .3s;
399
+ transform-style: preserve-3d;
400
+ }
401
+
402
+ .card:hover {
403
+ transform: translateY(-8px) rotateX(4deg) scale(1.02);
404
+ }
405
+
406
+ .card.dim {
407
+ filter: grayscale(.9) brightness(.3);
408
+ }
409
+
410
+ .card.dim:hover {
411
+ filter: grayscale(.6) brightness(.45);
412
+ }
413
+
414
+ .card-inner {
415
+ position: relative;
416
+ width: 100%;
417
+ height: 100%;
418
+ background: var(--bg3);
419
+ border-radius: 14px;
420
+ border: 1px solid rgba(139, 92, 246, .3);
421
+ display: flex;
422
+ flex-direction: column;
423
+ overflow: hidden;
424
+ }
425
+
426
+ /* rarity borders / glows */
427
+ .card[data-rarity="rare"] .card-inner {
428
+ border-color: rgba(59, 130, 246, .55);
429
+ box-shadow: 0 0 14px rgba(59, 130, 246, .2);
430
+ }
431
+
432
+ .card[data-rarity="epic"] .card-inner {
433
+ border-color: rgba(168, 85, 247, .7);
434
+ box-shadow: 0 0 20px rgba(168, 85, 247, .3), inset 0 0 20px rgba(168, 85, 247, .05);
435
+ }
436
+
437
+ .card[data-rarity="legendary"] .card-inner {
438
+ border-color: rgba(245, 158, 11, .85);
439
+ box-shadow: 0 0 28px rgba(245, 158, 11, .45), inset 0 0 28px rgba(245, 158, 11, .05);
440
+ animation: lpulse 2.2s ease-in-out infinite;
441
+ }
442
+
443
+ @keyframes lpulse {
444
+
445
+ 0%,
446
+ 100% {
447
+ box-shadow: 0 0 28px rgba(245, 158, 11, .45), inset 0 0 28px rgba(245, 158, 11, .05);
448
+ }
449
+
450
+ 50% {
451
+ box-shadow: 0 0 48px rgba(245, 158, 11, .75), inset 0 0 35px rgba(245, 158, 11, .1);
452
+ }
453
+ }
454
+
455
+ /* legendary holo shimmer */
456
+ .card[data-rarity="legendary"] .card-inner::after {
457
+ content: '';
458
+ position: absolute;
459
+ inset: 0;
460
+ pointer-events: none;
461
+ z-index: 20;
462
+ border-radius: 14px;
463
+ background: linear-gradient(110deg,
464
+ transparent 35%,
465
+ rgba(255, 215, 0, .15) 45%,
466
+ rgba(255, 255, 255, .25) 50%,
467
+ rgba(255, 215, 0, .15) 55%,
468
+ transparent 65%);
469
+ animation: holo 3s linear infinite;
470
+ }
471
+
472
+ @keyframes holo {
473
+ 0% {
474
+ transform: translateX(-120%)
475
+ }
476
+
477
+ 100% {
478
+ transform: translateX(220%)
479
+ }
480
+ }
481
+
482
+ /* artwork */
483
+ .artwork {
484
+ height: 145px;
485
+ flex-shrink: 0;
486
+ display: flex;
487
+ align-items: center;
488
+ justify-content: center;
489
+ font-size: 3.8rem;
490
+ position: relative;
491
+ overflow: hidden;
492
+ }
493
+
494
+ .artwork-emoji {
495
+ position: relative;
496
+ z-index: 2;
497
+ filter: drop-shadow(0 0 10px rgba(0, 0, 0, .8));
498
+ }
499
+
500
+ /* type gradients */
501
+ .tg-fire {
502
+ background: linear-gradient(135deg, #7f1d1d 0%, #b91c1c 40%, #ea580c 100%);
503
+ }
504
+
505
+ .tg-ice {
506
+ background: linear-gradient(135deg, #0c4a6e 0%, #0284c7 50%, #22d3ee 100%);
507
+ }
508
+
509
+ .tg-dark {
510
+ background: linear-gradient(135deg, #1a0a2e 0%, #4c1d95 60%, #3b0764 100%);
511
+ }
512
+
513
+ .tg-thunder {
514
+ background: linear-gradient(135deg, #451a03 0%, #b45309 50%, #eab308 100%);
515
+ }
516
+
517
+ .tg-earth {
518
+ background: linear-gradient(135deg, #14532d 0%, #15803d 50%, #713f12 100%);
519
+ }
520
+
521
+ .tg-void {
522
+ background: linear-gradient(135deg, #000000 0%, #1e1b4b 40%, #4c1d95 70%, #0f0a1e 100%);
523
+ }
524
+
525
+ /* rarity badge */
526
+ .rbadge {
527
+ position: absolute;
528
+ top: 8px;
529
+ right: 8px;
530
+ z-index: 10;
531
+ padding: 2px 7px;
532
+ border-radius: 10px;
533
+ font-size: .62rem;
534
+ font-weight: 800;
535
+ letter-spacing: .08em;
536
+ }
537
+
538
+ .rbadge.common {
539
+ background: rgba(107, 114, 128, .85);
540
+ color: #d1d5db;
541
+ }
542
+
543
+ .rbadge.rare {
544
+ background: rgba(59, 130, 246, .85);
545
+ color: #bfdbfe;
546
+ }
547
+
548
+ .rbadge.epic {
549
+ background: rgba(168, 85, 247, .85);
550
+ color: #ede9fe;
551
+ }
552
+
553
+ .rbadge.legendary {
554
+ background: rgba(245, 158, 11, .95);
555
+ color: #1a0800;
556
+ }
557
+
558
+ .new-badge {
559
+ position: absolute;
560
+ top: 8px;
561
+ left: 8px;
562
+ z-index: 10;
563
+ background: #ef4444;
564
+ color: #fff;
565
+ font-size: .58rem;
566
+ font-weight: 800;
567
+ padding: 2px 6px;
568
+ border-radius: 4px;
569
+ animation: npop .8s ease-in-out 4;
570
+ }
571
+
572
+ @keyframes npop {
573
+
574
+ 0%,
575
+ 100% {
576
+ transform: scale(1)
577
+ }
578
+
579
+ 50% {
580
+ transform: scale(1.15)
581
+ }
582
+ }
583
+
584
+ .card-body {
585
+ padding: .7rem .75rem;
586
+ flex: 1;
587
+ display: flex;
588
+ flex-direction: column;
589
+ gap: .35rem;
590
+ overflow: hidden;
591
+ }
592
+
593
+ .card-name {
594
+ font-size: .9rem;
595
+ font-weight: 700;
596
+ line-height: 1.25;
597
+ }
598
+
599
+ .tbadge {
600
+ display: inline-block;
601
+ padding: 1px 7px;
602
+ border-radius: 4px;
603
+ font-size: .62rem;
604
+ font-weight: 600;
605
+ border: 1px solid;
606
+ align-self: flex-start;
607
+ }
608
+
609
+ .tb-fire {
610
+ background: rgba(220, 38, 38, .25);
611
+ color: #fca5a5;
612
+ border-color: rgba(220, 38, 38, .45);
613
+ }
614
+
615
+ .tb-ice {
616
+ background: rgba(14, 165, 233, .25);
617
+ color: #7dd3fc;
618
+ border-color: rgba(14, 165, 233, .45);
619
+ }
620
+
621
+ .tb-dark {
622
+ background: rgba(168, 85, 247, .25);
623
+ color: #d8b4fe;
624
+ border-color: rgba(168, 85, 247, .45);
625
+ }
626
+
627
+ .tb-thunder {
628
+ background: rgba(245, 158, 11, .25);
629
+ color: #fde68a;
630
+ border-color: rgba(245, 158, 11, .45);
631
+ }
632
+
633
+ .tb-earth {
634
+ background: rgba(22, 163, 74, .25);
635
+ color: #86efac;
636
+ border-color: rgba(22, 163, 74, .45);
637
+ }
638
+
639
+ .tb-void {
640
+ background: rgba(99, 102, 241, .15);
641
+ color: #a5b4fc;
642
+ border-color: rgba(99, 102, 241, .45);
643
+ }
644
+
645
+ .card-desc {
646
+ font-size: .62rem;
647
+ color: var(--muted);
648
+ line-height: 1.4;
649
+ flex: 1;
650
+ }
651
+
652
+ .card-stats {
653
+ display: grid;
654
+ grid-template-columns: repeat(3, 1fr);
655
+ gap: .25rem;
656
+ margin-top: auto;
657
+ }
658
+
659
+ .s {
660
+ text-align: center;
661
+ background: rgba(255, 255, 255, .05);
662
+ border-radius: 4px;
663
+ padding: .2rem .1rem;
664
+ }
665
+
666
+ .s-n {
667
+ font-size: .52rem;
668
+ color: var(--muted);
669
+ text-transform: uppercase;
670
+ }
671
+
672
+ .s-v {
673
+ font-size: .82rem;
674
+ font-weight: 700;
675
+ color: var(--gold);
676
+ }
677
+
678
+ /* ── DRAW MODAL ──────────────────────────── */
679
+ .overlay {
680
+ position: fixed;
681
+ inset: 0;
682
+ z-index: 500;
683
+ background: rgba(0, 0, 0, .92);
684
+ display: flex;
685
+ align-items: center;
686
+ justify-content: center;
687
+ opacity: 0;
688
+ pointer-events: none;
689
+ transition: opacity .3s;
690
+ }
691
+
692
+ .overlay.show {
693
+ opacity: 1;
694
+ pointer-events: all;
695
+ }
696
+
697
+ .modal {
698
+ max-width: 960px;
699
+ width: 94%;
700
+ padding: 2rem 1.5rem;
701
+ text-align: center;
702
+ max-height: 92vh;
703
+ overflow-y: auto;
704
+ }
705
+
706
+ .modal-ttl {
707
+ font-size: 1.6rem;
708
+ font-weight: 900;
709
+ text-shadow: 0 0 24px currentColor;
710
+ margin-bottom: 1.4rem;
711
+ transition: color .3s;
712
+ }
713
+
714
+ .draw-grid {
715
+ display: flex;
716
+ flex-wrap: wrap;
717
+ gap: .85rem;
718
+ justify-content: center;
719
+ margin-bottom: 1.5rem;
720
+ }
721
+
722
+ /* flip card in modal */
723
+ .dc {
724
+ width: 120px;
725
+ height: 190px;
726
+ perspective: 900px;
727
+ flex-shrink: 0;
728
+ }
729
+
730
+ .dc-inner {
731
+ width: 100%;
732
+ height: 100%;
733
+ position: relative;
734
+ transform-style: preserve-3d;
735
+ transition: transform .55s cubic-bezier(.4, .2, .2, 1);
736
+ }
737
+
738
+ .dc-inner.flipped {
739
+ transform: rotateY(180deg);
740
+ }
741
+
742
+ .dc-face {
743
+ position: absolute;
744
+ inset: 0;
745
+ border-radius: 10px;
746
+ backface-visibility: hidden;
747
+ -webkit-backface-visibility: hidden;
748
+ display: flex;
749
+ align-items: center;
750
+ justify-content: center;
751
+ overflow: hidden;
752
+ }
753
+
754
+ .dc-back {
755
+ background: linear-gradient(135deg, #1e1b4b, #4c1d95, #1a0a2e);
756
+ border: 2px solid var(--purple);
757
+ }
758
+
759
+ .dc-back-ico {
760
+ font-size: 2.8rem;
761
+ opacity: .7;
762
+ animation: spin 5s linear infinite;
763
+ }
764
+
765
+ @keyframes spin {
766
+ to {
767
+ transform: rotate(360deg)
768
+ }
769
+ }
770
+
771
+ .dc-front {
772
+ transform: rotateY(180deg);
773
+ flex-direction: column;
774
+ padding: .5rem;
775
+ gap: .3rem;
776
+ background: var(--bg3);
777
+ }
778
+
779
+ .dc-art {
780
+ width: 100%;
781
+ height: 72px;
782
+ border-radius: 7px;
783
+ display: flex;
784
+ align-items: center;
785
+ justify-content: center;
786
+ font-size: 2rem;
787
+ flex-shrink: 0;
788
+ }
789
+
790
+ .dc-name {
791
+ font-size: .72rem;
792
+ font-weight: 700;
793
+ color: var(--text);
794
+ text-align: center;
795
+ line-height: 1.2;
796
+ }
797
+
798
+ .dc-stars {
799
+ font-size: .65rem;
800
+ text-align: center;
801
+ }
802
+
803
+ .dc-dup {
804
+ font-size: .55rem;
805
+ color: var(--muted);
806
+ background: rgba(0, 0, 0, .6);
807
+ padding: 1px 4px;
808
+ border-radius: 3px;
809
+ margin-top: auto;
810
+ }
811
+
812
+ .close-btn {
813
+ padding: .7rem 3rem;
814
+ border: none;
815
+ border-radius: 8px;
816
+ background: linear-gradient(135deg, var(--purple2), var(--purple));
817
+ color: #fff;
818
+ font-size: .95rem;
819
+ font-weight: 700;
820
+ cursor: pointer;
821
+ transition: all .2s;
822
+ }
823
+
824
+ .close-btn:hover {
825
+ transform: translateY(-2px);
826
+ box-shadow: 0 6px 24px rgba(109, 40, 217, .5);
827
+ }
828
+
829
+ /* ── TOAST ───────────────────────────────── */
830
+ .toast {
831
+ position: fixed;
832
+ bottom: 1.75rem;
833
+ right: 1.75rem;
834
+ z-index: 600;
835
+ background: var(--bg2);
836
+ border: 1px solid var(--border);
837
+ border-radius: 10px;
838
+ padding: .85rem 1.2rem;
839
+ max-width: 290px;
840
+ transform: translateX(130%);
841
+ transition: transform .3s ease;
842
+ }
843
+
844
+ .toast.show {
845
+ transform: translateX(0);
846
+ }
847
+
848
+ .toast-t {
849
+ font-weight: 700;
850
+ font-size: .88rem;
851
+ margin-bottom: .2rem;
852
+ }
853
+
854
+ .toast-b {
855
+ font-size: .75rem;
856
+ color: var(--muted);
857
+ line-height: 1.4;
858
+ }
859
+
860
+ /* ── PARTICLES ───────────────────────────── */
861
+ .ptcl {
862
+ position: fixed;
863
+ pointer-events: none;
864
+ z-index: 700;
865
+ animation: ptcl-up 1.4s ease-out forwards;
866
+ }
867
+
868
+ @keyframes ptcl-up {
869
+ 0% {
870
+ opacity: 1;
871
+ transform: translateY(0) scale(1);
872
+ }
873
+
874
+ 100% {
875
+ opacity: 0;
876
+ transform: translateY(-90px) scale(.4) rotate(30deg);
877
+ }
878
+ }
879
+
880
+ /* ── EMPTY ───────────────────────────────── */
881
+ .empty {
882
+ grid-column: 1/-1;
883
+ text-align: center;
884
+ padding: 4rem 1rem;
885
+ color: var(--muted);
886
+ }
887
+
888
+ .empty-ico {
889
+ font-size: 3.5rem;
890
+ margin-bottom: .75rem;
891
+ }
892
+
893
+ /* ── SCROLLBAR ───────────────────────────── */
894
+ ::-webkit-scrollbar {
895
+ width: 6px;
896
+ }
897
+
898
+ ::-webkit-scrollbar-track {
899
+ background: var(--bg);
900
+ }
901
+
902
+ ::-webkit-scrollbar-thumb {
903
+ background: var(--purple);
904
+ border-radius: 3px;
905
+ }
906
+
907
+ /* ── RESPONSIVE ──────────────────────────── */
908
+ @media(max-width:700px) {
909
+ header {
910
+ padding: .85rem 1rem;
911
+ }
912
+
913
+ .hdr {
914
+ flex-direction: column;
915
+ align-items: flex-start;
916
+ }
917
+
918
+ .hdr-stats {
919
+ flex-wrap: wrap;
920
+ gap: 1rem;
921
+ }
922
+
923
+ .controls {
924
+ padding: 0 1rem;
925
+ }
926
+
927
+ .grid {
928
+ grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
929
+ padding: 0 1rem 3rem;
930
+ gap: .85rem;
931
+ }
932
+
933
+ .card {
934
+ height: 290px;
935
+ }
936
+
937
+ .artwork {
938
+ height: 120px;
939
+ font-size: 3rem;
940
+ }
941
+
942
+ .dbtn {
943
+ padding: .5rem 1rem;
944
+ font-size: .8rem;
945
+ }
946
+ }
947
+ </style>
948
+ </head>
949
+
950
+ <body>
951
+
952
+ <header>
953
+ <div class="hdr">
954
+ <div class="hdr-title">
955
+ <h1 id="theme-name">🐉 恶龙宝可梦 · 图签系统</h1>
956
+ <p id="theme-desc">收集传说中的恶龙宝可梦,召唤黑暗力量</p>
957
+ </div>
958
+ <div class="hdr-stats">
959
+ <div class="stat">
960
+ <div class="stat-val" id="s-collected">0</div>
961
+ <div class="stat-lbl">已收集</div>
962
+ </div>
963
+ <div class="stat">
964
+ <div class="stat-val" id="s-total">0</div>
965
+ <div class="stat-lbl">全部图签</div>
966
+ </div>
967
+ <div class="stat">
968
+ <div class="stat-val" id="s-draws">0</div>
969
+ <div class="stat-lbl">召唤次数</div>
970
+ </div>
971
+ <div class="progress-wrap">
972
+ <div class="progress-top"><span>收集进度</span><span id="s-pct">0%</span></div>
973
+ <div class="prog-bar">
974
+ <div class="prog-fill" id="prog-fill" style="width:0%"></div>
975
+ </div>
976
+ </div>
977
+ </div>
978
+ </div>
979
+ </header>
980
+
981
+ <div class="controls">
982
+ <div class="filter-col">
983
+ <div class="filter-row">
984
+ <span class="filter-lbl">稀有度:</span>
985
+ <button class="fb active" data-f="rarity" data-v="all">全部</button>
986
+ <button class="fb" data-f="rarity" data-v="common">普通</button>
987
+ <button class="fb" data-f="rarity" data-v="rare">稀有</button>
988
+ <button class="fb" data-f="rarity" data-v="epic">史诗</button>
989
+ <button class="fb legend-btn" data-f="rarity" data-v="legendary">传说</button>
990
+ </div>
991
+ <div class="filter-row" id="type-filters">
992
+ <span class="filter-lbl">属性:</span>
993
+ </div>
994
+ </div>
995
+ <div class="action-col">
996
+ <div class="view-toggle">
997
+ <button class="vb active" data-view="all">全部图签</button>
998
+ <button class="vb" data-view="collected">已收集</button>
999
+ </div>
1000
+ <div class="draw-btns">
1001
+ <button class="dbtn dbtn-single" id="btn-draw1">✨ 单次召唤</button>
1002
+ <button class="dbtn dbtn-ten" id="btn-draw10">🎰 十连召唤</button>
1003
+ <button class="dbtn dbtn-reset" id="btn-reset">🗑️ 重置图鉴</button>
1004
+ </div>
1005
+ </div>
1006
+ </div>
1007
+
1008
+ <div class="grid" id="grid"></div>
1009
+
1010
+ <!-- Draw Modal -->
1011
+ <div class="overlay" id="overlay">
1012
+ <div class="modal">
1013
+ <div class="modal-ttl" id="modal-ttl">✨ 召唤结果</div>
1014
+ <div class="draw-grid" id="draw-grid"></div>
1015
+ <button class="close-btn" id="close-btn">收入图鉴</button>
1016
+ </div>
1017
+ </div>
1018
+
1019
+ <!-- Toast -->
1020
+ <div class="toast" id="toast">
1021
+ <div class="toast-t" id="toast-t"></div>
1022
+ <div class="toast-b" id="toast-b"></div>
1023
+ </div>
1024
+
1025
+ <!-- Confirm Reset Modal -->
1026
+ <div id="confirm-modal">
1027
+ <div class="confirm-box">
1028
+ <h3>⚠️ 确认重置图鉴?</h3>
1029
+ <p>这将清空所有已收集的卡牌和召唤记录,<br>操作不可撤销。</p>
1030
+ <div class="confirm-btns">
1031
+ <button class="cbtn cbtn-cancel" id="cfm-cancel">取消</button>
1032
+ <button class="cbtn cbtn-ok" id="cfm-ok">确认重置</button>
1033
+ </div>
1034
+ </div>
1035
+ </div>
1036
+
1037
+ <script>
1038
+ // ================================================================
1039
+ // THEME CONFIG ← Edit this object to swap themes entirely
1040
+ // ================================================================
1041
+ const THEME = {
1042
+ themeName: "恶龙宝可梦",
1043
+ themeDescription: "收集传说中的恶龙宝可梦,召唤黑暗力量",
1044
+
1045
+ // Each type maps to a CSS gradient class (tg-*) and a type-badge class (tb-*)
1046
+ types: [
1047
+ { id: "火焰龙", label: "火焰龙", tgClass: "tg-fire", tbClass: "tb-fire", color: "#ef4444" },
1048
+ { id: "冰霜龙", label: "冰霜龙", tgClass: "tg-ice", tbClass: "tb-ice", color: "#38bdf8" },
1049
+ { id: "暗影龙", label: "暗影龙", tgClass: "tg-dark", tbClass: "tb-dark", color: "#a855f7" },
1050
+ { id: "雷霆龙", label: "雷霆龙", tgClass: "tg-thunder", tbClass: "tb-thunder", color: "#fbbf24" },
1051
+ { id: "大地龙", label: "大地龙", tgClass: "tg-earth", tbClass: "tb-earth", color: "#4ade80" },
1052
+ { id: "虚空龙", label: "虚空龙", tgClass: "tg-void", tbClass: "tb-void", color: "#818cf8" },
1053
+ ],
1054
+
1055
+ // Weights must sum to 100
1056
+ rarities: [
1057
+ { id: "common", label: "普通", stars: "★", weight: 60, color: "#6b7280" },
1058
+ { id: "rare", label: "稀有", stars: "★★", weight: 25, color: "#3b82f6" },
1059
+ { id: "epic", label: "史诗", stars: "★★★", weight: 12, color: "#a855f7" },
1060
+ { id: "legendary", label: "传说", stars: "★★★★", weight: 3, color: "#f59e0b" },
1061
+ ],
1062
+
1063
+ cards: [
1064
+ // ── COMMON (8) ────────────────────────────────────────────
1065
+ {
1066
+ id: 1, name: "影翼幼龙", type: "暗影龙", rarity: "common",
1067
+ desc: "刚出生的暗影龙,翅膀尚未展开,但已充满邪气。",
1068
+ stats: { atk: 45, def: 30, spd: 55 }, emoji: "🐉"
1069
+ },
1070
+ {
1071
+ id: 2, name: "烈焰幼龙", type: "火焰龙", rarity: "common",
1072
+ desc: "喷出的火焰含腐蚀毒素,能熔化普通金属盔甲。",
1073
+ stats: { atk: 55, def: 25, spd: 48 }, emoji: "🔥"
1074
+ },
1075
+ {
1076
+ id: 3, name: "霜翼小龙", type: "冰霜龙", rarity: "common",
1077
+ desc: "飞翔时四周凝结冰晶,每片雪花都带有诅咒。",
1078
+ stats: { atk: 40, def: 48, spd: 42 }, emoji: "❄️"
1079
+ },
1080
+ {
1081
+ id: 4, name: "大地毒蛟", type: "大地龙", rarity: "common",
1082
+ desc: "以大地腐化之力为食,所到之处寸草不生。",
1083
+ stats: { atk: 50, def: 52, spd: 22 }, emoji: "🌿"
1084
+ },
1085
+ {
1086
+ id: 5, name: "暗雷幼龙", type: "雷霆龙", rarity: "common",
1087
+ desc: "尚未成熟的雷霆龙,雷鸣声中夹杂着黑暗诅咒。",
1088
+ stats: { atk: 48, def: 28, spd: 60 }, emoji: "⚡"
1089
+ },
1090
+ {
1091
+ id: 6, name: "虚空幼蛟", type: "虚空龙", rarity: "common",
1092
+ desc: "从虚空裂缝爬出的小生命,身形模糊如烟雾。",
1093
+ stats: { atk: 35, def: 35, spd: 62 }, emoji: "🌀"
1094
+ },
1095
+ {
1096
+ id: 7, name: "腐化地蜥", type: "大地龙", rarity: "common",
1097
+ desc: "外形似巨蜥,体内流动着大地腐化之力。",
1098
+ stats: { atk: 52, def: 50, spd: 20 }, emoji: "🦎"
1099
+ },
1100
+ {
1101
+ id: 8, name: "焰毒蛟龙", type: "火焰龙", rarity: "common",
1102
+ desc: "火焰与毒液共存的低阶恶龙,毒牙散发橙光。",
1103
+ stats: { atk: 58, def: 22, spd: 45 }, emoji: "🐍"
1104
+ },
1105
+
1106
+ // ── RARE (6) ──────────────────────────────────────────────
1107
+ {
1108
+ id: 9, name: "暗焰恶龙", type: "火焰龙", rarity: "rare",
1109
+ desc: "被黑暗诅咒的火焰龙,喷出能腐蚀一切的黑火。",
1110
+ stats: { atk: 78, def: 52, spd: 68 }, emoji: "🐉"
1111
+ },
1112
+ {
1113
+ id: 10, name: "诅咒冰龙", type: "冰霜龙", rarity: "rare",
1114
+ desc: "受古老诅咒侵染,冰冻能力能将时间本身冻住。",
1115
+ stats: { atk: 65, def: 78, spd: 58 }, emoji: "💀"
1116
+ },
1117
+ {
1118
+ id: 11, name: "骨骸暗龙", type: "暗影龙", rarity: "rare",
1119
+ desc: "由亡灵与暗影编织而成,骨骼在黑光中闪烁。",
1120
+ stats: { atk: 72, def: 68, spd: 55 }, emoji: "💀"
1121
+ },
1122
+ {
1123
+ id: 12, name: "虚空暗噬", type: "虚空龙", rarity: "rare",
1124
+ desc: "能吞噬光与影的虚空生物,存在令空间扭曲。",
1125
+ stats: { atk: 80, def: 45, spd: 72 }, emoji: "🕳️"
1126
+ },
1127
+ {
1128
+ id: 13, name: "雷霆黑龙", type: "雷霆龙", rarity: "rare",
1129
+ desc: "黑色闪电的化身,每次雷击都携带黑暗侵蚀。",
1130
+ stats: { atk: 88, def: 42, spd: 78 }, emoji: "⚡"
1131
+ },
1132
+ {
1133
+ id: 14, name: "碎地巨龙", type: "大地龙", rarity: "rare",
1134
+ desc: "巨尾横扫大地,地面碎裂如镜,毒气蔓延。",
1135
+ stats: { atk: 82, def: 72, spd: 35 }, emoji: "🌋"
1136
+ },
1137
+
1138
+ // ── EPIC (6) ──────────────────────────────────────────────
1139
+ {
1140
+ id: 15, name: "冰霜恶龙王", type: "冰霜龙", rarity: "epic",
1141
+ desc: "统治极寒地域的恶龙王,呼出的寒气能将太阳冻住。",
1142
+ stats: { atk: 108, def: 118, spd: 80 }, emoji: "🐲"
1143
+ },
1144
+ {
1145
+ id: 16, name: "地狱炎龙", type: "火焰龙", rarity: "epic",
1146
+ desc: "从地狱深处崛起,身上燃烧着永不熄灭的黑焰。",
1147
+ stats: { atk: 128, def: 88, spd: 98 }, emoji: "🔥"
1148
+ },
1149
+ {
1150
+ id: 17, name: "幽冥雷霆龙", type: "雷霆龙", rarity: "epic",
1151
+ desc: "以灵魂为雷源,每道闪电都能将目标精神击碎。",
1152
+ stats: { atk: 135, def: 72, spd: 112 }, emoji: "⚡"
1153
+ },
1154
+ {
1155
+ id: 18, name: "深渊暗龙", type: "暗影龙", rarity: "epic",
1156
+ desc: "从深渊涌现,身形如黑洞,吸收周围一切光线。",
1157
+ stats: { atk: 118, def: 100, spd: 90 }, emoji: "🌑"
1158
+ },
1159
+ {
1160
+ id: 19, name: "地狱守卫龙", type: "大地龙", rarity: "epic",
1161
+ desc: "看守地狱入口的巨型恶龙,咆哮声能开地狱之门。",
1162
+ stats: { atk: 120, def: 128, spd: 52 }, emoji: "🦕"
1163
+ },
1164
+ {
1165
+ id: 20, name: "虚空裂龙", type: "虚空龙", rarity: "epic",
1166
+ desc: "能撕裂空间维度的恶龙,在多个平面同时存在。",
1167
+ stats: { atk: 122, def: 90, spd: 115 }, emoji: "🌀"
1168
+ },
1169
+
1170
+ // ── LEGENDARY (4) ─────────────────────────────────────────
1171
+ {
1172
+ id: 21, name: "混沌始祖龙", type: "虚空龙", rarity: "legendary",
1173
+ desc: "创世之初便存在的混沌恶龙,是所有恶龙的始祖,能左右世界的命运。",
1174
+ stats: { atk: 182, def: 158, spd: 148 }, emoji: "🌌"
1175
+ },
1176
+ {
1177
+ id: 22, name: "血焰龙皇", type: "火焰龙", rarity: "legendary",
1178
+ desc: "以战神之血为燃料,焚烧整片大陆的恶龙皇帝,传说其死后化为火山群。",
1179
+ stats: { atk: 198, def: 138, spd: 162 }, emoji: "👑"
1180
+ },
1181
+ {
1182
+ id: 23, name: "寒冰暗王", type: "冰霜龙", rarity: "legendary",
1183
+ desc: "掌控绝对零度与黑暗诅咒的冰龙之王,其怒吼能使时空凝固。",
1184
+ stats: { atk: 168, def: 178, spd: 150 }, emoji: "❄️"
1185
+ },
1186
+ {
1187
+ id: 24, name: "龙皇·虚空", type: "虚空龙", rarity: "legendary",
1188
+ desc: "超越维度的龙皇,以虚空为身,以星辰为食,存在于一切时空之中。",
1189
+ stats: { atk: 202, def: 182, spd: 175 }, emoji: "✨"
1190
+ },
1191
+ ],
1192
+ };
1193
+
1194
+ // ================================================================
1195
+ // STATE
1196
+ // ================================================================
1197
+ const SAVE_KEY = `badge_${THEME.themeName}`;
1198
+
1199
+ const state = {
1200
+ collected: new Set(),
1201
+ fresh: new Set(), // newly drawn, cleared on modal close
1202
+ draws: 0,
1203
+ rFilter: 'all',
1204
+ tFilter: 'all',
1205
+ view: 'all',
1206
+ };
1207
+
1208
+ function load() {
1209
+ try {
1210
+ const d = JSON.parse(localStorage.getItem(SAVE_KEY) || '{}');
1211
+ state.collected = new Set(d.collected || []);
1212
+ state.draws = d.draws || 0;
1213
+ } catch (e) { }
1214
+ }
1215
+ function save() {
1216
+ localStorage.setItem(SAVE_KEY, JSON.stringify({
1217
+ collected: [...state.collected],
1218
+ draws: state.draws,
1219
+ }));
1220
+ }
1221
+
1222
+ // ================================================================
1223
+ // HELPERS
1224
+ // ================================================================
1225
+ const rInfo = id => THEME.rarities.find(r => r.id === id);
1226
+ const tInfo = id => THEME.types.find(t => t.id === id);
1227
+
1228
+ function weightedDraw() {
1229
+ let r = Math.random() * 100;
1230
+ for (const rar of THEME.rarities) { r -= rar.weight; if (r <= 0) return rar.id; }
1231
+ return THEME.rarities[0].id;
1232
+ }
1233
+ function drawOne() {
1234
+ const rarity = weightedDraw();
1235
+ const pool = THEME.cards.filter(c => c.rarity === rarity);
1236
+ return pool[Math.floor(Math.random() * pool.length)];
1237
+ }
1238
+
1239
+ // ================================================================
1240
+ // INIT UI SCAFFOLDING
1241
+ // ================================================================
1242
+ function buildTypeFilters() {
1243
+ const row = document.getElementById('type-filters');
1244
+ row.innerHTML = '<span class="filter-lbl">属性:</span>';
1245
+
1246
+ const all = document.createElement('button');
1247
+ all.className = 'fb active'; all.dataset.f = 'type'; all.dataset.v = 'all';
1248
+ all.textContent = '全部'; row.appendChild(all);
1249
+
1250
+ THEME.types.forEach(t => {
1251
+ const b = document.createElement('button');
1252
+ b.className = 'fb'; b.dataset.f = 'type'; b.dataset.v = t.id;
1253
+ b.textContent = t.label;
1254
+ b.style.borderColor = t.color + '50';
1255
+ b.style.color = t.color;
1256
+ row.appendChild(b);
1257
+ });
1258
+
1259
+ row.querySelectorAll('.fb').forEach(b => b.addEventListener('click', handleFilter));
1260
+ }
1261
+
1262
+ document.getElementById('theme-name').textContent = `🐉 ${THEME.themeName} · 图签系统`;
1263
+ document.getElementById('theme-desc').textContent = THEME.themeDescription;
1264
+
1265
+ // ================================================================
1266
+ // RENDER CARD
1267
+ // ================================================================
1268
+ function cardHTML(card) {
1269
+ const got = state.collected.has(card.id);
1270
+ const isNew = state.fresh.has(card.id);
1271
+ const ri = rInfo(card.rarity);
1272
+ const ti = tInfo(card.type);
1273
+
1274
+ return `
1275
+ <div class="card-wrap">
1276
+ <div class="card ${got ? '' : 'dim'}" data-rarity="${card.rarity}" data-id="${card.id}">
1277
+ <div class="card-inner">
1278
+ ${isNew ? '<div class="new-badge">NEW</div>' : ''}
1279
+ <div class="rbadge ${card.rarity}" style="color:${ri.color}">${ri.stars} ${ri.label}</div>
1280
+ <div class="artwork ${ti.tgClass}">
1281
+ <span class="artwork-emoji">${got ? card.emoji : '❓'}</span>
1282
+ </div>
1283
+ <div class="card-body">
1284
+ <div class="card-name">${got ? card.name : '???'}</div>
1285
+ <span class="tbadge ${ti.tbClass}">${card.type}</span>
1286
+ ${got ? `
1287
+ <div class="card-desc">${card.desc.slice(0, 48)}${card.desc.length > 48 ? '…' : ''}</div>
1288
+ <div class="card-stats">
1289
+ <div class="s"><div class="s-n">攻击</div><div class="s-v">${card.stats.atk}</div></div>
1290
+ <div class="s"><div class="s-n">防御</div><div class="s-v">${card.stats.def}</div></div>
1291
+ <div class="s"><div class="s-n">速度</div><div class="s-v">${card.stats.spd}</div></div>
1292
+ </div>
1293
+ ` : '<div class="card-desc" style="margin-top:.25rem">尚未发现此图签</div>'}
1294
+ </div>
1295
+ </div>
1296
+ </div>
1297
+ </div>`;
1298
+ }
1299
+
1300
+ function filtered() {
1301
+ return THEME.cards.filter(c => {
1302
+ if (state.rFilter !== 'all' && c.rarity !== state.rFilter) return false;
1303
+ if (state.tFilter !== 'all' && c.type !== state.tFilter) return false;
1304
+ if (state.view === 'collected' && !state.collected.has(c.id)) return false;
1305
+ return true;
1306
+ });
1307
+ }
1308
+
1309
+ function renderGrid() {
1310
+ const grid = document.getElementById('grid');
1311
+ const cards = filtered();
1312
+ if (!cards.length) {
1313
+ grid.innerHTML = `<div class="empty"><div class="empty-ico">🐉</div><p>没有符合条件的图签</p><p style="font-size:.8rem;margin-top:.4rem;color:var(--muted)">试试其他筛选,或继续召唤!</p></div>`;
1314
+ return;
1315
+ }
1316
+ grid.innerHTML = cards.map(cardHTML).join('');
1317
+
1318
+ // card click → detail toast
1319
+ grid.querySelectorAll('.card').forEach(el => {
1320
+ el.addEventListener('click', () => {
1321
+ const id = +el.dataset.id;
1322
+ const card = THEME.cards.find(c => c.id === id);
1323
+ if (card && state.collected.has(id)) {
1324
+ toast(`${card.emoji} ${card.name}`,
1325
+ `${card.desc} | 攻击:${card.stats.atk} 防御:${card.stats.def} 速度:${card.stats.spd}`);
1326
+ }
1327
+ });
1328
+ });
1329
+ }
1330
+
1331
+ function updateStats() {
1332
+ const got = state.collected.size;
1333
+ const total = THEME.cards.length;
1334
+ const pct = Math.round(got / total * 100);
1335
+ document.getElementById('s-collected').textContent = got;
1336
+ document.getElementById('s-total').textContent = total;
1337
+ document.getElementById('s-draws').textContent = state.draws;
1338
+ document.getElementById('s-pct').textContent = `${pct}%`;
1339
+ document.getElementById('prog-fill').style.width = `${pct}%`;
1340
+ }
1341
+
1342
+ // ================================================================
1343
+ // GACHA
1344
+ // ================================================================
1345
+ function drawCards(n) {
1346
+ const drawn = Array.from({ length: n }, drawOne);
1347
+ state.draws += n;
1348
+
1349
+ // track new additions
1350
+ const seenThisBatch = new Set();
1351
+ drawn.forEach(c => {
1352
+ const wasNew = !state.collected.has(c.id) && !seenThisBatch.has(c.id);
1353
+ if (wasNew) { state.collected.add(c.id); state.fresh.add(c.id); }
1354
+ seenThisBatch.add(c.id);
1355
+ });
1356
+
1357
+ save();
1358
+ updateStats();
1359
+ showModal(drawn);
1360
+ }
1361
+
1362
+ // ================================================================
1363
+ // MODAL
1364
+ // ================================================================
1365
+ function typeGradClass(typeId) {
1366
+ return tInfo(typeId)?.tgClass || 'tg-void';
1367
+ }
1368
+ function typeBadgeClass(typeId) {
1369
+ return tInfo(typeId)?.tbClass || 'tb-void';
1370
+ }
1371
+
1372
+ function showModal(cards) {
1373
+ const overlay = document.getElementById('overlay');
1374
+ const dGrid = document.getElementById('draw-grid');
1375
+ const ttl = document.getElementById('modal-ttl');
1376
+
1377
+ const hasLeg = cards.some(c => c.rarity === 'legendary');
1378
+ const hasEpic = cards.some(c => c.rarity === 'epic');
1379
+ if (hasLeg) { ttl.textContent = '🌟 传说降临!'; ttl.style.color = '#f59e0b'; }
1380
+ else if (hasEpic) { ttl.textContent = '💜 史诗召唤!'; ttl.style.color = '#a855f7'; }
1381
+ else { ttl.textContent = '✨ 召唤结果'; ttl.style.color = '#f59e0b'; }
1382
+
1383
+ // Track first occurrence in batch for "dup" label
1384
+ const batchFirst = new Map();
1385
+ cards.forEach((c, i) => { if (!batchFirst.has(c.id)) batchFirst.set(c.id, i); });
1386
+
1387
+ dGrid.innerHTML = cards.map((card, i) => {
1388
+ const ri = rInfo(card.rarity);
1389
+ const isDup = batchFirst.get(card.id) !== i || !state.fresh.has(card.id);
1390
+ return `
1391
+ <div class="dc">
1392
+ <div class="dc-inner" id="dci-${i}">
1393
+ <div class="dc-face dc-back"><div class="dc-back-ico">🐉</div></div>
1394
+ <div class="dc-face dc-front" data-rarity="${card.rarity}" style="border:2px solid ${ri.color}">
1395
+ <div class="dc-art ${typeGradClass(card.type)}">${card.emoji}</div>
1396
+ <div class="dc-name">${card.name}</div>
1397
+ <div class="dc-stars" style="color:${ri.color}">${ri.stars}</div>
1398
+ <span class="tbadge ${typeBadgeClass(card.type)}" style="font-size:.55rem">${card.type}</span>
1399
+ ${isDup ? '<div class="dc-dup">重复</div>' : ''}
1400
+ </div>
1401
+ </div>
1402
+ </div>`;
1403
+ }).join('');
1404
+
1405
+ overlay.classList.add('show');
1406
+
1407
+ const delay = cards.length === 1 ? 300 : 200;
1408
+ cards.forEach((card, i) => {
1409
+ setTimeout(() => {
1410
+ document.getElementById(`dci-${i}`)?.classList.add('flipped');
1411
+ if (card.rarity === 'legendary' || card.rarity === 'epic') particles(card.rarity);
1412
+ }, 350 + i * delay);
1413
+ });
1414
+ }
1415
+
1416
+ document.getElementById('close-btn').addEventListener('click', () => {
1417
+ document.getElementById('overlay').classList.remove('show');
1418
+ state.fresh.clear();
1419
+ renderGrid();
1420
+ });
1421
+
1422
+ // click outside modal content closes it
1423
+ document.getElementById('overlay').addEventListener('click', e => {
1424
+ if (e.target === document.getElementById('overlay')) {
1425
+ document.getElementById('close-btn').click();
1426
+ }
1427
+ });
1428
+
1429
+ // ================================================================
1430
+ // PARTICLES
1431
+ // ================================================================
1432
+ function particles(rarity) {
1433
+ const emojis = rarity === 'legendary'
1434
+ ? ['⭐', '✨', '💫', '🌟', '👑', '🔱']
1435
+ : ['💜', '✨', '🌀', '💎', '⚡'];
1436
+
1437
+ for (let i = 0; i < 10; i++) {
1438
+ setTimeout(() => {
1439
+ const el = document.createElement('div');
1440
+ el.className = 'ptcl';
1441
+ el.textContent = emojis[Math.floor(Math.random() * emojis.length)];
1442
+ el.style.cssText = `
1443
+ left:${15 + Math.random() * 70}%;
1444
+ top:${20 + Math.random() * 50}%;
1445
+ font-size:${1 + Math.random()}rem;
1446
+ animation-duration:${1 + Math.random() * .8}s;
1447
+ `;
1448
+ document.body.appendChild(el);
1449
+ setTimeout(() => el.remove(), 2000);
1450
+ }, i * 80);
1451
+ }
1452
+ }
1453
+
1454
+ // ================================================================
1455
+ // FILTERS
1456
+ // ================================================================
1457
+ function handleFilter(e) {
1458
+ const b = e.currentTarget;
1459
+ const f = b.dataset.f, v = b.dataset.v;
1460
+ if (f === 'rarity') { state.rFilter = v; document.querySelectorAll('[data-f="rarity"]').forEach(x => x.classList.remove('active')); }
1461
+ else { state.tFilter = v; document.querySelectorAll('[data-f="type"]').forEach(x => x.classList.remove('active')); }
1462
+ b.classList.add('active');
1463
+ renderGrid();
1464
+ }
1465
+
1466
+ document.querySelectorAll('[data-f="rarity"]').forEach(b => b.addEventListener('click', handleFilter));
1467
+
1468
+ document.querySelectorAll('.vb').forEach(b => b.addEventListener('click', () => {
1469
+ state.view = b.dataset.view;
1470
+ document.querySelectorAll('.vb').forEach(x => x.classList.remove('active'));
1471
+ b.classList.add('active');
1472
+ renderGrid();
1473
+ }));
1474
+
1475
+ // Draw buttons
1476
+ document.getElementById('btn-draw1').addEventListener('click', () => drawCards(1));
1477
+ document.getElementById('btn-draw10').addEventListener('click', () => drawCards(10));
1478
+
1479
+ // Reset button
1480
+ const cfm = document.getElementById('confirm-modal');
1481
+ document.getElementById('btn-reset').addEventListener('click', () => cfm.classList.add('show'));
1482
+ document.getElementById('cfm-cancel').addEventListener('click', () => cfm.classList.remove('show'));
1483
+ document.getElementById('cfm-ok').addEventListener('click', () => {
1484
+ cfm.classList.remove('show');
1485
+ localStorage.removeItem(SAVE_KEY);
1486
+ state.collected = new Set();
1487
+ state.fresh = new Set();
1488
+ state.draws = 0;
1489
+ state.rFilter = 'all';
1490
+ state.tFilter = 'all';
1491
+ state.view = 'all';
1492
+ // reset filter UI
1493
+ document.querySelectorAll('.fb').forEach(b => b.classList.remove('active'));
1494
+ document.querySelector('[data-f="rarity"][data-v="all"]').classList.add('active');
1495
+ document.querySelector('[data-f="type"][data-v="all"]').classList.add('active');
1496
+ document.querySelectorAll('.vb').forEach(b => b.classList.remove('active'));
1497
+ document.querySelector('[data-view="all"]').classList.add('active');
1498
+ updateStats();
1499
+ renderGrid();
1500
+ toast('🔄 图鉴已重置', '所有收集进度已清空,可以重新开始啦!');
1501
+ });
1502
+ cfm.addEventListener('click', e => { if (e.target === cfm) cfm.classList.remove('show'); });
1503
+
1504
+ // ================================================================
1505
+ // TOAST
1506
+ // ================================================================
1507
+ let toastTmr;
1508
+ function toast(title, body) {
1509
+ const el = document.getElementById('toast');
1510
+ document.getElementById('toast-t').textContent = title;
1511
+ document.getElementById('toast-b').textContent = body;
1512
+ el.classList.add('show');
1513
+ clearTimeout(toastTmr);
1514
+ toastTmr = setTimeout(() => el.classList.remove('show'), 3800);
1515
+ }
1516
+
1517
+ // ================================================================
1518
+ // BOOT
1519
+ // ================================================================
1520
+ load();
1521
+ buildTypeFilters();
1522
+ updateStats();
1523
+ renderGrid();
1524
+
1525
+ if (state.draws === 0) {
1526
+ setTimeout(() => toast('🐉 欢迎来到恶龙宝可梦图签系统', '点击「单次召唤」或「十连召唤」开始收集!'), 600);
1527
+ }
1528
+ </script>
1529
+ </body>
1530
+
1531
+ </html>