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

Update index.html

Browse files
Files changed (1) hide show
  1. index.html +924 -326
index.html CHANGED
@@ -3,183 +3,636 @@
3
  <head>
4
  <meta charset="UTF-8">
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
- <title>MTG Card Explorer</title>
7
  <script src="https://cdn.tailwindcss.com"></script>
8
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
9
  <style>
10
- .mana-symbol {
11
- display: inline-block;
12
- width: 1.2em;
13
- height: 1.2em;
14
- background-size: contain;
15
- background-repeat: no-repeat;
16
- vertical-align: middle;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
17
  }
18
 
19
- .card-container {
20
- perspective: 1000px;
 
 
 
 
 
 
 
 
21
  }
22
 
23
- .card-inner {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
24
  transition: transform 0.6s;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
25
  transform-style: preserve-3d;
 
26
  }
27
 
28
- .card-container:hover .card-inner,
29
- .card-container.flipped .card-inner {
30
  transform: rotateY(180deg);
31
  }
32
 
33
- .card-front, .card-back {
 
 
 
34
  backface-visibility: hidden;
 
 
 
 
 
35
  }
36
 
37
- .card-back {
38
  transform: rotateY(180deg);
39
  }
40
 
41
- @keyframes pulse {
42
- 0% { transform: scale(1); }
43
- 50% { transform: scale(1.05); }
44
- 100% { transform: scale(1); }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
45
  }
46
 
47
- .pulse {
48
- animation: pulse 2s infinite;
 
 
 
 
 
 
 
 
 
 
 
49
  }
50
 
51
- .card-shadow {
52
- box-shadow: 0 10px 30px -5px rgba(0, 0, 0, 0.3);
 
 
53
  }
54
 
55
- .card-border {
56
- border-radius: 3.5% / 4.7%;
 
 
57
  }
58
 
59
- .type-line {
60
- border-bottom: 2px solid #d9c8a9;
 
 
61
  }
62
 
63
- .legal-badge {
64
- transition: all 0.2s ease;
 
 
65
  }
66
 
67
- .legal-badge:hover {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
68
  transform: translateY(-2px);
69
  }
70
 
71
- .card-container .card-back {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
72
  opacity: 0;
73
- transition: opacity 0.3s ease;
 
 
 
74
  }
75
 
76
- .card-container:hover .card-back {
77
  opacity: 1;
78
  }
79
  </style>
80
  </head>
81
- <body class="bg-gray-900 text-gray-100 min-h-screen">
 
 
 
 
 
82
  <div class="container mx-auto px-4 py-8">
83
- <header class="text-center mb-12">
84
- <h1 class="text-4xl md:text-5xl font-bold bg-gradient-to-r from-purple-600 via-blue-500 to-green-400 bg-clip-text text-transparent mb-2">
85
- MTG Card Explorer
 
 
86
  </h1>
87
- <p class="text-lg text-gray-300 max-w-2xl mx-auto">
88
- Discover random Magic: The Gathering cards with detailed information. Click below to get started!
89
- </p>
 
 
90
  </header>
91
 
92
- <div class="flex flex-col lg:flex-row gap-4 lg:gap-8 items-center lg:items-start justify-center">
93
  <!-- Card Display Section -->
94
- <div class="w-full lg:w-1/2 xl:w-2/5 flex flex-col items-center">
95
- <div class="card-container w-full max-w-md mb-6">
96
- <div class="card-inner relative">
97
- <!-- Front of Card -->
98
- <div class="card-front">
99
- <div id="card-image" class="card-border overflow-hidden bg-gray-800 relative">
100
- <div class="aspect-[0.72] w-full flex items-center justify-center bg-gray-700">
101
- <div class="text-center p-4">
102
- <i class="fas fa-cards text-6xl text-gray-600 mb-4"></i>
103
- <p class="text-gray-400">Click "Random Card" to load a Magic card</p>
104
- </div>
105
  </div>
106
  </div>
 
 
 
107
  </div>
108
 
109
- <!-- Back of Card (Details) -->
110
- <div class="card-back absolute top-0 left-0 w-full h-full bg-gray-800 p-6 rounded-lg card-shadow card-border">
111
  <div id="card-details" class="h-full overflow-y-auto">
112
- <p class="text-gray-400">Card details will appear here</p>
 
 
 
113
  </div>
114
  </div>
115
  </div>
116
  </div>
117
 
118
- <div class="flex gap-4 mt-4">
119
- <button id="random-btn" class="bg-gradient-to-r from-blue-600 to-blue-800 hover:from-blue-700 hover:to-blue-900 text-white font-bold py-3 px-6 rounded-full shadow-lg transition-all duration-300 transform hover:scale-105 active:scale-95 flex items-center gap-2">
120
- <i class="fas fa-random"></i>
121
- <span>Random Card</span>
 
 
 
 
122
  </button>
123
 
124
- <button id="flip-btn" class="bg-gray-700 hover:bg-gray-600 text-white font-bold py-3 px-6 rounded-full shadow-lg transition-all duration-300 transform hover:scale-105 active:scale-95 flex items-center gap-2">
125
- <i class="fas fa-exchange-alt"></i>
126
- <span>Flip Card</span>
 
 
 
 
 
 
 
 
 
 
127
  </button>
128
  </div>
129
  </div>
130
 
131
- <!-- Card Info Section -->
132
- <div id="card-info" class="w-full lg:w-1/2 xl:w-3/5 bg-gray-800 rounded-lg p-4 lg:p-6 shadow-xl">
133
- <div class="flex flex-col h-full">
134
- <h2 id="card-name" class="text-2xl font-bold mb-2 text-center lg:text-left">No Card Loaded</h2>
135
- <div id="card-type" class="text-lg text-gray-300 mb-4 text-center lg:text-left"></div>
 
136
 
137
- <div class="flex flex-wrap gap-2 justify-center lg:justify-start mb-6" id="mana-cost"></div>
138
-
139
- <div class="bg-gray-700 rounded-lg p-4 mb-6">
140
- <div id="card-text" class="text-gray-100 whitespace-pre-line"></div>
141
  </div>
142
 
143
- <div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-3">
144
- <div class="bg-gray-700 rounded-lg p-4">
145
- <h3 class="font-semibold text-gray-300 mb-2">Legalities</h3>
146
- <div id="card-legalities" class="flex flex-wrap gap-2"></div>
 
147
  </div>
148
- <div class="bg-gray-700 rounded-lg p-4">
149
- <h3 class="font-semibold text-gray-300 mb-2">Details</h3>
150
- <div id="card-stats" class="text-gray-100"></div>
 
151
  </div>
152
 
153
- <div class="bg-gray-700 rounded-lg p-4">
154
- <h3 class="font-semibold text-gray-300 mb-2">Prices</h3>
155
- <div id="card-prices" class="text-gray-100"></div>
156
  </div>
157
 
158
- <div class="bg-gray-700 rounded-lg p-4">
159
- <h3 class="font-semibold text-gray-300 mb-2">Links</h3>
160
- <div id="card-links" class="flex flex-wrap gap-2"></div>
161
  </div>
162
  </div>
163
  </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
164
  </div>
165
  </div>
166
 
167
- <!-- Card Gallery Section -->
168
- <div id="card-gallery-section" class="mt-16 hidden">
169
- <h2 class="text-3xl font-bold text-center mb-8 bg-gradient-to-r from-purple-600 to-blue-500 bg-clip-text text-transparent">
170
- Related Cards
 
171
  </h2>
172
  <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">
173
- <!-- Gallery cards will be populated here -->
174
  </div>
175
  </div>
176
 
177
- <!-- Loading Indicator -->
178
- <div id="loading" class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center hidden z-50">
179
- <div class="bg-gray-800 p-8 rounded-lg shadow-2xl text-center max-w-md">
180
- <div class="animate-spin rounded-full h-16 w-16 border-t-2 border-b-2 border-blue-500 mx-auto mb-4"></div>
181
- <h3 class="text-xl font-bold mb-2">Fetching Card</h3>
182
- <p class="text-gray-300">Contacting the Multiverse...</p>
183
  </div>
184
  </div>
185
  </div>
@@ -188,324 +641,469 @@
188
  document.addEventListener('DOMContentLoaded', function() {
189
  const randomBtn = document.getElementById('random-btn');
190
  const flipBtn = document.getElementById('flip-btn');
 
 
 
191
  const loading = document.getElementById('loading');
192
  const cardImage = document.getElementById('card-image');
193
  const cardDetails = document.getElementById('card-details');
194
  const cardName = document.getElementById('card-name');
195
  const cardType = document.getElementById('card-type');
196
- const manaCost = document.getElementById('mana-cost');
197
  const cardText = document.getElementById('card-text');
198
  const cardStats = document.getElementById('card-stats');
199
  const cardLegalities = document.getElementById('card-legalities');
200
- const cardInfo = document.getElementById('card-info');
 
 
 
201
 
202
- // Flip card functionality
203
- flipBtn.addEventListener('click', function() {
204
- document.querySelector('.card-container').classList.toggle('flipped');
 
 
 
 
 
 
 
 
 
 
 
205
  });
206
 
207
- // Fetch random card
208
- randomBtn.addEventListener('click', fetchRandomCard);
 
209
 
210
- // Initial fetch
211
  fetchRandomCard();
212
 
213
- function fetchRandomCard() {
214
- loading.classList.remove('hidden');
215
- document.body.style.overflow = 'hidden';
216
- cardInfo.classList.add('opacity-50');
217
- randomBtn.disabled = true;
218
- flipBtn.disabled = true;
 
 
 
 
219
 
220
- fetch('https://api.scryfall.com/cards/random')
221
- .then(response => {
222
- if (!response.ok) {
223
- throw new Error('Network response was not ok');
224
- }
225
- return response.json();
226
- })
227
- .then(data => {
228
- displayCard(data);
229
- loading.classList.add('hidden');
230
- document.body.style.overflow = '';
231
- cardInfo.classList.remove('opacity-50');
232
- randomBtn.disabled = false;
233
- flipBtn.disabled = false;
234
- })
235
- .catch(error => {
236
- console.error('Error fetching card:', error);
237
- loading.classList.add('hidden');
238
- document.body.style.overflow = '';
239
- cardInfo.classList.remove('opacity-50');
240
- randomBtn.disabled = false;
241
- flipBtn.disabled = false;
242
- alert('Error fetching card. Please try again.');
243
- });
244
  }
245
 
246
- function displayCard(card) {
247
- // Display card image
248
 
249
- // Fetch related cards based on oracle text
250
- if (card.oracle_text) {
251
- fetchRelatedCards(card.oracle_text);
252
- } else {
253
- document.getElementById('card-gallery-section').classList.add('hidden');
 
 
 
 
 
 
 
 
 
 
254
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
255
 
256
- if (card.image_uris) {
257
- cardImage.innerHTML = `
258
- <img src="${card.image_uris.large}"
259
- alt="${card.name}"
260
- class="w-full h-full object-cover card-border"
261
- onerror="this.onerror=null;this.src='${card.image_uris.normal}';">
262
- `;
263
- } else if (card.card_faces) {
264
- // Handle double-faced cards
265
- cardImage.innerHTML = `
266
- <img src="${card.card_faces[0].image_uris.large}"
267
- alt="${card.name}"
268
- class="w-full h-full object-cover card-border"
269
- onerror="this.onerror=null;this.src='${card.card_faces[0].image_uris.normal}';">
270
- `;
271
  }
272
 
273
- // Display card details on back
274
- let detailsHtml = `
275
- <h3 class="text-xl font-bold mb-4">${card.name}</h3>
276
- <div class="mb-4">
277
- <p class="text-gray-300"><span class="font-semibold">Set:</span> ${card.set_name} (${card.set.toUpperCase()})</p>
278
- <p class="text-gray-300"><span class="font-semibold">Artist:</span> ${card.artist}</p>
279
- </div>
280
- `;
 
 
 
 
281
 
282
- if (card.flavor_text) {
283
- detailsHtml += `
284
- <div class="bg-gray-700 rounded-lg p-4 mb-4 italic text-gray-300">
285
- ${card.flavor_text}
286
- </div>
287
- `;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
288
  }
289
 
290
- cardDetails.innerHTML = detailsHtml;
291
 
292
- // Display card info
293
- cardName.textContent = card.name;
 
294
 
295
- // Display type line
296
- cardType.textContent = card.type_line;
297
-
298
- // Display mana cost
299
- manaCost.innerHTML = '';
300
- if (card.mana_cost) {
301
- const cost = card.mana_cost;
302
- const symbols = cost.match(/\{[^{}]+\}/g) || [];
303
-
304
- symbols.forEach(symbol => {
305
- const symbolName = symbol.replace(/[{}]/g, '').toLowerCase();
306
- const symbolElement = document.createElement('div');
307
- symbolElement.className = 'mana-symbol';
308
- symbolElement.style.backgroundImage = `url(https://c2.scryfall.com/file/scryfall-symbols/card-symbols/${symbolName}.svg)`;
309
- symbolElement.title = symbolName.toUpperCase();
310
- manaCost.appendChild(symbolElement);
311
- });
 
 
 
 
 
 
 
 
312
  }
313
-
314
- // Display card text
315
- cardText.textContent = card.oracle_text || '';
316
-
317
- // Display stats (power/toughness or loyalty)
318
  let statsHtml = '';
319
- if (card.power && card.toughness) {
320
- statsHtml += `<p><span class="font-semibold">P/T:</span> ${card.power}/${card.toughness}</p>`;
 
321
  }
322
- if (card.loyalty) {
323
- statsHtml += `<p><span class="font-semibold">Loyalty:</span> ${card.loyalty}</p>`;
324
  }
325
- statsHtml += `<p><span class="font-semibold">CMC:</span> ${card.cmc}</p>`;
326
 
327
- if (card.colors) {
328
- const colorMap = {
329
- 'W': 'White',
330
- 'U': 'Blue',
331
- 'B': 'Black',
332
- 'R': 'Red',
333
- 'G': 'Green'
334
- };
335
- const colorNames = card.colors.map(c => colorMap[c]).join(', ');
336
- statsHtml += `<p><span class="font-semibold">Colors:</span> ${colorNames}</p>`;
337
- }
338
 
339
- if (card.keywords && card.keywords.length > 0) {
340
- statsHtml += `<p><span class="font-semibold">Keywords:</span> ${card.keywords.join(', ')}</p>`;
341
  }
342
 
343
- cardStats.innerHTML = statsHtml;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
344
 
345
- // Display prices and purchase links
 
 
 
346
  let pricesHtml = '';
347
  if (card.prices) {
348
- pricesHtml += `
349
- ${card.prices.usd ? `<p><span class="font-semibold">USD:</span> ${card.prices.usd}</p>` : ''}
350
- ${card.prices.usd_foil ? `<p><span class="font-semibold">Foil:</span> ${card.prices.usd_foil}</p>` : ''}
351
- ${card.prices.eur ? `<p><span class="font-semibold">EUR:</span> €${card.prices.eur}</p>` : ''}
352
- ${card.prices.tix ? `<p><span class="font-semibold">MTGO:</span> ${card.prices.tix} tix</p>` : ''}
353
- `;
354
-
355
- // Add purchase links if they exist
356
- if (card.purchase_uris) {
357
- pricesHtml += `<div class="mt-4"><h4 class="font-semibold mb-2">Buy From:</h4>`;
358
- if (card.purchase_uris.tcgplayer) {
359
- pricesHtml += `<a href="${card.purchase_uris.tcgplayer}" target="_blank" class="bg-orange-600 hover:bg-orange-700 text-white px-3 py-1 rounded text-sm mr-2 mb-2 inline-block">TCGplayer</a>`;
360
- }
361
- if (card.purchase_uris.cardmarket) {
362
- pricesHtml += `<a href="${card.purchase_uris.cardmarket}" target="_blank" class="bg-blue-600 hover:bg-blue-700 text-white px-3 py-1 rounded text-sm mr-2 mb-2 inline-block">Cardmarket</a>`;
363
- }
364
- if (card.purchase_uris.cardhoarder) {
365
- pricesHtml += `<a href="${card.purchase_uris.cardhoarder}" target="_blank" class="bg-green-600 hover:bg-green-700 text-white px-3 py-1 rounded text-sm mr-2 mb-2 inline-block">Cardhoarder</a>`;
366
- }
367
- pricesHtml += `</div>`;
368
  }
 
 
369
  }
370
  document.getElementById('card-prices').innerHTML = pricesHtml;
371
-
372
- // Display links
 
373
  let linksHtml = '';
374
- if (card.related_uris) {
375
- linksHtml += `
376
- ${card.related_uris.gatherer ? `<a href="${card.related_uris.gatherer}" target="_blank" class="bg-blue-600 hover:bg-blue-700 text-white px-3 py-1 rounded text-sm">Gatherer</a>` : ''}
377
- ${card.related_uris.edhrec ? `<a href="${card.related_uris.edhrec}" target="_blank" class="bg-purple-600 hover:bg-purple-700 text-white px-3 py-1 rounded text-sm">EDHREC</a>` : ''}
378
- ${card.scryfall_uri ? `<a href="${card.scryfall_uri}" target="_blank" class="bg-green-600 hover:bg-green-700 text-white px-3 py-1 rounded text-sm">Scryfall</a>` : ''}
379
- `;
 
 
 
 
 
 
 
 
 
 
 
 
 
380
  }
381
- document.getElementById('card-links').innerHTML = linksHtml;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
382
 
383
- // Display legalities
 
 
 
384
  cardLegalities.innerHTML = '';
385
  if (card.legalities) {
386
- const formatMap = {
387
- 'standard': 'Standard',
388
- 'modern': 'Modern',
389
- 'legacy': 'Legacy',
390
- 'vintage': 'Vintage',
391
- 'commander': 'Commander',
392
- 'pioneer': 'Pioneer',
393
- 'historic': 'Historic',
394
- 'pauper': 'Pauper',
395
- 'brawl': 'Brawl'
396
- };
397
 
398
- for (const [format, status] of Object.entries(card.legalities)) {
399
- if (formatMap[format]) {
 
400
  const badge = document.createElement('div');
401
- badge.className = `legal-badge px-3 py-1 rounded-full text-xs font-semibold ${
402
- status === 'legal' ? 'bg-green-600 text-white' :
403
- status === 'not_legal' ? 'bg-red-600 text-white' :
404
- 'bg-yellow-600 text-black'
405
- }`;
406
- badge.textContent = `${formatMap[format]}: ${status.replace('_', ' ')}`;
407
- badge.title = `${formatMap[format]} is ${status.replace('_', ' ')}`;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
408
  cardLegalities.appendChild(badge);
409
  }
410
- }
411
  }
412
  }
413
 
414
- async function fetchRelatedCards(oracleText) {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
415
  try {
416
  document.getElementById('card-gallery-section').classList.remove('hidden');
417
  const gallery = document.getElementById('card-gallery');
418
- gallery.innerHTML = '<div class="col-span-full text-center py-8"><div class="animate-spin rounded-full h-8 w-8 border-t-2 border-b-2 border-blue-500 mx-auto mb-4"></div><p>Loading related cards...</p></div>';
419
 
420
- // Extract key phrases from oracle text for better search
421
- const searchQuery = extractKeyPhrase(oracleText);
422
-
423
- const response = await fetch(`https://api.deck.doctor/v1/mtg/search?q=${encodeURIComponent(searchQuery)}&topk=10&price_threshold=0`, {
424
- headers: {
425
- 'accept': 'application/json'
426
- }
427
  });
428
 
429
- if (!response.ok) {
430
- throw new Error('Failed to fetch related cards');
431
- }
432
 
433
  const data = await response.json();
434
- displayCardGallery(data.cards || []);
 
435
  } catch (error) {
436
  console.error('Error fetching related cards:', error);
437
- const gallery = document.getElementById('card-gallery');
438
- gallery.innerHTML = '<div class="col-span-full text-center py-8 text-gray-400"><p>Unable to load related cards</p></div>';
439
  }
440
  }
441
 
442
- function extractKeyPhrase(oracleText) {
443
- // Simple extraction of what might be a key phrase
444
- const lines = oracleText.split('\n');
445
- const firstLine = lines[0].trim();
 
 
 
 
 
 
 
 
 
 
446
 
447
- // Remove parenthetical text and take first few words
448
- const cleanText = firstLine.replace(/\([^)]*\)/g, '').trim();
449
- const words = cleanText.split(' ');
 
 
 
450
 
451
- // Take up to 4 words for the search query
452
- return words.slice(0, 4).join(' ');
453
  }
454
 
455
  function displayCardGallery(cards) {
456
  const gallery = document.getElementById('card-gallery');
457
 
458
  if (cards.length === 0) {
459
- gallery.innerHTML = '<div class="col-span-full text-center py-8 text-gray-400"><p>No related cards found</p></div>';
460
  return;
461
  }
462
 
463
  gallery.innerHTML = cards.map(card => `
464
- <div class="card-container relative group" style="height: 300px;">
465
- <div class="card-inner relative w-full h-full">
466
- <!-- Front of Gallery Card -->
467
- <div class="card-front w-full h-full">
468
- <div class="card-border overflow-hidden bg-gray-800 h-full">
469
- <img src="${card.image_uris?.normal || 'https://via.placeholder.com/244x340/374151/6B7280?text=No+Image'}"
470
- alt="${card.name}"
471
- class="w-full h-full object-cover"
472
- onerror="this.onerror=null;this.src='https://via.placeholder.com/244x340/374151/6B7280?text=Image+Error';">
473
- </div>
474
- </div>
475
 
476
- <!-- Back of Gallery Card (Info) -->
477
- <div class="card-back absolute top-0 left-0 w-full h-full bg-gray-800 p-3 rounded-lg card-shadow card-border opacity-0 group-hover:opacity-100 transition-opacity duration-300">
478
- <div class="h-full overflow-y-auto">
479
- <h4 class="font-bold text-sm mb-2">${card.name}</h4>
480
- <p class="text-xs text-gray-300 mb-2">${card.type_line}</p>
481
- ${card.mana_cost ? `
482
- <div class="flex flex-wrap gap-1 mb-2">
483
- ${renderManaCost(card.mana_cost)}
484
- </div>
485
- ` : ''}
486
- ${card.oracle_text ? `
487
- <p class="text-xs text-gray-100 whitespace-pre-line mb-2">${card.oracle_text}</p>
488
- ` : ''}
489
- ${card.power && card.toughness ? `
490
- <p class="text-xs"><strong>P/T:</strong> ${card.power}/${card.toughness}</p>
491
- ` : ''}
492
- ${card.cmc ? `
493
- <p class="text-xs"><strong>CMC:</strong> ${card.cmc}</p>
494
- ` : ''}
495
  </div>
 
 
 
 
 
496
  </div>
497
  </div>
498
  </div>
499
  `).join('');
500
  }
501
 
502
- function renderManaCost(manaCost) {
503
- const symbols = manaCost.match(/\{[^{}]+\}/g) || [];
504
- return symbols.map(symbol => {
505
- const symbolName = symbol.replace(/[{}]/g, '').toLowerCase();
506
- return `<div class="mana-symbol" style="background-image: url(https://c2.scryfall.com/file/scryfall-symbols/card-symbols/${symbolName}.svg); width: 1em; height: 1em;" title="${symbolName.toUpperCase()}"></div>`;
507
- }).join('');
 
 
508
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
509
  });
510
  </script>
511
  </body>
 
3
  <head>
4
  <meta charset="UTF-8">
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>MTG Card Explorer - Glass Edition</title>
7
  <script src="https://cdn.tailwindcss.com"></script>
8
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
9
  <style>
10
+ @import url('https://fonts.googleapis.com/css2?family=Inter:wght@200;300;400;500;600;700;800&family=Space+Grotesk:wght@300;400;500;600;700&display=swap');
11
+
12
+ * {
13
+ margin: 0;
14
+ padding: 0;
15
+ box-sizing: border-box;
16
+ }
17
+
18
+ body {
19
+ font-family: 'Inter', sans-serif;
20
+ background: #0a0a0a;
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%;
54
+ filter: blur(40px);
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%;
66
+ animation-duration: 25s;
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%;
75
+ animation-duration: 30s;
76
+ animation-delay: -5s;
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%;
85
+ animation-duration: 35s;
86
+ animation-delay: -10s;
87
+ }
88
+
89
+ @keyframes float {
90
+ 0%, 100% {
91
+ transform: translate(0, 0) scale(1);
92
+ }
93
+ 33% {
94
+ transform: translate(30px, -50px) scale(1.1);
95
+ }
96
+ 66% {
97
+ transform: translate(-20px, 30px) scale(0.9);
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;
164
+ background: linear-gradient(135deg, #fff 0%, rgba(255, 255, 255, 0.7) 100%);
165
+ -webkit-background-clip: text;
166
+ -webkit-text-fill-color: transparent;
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 {
173
+ color: rgba(255, 255, 255, 0.6);
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 {
197
+ content: '';
198
+ position: absolute;
199
+ top: 0;
200
+ left: 0;
201
+ width: 100%;
202
+ height: 100%;
203
+ background: linear-gradient(135deg,
204
+ rgba(255, 255, 255, 0) 0%,
205
+ rgba(255, 255, 255, 0.1) 50%,
206
+ rgba(255, 255, 255, 0) 100%);
207
+ transform: translateX(-100%);
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:
220
+ 0 10px 30px -10px rgba(0, 0, 0, 0.5),
221
+ 0 0 40px -20px rgba(255, 255, 255, 0.3);
222
+ }
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 {
254
+ width: 100%;
255
+ aspect-ratio: 0.72;
256
+ transition: transform 0.8s cubic-bezier(0.4, 0, 0.2, 1);
257
  transform-style: preserve-3d;
258
+ position: relative;
259
  }
260
 
261
+ .card-3d.flipped {
 
262
  transform: rotateY(180deg);
263
  }
264
 
265
+ .card-face {
266
+ position: absolute;
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),
274
+ 0 10px 20px -10px rgba(0, 0, 0, 0.6);
275
  }
276
 
277
+ .card-face-back {
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%;
403
+ animation: spin 1s linear infinite;
404
+ }
405
+
406
+ @keyframes spin {
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>
533
  </div>
534
  </div>
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>
 
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');
648
  const cardImage = document.getElementById('card-image');
649
  const cardDetails = document.getElementById('card-details');
650
  const cardName = document.getElementById('card-name');
651
  const cardType = document.getElementById('card-type');
 
652
  const cardText = document.getElementById('card-text');
653
  const cardStats = document.getElementById('card-stats');
654
  const cardLegalities = document.getElementById('card-legalities');
655
+ const faceIndicator = document.getElementById('face-indicator');
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
+
691
+ function switchFace(direction) {
692
+ if (!currentCardData || !currentCardData.card_faces) return;
693
 
694
+ currentCardFace = (currentCardFace + direction + currentCardData.card_faces.length) % currentCardData.card_faces.length;
695
+ displayCardFace(currentCardData, currentCardFace);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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');
737
+ faceCounter.textContent = `1/${card.card_faces.length}`;
738
+ flipBtn.querySelector('span').textContent = 'Switch Face';
739
+ } else {
740
+ cardFaceNav.classList.add('hidden');
741
+ flipBtn.querySelector('span').textContent = 'Details';
 
 
 
 
 
 
 
742
  }
743
 
744
+ displayCardFace(card, currentCardFace);
745
+ displayCardBackDetails(card);
746
+ displayPrices(card);
747
+ displayPurchaseLinks(card);
748
+ displayLinks(card);
749
+ displayLegalities(card);
750
+ displayRankings(card);
751
+ displaySetInfo(card);
752
+ }
753
+
754
+ function displayCardFace(card, faceIndex) {
755
+ let face, imageUris, name, typeText, oracleText, stats;
756
 
757
+ if (card.card_faces && card.card_faces[faceIndex]) {
758
+ face = card.card_faces[faceIndex];
759
+ imageUris = face.image_uris || card.image_uris;
760
+ name = face.name;
761
+ typeText = face.type_line;
762
+ oracleText = face.oracle_text;
763
+
764
+ stats = {
765
+ power: face.power || card.power,
766
+ toughness: face.toughness || card.toughness,
767
+ loyalty: face.loyalty || card.loyalty
768
+ };
769
+
770
+ faceIndicator.classList.remove('hidden');
771
+ faceIndicator.textContent = `Face ${faceIndex + 1}/${card.card_faces.length}`;
772
+ faceCounter.textContent = `${faceIndex + 1}/${card.card_faces.length}`;
773
+ } else {
774
+ imageUris = card.image_uris;
775
+ name = card.name;
776
+ typeText = card.type_line;
777
+ oracleText = card.oracle_text;
778
+ stats = {
779
+ power: card.power,
780
+ toughness: card.toughness,
781
+ loyalty: card.loyalty
782
+ };
783
+
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 || ''}`;
791
+ cardText.textContent = oracleText || 'No oracle text available.';
792
 
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
+ }
820
+
821
+ function displayStats(stats, card) {
 
 
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>';
838
+ }
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
 
873
+ document.getElementById('detailed-info').innerHTML = detailsHtml;
874
+ }
875
+
876
+ function displayPrices(card) {
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>';
887
  }
888
  document.getElementById('card-prices').innerHTML = pricesHtml;
889
+ }
890
+
891
+ function displayPurchaseLinks(card) {
892
  let linksHtml = '';
893
+ if (card.purchase_uris) {
894
+ const stores = [
895
+ { name: 'TCGPlayer', url: card.purchase_uris.tcgplayer, icon: 'fa-shopping-cart' },
896
+ { name: 'Cardmarket', url: card.purchase_uris.cardmarket, icon: 'fa-store' },
897
+ { name: 'Cardhoarder', url: card.purchase_uris.cardhoarder, icon: 'fa-warehouse' }
898
+ ];
899
+
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
+ }
915
+
916
+ function displayLinks(card) {
917
+ let linksHtml = '';
918
+ const links = [
919
+ { name: 'Scryfall', url: card.scryfall_uri, icon: 'fa-search' },
920
+ { name: 'Gatherer', url: card.related_uris?.gatherer, icon: 'fa-book' },
921
+ { name: 'EDHREC', url: card.related_uris?.edhrec, icon: 'fa-chart-bar' }
922
+ ];
923
+
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
+ }
933
+ });
934
 
935
+ document.getElementById('card-links').innerHTML = linksHtml;
936
+ }
937
+
938
+ function displayLegalities(card) {
939
  cardLegalities.innerHTML = '';
940
  if (card.legalities) {
941
+ const formats = ['standard', 'modern', 'legacy', 'vintage', 'commander', 'pioneer'];
 
 
 
 
 
 
 
 
 
 
942
 
943
+ formats.forEach(format => {
944
+ if (card.legalities[format]) {
945
+ const status = card.legalities[format];
946
  const badge = document.createElement('div');
947
+
948
+ let badgeClass = 'legality-badge ';
949
+ let icon = '';
950
+
951
+ if (status === 'legal') {
952
+ badgeClass += 'legality-legal';
953
+ icon = 'fa-check';
954
+ } else if (status === 'not_legal') {
955
+ badgeClass += 'legality-not-legal';
956
+ icon = 'fa-times';
957
+ } else if (status === 'restricted') {
958
+ badgeClass += 'legality-restricted';
959
+ icon = 'fa-exclamation';
960
+ } else if (status === 'banned') {
961
+ badgeClass += 'legality-banned';
962
+ icon = 'fa-ban';
963
+ }
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
+ });
973
  }
974
  }
975
 
976
+ function displayRankings(card) {
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
+ }
984
+
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 || '';
1025
+ } else {
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) {
1077
+ if (card.image_uris) {
1078
+ return card.image_uris.normal || card.image_uris.large || card.image_uris.small;
1079
+ }
1080
+ if (card.card_faces && card.card_faces[0] && card.card_faces[0].image_uris) {
1081
+ return card.card_faces[0].image_uris.normal || card.card_faces[0].image_uris.large;
1082
+ }
1083
+ return 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjgwIiBoZWlnaHQ9IjM5MCIgdmlld0JveD0iMCAwIDI4MCAzOTAiIGZpbGw9Im5vbmUiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CjxyZWN0IHdpZHRoPSIyODAiIGhlaWdodD0iMzkwIiBmaWxsPSIjMUExQTJFIi8+Cjx0ZXh0IHg9IjE0MCIgeT0iMTk1IiBmb250LWZhbWlseT0iQXJpYWwiIGZvbnQtc2l6ZT0iMjAiIGZpbGw9IiM2NjY2NjYiIHRleHQtYW5jaG9yPSJtaWRkbGUiPk5vIEltYWdlPC90ZXh0Pgo8L3N2Zz4=';
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
+ }
1106
+ };
1107
  });
1108
  </script>
1109
  </body>