pearsonkyle commited on
Commit
84818b1
·
verified ·
1 Parent(s): f861d7b

Update index.html

Browse files
Files changed (1) hide show
  1. index.html +1045 -403
index.html CHANGED
@@ -21,33 +21,51 @@
21
  min-height: 100vh;
22
  position: relative;
23
  overflow-x: hidden;
 
24
  }
25
 
26
- /* Animated gradient background */
27
  body::before {
28
  content: '';
29
  position: fixed;
30
- top: -50%;
31
- left: -50%;
32
- width: 200%;
33
- height: 200%;
34
- background:
35
- radial-gradient(circle at 20% 80%, rgba(120, 119, 198, 0.3) 0%, transparent 50%),
36
- radial-gradient(circle at 80% 20%, rgba(255, 119, 198, 0.3) 0%, transparent 50%),
37
- radial-gradient(circle at 40% 40%, rgba(0, 212, 255, 0.2) 0%, transparent 50%),
38
- radial-gradient(circle at 90% 90%, rgba(120, 255, 119, 0.2) 0%, transparent 50%);
39
- animation: gradientShift 20s ease infinite;
40
  z-index: 0;
 
 
41
  }
42
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
43
  @keyframes gradientShift {
44
- 0%, 100% { transform: rotate(0deg) scale(1.5); }
45
- 25% { transform: rotate(90deg) scale(1.8); }
46
- 50% { transform: rotate(180deg) scale(1.5); }
47
- 75% { transform: rotate(270deg) scale(1.8); }
48
  }
49
 
50
- /* Floating orbs for depth */
51
  .orb {
52
  position: fixed;
53
  border-radius: 50%;
@@ -55,11 +73,12 @@
55
  opacity: 0.4;
56
  animation: float 20s infinite ease-in-out;
57
  pointer-events: none;
 
58
  }
59
 
60
  .orb1 {
61
- width: 400px;
62
- height: 400px;
63
  background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
64
  top: 10%;
65
  left: 10%;
@@ -67,8 +86,8 @@
67
  }
68
 
69
  .orb2 {
70
- width: 300px;
71
- height: 300px;
72
  background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);
73
  bottom: 20%;
74
  right: 10%;
@@ -77,8 +96,8 @@
77
  }
78
 
79
  .orb3 {
80
- width: 350px;
81
- height: 350px;
82
  background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%);
83
  top: 50%;
84
  left: 50%;
@@ -98,66 +117,76 @@
98
  }
99
  }
100
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
101
  .container {
102
  position: relative;
103
  z-index: 10;
104
  }
105
 
106
- /* Enhanced glass panel effect */
 
 
 
 
 
 
107
  .glass {
108
- background: rgba(255, 255, 255, 0.03);
109
  backdrop-filter: blur(20px);
110
  -webkit-backdrop-filter: blur(20px);
111
  border: 1px solid rgba(255, 255, 255, 0.08);
112
  box-shadow:
113
- 0 8px 32px 0 rgba(0, 0, 0, 0.37),
114
- inset 0 0 0 1px rgba(255, 255, 255, 0.08),
115
- inset 0 -1px 0 0 rgba(255, 255, 255, 0.05);
116
  transition: all 0.3s ease;
117
  }
118
 
119
  .glass-hover:hover {
120
- background: rgba(255, 255, 255, 0.05);
121
  border-color: rgba(255, 255, 255, 0.15);
122
  box-shadow:
123
- 0 8px 32px 0 rgba(0, 0, 0, 0.5),
124
  inset 0 0 0 1px rgba(255, 255, 255, 0.1),
125
  inset 0 -1px 0 0 rgba(255, 255, 255, 0.08),
126
  0 0 80px -20px rgba(120, 119, 198, 0.5);
127
  transform: translateY(-2px);
128
  }
129
 
130
- /* Ultra glass card */
131
  .glass-card {
132
  background: linear-gradient(135deg,
133
- rgba(255, 255, 255, 0.07) 0%,
134
- rgba(255, 255, 255, 0.02) 100%);
135
- backdrop-filter: blur(40px) saturate(150%);
136
- -webkit-backdrop-filter: blur(40px) saturate(150%);
137
- border: 1px solid rgba(255, 255, 255, 0.1);
138
- border-radius: 24px;
139
  box-shadow:
140
- 0 20px 40px -15px rgba(0, 0, 0, 0.5),
141
- inset 0 0 0 1px rgba(255, 255, 255, 0.1),
142
- inset 0 0 30px rgba(255, 255, 255, 0.02);
143
  overflow: hidden;
144
  position: relative;
145
  }
146
 
147
- .glass-card::before {
148
- content: '';
149
- position: absolute;
150
- top: 0;
151
- left: 0;
152
- right: 0;
153
- height: 1px;
154
- background: linear-gradient(90deg,
155
- transparent,
156
- rgba(255, 255, 255, 0.3),
157
- transparent);
158
- }
159
-
160
- /* Header styling */
161
  .header-text {
162
  font-family: 'Space Grotesk', sans-serif;
163
  font-weight: 700;
@@ -167,6 +196,7 @@
167
  background-clip: text;
168
  text-shadow: 0 0 30px rgba(255, 255, 255, 0.3);
169
  letter-spacing: -1px;
 
170
  }
171
 
172
  .subheader-text {
@@ -174,23 +204,50 @@
174
  font-weight: 300;
175
  letter-spacing: 2px;
176
  text-transform: uppercase;
177
- font-size: 0.875rem;
178
  }
179
 
180
- /* Modern button style */
181
  .glass-button {
182
- background: rgba(255, 255, 255, 0.05);
183
  backdrop-filter: blur(10px);
184
  border: 1px solid rgba(255, 255, 255, 0.1);
185
  color: rgba(255, 255, 255, 0.9);
186
- padding: 14px 28px;
187
- border-radius: 16px;
188
  font-weight: 500;
189
  transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
190
  position: relative;
191
  overflow: hidden;
192
- font-size: 0.95rem;
193
  letter-spacing: 0.5px;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
194
  }
195
 
196
  .glass-button::before {
@@ -208,12 +265,14 @@
208
  transition: transform 0.6s;
209
  }
210
 
211
- .glass-button:hover::before {
 
212
  transform: translateX(100%);
213
  }
214
 
215
- .glass-button:hover {
216
- background: rgba(255, 255, 255, 0.08);
 
217
  border-color: rgba(255, 255, 255, 0.2);
218
  transform: translateY(-2px);
219
  box-shadow:
@@ -223,31 +282,15 @@
223
 
224
  .glass-button:active {
225
  transform: translateY(0);
 
226
  }
227
 
228
- /* Primary button variant */
229
- .glass-button-primary {
230
- background: linear-gradient(135deg,
231
- rgba(102, 126, 234, 0.2) 0%,
232
- rgba(118, 75, 162, 0.2) 100%);
233
- border-color: rgba(102, 126, 234, 0.3);
234
- }
235
-
236
- .glass-button-primary:hover {
237
- background: linear-gradient(135deg,
238
- rgba(102, 126, 234, 0.3) 0%,
239
- rgba(118, 75, 162, 0.3) 100%);
240
- border-color: rgba(102, 126, 234, 0.5);
241
- box-shadow:
242
- 0 10px 30px -10px rgba(102, 126, 234, 0.5),
243
- 0 0 40px -20px rgba(102, 126, 234, 0.3);
244
- }
245
-
246
- /* Card display enhancements */
247
  .card-3d-container {
248
  perspective: 2000px;
249
  width: 100%;
250
- max-width: 420px;
 
251
  }
252
 
253
  .card-3d {
@@ -267,7 +310,7 @@
267
  width: 100%;
268
  height: 100%;
269
  backface-visibility: hidden;
270
- border-radius: 18px;
271
  overflow: hidden;
272
  box-shadow:
273
  0 20px 60px -20px rgba(0, 0, 0, 0.8),
@@ -278,125 +321,178 @@
278
  transform: rotateY(180deg);
279
  }
280
 
281
- /* Price badge styling */
282
- .price-tag {
283
- background: linear-gradient(135deg,
284
- rgba(16, 185, 129, 0.9) 0%,
285
- rgba(5, 150, 105, 0.9) 100%);
286
- backdrop-filter: blur(10px);
287
- color: white;
288
- padding: 6px 14px;
289
- border-radius: 12px;
290
- font-weight: 600;
291
- font-size: 0.875rem;
292
- box-shadow:
293
- 0 4px 15px -3px rgba(16, 185, 129, 0.5),
294
- inset 0 0 0 1px rgba(255, 255, 255, 0.2);
295
  }
296
 
297
- /* Info panel styling */
298
- .info-label {
299
- color: rgba(255, 255, 255, 0.5);
300
- font-size: 0.75rem;
301
- text-transform: uppercase;
302
- letter-spacing: 1px;
303
- font-weight: 500;
304
- margin-bottom: 0.25rem;
305
  }
306
 
307
- .info-value {
308
- color: rgba(255, 255, 255, 0.95);
309
- font-size: 1rem;
310
- font-weight: 400;
311
  }
312
 
313
- /* Legality badges */
314
- .legality-badge {
315
- padding: 6px 12px;
316
- border-radius: 10px;
317
- font-size: 0.75rem;
318
- font-weight: 500;
319
- text-transform: uppercase;
320
- letter-spacing: 0.5px;
321
- backdrop-filter: blur(10px);
322
- transition: all 0.2s;
323
- display: inline-flex;
324
- align-items: center;
325
- gap: 4px;
326
  }
327
 
328
- .legality-legal {
329
- background: rgba(16, 185, 129, 0.15);
330
- border: 1px solid rgba(16, 185, 129, 0.3);
331
- color: rgb(52, 211, 153);
332
  }
333
 
334
- .legality-not-legal {
335
- background: rgba(239, 68, 68, 0.15);
336
- border: 1px solid rgba(239, 68, 68, 0.3);
337
- color: rgb(248, 113, 113);
 
 
 
 
338
  }
339
 
340
- .legality-restricted {
341
- background: rgba(251, 191, 36, 0.15);
342
- border: 1px solid rgba(251, 191, 36, 0.3);
343
- color: rgb(251, 191, 36);
344
  }
345
 
346
- .legality-banned {
347
- background: rgba(127, 29, 29, 0.15);
348
- border: 1px solid rgba(127, 29, 29, 0.3);
349
- color: rgb(239, 68, 68);
 
350
  }
351
 
352
- .legality-badge:hover {
353
- transform: scale(1.05);
354
- box-shadow: 0 0 20px -5px currentColor;
 
355
  }
356
 
357
- /* Gallery cards */
358
  .gallery-item {
359
  position: relative;
360
- border-radius: 16px;
361
  overflow: hidden;
362
  transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
363
  cursor: pointer;
364
- background: rgba(255, 255, 255, 0.02);
365
  border: 1px solid rgba(255, 255, 255, 0.05);
 
 
366
  }
367
 
368
- .gallery-item:hover {
369
- transform: translateY(-8px) scale(1.02);
370
- background: rgba(255, 255, 255, 0.05);
 
371
  border-color: rgba(255, 255, 255, 0.1);
372
  box-shadow:
373
- 0 20px 40px -15px rgba(0, 0, 0, 0.5),
374
- 0 0 40px -10px rgba(102, 126, 234, 0.3);
375
  }
376
 
377
  .gallery-overlay {
378
  position: absolute;
379
  inset: 0;
380
  background: linear-gradient(to top,
381
- rgba(0, 0, 0, 0.9) 0%,
382
- rgba(0, 0, 0, 0.4) 40%,
383
  transparent 100%);
384
  opacity: 0;
385
  transition: opacity 0.3s;
386
- padding: 1rem;
387
  display: flex;
388
  flex-direction: column;
389
  justify-content: flex-end;
390
  }
391
 
392
- .gallery-item:hover .gallery-overlay {
 
393
  opacity: 1;
394
  }
395
 
396
- /* Loading animation */
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
397
  .loading-ring {
398
- width: 64px;
399
- height: 64px;
400
  border: 3px solid rgba(255, 255, 255, 0.1);
401
  border-top-color: rgba(102, 126, 234, 0.8);
402
  border-radius: 50%;
@@ -407,126 +503,344 @@
407
  to { transform: rotate(360deg); }
408
  }
409
 
410
- /* Smooth scrollbar */
411
  ::-webkit-scrollbar {
412
- width: 10px;
413
- height: 10px;
414
  }
415
 
416
  ::-webkit-scrollbar-track {
417
- background: rgba(255, 255, 255, 0.02);
418
- border-radius: 10px;
419
  }
420
 
421
  ::-webkit-scrollbar-thumb {
422
- background: rgba(255, 255, 255, 0.1);
423
- border-radius: 10px;
424
- border: 2px solid transparent;
425
  background-clip: content-box;
426
  }
427
 
428
  ::-webkit-scrollbar-thumb:hover {
429
- background: rgba(255, 255, 255, 0.15);
430
  background-clip: content-box;
431
  }
432
 
433
- /* Divider */
434
- .glass-divider {
435
- height: 1px;
436
- background: linear-gradient(90deg,
437
- transparent,
438
- rgba(255, 255, 255, 0.1),
439
- transparent);
440
- margin: 2rem 0;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
441
  }
442
 
443
- /* Stats grid */
444
- .stat-box {
445
- background: rgba(255, 255, 255, 0.02);
446
- border: 1px solid rgba(255, 255, 255, 0.05);
447
- border-radius: 12px;
448
- padding: 1rem;
449
- transition: all 0.3s;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
450
  }
451
 
452
- .stat-box:hover {
453
- background: rgba(255, 255, 255, 0.04);
454
- border-color: rgba(255, 255, 255, 0.1);
455
- transform: translateY(-2px);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
456
  }
457
 
458
- /* Tooltip */
459
- .tooltip {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
460
  position: relative;
 
461
  }
462
 
463
- .tooltip-content {
 
464
  position: absolute;
465
- bottom: 100%;
466
  left: 50%;
467
- transform: translateX(-50%);
468
- background: rgba(0, 0, 0, 0.9);
469
- backdrop-filter: blur(10px);
 
 
 
 
 
 
 
 
 
470
  color: white;
471
- padding: 8px 12px;
472
- border-radius: 8px;
473
- font-size: 0.75rem;
474
- white-space: nowrap;
475
- opacity: 0;
476
- pointer-events: none;
477
- transition: opacity 0.3s;
478
- margin-bottom: 8px;
479
- box-shadow: 0 10px 20px -10px rgba(0, 0, 0, 0.5);
480
  }
481
 
482
- .tooltip:hover .tooltip-content {
483
- opacity: 1;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
484
  }
485
  </style>
486
  </head>
487
  <body>
488
  <!-- Animated background orbs -->
489
- <div class="orb orb1"></div>
490
- <div class="orb orb2"></div>
491
- <div class="orb orb3"></div>
492
 
493
- <div class="container mx-auto px-4 py-8">
494
  <!-- Header -->
495
- <header class="text-center mb-16 relative">
496
- <p class="subheader-text mb-3">Advanced Card Database</p>
497
- <h1 class="header-text text-6xl md:text-8xl mb-6">
498
  MTG NEXUS
499
  </h1>
500
- <div class="flex justify-center gap-8 text-sm">
501
- <span class="text-white/40"><i class="fas fa-layer-group mr-2"></i>Infinite Library</span>
502
- <span class="text-white/40"><i class="fas fa-chart-line mr-2"></i>Real-time Prices</span>
503
  <span class="text-white/40"><i class="fas fa-shield-alt mr-2"></i>Format Analysis</span>
 
 
504
  </div>
505
  </header>
506
 
507
- <div class="grid grid-cols-1 xl:grid-cols-2 gap-8 max-w-7xl mx-auto">
508
  <!-- Card Display Section -->
509
- <div class="flex flex-col items-center">
510
- <div class="card-3d-container mb-8">
511
  <div class="card-3d" id="card-3d">
512
  <!-- Card Front -->
513
  <div class="card-face">
514
- <div id="card-image" class="w-full h-full bg-gradient-to-br from-gray-900 to-gray-800 flex items-center justify-center">
515
- <div class="text-center p-8 glass-card">
516
- <i class="fas fa-cube text-6xl text-white/30 mb-4"></i>
517
- <p class="text-white/60">Initializing...</p>
518
  </div>
519
  </div>
520
- <div id="face-indicator" class="absolute top-4 right-4 price-tag hidden">
521
  Face 1/2
522
  </div>
523
  </div>
524
 
525
  <!-- Card Back -->
526
- <div class="card-face card-face-back glass-card p-6">
527
  <div id="card-details" class="h-full overflow-y-auto">
528
- <h3 class="text-xl font-semibold text-white mb-4">Card Analysis</h3>
529
- <div id="detailed-info" class="text-white/80">
530
  <p>Loading card data...</p>
531
  </div>
532
  </div>
@@ -535,113 +849,123 @@
535
  </div>
536
 
537
  <!-- Control Buttons -->
538
- <div class="flex flex-wrap gap-3 justify-center mb-6">
539
- <button id="random-btn" class="glass-button glass-button-primary">
540
- <i class="fas fa-shuffle mr-2"></i>Random Card
541
  </button>
542
 
543
- <button id="flip-btn" class="glass-button">
544
  <i class="fas fa-rotate mr-2"></i><span>Details</span>
545
  </button>
546
-
547
- <button id="refresh-btn" class="glass-button">
548
- <i class="fas fa-sync-alt mr-2"></i>Refresh
549
- </button>
550
  </div>
551
 
552
  <!-- Face Navigation -->
553
- <div id="card-face-nav" class="hidden glass-card px-6 py-3 flex items-center gap-6">
554
- <button id="prev-face" class="text-white/60 hover:text-white transition-colors">
555
  <i class="fas fa-chevron-left"></i>
556
  </button>
557
- <span id="face-counter" class="text-white font-medium">1/2</span>
558
- <button id="next-face" class="text-white/60 hover:text-white transition-colors">
559
  <i class="fas fa-chevron-right"></i>
560
  </button>
561
  </div>
562
  </div>
563
 
564
- <!-- Info Panel -->
565
- <div class="space-y-6">
566
  <!-- Main Info Card -->
567
- <div class="glass-card p-6">
568
- <h2 id="card-name" class="text-3xl font-semibold text-white mb-2">No Card Selected</h2>
569
- <div id="card-type" class="text-white/60 mb-6"></div>
570
 
571
- <div class="glass p-4 rounded-xl mb-6">
572
- <div id="card-text" class="text-white/80 leading-relaxed whitespace-pre-line"></div>
573
  </div>
574
 
575
- <!-- Stats Grid -->
576
- <div class="grid grid-cols-2 gap-4">
577
- <div class="stat-box">
578
- <p class="info-label">Stats</p>
579
- <div id="card-stats" class="info-value"></div>
580
  </div>
581
 
582
- <div class="stat-box">
583
- <p class="info-label">Market Price</p>
584
- <div id="card-prices" class="info-value"></div>
585
  </div>
586
 
587
- <div class="stat-box">
588
- <p class="info-label">Rankings</p>
589
- <div id="card-rankings" class="info-value"></div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
590
  </div>
591
 
592
- <div class="stat-box">
593
- <p class="info-label">Set Info</p>
594
- <div id="card-set-info" class="info-value"></div>
 
595
  </div>
596
  </div>
597
  </div>
598
-
599
- <!-- Purchase Links -->
600
- <div class="glass-card p-6">
601
- <h3 class="text-lg font-semibold text-white mb-4">Purchase Options</h3>
602
- <div id="purchase-links" class="flex flex-wrap gap-3"></div>
603
- </div>
604
-
605
- <!-- Legalities -->
606
- <div class="glass-card p-6">
607
- <h3 class="text-lg font-semibold text-white mb-4">Format Legality</h3>
608
- <div id="card-legalities" class="flex flex-wrap gap-2"></div>
609
- </div>
610
-
611
- <!-- External Links -->
612
- <div class="glass-card p-6">
613
- <h3 class="text-lg font-semibold text-white mb-4">Resources</h3>
614
- <div id="card-links" class="flex flex-wrap gap-3"></div>
615
- </div>
616
  </div>
617
  </div>
618
 
619
  <!-- Related Cards Gallery -->
620
- <div id="card-gallery-section" class="mt-20 hidden">
621
  <div class="glass-divider"></div>
622
- <h2 class="text-4xl font-bold text-center text-white mb-10">
623
  Similar Cards
624
  </h2>
625
- <div id="card-gallery" class="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-5 xl:grid-cols-6 gap-4">
626
  <!-- Populated dynamically -->
627
  </div>
628
  </div>
629
 
630
  <!-- Loading Modal -->
631
- <div id="loading" class="fixed inset-0 bg-black/60 backdrop-blur-sm flex items-center justify-center hidden z-50">
632
- <div class="glass-card p-8 text-center">
633
- <div class="loading-ring mx-auto mb-4"></div>
634
- <h3 class="text-xl font-semibold text-white mb-2">Loading</h3>
635
- <p class="text-white/60">Fetching card data...</p>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
636
  </div>
637
  </div>
638
  </div>
639
 
640
  <script>
641
  document.addEventListener('DOMContentLoaded', function() {
 
642
  const randomBtn = document.getElementById('random-btn');
643
  const flipBtn = document.getElementById('flip-btn');
644
- const refreshBtn = document.getElementById('refresh-btn');
645
  const prevFaceBtn = document.getElementById('prev-face');
646
  const nextFaceBtn = document.getElementById('next-face');
647
  const loading = document.getElementById('loading');
@@ -656,35 +980,141 @@
656
  const cardFaceNav = document.getElementById('card-face-nav');
657
  const faceCounter = document.getElementById('face-counter');
658
  const card3d = document.getElementById('card-3d');
 
 
659
 
660
  let currentCardFace = 0;
661
  let currentCardData = null;
 
662
 
663
- // Event listeners
664
- randomBtn.addEventListener('click', () => {
665
  currentCardFace = 0;
666
  fetchRandomCard();
667
- });
668
-
669
- refreshBtn.addEventListener('click', () => {
670
- if (currentCardData) {
671
- displayCard(currentCardData);
672
- fetchRelatedCards(currentCardData);
673
- }
674
- });
675
 
676
  flipBtn.addEventListener('click', handleFlipButton);
677
  prevFaceBtn.addEventListener('click', () => switchFace(-1));
678
  nextFaceBtn.addEventListener('click', () => switchFace(1));
679
 
 
 
 
 
 
 
 
 
 
 
680
  // Initialize
681
  fetchRandomCard();
682
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
683
  function handleFlipButton() {
684
  if (currentCardData && currentCardData.card_faces) {
685
  switchFace(1);
686
  } else {
687
  card3d.classList.toggle('flipped');
 
 
688
  }
689
  }
690
 
@@ -696,41 +1126,96 @@
696
  }
697
 
698
  async function fetchRandomCard() {
 
 
699
  setLoadingState(true);
700
 
701
  try {
702
- const response = await fetch('https://api.scryfall.com/cards/random');
703
- if (!response.ok) throw new Error('Network response was not ok');
704
 
705
  const data = await response.json();
706
  displayCard(data);
707
 
708
- if (data.oracle_text || data.type_line) {
709
- fetchRelatedCards(data);
710
- }
 
 
 
 
 
 
 
 
711
  } catch (error) {
712
  console.error('Error fetching card:', error);
713
- showError('Failed to fetch card. Please try again.');
714
  } finally {
715
  setLoadingState(false);
716
  }
717
  }
718
 
719
- function setLoadingState(isLoading) {
720
- loading.classList.toggle('hidden', !isLoading);
721
- document.body.style.overflow = isLoading ? 'hidden' : '';
722
- randomBtn.disabled = isLoading;
723
- flipBtn.disabled = isLoading;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
724
  }
725
 
726
- function showError(message) {
727
- console.error(message);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
728
  }
729
 
730
  function displayCard(card) {
731
  currentCardData = card;
732
  currentCardFace = 0;
733
 
 
 
 
734
  // Update face navigation
735
  if (card.card_faces && card.card_faces.length > 1) {
736
  cardFaceNav.classList.remove('hidden');
@@ -748,7 +1233,6 @@
748
  displayLinks(card);
749
  displayLegalities(card);
750
  displayRankings(card);
751
- displaySetInfo(card);
752
  }
753
 
754
  function displayCardFace(card, faceIndex) {
@@ -784,7 +1268,7 @@
784
  faceIndicator.classList.add('hidden');
785
  }
786
 
787
- displayCardImage(imageUris, name);
788
 
789
  cardName.textContent = name || 'Unknown Card';
790
  cardType.innerHTML = `<i class="fas fa-layer-group mr-2"></i>${typeText || ''}`;
@@ -793,27 +1277,60 @@
793
  displayStats(stats, card);
794
  }
795
 
796
- function displayCardImage(imageUris, name) {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
797
  if (imageUris && imageUris.large) {
798
  cardImage.innerHTML = `
799
  <img src="${imageUris.large}"
800
  alt="${name}"
801
  class="w-full h-full object-contain"
 
802
  onerror="this.onerror=null; this.src='${imageUris.normal || imageUris.small}';">
803
- ${currentCardData.prices?.usd ? `
804
- <div class="absolute top-4 left-4 price-tag">
805
- $${currentCardData.prices.usd}
806
- </div>
807
- ` : ''}
808
  `;
809
  } else {
810
  cardImage.innerHTML = `
811
  <div class="w-full h-full flex items-center justify-center bg-gradient-to-br from-gray-900 to-gray-800">
812
- <div class="text-center p-8 glass-card">
813
- <i class="fas fa-image-slash text-6xl text-white/30 mb-4"></i>
814
- <p class="text-white/60">Image unavailable</p>
815
  </div>
816
  </div>
 
817
  `;
818
  }
819
  }
@@ -822,16 +1339,16 @@
822
  let statsHtml = '';
823
 
824
  if (stats.power && stats.toughness) {
825
- statsHtml += `<div class="mb-1">${stats.power}/${stats.toughness}</div>`;
826
  }
827
  if (stats.loyalty) {
828
  statsHtml += `<div class="mb-1">Loyalty: ${stats.loyalty}</div>`;
829
  }
830
 
831
- statsHtml += `<div>CMC: ${card.cmc || 0}</div>`;
832
 
833
  if (card.mana_cost) {
834
- statsHtml += `<div>${card.mana_cost}</div>`;
835
  }
836
 
837
  cardStats.innerHTML = statsHtml || '<span class="text-white/40">—</span>';
@@ -839,34 +1356,26 @@
839
 
840
  function displayCardBackDetails(card) {
841
  let detailsHtml = `
842
- <h3 class="text-2xl font-semibold mb-4 text-white">${card.name}</h3>
843
 
844
- <div class="grid grid-cols-2 gap-4 mb-6">
845
- <div class="glass p-3 rounded-lg">
846
  <h4 class="info-label">Set</h4>
847
- <p class="text-sm text-white/80">${card.set_name}</p>
848
- <p class="text-sm text-white/60">${card.set.toUpperCase()} • #${card.collector_number || 'N/A'}</p>
849
- </div>
850
-
851
- <div class="glass p-3 rounded-lg">
852
- <h4 class="info-label">Market</h4>
853
- ${card.prices ? `
854
- ${card.prices.usd ? `<p class="text-sm text-white/80">$${card.prices.usd}</p>` : ''}
855
- ${card.prices.usd_foil ? `<p class="text-sm text-white/60">Foil: $${card.prices.usd_foil}</p>` : ''}
856
- ` : '<p class="text-sm text-white/40">No data</p>'}
857
  </div>
858
  </div>
859
 
860
  ${card.flavor_text ? `
861
- <div class="glass p-3 rounded-lg mb-4">
862
  <h4 class="info-label">Flavor</h4>
863
- <p class="italic text-sm text-white/70">"${card.flavor_text}"</p>
864
  </div>
865
  ` : ''}
866
 
867
- <div class="glass p-3 rounded-lg">
868
  <h4 class="info-label">Artist</h4>
869
- <p class="text-sm text-white/80">${card.artist}</p>
870
  </div>
871
  `;
872
 
@@ -877,10 +1386,16 @@
877
  let pricesHtml = '';
878
  if (card.prices) {
879
  if (card.prices.usd) {
880
- pricesHtml = `<span class="text-green-400 font-semibold">$${card.prices.usd}</span>`;
 
881
  }
882
  if (card.prices.usd_foil) {
883
- pricesHtml += `<br><span class="text-sm text-white/60">Foil: $${card.prices.usd_foil}</span>`;
 
 
 
 
 
884
  }
885
  } else {
886
  pricesHtml = '<span class="text-white/40">—</span>';
@@ -900,15 +1415,15 @@
900
  stores.forEach(store => {
901
  if (store.url) {
902
  linksHtml += `
903
- <a href="${store.url}" target="_blank"
904
- class="glass-button glass-button-primary text-sm">
905
- <i class="fas ${store.icon} mr-2"></i>${store.name}
906
  </a>
907
  `;
908
  }
909
  });
910
  } else {
911
- linksHtml = '<p class="text-white/40 text-sm">No purchase links available</p>';
912
  }
913
  document.getElementById('purchase-links').innerHTML = linksHtml;
914
  }
@@ -924,9 +1439,9 @@
924
  links.forEach(link => {
925
  if (link.url) {
926
  linksHtml += `
927
- <a href="${link.url}" target="_blank"
928
- class="glass-button text-sm">
929
- <i class="fas ${link.icon} mr-2"></i>${link.name}
930
  </a>
931
  `;
932
  }
@@ -964,9 +1479,11 @@
964
 
965
  badge.className = badgeClass;
966
  badge.innerHTML = `
967
- <i class="fas ${icon}"></i>
968
- ${format.charAt(0).toUpperCase() + format.slice(1)}
 
969
  `;
 
970
  cardLegalities.appendChild(badge);
971
  }
972
  });
@@ -977,7 +1494,7 @@
977
  let rankingsHtml = '';
978
 
979
  if (card.edhrec_rank) {
980
- rankingsHtml = `#${card.edhrec_rank} EDH`;
981
  } else {
982
  rankingsHtml = '<span class="text-white/40">—</span>';
983
  }
@@ -985,40 +1502,68 @@
985
  document.getElementById('card-rankings').innerHTML = rankingsHtml;
986
  }
987
 
988
- function displaySetInfo(card) {
989
- let setInfoHtml = `
990
- <div class="text-sm">
991
- <div>${card.set_name}</div>
992
- <div class="text-white/60">${card.set.toUpperCase()}</div>
993
- </div>
994
- `;
995
-
996
- document.getElementById('card-set-info').innerHTML = setInfoHtml;
997
- }
998
-
999
  async function fetchRelatedCards(card) {
1000
  try {
 
 
 
 
 
 
 
 
 
 
1001
  document.getElementById('card-gallery-section').classList.remove('hidden');
1002
  const gallery = document.getElementById('card-gallery');
1003
- gallery.innerHTML = '<div class="col-span-full text-center py-8"><div class="loading-ring mx-auto"></div></div>';
 
 
1004
 
1005
- const searchQuery = buildSearchText(card);
1006
  const response = await fetch(`https://api.deck.doctor/v1/mtg/search?q=${encodeURIComponent(searchQuery)}&topk=12&price_threshold=0`, {
1007
  headers: { 'accept': 'application/json' }
1008
  });
1009
 
1010
- if (!response.ok) throw new Error('Failed to fetch related cards');
 
 
 
1011
 
1012
  const data = await response.json();
1013
- const cards = Array.isArray(data) ? data.map(item => item[0]) : [];
1014
- displayCardGallery(cards);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1015
  } catch (error) {
1016
  console.error('Error fetching related cards:', error);
1017
- document.getElementById('card-gallery').innerHTML = '<div class="col-span-full text-center py-8 text-white/40">Unable to load related cards</div>';
1018
  }
1019
  }
1020
 
1021
- function buildSearchText(card) {
 
 
 
 
1022
  let oracleText = '';
1023
  if (card.card_faces && card.card_faces[currentCardFace]) {
1024
  oracleText = card.card_faces[currentCardFace].oracle_text || '';
@@ -1026,51 +1571,139 @@
1026
  oracleText = card.oracle_text || '';
1027
  }
1028
 
1029
- let cleanText = oracleText.replace(/•/g, '--')
1030
- .replace(/\n/g, '. ')
1031
- .replace(/[{}]/g, '')
1032
- .replace(/\u2014/g, '--')
1033
- .replace(/\s+/g, ' ')
1034
- .trim();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1035
 
1036
- if (card.name) {
1037
- cleanText = cleanText.replace(new RegExp(`\\b${card.name}\\b`, 'g'), 'this card');
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1038
  }
1039
 
1040
- const typeText = card.card_faces && card.card_faces[currentCardFace] ?
1041
- card.card_faces[currentCardFace].type_line : card.type_line;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1042
 
1043
- return `${typeText || ''} ${cleanText}`.trim();
1044
  }
1045
 
1046
- function displayCardGallery(cards) {
1047
  const gallery = document.getElementById('card-gallery');
1048
 
1049
- if (cards.length === 0) {
1050
- gallery.innerHTML = '<div class="col-span-full text-center py-8 text-white/40">No related cards found</div>';
1051
  return;
1052
  }
1053
 
1054
- gallery.innerHTML = cards.map(card => `
1055
- <div class="gallery-item" onclick="loadGalleryCard('${card.id}')">
1056
- <div class="relative w-full" style="aspect-ratio: 0.72;">
1057
- <img src="${getCardImageUrl(card)}"
1058
- alt="${card.name}"
1059
- class="w-full h-full object-cover">
1060
-
1061
- ${card.prices?.usd ? `
1062
- <div class="absolute top-2 right-2 price-tag text-xs">
1063
- $${card.prices.usd}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1064
  </div>
1065
- ` : ''}
1066
-
1067
- <div class="gallery-overlay">
1068
- <h4 class="font-semibold text-sm text-white mb-1">${card.name}</h4>
1069
- <p class="text-xs text-white/60">${card.type_line || ''}</p>
1070
  </div>
1071
  </div>
1072
- </div>
1073
- `).join('');
 
 
 
 
 
 
 
 
 
 
1074
  }
1075
 
1076
  function getCardImageUrl(card) {
@@ -1084,22 +1717,31 @@
1084
  }
1085
 
1086
  window.loadGalleryCard = async function(cardId) {
 
 
1087
  setLoadingState(true);
1088
  try {
1089
  const response = await fetch(`https://api.scryfall.com/cards/${cardId}`);
1090
- if (!response.ok) throw new Error('Failed to fetch card');
1091
  const card = await response.json();
1092
  currentCardFace = 0;
1093
  displayCard(card);
1094
 
 
1095
  window.scrollTo({ top: 0, behavior: 'smooth' });
1096
 
1097
- if (card.oracle_text || card.type_line) {
1098
- fetchRelatedCards(card);
1099
- }
 
 
 
 
 
 
1100
  } catch (error) {
1101
  console.error('Error loading gallery card:', error);
1102
- showError('Failed to load selected card.');
1103
  } finally {
1104
  setLoadingState(false);
1105
  }
 
21
  min-height: 100vh;
22
  position: relative;
23
  overflow-x: hidden;
24
+ scroll-behavior: smooth;
25
  }
26
 
27
+ /* Card art background with subtle blur */
28
  body::before {
29
  content: '';
30
  position: fixed;
31
+ top: 0;
32
+ left: 0;
33
+ width: 100%;
34
+ height: 100%;
35
+ background-size: cover;
36
+ background-position: center;
37
+ background-repeat: no-repeat;
38
+ filter: blur(10px) brightness(0.7); /* Reduced blur and adjusted brightness */
 
 
39
  z-index: 0;
40
+ opacity: 0.4; /* Reduced opacity for subty */
41
+ transform: scale(1.1); /* Reduced scale to prevent edge issues */
42
  }
43
 
44
+ /* Static glass panel overlay */
45
+ body::after {
46
+ content: '';
47
+ position: fixed;
48
+ top: 0;
49
+ left: 0;
50
+ width: 100%;
51
+ height: 100%;
52
+ background: rgba(255, 255, 255, 0.1); /* 20% opacity white overlay */
53
+ backdrop-filter: blur(10px); /* Subtle blur effect */
54
+ -webkit-backdrop-filter: blur(10px); /* Safari support */
55
+ z-index: 1; /* Above background but below content */
56
+ pointer-events: none; /* Allow interactions to pass through */
57
+ }
58
+
59
+ /* Remove the additional blur overlay */
60
+
61
  @keyframes gradientShift {
62
+ 0%, 100% { transform: rotate(0deg) scale(1.2); }
63
+ 25% { transform: rotate(90deg) scale(1.5); }
64
+ 50% { transform: rotate(180deg) scale(1.2); }
65
+ 75% { transform: rotate(270deg) scale(1.5); }
66
  }
67
 
68
+ /* Optimized floating orbs with better mobile performance */
69
  .orb {
70
  position: fixed;
71
  border-radius: 50%;
 
73
  opacity: 0.4;
74
  animation: float 20s infinite ease-in-out;
75
  pointer-events: none;
76
+ will-change: transform;
77
  }
78
 
79
  .orb1 {
80
+ width: min(400px, 40vw);
81
+ height: min(400px, 40vw);
82
  background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
83
  top: 10%;
84
  left: 10%;
 
86
  }
87
 
88
  .orb2 {
89
+ width: min(300px, 30vw);
90
+ height: min(300px, 30vw);
91
  background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);
92
  bottom: 20%;
93
  right: 10%;
 
96
  }
97
 
98
  .orb3 {
99
+ width: min(350px, 35vw);
100
+ height: min(350px, 35vw);
101
  background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%);
102
  top: 50%;
103
  left: 50%;
 
117
  }
118
  }
119
 
120
+ /* Mobile optimizations - reduce animations on smaller screens */
121
+ @media (max-width: 768px) {
122
+ body::before {
123
+ animation-duration: 30s;
124
+ filter: blur(80px) brightness(0.6); /* Reduced blur but kept darkening */
125
+ }
126
+
127
+ .orb {
128
+ filter: blur(20px);
129
+ opacity: 0.2;
130
+ }
131
+
132
+ .orb1, .orb2, .orb3 {
133
+ animation-duration: 40s;
134
+ }
135
+ }
136
+
137
  .container {
138
  position: relative;
139
  z-index: 10;
140
  }
141
 
142
+ /* Ensure the card art is properly positioned */
143
+ #card-image {
144
+ position: relative;
145
+ z-index: 5;
146
+ }
147
+
148
+ /* Enhanced glass panel effect with better mobile support */
149
  .glass {
150
+ background: rgba(0, 0, 0, 0.4); /* Darker background */
151
  backdrop-filter: blur(20px);
152
  -webkit-backdrop-filter: blur(20px);
153
  border: 1px solid rgba(255, 255, 255, 0.08);
154
  box-shadow:
155
+ 0 8px 32px 0 rgba(0, 0, 0, 0.6), /* Darker shadow */
156
+ inset 0 0 0 1px rgba(255, 255, 255, 0.05), /* Reduced inner glow */
157
+ inset 0 -1px 0 0 rgba(255, 255, 255, 0.03); /* Reduced inner highlight */
158
  transition: all 0.3s ease;
159
  }
160
 
161
  .glass-hover:hover {
162
+ background: rgba(0, 0, 0, 0.5); /* Darker on hover */
163
  border-color: rgba(255, 255, 255, 0.15);
164
  box-shadow:
165
+ 0 8px 32px 0 rgba(0, 0, 0, 0.7),
166
  inset 0 0 0 1px rgba(255, 255, 255, 0.1),
167
  inset 0 -1px 0 0 rgba(255, 255, 255, 0.08),
168
  0 0 80px -20px rgba(120, 119, 198, 0.5);
169
  transform: translateY(-2px);
170
  }
171
 
172
+ /* Mobile-first glass card design - Much darker */
173
  .glass-card {
174
  background: linear-gradient(135deg,
175
+ rgba(0, 0, 0, 0.8) 0%, /* Much darker gradient */
176
+ rgba(0, 0, 0, 0.7) 100%);
177
+ backdrop-filter: blur(min(40px, 20px));
178
+ -webkit-backdrop-filter: blur(min(40px, 20px));
179
+ border: 1px solid rgba(255, 255, 255, 0.08); /* Subtle border */
180
+ border-radius: min(24px, 16px);
181
  box-shadow:
182
+ 0 20px 40px -15px rgba(0, 0, 0, 0.8), /* Much darker shadow */
183
+ inset 0 0 0 1px rgba(255, 255, 255, 0.05), /* Minimal inner glow */
184
+ inset 0 0 30px rgba(0, 0, 0, 0.3); /* Dark inner shadow instead of light */
185
  overflow: hidden;
186
  position: relative;
187
  }
188
 
189
+ /* Responsive typography */
 
 
 
 
 
 
 
 
 
 
 
 
 
190
  .header-text {
191
  font-family: 'Space Grotesk', sans-serif;
192
  font-weight: 700;
 
196
  background-clip: text;
197
  text-shadow: 0 0 30px rgba(255, 255, 255, 0.3);
198
  letter-spacing: -1px;
199
+ line-height: 1;
200
  }
201
 
202
  .subheader-text {
 
204
  font-weight: 300;
205
  letter-spacing: 2px;
206
  text-transform: uppercase;
207
+ font-size: clamp(0.75rem, 2vw, 0.875rem);
208
  }
209
 
210
+ /* Responsive button styles */
211
  .glass-button {
212
+ background: rgba(0, 0, 0, 0.4); /* Darker button background */
213
  backdrop-filter: blur(10px);
214
  border: 1px solid rgba(255, 255, 255, 0.1);
215
  color: rgba(255, 255, 255, 0.9);
216
+ padding: min(14px, 3vw) min(28px, 6vw);
217
+ border-radius: min(16px, 12px);
218
  font-weight: 500;
219
  transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
220
  position: relative;
221
  overflow: hidden;
222
+ font-size: clamp(0.85rem, 2.5vw, 0.95rem);
223
  letter-spacing: 0.5px;
224
+ touch-action: manipulation;
225
+ -webkit-tap-highlight-color: transparent;
226
+ user-select: none;
227
+ display: inline-flex;
228
+ align-items: center;
229
+ justify-content: center;
230
+ min-height: 44px; /* iOS minimum tap target */
231
+ white-space: nowrap;
232
+ }
233
+
234
+ /* Specific styles for purchase buttons */
235
+ #purchase-links .glass-button {
236
+ padding: 8px 12px;
237
+ font-size: 0.85rem;
238
+ min-width: auto;
239
+ }
240
+
241
+ /* Ensure full text is visible on mobile */
242
+ @media (max-width: 768px) {
243
+ #purchase-links .glass-button {
244
+ padding: 6px 10px;
245
+ font-size: 0.8rem;
246
+ }
247
+
248
+ #purchase-links .glass-button i {
249
+ margin-right: 4px;
250
+ }
251
  }
252
 
253
  .glass-button::before {
 
265
  transition: transform 0.6s;
266
  }
267
 
268
+ .glass-button:hover::before,
269
+ .glass-button:focus::before {
270
  transform: translateX(100%);
271
  }
272
 
273
+ .glass-button:hover,
274
+ .glass-button:focus {
275
+ background: rgba(0, 0, 0, 0.5); /* Darker on hover */
276
  border-color: rgba(255, 255, 255, 0.2);
277
  transform: translateY(-2px);
278
  box-shadow:
 
282
 
283
  .glass-button:active {
284
  transform: translateY(0);
285
+ transition: transform 0.1s;
286
  }
287
 
288
+ /* Mobile-optimized card container */
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
289
  .card-3d-container {
290
  perspective: 2000px;
291
  width: 100%;
292
+ max-width: min(420px, 90vw);
293
+ margin: 0 auto;
294
  }
295
 
296
  .card-3d {
 
310
  width: 100%;
311
  height: 100%;
312
  backface-visibility: hidden;
313
+ border-radius: min(18px, 4vw);
314
  overflow: hidden;
315
  box-shadow:
316
  0 20px 60px -20px rgba(0, 0, 0, 0.8),
 
321
  transform: rotateY(180deg);
322
  }
323
 
324
+ /* Responsive grid layouts */
325
+ .responsive-grid {
326
+ display: grid;
327
+ gap: min(1.5rem, 4vw);
328
+ grid-template-columns: 1fr;
 
 
 
 
 
 
 
 
 
329
  }
330
 
331
+ @media (min-width: 1024px) {
332
+ .responsive-grid {
333
+ grid-template-columns: 1fr 1fr;
334
+ }
 
 
 
 
335
  }
336
 
337
+ .stats-grid {
338
+ display: grid;
339
+ grid-template-columns: repeat(auto-fit, minmax(min(140px, 45vw), 1fr));
340
+ gap: min(0.75rem, 3vw);
341
  }
342
 
343
+ .stat-box {
344
+ background: rgba(0, 0, 0, 0.6); /* Much darker stat boxes */
345
+ border: 1px solid rgba(255, 255, 255, 0.08); /* Subtle border */
346
+ border-radius: min(12px, 3vw);
347
+ padding: min(1rem, 4vw);
348
+ transition: all 0.3s;
349
+ min-height: min(80px, 20vw);
350
+ display: flex;
351
+ flex-direction: column;
352
+ justify-content: center;
353
+ font-size: 1.2em; /* Increased font size */
 
 
354
  }
355
 
356
+ .stat-box:hover {
357
+ background: rgba(0, 0, 0, 0.7); /* Darker on hover */
358
+ border-color: rgba(255, 255, 255, 0.15);
359
+ transform: translateY(-2px);
360
  }
361
 
362
+ /* Increase font size for info labels and values */
363
+ .info-label {
364
+ font-size: 0.85rem; /* Increased from 0.75rem */
365
+ font-weight: 600;
366
+ text-transform: uppercase;
367
+ letter-spacing: 0.05em;
368
+ color: rgba(255, 255, 255, 0.4);
369
+ margin-bottom: 0.25rem;
370
  }
371
 
372
+ .info-value {
373
+ color: rgba(255, 255, 255, 0.9);
374
+ font-weight: 500;
375
+ font-size: 1.2em; /* Increased font size */
376
  }
377
 
378
+ /* Mobile-optimized gallery */
379
+ .gallery-grid {
380
+ display: grid;
381
+ grid-template-columns: repeat(auto-fill, minmax(min(150px, 45vw), 1fr));
382
+ gap: min(1rem, 3vw);
383
  }
384
 
385
+ @media (min-width: 640px) {
386
+ .gallery-grid {
387
+ grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
388
+ }
389
  }
390
 
 
391
  .gallery-item {
392
  position: relative;
393
+ border-radius: min(16px, 4vw);
394
  overflow: hidden;
395
  transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
396
  cursor: pointer;
397
+ background: rgba(0, 0, 0, 0.4); /* Darker gallery items */
398
  border: 1px solid rgba(255, 255, 255, 0.05);
399
+ touch-action: manipulation;
400
+ -webkit-tap-highlight-color: transparent;
401
  }
402
 
403
+ .gallery-item:hover,
404
+ .gallery-item:focus {
405
+ transform: translateY(-4px) scale(1.02);
406
+ background: rgba(0, 0, 0, 0.5);
407
  border-color: rgba(255, 255, 255, 0.1);
408
  box-shadow:
409
+ 0 15px 30px -10px rgba(0, 0, 0, 0.5),
410
+ 0 0 30px -8px rgba(102, 126, 234, 0.3);
411
  }
412
 
413
  .gallery-overlay {
414
  position: absolute;
415
  inset: 0;
416
  background: linear-gradient(to top,
417
+ rgba(0, 0, 0, 0.95) 0%, /* Much darker gradient */
418
+ rgba(0, 0, 0, 0.6) 40%,
419
  transparent 100%);
420
  opacity: 0;
421
  transition: opacity 0.3s;
422
+ padding: min(1rem, 3vw);
423
  display: flex;
424
  flex-direction: column;
425
  justify-content: flex-end;
426
  }
427
 
428
+ .gallery-item:hover .gallery-overlay,
429
+ .gallery-item:focus .gallery-overlay {
430
  opacity: 1;
431
  }
432
 
433
+ /* Responsive pricing and badges */
434
+ .price-tag {
435
+ background: linear-gradient(135deg,
436
+ rgba(16, 185, 129, 0.9) 0%,
437
+ rgba(5, 150, 105, 0.9) 100%);
438
+ backdrop-filter: blur(10px);
439
+ color: white;
440
+ padding: min(6px, 2vw) min(14px, 4vw);
441
+ border-radius: min(12px, 3vw);
442
+ font-weight: 600;
443
+ font-size: clamp(0.75rem, 2.5vw, 0.875rem);
444
+ box-shadow:
445
+ 0 4px 15px -3px rgba(16, 185, 129, 0.5),
446
+ inset 0 0 0 1px rgba(255, 255, 255, 0.2);
447
+ white-space: nowrap;
448
+ }
449
+
450
+ .legality-badge {
451
+ padding: min(8px, 2.5vw) min(14px, 3.5vw);
452
+ border-radius: min(12px, 3vw);
453
+ font-size: clamp(0.8rem, 2.5vw, 0.9rem);
454
+ font-weight: 500;
455
+ text-transform: uppercase;
456
+ letter-spacing: 0.5px;
457
+ backdrop-filter: blur(10px);
458
+ transition: all 0.2s;
459
+ display: inline-flex;
460
+ align-items: center;
461
+ gap: min(5px, 1.2vw);
462
+ white-space: nowrap;
463
+ touch-action: manipulation;
464
+ -webkit-tap-highlight-color: transparent;
465
+ min-height: 36px;
466
+ }
467
+
468
+ .legality-legal {
469
+ background: rgba(16, 185, 129, 0.2);
470
+ border: 1px solid rgba(16, 185, 129, 0.4);
471
+ color: rgb(52, 211, 153);
472
+ }
473
+
474
+ .legality-not-legal {
475
+ background: rgba(148, 163, 184, 0.2);
476
+ border: 1px solid rgba(148, 163, 184, 0.4);
477
+ color: rgb(148, 163, 184);
478
+ }
479
+
480
+ .legality-restricted {
481
+ background: rgba(251, 191, 36, 0.2);
482
+ border: 1px solid rgba(251, 191, 36, 0.4);
483
+ color: rgb(251, 191, 36);
484
+ }
485
+
486
+ .legality-banned {
487
+ background: rgba(239, 68, 68, 0.2);
488
+ border: 1px solid rgba(239, 68, 68, 0.4);
489
+ color: rgb(248, 113, 113);
490
+ }
491
+
492
+ /* Loading states with better mobile support */
493
  .loading-ring {
494
+ width: min(64px, 15vw);
495
+ height: min(64px, 15vw);
496
  border: 3px solid rgba(255, 255, 255, 0.1);
497
  border-top-color: rgba(102, 126, 234, 0.8);
498
  border-radius: 50%;
 
503
  to { transform: rotate(360deg); }
504
  }
505
 
506
+ /* Enhanced scrollbar for all devices */
507
  ::-webkit-scrollbar {
508
+ width: min(10px, 2vw);
509
+ height: min(10px, 2vw);
510
  }
511
 
512
  ::-webkit-scrollbar-track {
513
+ background: rgba(0, 0, 0, 0.3); /* Darker scrollbar track */
514
+ border-radius: min(10px, 2vw);
515
  }
516
 
517
  ::-webkit-scrollbar-thumb {
518
+ background: rgba(255, 255, 255, 0.15); /* Slightly brighter thumb */
519
+ border-radius: min(10px, 2vw);
520
+ border: 1px solid transparent;
521
  background-clip: content-box;
522
  }
523
 
524
  ::-webkit-scrollbar-thumb:hover {
525
+ background: rgba(255, 255, 255, 0.25);
526
  background-clip: content-box;
527
  }
528
 
529
+ /* Mobile-specific improvements */
530
+ @media (max-width: 768px) {
531
+ .container {
532
+ padding: 1rem;
533
+ }
534
+
535
+ .glass-card {
536
+ border-radius: 16px;
537
+ padding: 1rem;
538
+ }
539
+
540
+ .glass-button {
541
+ padding: 12px 20px;
542
+ border-radius: 12px;
543
+ font-size: 0.9rem;
544
+ }
545
+
546
+ .card-3d-container {
547
+ max-width: min(350px, 85vw);
548
+ }
549
+
550
+ .stats-grid {
551
+ grid-template-columns: 1fr 1fr;
552
+ }
553
+
554
+ .gallery-grid {
555
+ grid-template-columns: repeat(2, 1fr);
556
+ gap: 0.75rem;
557
+ }
558
+
559
+ /* Stack layout for mobile */
560
+ .responsive-grid {
561
+ gap: 1rem;
562
+ }
563
+
564
+ .header-text {
565
+ font-size: clamp(2.5rem, 12vw, 4rem);
566
+ margin-bottom: 1rem;
567
+ }
568
  }
569
 
570
+ /* Very small mobile screens */
571
+ @media (max-width: 480px) {
572
+ .container {
573
+ padding: 0.75rem;
574
+ }
575
+
576
+ .glass-card {
577
+ padding: 0.75rem;
578
+ }
579
+
580
+ .stats-grid {
581
+ grid-template-columns: 1fr;
582
+ gap: 0.5rem;
583
+ }
584
+
585
+ .stat-box {
586
+ padding: 0.75rem;
587
+ min-height: 60px;
588
+ }
589
+
590
+ .gallery-grid {
591
+ grid-template-columns: 1fr 1fr;
592
+ }
593
+
594
+ .header-text {
595
+ font-size: clamp(2rem, 10vw, 3rem);
596
+ }
597
  }
598
 
599
+ /* Tablet optimizations */
600
+ @media (min-width: 768px) and (max-width: 1024px) {
601
+ .responsive-grid {
602
+ gap: 1.25rem;
603
+ }
604
+
605
+ .gallery-grid {
606
+ grid-template-columns: repeat(3, 1fr);
607
+ }
608
+
609
+ .stats-grid {
610
+ grid-template-columns: repeat(3, 1fr);
611
+ }
612
+ }
613
+
614
+ /* Large desktop optimizations */
615
+ @media (min-width: 1280px) {
616
+ .gallery-grid {
617
+ grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
618
+ }
619
+
620
+ .responsive-grid {
621
+ gap: 2rem;
622
+ }
623
  }
624
 
625
+ /* Focus management for accessibility */
626
+ .glass-button:focus,
627
+ .gallery-item:focus,
628
+ .legality-badge:focus {
629
+ outline: 2px solid rgba(102, 126, 234, 0.6);
630
+ outline-offset: 2px;
631
+ }
632
+
633
+ /* Reduced motion preferences */
634
+ @media (prefers-reduced-motion: reduce) {
635
+ * {
636
+ animation-duration: 0.01ms !important;
637
+ animation-iteration-count: 1 !important;
638
+ transition-duration: 0.01ms !important;
639
+ }
640
+
641
+ .orb {
642
+ animation: none;
643
+ }
644
+
645
+ body::before {
646
+ animation: none;
647
+ }
648
+ }
649
+
650
+ /* High contrast mode support */
651
+ @media (prefers-contrast: high) {
652
+ .glass-card {
653
+ background: rgba(0, 0, 0, 0.9);
654
+ border: 2px solid rgba(255, 255, 255, 0.3);
655
+ }
656
+
657
+ .glass-button {
658
+ background: rgba(0, 0, 0, 0.8);
659
+ border: 2px solid rgba(255, 255, 255, 0.4);
660
+ }
661
+ }
662
+
663
+ /* Dark mode optimizations */
664
+ @media (prefers-color-scheme: dark) {
665
+ .glass {
666
+ background: rgba(0, 0, 0, 0.6);
667
+ border: 1px solid rgba(255, 255, 255, 0.1);
668
+ }
669
+ }
670
+
671
+ /* Print styles */
672
+ @media print {
673
+ body::before,
674
+ .orb {
675
+ display: none;
676
+ }
677
+
678
+ .glass-card,
679
+ .glass {
680
+ background: white;
681
+ color: black;
682
+ border: 1px solid #ccc;
683
+ box-shadow: none;
684
+ }
685
+ }
686
+
687
+ /* Smooth scrolling behavior */
688
+ html {
689
+ scroll-behavior: smooth;
690
+ }
691
+
692
+ /* Enhanced touch scrolling for iOS */
693
+ .card-face-back {
694
+ -webkit-overflow-scrolling: touch;
695
+ }
696
+
697
+ /* Button loading state */
698
+ .glass-button:disabled {
699
+ opacity: 0.6;
700
+ pointer-events: none;
701
+ cursor: not-allowed;
702
+ }
703
+
704
+ .glass-button.loading {
705
  position: relative;
706
+ color: transparent;
707
  }
708
 
709
+ .glass-button.loading::after {
710
+ content: '';
711
  position: absolute;
712
+ top: 50%;
713
  left: 50%;
714
+ width: 16px;
715
+ height: 16px;
716
+ border: 2px solid rgba(255, 255, 255, 0.3);
717
+ border-top-color: white;
718
+ border-radius: 50%;
719
+ animation: spin 1s linear infinite;
720
+ transform: translate(-50%, -50%);
721
+ }
722
+
723
+ /* Improved text selection */
724
+ ::selection {
725
+ background: rgba(102, 126, 234, 0.3);
726
  color: white;
 
 
 
 
 
 
 
 
 
727
  }
728
 
729
+ ::-moz-selection {
730
+ background: rgba(102, 126, 234, 0.3);
731
+ color: white;
732
+ }
733
+
734
+ /* Better image loading states */
735
+ .card-image-loading {
736
+ background: linear-gradient(90deg,
737
+ rgba(0, 0, 0, 0.4) 0%,
738
+ rgba(255, 255, 255, 0.05) 50%,
739
+ rgba(0, 0, 0, 0.4) 100%);
740
+ background-size: 200% 100%;
741
+ animation: shimmer 2s infinite;
742
+ }
743
+
744
+ @keyframes shimmer {
745
+ 0% { background-position: -200% 0; }
746
+ 100% { background-position: 200% 0; }
747
+ }
748
+
749
+ /* Safe area support for iOS devices with notches */
750
+ @supports (padding: max(0px)) {
751
+ .container {
752
+ padding-left: max(1rem, env(safe-area-inset-left));
753
+ padding-right: max(1rem, env(safe-area-inset-right));
754
+ padding-top: max(1rem, env(safe-area-inset-top));
755
+ padding-bottom: max(1rem, env(safe-area-inset-bottom));
756
+ }
757
+ }
758
+
759
+ /* Keyboard navigation improvements */
760
+ .glass-button:focus-visible,
761
+ .gallery-item:focus-visible {
762
+ outline: 3px solid rgba(102, 126, 234, 0.8);
763
+ outline-offset: 2px;
764
+ }
765
+
766
+ /* Error state styling */
767
+ .error-state {
768
+ background: rgba(239, 68, 68, 0.1);
769
+ border: 1px solid rgba(239, 68, 68, 0.3);
770
+ color: rgb(248, 113, 113);
771
+ padding: 1rem;
772
+ border-radius: 12px;
773
+ text-align: center;
774
+ }
775
+
776
+ /* Success state styling */
777
+ .success-state {
778
+ background: rgba(16, 185, 129, 0.1);
779
+ border: 1px solid rgba(16, 185, 129, 0.3);
780
+ color: rgb(52, 211, 153);
781
+ padding: 1rem;
782
+ border-radius: 12px;
783
+ text-align: center;
784
+ }
785
+
786
+ .info-label {
787
+ font-size: 0.65rem;
788
+ font-weight: 600;
789
+ text-transform: uppercase;
790
+ letter-spacing: 0.05em;
791
+ color: rgba(255, 255, 255, 0.4);
792
+ margin-bottom: 0.25rem;
793
+ }
794
+
795
+ .info-value {
796
+ color: rgba(255, 255, 255, 0.9);
797
+ font-weight: 500;
798
  }
799
  </style>
800
  </head>
801
  <body>
802
  <!-- Animated background orbs -->
803
+ <div class="orb orb1" aria-hidden="true"></div>
804
+ <div class="orb orb2" aria-hidden="true"></div>
805
+ <div class="orb orb3" aria-hidden="true"></div>
806
 
807
+ <div class="container mx-auto py-4 md:py-8">
808
  <!-- Header -->
809
+ <header class="text-center mb-8 md:mb-16 relative px-4">
810
+ <p class="subheader-text mb-2 md:mb-3">Advanced Card Database</p>
811
+ <h1 class="header-text text-4xl md:text-6xl lg:text-8xl mb-4 md:mb-6">
812
  MTG NEXUS
813
  </h1>
814
+ <div class="flex justify-center gap-4 md:gap-8 text-xs md:text-sm flex-wrap">
 
 
815
  <span class="text-white/40"><i class="fas fa-shield-alt mr-2"></i>Format Analysis</span>
816
+ <span class="text-white/40"><i class="fas fa-chart-line mr-2"></i>Price Tracking</span>
817
+ <span class="text-white/40"><i class="fas fa-search mr-2"></i>Smart Discovery</span>
818
  </div>
819
  </header>
820
 
821
+ <div class="responsive-grid max-w-7xl mx-auto">
822
  <!-- Card Display Section -->
823
+ <div class="flex flex-col items-center order-1 lg:order-none">
824
+ <div class="card-3d-container mb-4 md:mb-6">
825
  <div class="card-3d" id="card-3d">
826
  <!-- Card Front -->
827
  <div class="card-face">
828
+ <div id="card-image" class="w-full h-full bg-gradient-to-br from-gray-900 to-gray-800 flex items-center justify-center card-image-loading">
829
+ <div class="text-center p-4 md:p-8 glass-card">
830
+ <i class="fas fa-cube text-4xl md:text-6xl text-white/30 mb-2 md:mb-4"></i>
831
+ <p class="text-white/60 text-sm md:text-base">Initializing...</p>
832
  </div>
833
  </div>
834
+ <div id="face-indicator" class="absolute top-2 md:top-4 right-2 md:right-4 price-tag hidden text-xs md:text-sm">
835
  Face 1/2
836
  </div>
837
  </div>
838
 
839
  <!-- Card Back -->
840
+ <div class="card-face card-face-back glass-card p-3 md:p-6">
841
  <div id="card-details" class="h-full overflow-y-auto">
842
+ <h3 class="text-lg md:text-xl font-semibold text-white mb-3 md:mb-4">Card Analysis</h3>
843
+ <div id="detailed-info" class="text-white/80 text-sm md:text-base">
844
  <p>Loading card data...</p>
845
  </div>
846
  </div>
 
849
  </div>
850
 
851
  <!-- Control Buttons -->
852
+ <div class="flex flex-wrap gap-2 justify-center mb-3 md:mb-4 px-4">
853
+ <button id="random-btn" class="glass-button glass-button-primary" aria-label="Get Random Card">
854
+ <i class="fas fa-shuffle mr-2"></i><span class="hidden sm:inline">Random Card</span><span class="sm:hidden">Random</span>
855
  </button>
856
 
857
+ <button id="flip-btn" class="glass-button" aria-label="Flip Card">
858
  <i class="fas fa-rotate mr-2"></i><span>Details</span>
859
  </button>
 
 
 
 
860
  </div>
861
 
862
  <!-- Face Navigation -->
863
+ <div id="card-face-nav" class="hidden glass-card px-3 md:px-4 py-2 flex items-center gap-3 md:gap-4">
864
+ <button id="prev-face" class="text-white/60 hover:text-white transition-colors p-2" aria-label="Previous Face">
865
  <i class="fas fa-chevron-left"></i>
866
  </button>
867
+ <span id="face-counter" class="text-white font-medium text-sm md:text-base">1/2</span>
868
+ <button id="next-face" class="text-white/60 hover:text-white transition-colors p-2" aria-label="Next Face">
869
  <i class="fas fa-chevron-right"></i>
870
  </button>
871
  </div>
872
  </div>
873
 
874
+ <!-- Consolidated Info Panel -->
875
+ <div class="space-y-3 md:space-y-4 order-2 lg:order-none">
876
  <!-- Main Info Card -->
877
+ <div class="glass-card p-3 md:p-5">
878
+ <h2 id="card-name" class="text-xl md:text-2xl font-semibold text-white mb-1 break-words">No Card Selected</h2>
879
+ <div id="card-type" class="text-white/60 mb-3 md:mb-4 text-sm md:text-base"></div>
880
 
881
+ <div class="glass p-2 md:p-3 rounded-lg mb-3 md:mb-4 max-h-32 md:max-h-40 overflow-y-auto">
882
+ <div id="card-text" class="text-white/80 leading-relaxed whitespace-pre-line text-xs md:text-sm"></div>
883
  </div>
884
 
885
+ <!-- Stats Grid - Removed Set Info -->
886
+ <div class="stats-grid mb-4 md:mb-6">
887
+ <div class="stat-box bg-black/40 border border-white/5">
888
+ <p class="info-label text-xs">Stats</p>
889
+ <div id="card-stats" class="info-value text-sm md:text-base"></div>
890
  </div>
891
 
892
+ <div class="stat-box bg-black/40 border border-white/5">
893
+ <p class="info-label text-xs">Market Price</p>
894
+ <div id="card-prices" class="info-value text-sm md:text-base"></div>
895
  </div>
896
 
897
+ <div class="stat-box bg-black/40 border border-white/5">
898
+ <p class="info-label text-xs">Rankings</p>
899
+ <div id="card-rankings" class="info-value text-sm md:text-base"></div>
900
+ </div>
901
+ </div>
902
+
903
+ <!-- Purchase Links Section -->
904
+ <div class="mb-4 md:mb-6">
905
+ <h3 class="font-semibold text-white mb-2 text-sm md:text-base">Purchase Options</h3>
906
+ <div id="purchase-links" class="flex flex-wrap gap-2 justify-start">
907
+ <!-- Buttons will be inserted here dynamically -->
908
+ </div>
909
+ </div>
910
+
911
+ <!-- Combined Sections -->
912
+ <div class="grid grid-cols-1 md:grid-cols-2 gap-3 md:gap-4">
913
+ <!-- Legalities -->
914
+ <div>
915
+ <h3 class="font-semibold text-white mb-2 text-sm md:text-base">Format Legality</h3>
916
+ <div id="card-legalities" class="flex flex-wrap gap-1"></div>
917
  </div>
918
 
919
+ <!-- Links -->
920
+ <div>
921
+ <h3 class="font-semibold text-white mb-2 text-sm md:text-base">Resources & Links</h3>
922
+ <div id="card-links" class="flex flex-wrap gap-2"></div>
923
  </div>
924
  </div>
925
  </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
926
  </div>
927
  </div>
928
 
929
  <!-- Related Cards Gallery -->
930
+ <div id="card-gallery-section" class="mt-8 md:mt-16 hidden px-4">
931
  <div class="glass-divider"></div>
932
+ <h2 class="text-2xl md:text-3xl font-bold text-center text-white mb-6 md:mb-8">
933
  Similar Cards
934
  </h2>
935
+ <div id="card-gallery" class="gallery-grid max-w-7xl mx-auto">
936
  <!-- Populated dynamically -->
937
  </div>
938
  </div>
939
 
940
  <!-- Loading Modal -->
941
+ <div id="loading" class="fixed inset-0 bg-black/60 backdrop-blur-sm flex items-center justify-center hidden z-50 p-4">
942
+ <div class="glass-card p-4 md:p-6 text-center max-w-sm w-full">
943
+ <div class="loading-ring mx-auto mb-2 md:mb-3"></div>
944
+ <h3 class="text-base md:text-lg font-semibold text-white mb-1">Loading</h3>
945
+ <p class="text-white/60 text-xs md:text-sm">Fetching card data...</p>
946
+ </div>
947
+ </div>
948
+
949
+ <!-- Error Toast -->
950
+ <div id="error-toast" class="fixed top-4 right-4 z-50 hidden">
951
+ <div class="error-state max-w-sm">
952
+ <p id="error-message" class="text-sm"></p>
953
+ </div>
954
+ </div>
955
+
956
+ <!-- Success Toast -->
957
+ <div id="success-toast" class="fixed top-4 right-4 z-50 hidden">
958
+ <div class="success-state max-w-sm">
959
+ <p id="success-message" class="text-sm"></p>
960
  </div>
961
  </div>
962
  </div>
963
 
964
  <script>
965
  document.addEventListener('DOMContentLoaded', function() {
966
+ // DOM elements
967
  const randomBtn = document.getElementById('random-btn');
968
  const flipBtn = document.getElementById('flip-btn');
 
969
  const prevFaceBtn = document.getElementById('prev-face');
970
  const nextFaceBtn = document.getElementById('next-face');
971
  const loading = document.getElementById('loading');
 
980
  const cardFaceNav = document.getElementById('card-face-nav');
981
  const faceCounter = document.getElementById('face-counter');
982
  const card3d = document.getElementById('card-3d');
983
+ const errorToast = document.getElementById('error-toast');
984
+ const successToast = document.getElementById('success-toast');
985
 
986
  let currentCardFace = 0;
987
  let currentCardData = null;
988
+ let isLoading = false;
989
 
990
+ // Event listeners with debouncing
991
+ randomBtn.addEventListener('click', debounce(() => {
992
  currentCardFace = 0;
993
  fetchRandomCard();
994
+ }, 500));
 
 
 
 
 
 
 
995
 
996
  flipBtn.addEventListener('click', handleFlipButton);
997
  prevFaceBtn.addEventListener('click', () => switchFace(-1));
998
  nextFaceBtn.addEventListener('click', () => switchFace(1));
999
 
1000
+ // Keyboard navigation
1001
+ document.addEventListener('keydown', handleKeydown);
1002
+
1003
+ // Touch gestures for mobile
1004
+ let touchStartX = 0;
1005
+ let touchStartY = 0;
1006
+
1007
+ cardImage.addEventListener('touchstart', handleTouchStart, { passive: true });
1008
+ cardImage.addEventListener('touchend', handleTouchEnd, { passive: true });
1009
+
1010
  // Initialize
1011
  fetchRandomCard();
1012
 
1013
+ // Utility functions
1014
+ function debounce(func, wait) {
1015
+ let timeout;
1016
+ return function executedFunction(...args) {
1017
+ const later = () => {
1018
+ clearTimeout(timeout);
1019
+ func(...args);
1020
+ };
1021
+ clearTimeout(timeout);
1022
+ timeout = setTimeout(later, wait);
1023
+ };
1024
+ }
1025
+
1026
+ function showToast(element, message, duration = 3000) {
1027
+ element.querySelector('p').textContent = message;
1028
+ element.classList.remove('hidden');
1029
+ setTimeout(() => {
1030
+ element.classList.add('hidden');
1031
+ }, duration);
1032
+ }
1033
+
1034
+ function showError(message) {
1035
+ showToast(errorToast, message);
1036
+ }
1037
+
1038
+ function showSuccess(message) {
1039
+ showToast(successToast, message);
1040
+ }
1041
+
1042
+ function handleKeydown(e) {
1043
+ if (e.target.tagName.toLowerCase() === 'input' || e.target.tagName.toLowerCase() === 'textarea') {
1044
+ return;
1045
+ }
1046
+
1047
+ switch(e.key) {
1048
+ case ' ':
1049
+ case 'Enter':
1050
+ if (e.target === flipBtn || e.target === randomBtn) return;
1051
+ e.preventDefault();
1052
+ fetchRandomCard();
1053
+ break;
1054
+ case 'f':
1055
+ case 'F':
1056
+ e.preventDefault();
1057
+ handleFlipButton();
1058
+ break;
1059
+ case 'ArrowLeft':
1060
+ if (currentCardData && currentCardData.card_faces) {
1061
+ e.preventDefault();
1062
+ switchFace(-1);
1063
+ }
1064
+ break;
1065
+ case 'ArrowRight':
1066
+ if (currentCardData && currentCardData.card_faces) {
1067
+ e.preventDefault();
1068
+ switchFace(1);
1069
+ }
1070
+ break;
1071
+ }
1072
+ }
1073
+
1074
+ function handleTouchStart(e) {
1075
+ touchStartX = e.touches[0].clientX;
1076
+ touchStartY = e.touches[0].clientY;
1077
+ }
1078
+
1079
+ function handleTouchEnd(e) {
1080
+ if (!touchStartX || !touchStartY) return;
1081
+
1082
+ const touchEndX = e.changedTouches[0].clientX;
1083
+ const touchEndY = e.changedTouches[0].clientY;
1084
+
1085
+ const deltaX = touchStartX - touchEndX;
1086
+ const deltaY = touchStartY - touchEndY;
1087
+
1088
+ // Minimum swipe distance
1089
+ const minSwipe = 50;
1090
+
1091
+ // Horizontal swipe
1092
+ if (Math.abs(deltaX) > Math.abs(deltaY) && Math.abs(deltaX) > minSwipe) {
1093
+ if (currentCardData && currentCardData.card_faces) {
1094
+ if (deltaX > 0) {
1095
+ switchFace(1); // Swipe left - next face
1096
+ } else {
1097
+ switchFace(-1); // Swipe right - previous face
1098
+ }
1099
+ }
1100
+ }
1101
+
1102
+ // Vertical swipe up for random card
1103
+ if (deltaY > minSwipe && Math.abs(deltaY) > Math.abs(deltaX)) {
1104
+ fetchRandomCard();
1105
+ }
1106
+
1107
+ touchStartX = 0;
1108
+ touchStartY = 0;
1109
+ }
1110
+
1111
  function handleFlipButton() {
1112
  if (currentCardData && currentCardData.card_faces) {
1113
  switchFace(1);
1114
  } else {
1115
  card3d.classList.toggle('flipped');
1116
+ flipBtn.querySelector('span').textContent =
1117
+ card3d.classList.contains('flipped') ? 'Card' : 'Details';
1118
  }
1119
  }
1120
 
 
1126
  }
1127
 
1128
  async function fetchRandomCard() {
1129
+ if (isLoading) return;
1130
+
1131
  setLoadingState(true);
1132
 
1133
  try {
1134
+ const response = await fetch('https://api.scryfall.com/cards/random?q=is%3Acommander');
1135
+ if (!response.ok) throw new Error(`HTTP ${response.status}: ${response.statusText}`);
1136
 
1137
  const data = await response.json();
1138
  displayCard(data);
1139
 
1140
+ // Reset flip state
1141
+ card3d.classList.remove('flipped');
1142
+ flipBtn.querySelector('span').textContent = 'Details';
1143
+
1144
+ // Fetch related cards with improved query
1145
+ fetchRelatedCards(data);
1146
+
1147
+ // Announce to screen readers
1148
+ const announcement = `Loaded card: ${data.name}`;
1149
+ announceToScreenReader(announcement);
1150
+
1151
  } catch (error) {
1152
  console.error('Error fetching card:', error);
1153
+ showError('Failed to fetch card. Please check your connection and try again.');
1154
  } finally {
1155
  setLoadingState(false);
1156
  }
1157
  }
1158
 
1159
+ function setLoadingState(loading) {
1160
+ isLoading = loading;
1161
+
1162
+ // Update loading modal
1163
+ loading ? document.getElementById('loading').classList.remove('hidden') : document.getElementById('loading').classList.add('hidden');
1164
+ document.body.style.overflow = loading ? 'hidden' : '';
1165
+
1166
+ // Update button states
1167
+ [randomBtn, flipBtn].forEach(btn => {
1168
+ btn.disabled = loading;
1169
+ btn.classList.toggle('loading', loading);
1170
+ });
1171
+
1172
+ // Update card image loading state
1173
+ if (loading) {
1174
+ cardImage.classList.add('card-image-loading');
1175
+ } else {
1176
+ cardImage.classList.remove('card-image-loading');
1177
+ }
1178
+ }
1179
+
1180
+ function announceToScreenReader(message) {
1181
+ const announcement = document.createElement('div');
1182
+ announcement.setAttribute('aria-live', 'polite');
1183
+ announcement.setAttribute('aria-atomic', 'true');
1184
+ announcement.className = 'sr-only';
1185
+ announcement.textContent = message;
1186
+ document.body.appendChild(announcement);
1187
+ setTimeout(() => document.body.removeChild(announcement), 1000);
1188
  }
1189
 
1190
+ function setCardArtBackground(card) {
1191
+ let artUrl = '';
1192
+
1193
+ // Try to get art crop from current face or main card
1194
+ if (card.card_faces && card.card_faces[0] && card.card_faces[0].image_uris) {
1195
+ artUrl = card.card_faces[0].image_uris.art_crop || card.image_uris?.art_crop;
1196
+ } else if (card.image_uris) {
1197
+ artUrl = card.image_uris.art_crop;
1198
+ }
1199
+
1200
+ if (artUrl) {
1201
+ document.body.style.backgroundImage = `url('${artUrl}')`;
1202
+ document.body.style.backgroundSize = 'cover';
1203
+ document.body.style.backgroundPosition = 'center';
1204
+ document.body.style.backgroundAttachment = 'fixed';
1205
+ } else {
1206
+ // Fallback to default gradient if no art crop available
1207
+ document.body.style.backgroundImage = 'none';
1208
+ document.body.style.backgroundColor = '#0a0a0a';
1209
+ }
1210
  }
1211
 
1212
  function displayCard(card) {
1213
  currentCardData = card;
1214
  currentCardFace = 0;
1215
 
1216
+ // Set the card art as background
1217
+ setCardArtBackground(card);
1218
+
1219
  // Update face navigation
1220
  if (card.card_faces && card.card_faces.length > 1) {
1221
  cardFaceNav.classList.remove('hidden');
 
1233
  displayLinks(card);
1234
  displayLegalities(card);
1235
  displayRankings(card);
 
1236
  }
1237
 
1238
  function displayCardFace(card, faceIndex) {
 
1268
  faceIndicator.classList.add('hidden');
1269
  }
1270
 
1271
+ displayCardImage(imageUris, name, card, faceIndex);
1272
 
1273
  cardName.textContent = name || 'Unknown Card';
1274
  cardType.innerHTML = `<i class="fas fa-layer-group mr-2"></i>${typeText || ''}`;
 
1277
  displayStats(stats, card);
1278
  }
1279
 
1280
+ function displayCardImage(imageUris, name, card, faceIndex) {
1281
+ let priceLink = '';
1282
+ let priceValue = null;
1283
+ let purchaseLink = null;
1284
+
1285
+ // Check for prices in order of preference: USD, USD Foil, EUR
1286
+ if (card.prices?.usd) {
1287
+ priceValue = parseFloat(card.prices.usd).toFixed(2);
1288
+ } else if (card.prices?.usd_foil) {
1289
+ priceValue = parseFloat(card.prices.usd_foil).toFixed(2);
1290
+ } else if (card.prices?.eur) {
1291
+ priceValue = parseFloat(card.prices.eur).toFixed(2);
1292
+ }
1293
+
1294
+ // Determine purchase link
1295
+ if (card.purchase_uris) {
1296
+ purchaseLink = card.purchase_uris.tcgplayer || card.purchase_uris.cardmarket || card.purchase_uris.cardhoarder;
1297
+ }
1298
+
1299
+ if (priceValue) {
1300
+ if (purchaseLink) {
1301
+ priceLink = `
1302
+ <a href="${purchaseLink}" target="_blank" rel="noopener noreferrer"
1303
+ class="absolute bottom-1 md:bottom-2 left-1 md:left-2 price-tag text-xs no-underline hover:scale-105 transition-transform">
1304
+ ${priceValue}
1305
+ </a>
1306
+ `;
1307
+ } else {
1308
+ priceLink = `
1309
+ <div class="absolute bottom-1 md:bottom-2 left-1 md:left-2 price-tag text-xs">
1310
+ ${priceValue}
1311
+ </div>
1312
+ `;
1313
+ }
1314
+ }
1315
+
1316
  if (imageUris && imageUris.large) {
1317
  cardImage.innerHTML = `
1318
  <img src="${imageUris.large}"
1319
  alt="${name}"
1320
  class="w-full h-full object-contain"
1321
+ loading="lazy"
1322
  onerror="this.onerror=null; this.src='${imageUris.normal || imageUris.small}';">
1323
+ ${priceLink}
 
 
 
 
1324
  `;
1325
  } else {
1326
  cardImage.innerHTML = `
1327
  <div class="w-full h-full flex items-center justify-center bg-gradient-to-br from-gray-900 to-gray-800">
1328
+ <div class="text-center p-4 md:p-8 glass-card">
1329
+ <i class="fas fa-image-slash text-4xl md:text-6xl text-white/30 mb-2 md:mb-4"></i>
1330
+ <p class="text-white/60 text-sm md:text-base">Image unavailable</p>
1331
  </div>
1332
  </div>
1333
+ ${priceLink}
1334
  `;
1335
  }
1336
  }
 
1339
  let statsHtml = '';
1340
 
1341
  if (stats.power && stats.toughness) {
1342
+ statsHtml += `<div class="mb-1 font-semibold">${stats.power}/${stats.toughness}</div>`;
1343
  }
1344
  if (stats.loyalty) {
1345
  statsHtml += `<div class="mb-1">Loyalty: ${stats.loyalty}</div>`;
1346
  }
1347
 
1348
+ statsHtml += `<div class="text-white/70">CMC: ${card.cmc || 0}</div>`;
1349
 
1350
  if (card.mana_cost) {
1351
+ statsHtml += `<div class="text-xs md:text-sm text-white/60 mt-1">${card.mana_cost}</div>`;
1352
  }
1353
 
1354
  cardStats.innerHTML = statsHtml || '<span class="text-white/40">—</span>';
 
1356
 
1357
  function displayCardBackDetails(card) {
1358
  let detailsHtml = `
1359
+ <h3 class="text-lg md:text-2xl font-semibold mb-3 md:mb-4 text-white">${card.name}</h3>
1360
 
1361
+ <div class="mb-4 md:mb-6">
1362
+ <div class="glass p-2 md:p-3 rounded-lg">
1363
  <h4 class="info-label">Set</h4>
1364
+ <p class="text-xs md:text-sm text-white/80">${card.set_name}</p>
1365
+ <p class="text-xs text-white/60">${card.set.toUpperCase()} • #${card.collector_number || 'N/A'}</p>
 
 
 
 
 
 
 
 
1366
  </div>
1367
  </div>
1368
 
1369
  ${card.flavor_text ? `
1370
+ <div class="glass p-2 md:p-3 rounded-lg mb-3 md:mb-4">
1371
  <h4 class="info-label">Flavor</h4>
1372
+ <p class="italic text-xs md:text-sm text-white/70 leading-relaxed">"${card.flavor_text}"</p>
1373
  </div>
1374
  ` : ''}
1375
 
1376
+ <div class="glass p-2 md:p-3 rounded-lg">
1377
  <h4 class="info-label">Artist</h4>
1378
+ <p class="text-xs md:text-sm text-white/80">${card.artist}</p>
1379
  </div>
1380
  `;
1381
 
 
1386
  let pricesHtml = '';
1387
  if (card.prices) {
1388
  if (card.prices.usd) {
1389
+ const formattedUsd = parseFloat(card.prices.usd).toFixed(2);
1390
+ pricesHtml = `<span class="text-green-400 font-semibold">${formattedUsd}</span>`;
1391
  }
1392
  if (card.prices.usd_foil) {
1393
+ const formattedFoil = parseFloat(card.prices.usd_foil).toFixed(2);
1394
+ pricesHtml += `<br><span class="text-xs md:text-sm text-white/60">Foil: ${formattedFoil}</span>`;
1395
+ }
1396
+ if (card.prices.eur) {
1397
+ const formattedEur = parseFloat(card.prices.eur).toFixed(2);
1398
+ pricesHtml += `<br><span class="text-xs text-white/50">€${formattedEur}</span>`;
1399
  }
1400
  } else {
1401
  pricesHtml = '<span class="text-white/40">—</span>';
 
1415
  stores.forEach(store => {
1416
  if (store.url) {
1417
  linksHtml += `
1418
+ <a href="${store.url}" target="_blank" rel="noopener noreferrer"
1419
+ class="glass-button glass-button-primary text-xs">
1420
+ <i class="fas ${store.icon} mr-1"></i><span>${store.name}</span>
1421
  </a>
1422
  `;
1423
  }
1424
  });
1425
  } else {
1426
+ linksHtml = '<p class="text-white/40 text-xs">No purchase links</p>';
1427
  }
1428
  document.getElementById('purchase-links').innerHTML = linksHtml;
1429
  }
 
1439
  links.forEach(link => {
1440
  if (link.url) {
1441
  linksHtml += `
1442
+ <a href="${link.url}" target="_blank" rel="noopener noreferrer"
1443
+ class="glass-button text-xs md:text-sm">
1444
+ <i class="fas ${link.icon} mr-1 md:mr-2"></i><span class="hidden sm:inline">${link.name}</span><span class="sm:hidden">${link.name.slice(0, 4)}</span>
1445
  </a>
1446
  `;
1447
  }
 
1479
 
1480
  badge.className = badgeClass;
1481
  badge.innerHTML = `
1482
+ <i class="fas ${icon}" aria-hidden="true"></i>
1483
+ <span class="hidden sm:inline">${format.charAt(0).toUpperCase() + format.slice(1)}</span>
1484
+ <span class="sm:hidden">${format.slice(0, 3).toUpperCase()}</span>
1485
  `;
1486
+ badge.setAttribute('title', `${format}: ${status}`);
1487
  cardLegalities.appendChild(badge);
1488
  }
1489
  });
 
1494
  let rankingsHtml = '';
1495
 
1496
  if (card.edhrec_rank) {
1497
+ rankingsHtml = `<span class="text-blue-400">#${card.edhrec_rank.toLocaleString()}</span><br><span class="text-xs text-white/60">EDH Rank</span>`;
1498
  } else {
1499
  rankingsHtml = '<span class="text-white/40">—</span>';
1500
  }
 
1502
  document.getElementById('card-rankings').innerHTML = rankingsHtml;
1503
  }
1504
 
1505
+ // Updated and improved fetchRelatedCards function
 
 
 
 
 
 
 
 
 
 
1506
  async function fetchRelatedCards(card) {
1507
  try {
1508
+ // Build a better search query
1509
+ const searchQuery = buildImprovedSearchText(card);
1510
+
1511
+ // Only proceed if we have a valid query
1512
+ if (!searchQuery || searchQuery.trim().length < 3) {
1513
+ console.log('Search query too short, skipping related cards');
1514
+ document.getElementById('card-gallery-section').classList.add('hidden');
1515
+ return;
1516
+ }
1517
+
1518
  document.getElementById('card-gallery-section').classList.remove('hidden');
1519
  const gallery = document.getElementById('card-gallery');
1520
+ gallery.innerHTML = '<div class="col-span-full text-center py-6 md:py-8"><div class="loading-ring mx-auto"></div></div>';
1521
+
1522
+ console.log('Searching for:', searchQuery);
1523
 
 
1524
  const response = await fetch(`https://api.deck.doctor/v1/mtg/search?q=${encodeURIComponent(searchQuery)}&topk=12&price_threshold=0`, {
1525
  headers: { 'accept': 'application/json' }
1526
  });
1527
 
1528
+ if (!response.ok) {
1529
+ console.error('API response not ok:', response.status);
1530
+ throw new Error('Failed to fetch related cards');
1531
+ }
1532
 
1533
  const data = await response.json();
1534
+ console.log('API Response:', data);
1535
+
1536
+ // The API returns an array of tuples: [card_object, similarity_score]
1537
+ if (Array.isArray(data) && data.length > 0) {
1538
+ // Filter out the current card and very low similarity scores
1539
+ const filteredCards = data
1540
+ .filter(tuple => {
1541
+ if (!Array.isArray(tuple) || tuple.length < 2) return false;
1542
+ const [relatedCard, score] = tuple;
1543
+ return relatedCard && relatedCard.id !== card.id && score > 0.05;
1544
+ })
1545
+ .sort((a, b) => b[1] - a[1]) // Sort by similarity score
1546
+ .slice(0, 12); // Limit to 12 cards
1547
+
1548
+ if (filteredCards.length > 0) {
1549
+ displayCardGallery(filteredCards);
1550
+ } else {
1551
+ gallery.innerHTML = '<div class="col-span-full text-center py-6 md:py-8 text-white/40 text-sm">No similar cards found</div>';
1552
+ }
1553
+ } else {
1554
+ gallery.innerHTML = '<div class="col-span-full text-center py-6 md:py-8 text-white/40 text-sm">No related cards found</div>';
1555
+ }
1556
  } catch (error) {
1557
  console.error('Error fetching related cards:', error);
1558
+ document.getElementById('card-gallery').innerHTML = '<div class="col-span-full text-center py-6 md:py-8 text-white/40 text-sm">Unable to load related cards</div>';
1559
  }
1560
  }
1561
 
1562
+ // Improved search text builder
1563
+ function buildImprovedSearchText(card) {
1564
+ let searchTerms = [];
1565
+
1566
+ // Get oracle text from current face or main card
1567
  let oracleText = '';
1568
  if (card.card_faces && card.card_faces[currentCardFace]) {
1569
  oracleText = card.card_faces[currentCardFace].oracle_text || '';
 
1571
  oracleText = card.oracle_text || '';
1572
  }
1573
 
1574
+ // Priority 1: Extract key abilities and mechanics
1575
+ const mechanics = extractMechanics(oracleText.toLowerCase());
1576
+ if (mechanics.length > 0) {
1577
+ // Take top 3 mechanics
1578
+ searchTerms = mechanics.slice(0, 3);
1579
+ }
1580
+
1581
+ // Priority 2: If no mechanics found, try card type
1582
+ if (searchTerms.length === 0) {
1583
+ const typeText = card.card_faces && card.card_faces[currentCardFace] ?
1584
+ card.card_faces[currentCardFace].type_line : card.type_line;
1585
+
1586
+ if (typeText) {
1587
+ // Extract creature types or important card types
1588
+ const typeParts = typeText.split('—').map(s => s.trim());
1589
+ if (typeParts.length > 1) {
1590
+ // Use subtype (after the dash)
1591
+ searchTerms.push(typeParts[1].toLowerCase());
1592
+ } else {
1593
+ // Use main type
1594
+ const mainType = typeParts[0].replace(/legendary/i, '').trim();
1595
+ searchTerms.push(mainType.toLowerCase());
1596
+ }
1597
+ }
1598
+ }
1599
 
1600
+ // Priority 3: If still no terms, use first ability word
1601
+ if (searchTerms.length === 0 && oracleText) {
1602
+ const firstSentence = oracleText.split('.')[0];
1603
+ if (firstSentence) {
1604
+ const cleanedSentence = firstSentence
1605
+ .replace(/[{}]/g, '')
1606
+ .replace(/\(.*?\)/g, '')
1607
+ .trim()
1608
+ .toLowerCase();
1609
+
1610
+ // Take first few meaningful words
1611
+ const words = cleanedSentence.split(' ').filter(w => w.length > 3);
1612
+ if (words.length > 0) {
1613
+ searchTerms = words.slice(0, 3);
1614
+ }
1615
+ }
1616
  }
1617
 
1618
+ return searchTerms.join(' ');
1619
+ }
1620
+
1621
+ // Extract MTG mechanics and keywords from text
1622
+ function extractMechanics(text) {
1623
+ const keywords = [
1624
+ // Evergreen keywords
1625
+ 'flying', 'trample', 'haste', 'vigilance', 'deathtouch',
1626
+ 'lifelink', 'first strike', 'double strike', 'hexproof',
1627
+ 'indestructible', 'menace', 'reach', 'flash', 'defender',
1628
+ 'protection', 'ward',
1629
+
1630
+ // Common mechanics
1631
+ 'landfall', 'cascade', 'storm', 'prowess', 'kicker',
1632
+ 'flashback', 'madness', 'cycling', 'morph', 'evoke',
1633
+ 'convoke', 'delve', 'prowl', 'rebound', 'suspend',
1634
+ 'proliferate', 'scry', 'surveil', 'explore', 'adapt',
1635
+ 'riot', 'spectacle', 'afterlife', 'amass', 'mentor',
1636
+ 'mutate', 'companion', 'foretell', 'boast', 'learn',
1637
+ 'magecraft', 'ward', 'cleave', 'disturb', 'daybound',
1638
+ 'nightbound', 'decayed', 'exploit', 'training', 'modified',
1639
+ 'reconfigure', 'channel', 'ninjutsu', 'compleated',
1640
+ 'toxic', 'corrupted', 'proliferate', 'oil counter',
1641
+
1642
+ // Common actions
1643
+ 'draw', 'discard', 'destroy', 'counter', 'exile',
1644
+ 'return', 'sacrifice', 'tap', 'untap', 'damage',
1645
+ 'gain life', 'lose life', 'mill', 'search', 'shuffle',
1646
+ 'reveal', 'put', 'create', 'copy', 'fight', 'goad'
1647
+ ];
1648
+
1649
+ const foundKeywords = [];
1650
+ keywords.forEach(keyword => {
1651
+ if (text.includes(keyword) && !foundKeywords.includes(keyword)) {
1652
+ foundKeywords.push(keyword);
1653
+ }
1654
+ });
1655
 
1656
+ return foundKeywords;
1657
  }
1658
 
1659
+ function displayCardGallery(cardPairs) {
1660
  const gallery = document.getElementById('card-gallery');
1661
 
1662
+ if (!cardPairs || cardPairs.length === 0) {
1663
+ gallery.innerHTML = '<div class="col-span-full text-center py-6 md:py-8 text-white/40 text-sm">No related cards found</div>';
1664
  return;
1665
  }
1666
 
1667
+ gallery.innerHTML = cardPairs.map(tuple => {
1668
+ const [card, similarityScore] = tuple;
1669
+ return `
1670
+ <div class="gallery-item" onclick="loadGalleryCard('${card.id}')"
1671
+ tabindex="0" role="button" aria-label="Load ${card.name} (${Math.round(similarityScore * 100)}% similar)">
1672
+ <div class="relative w-full" style="aspect-ratio: 0.72;">
1673
+ <img src="${getCardImageUrl(card)}"
1674
+ alt="${card.name}"
1675
+ class="w-full h-full object-cover"
1676
+ loading="lazy">
1677
+
1678
+ ${card.prices?.usd ? `
1679
+ <div class="absolute bottom-1 md:bottom-2 left-1 md:left-2 price-tag text-xs">
1680
+ ${parseFloat(card.prices.usd).toFixed(2)}
1681
+ </div>
1682
+ ` : ''}
1683
+
1684
+ <!-- Similarity Score Badge -->
1685
+ <div class="absolute bottom-1 md:bottom-2 left-1/2 transform -translate-x-1/2 glass-card px-2 py-1 text-xs">
1686
+ <span class="text-green-400 font-semibold">${Math.round(similarityScore * 100)}%</span>
1687
+ </div>
1688
+
1689
+ <div class="gallery-overlay">
1690
+ <h4 class="font-semibold text-sm md:text-base text-white mb-1 line-clamp-2">${card.name}</h4>
1691
+ <p class="text-xs md:text-sm text-white/60 line-clamp-1">${card.type_line || ''}</p>
1692
  </div>
 
 
 
 
 
1693
  </div>
1694
  </div>
1695
+ `;
1696
+ }).join('');
1697
+
1698
+ // Add keyboard navigation for gallery items
1699
+ gallery.querySelectorAll('.gallery-item').forEach(item => {
1700
+ item.addEventListener('keydown', (e) => {
1701
+ if (e.key === 'Enter' || e.key === ' ') {
1702
+ e.preventDefault();
1703
+ item.click();
1704
+ }
1705
+ });
1706
+ });
1707
  }
1708
 
1709
  function getCardImageUrl(card) {
 
1717
  }
1718
 
1719
  window.loadGalleryCard = async function(cardId) {
1720
+ if (isLoading) return;
1721
+
1722
  setLoadingState(true);
1723
  try {
1724
  const response = await fetch(`https://api.scryfall.com/cards/${cardId}`);
1725
+ if (!response.ok) throw new Error(`HTTP ${response.status}: ${response.statusText}`);
1726
  const card = await response.json();
1727
  currentCardFace = 0;
1728
  displayCard(card);
1729
 
1730
+ // Smooth scroll to top
1731
  window.scrollTo({ top: 0, behavior: 'smooth' });
1732
 
1733
+ // Reset flip state
1734
+ card3d.classList.remove('flipped');
1735
+ flipBtn.querySelector('span').textContent = 'Details';
1736
+
1737
+ // Announce to screen readers
1738
+ announceToScreenReader(`Loaded card: ${card.name}`);
1739
+
1740
+ // Fetch new related cards
1741
+ fetchRelatedCards(card);
1742
  } catch (error) {
1743
  console.error('Error loading gallery card:', error);
1744
+ showError('Failed to load selected card. Please try again.');
1745
  } finally {
1746
  setLoadingState(false);
1747
  }