pearsonkyle commited on
Commit
5156c21
·
verified ·
1 Parent(s): 595fdeb

Update index.html

Browse files
Files changed (1) hide show
  1. index.html +520 -100
index.html CHANGED
@@ -186,6 +186,99 @@
186
  background: rgba(255, 255, 255, 0.1);
187
  }
188
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
189
  /* Simplified autocomplete */
190
  .autocomplete-dropdown {
191
  position: absolute;
@@ -278,6 +371,8 @@
278
  display: flex;
279
  align-items: center;
280
  justify-content: space-between;
 
 
281
  }
282
 
283
  .search-results-title {
@@ -299,6 +394,13 @@
299
  font-size: 0.875rem;
300
  }
301
 
 
 
 
 
 
 
 
302
  /* Optimized buttons */
303
  .glass-button {
304
  background: var(--glass-bg);
@@ -329,6 +431,86 @@
329
  cursor: not-allowed;
330
  }
331
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
332
  /* Card container */
333
  .card-3d-container {
334
  perspective: 2000px;
@@ -399,7 +581,7 @@
399
  text-transform: uppercase;
400
  letter-spacing: 0.05em;
401
  color: var(--text-muted);
402
- margin-bottom: 0.25rem;
403
  }
404
 
405
  .info-value {
@@ -471,7 +653,7 @@
471
  color: rgb(248, 113, 113);
472
  }
473
 
474
- /* Gallery */
475
  .gallery-grid {
476
  display: grid;
477
  grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
@@ -483,6 +665,11 @@
483
  grid-template-columns: repeat(2, 1fr);
484
  gap: 1rem;
485
  }
 
 
 
 
 
486
  }
487
 
488
  @media (max-width: 480px) {
@@ -497,7 +684,6 @@
497
  border-radius: 12px;
498
  overflow: hidden;
499
  transition: var(--transition-fast);
500
- cursor: pointer;
501
  background: var(--glass-bg);
502
  border: 1px solid var(--glass-border);
503
  }
@@ -506,10 +692,11 @@
506
  transform: translateY(-4px) scale(1.02);
507
  }
508
 
 
509
  .gallery-overlay {
510
  position: absolute;
511
  inset: 0;
512
- background: linear-gradient(to top, rgba(0, 0, 0, 0.9) 0%, transparent 100%);
513
  opacity: 0;
514
  transition: var(--transition-fast);
515
  padding: 1rem;
@@ -522,41 +709,63 @@
522
  opacity: 1;
523
  }
524
 
525
- /* Modal */
526
- .details-modal {
527
- position: fixed;
528
- inset: 0;
529
- background: rgba(0, 0, 0, 0.95);
530
- backdrop-filter: blur(20px);
531
- z-index: 1000;
532
- display: none;
533
- overflow-y: auto;
534
- }
535
-
536
- .details-modal.active {
537
- display: block;
538
  }
539
 
540
- .details-modal-content {
541
- min-height: 100%;
542
- padding: 60px 20px 20px;
 
 
543
  }
544
 
545
- .details-close-btn {
546
- position: fixed;
547
- top: 20px;
548
- right: 20px;
549
- width: 40px;
550
- height: 40px;
551
  border-radius: 50%;
552
- background: var(--glass-bg);
553
- border: 1px solid var(--glass-border);
554
- color: white;
555
- display: flex;
556
  align-items: center;
557
  justify-content: center;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
558
  cursor: pointer;
559
- z-index: 1001;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
560
  }
561
 
562
  /* Loading */
@@ -663,16 +872,6 @@
663
  <!-- Toast notifications -->
664
  <div id="toast" class="toast"></div>
665
 
666
- <!-- Details Modal -->
667
- <div class="details-modal" id="details-modal">
668
- <button class="details-close-btn" onclick="closeModal()" aria-label="Close">
669
- <i class="fas fa-times"></i>
670
- </button>
671
- <div class="details-modal-content">
672
- <div id="modal-content" class="glass-card p-4"></div>
673
- </div>
674
- </div>
675
-
676
  <div class="container mx-auto py-4 md:py-8 px-4">
677
  <!-- Header -->
678
  <header class="text-center mb-8 md:mb-12">
@@ -687,7 +886,7 @@
687
  </div>
688
 
689
  <!-- Search -->
690
- <div class="search-container">
691
  <i class="fas fa-search search-icon"></i>
692
  <textarea
693
  id="search-input"
@@ -702,8 +901,6 @@
702
  </button>
703
  <div class="autocomplete-dropdown" id="autocomplete"></div>
704
  </div>
705
-
706
-
707
  </header>
708
 
709
  <!-- Main Card Display -->
@@ -729,9 +926,6 @@
729
  <button id="random-btn" class="glass-button">
730
  <i class="fas fa-shuffle mr-2"></i>Random
731
  </button>
732
- <button id="details-btn" class="glass-button">
733
- <i class="fas fa-info-circle mr-2"></i>Details
734
- </button>
735
  </div>
736
 
737
  <!-- Face Navigation -->
@@ -752,8 +946,10 @@
752
  <h2 id="card-name" class="text-2xl font-semibold text-white mb-1">No Card Selected</h2>
753
  <div id="card-type" class="text-white/60 mb-4"></div>
754
 
755
- <div class="glass p-3 rounded-lg mb-4 max-h-40 overflow-y-auto">
756
- <div id="card-text" class="text-white/80 text-sm whitespace-pre-line"></div>
 
 
757
  </div>
758
 
759
  <!-- Legalities -->
@@ -783,9 +979,19 @@
783
  </div>
784
  </div>
785
 
786
- <!-- Gallery Section -->
787
  <div id="gallery-section" class="mt-12 hidden">
788
- <h2 id="gallery-header" class="text-2xl font-bold text-center text-white mb-6">Similar Cards</h2>
 
 
 
 
 
 
 
 
 
 
789
  <div id="gallery" class="gallery-grid max-w-7xl mx-auto"></div>
790
  </div>
791
 
@@ -796,7 +1002,16 @@
796
  <i class="fas fa-search"></i>
797
  <span>Search: <span class="search-query" id="search-query-text"></span></span>
798
  </div>
799
- <div class="results-count" id="results-count"></div>
 
 
 
 
 
 
 
 
 
800
  </div>
801
  <div id="search-results" class="gallery-grid max-w-7xl mx-auto"></div>
802
  </div>
@@ -819,6 +1034,18 @@
819
  let autocompleteResults = [];
820
  let isSearchMode = false;
821
  let lastSearchQuery = '';
 
 
 
 
 
 
 
 
 
 
 
 
822
 
823
  // DOM Elements
824
  const $ = id => document.getElementById(id);
@@ -826,7 +1053,6 @@
826
  const searchClear = $('search-clear');
827
  const autocomplete = $('autocomplete');
828
  const randomBtn = $('random-btn');
829
- const detailsBtn = $('details-btn');
830
  const loading = $('loading');
831
  const toast = $('toast');
832
  const mainCardSection = $('main-card-section');
@@ -836,10 +1062,91 @@
836
 
837
  // Initialize
838
  document.addEventListener('DOMContentLoaded', () => {
 
 
839
  fetchRandomCard();
840
  setupEventListeners();
841
  });
842
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
843
  // Event Listeners
844
  function setupEventListeners() {
845
  searchInput.addEventListener('input', handleSearch);
@@ -847,9 +1154,9 @@
847
  searchClear.addEventListener('click', clearSearch);
848
  randomBtn.addEventListener('click', () => {
849
  exitSearchMode();
 
850
  fetchRandomCard();
851
  });
852
- detailsBtn.addEventListener('click', showDetails);
853
 
854
  // Close autocomplete on outside click
855
  document.addEventListener('click', e => {
@@ -865,7 +1172,6 @@
865
  searchInput.focus();
866
  }
867
  if (e.key === 'Escape') {
868
- closeModal();
869
  searchInput.blur();
870
  hideAutocomplete();
871
  }
@@ -936,6 +1242,7 @@
936
  searchInput.value = '';
937
  searchClear.classList.remove('visible');
938
  hideAutocomplete();
 
939
  if (isSearchMode) {
940
  exitSearchMode();
941
  fetchRandomCard();
@@ -1008,17 +1315,26 @@
1008
  }
1009
  }
1010
 
1011
- // Deck Doctor Search
1012
  async function performDeckDoctorSearch(query) {
1013
  setLoading(true);
1014
  lastSearchQuery = query;
1015
 
1016
  try {
1017
- const response = await fetch(`https://api.deck.doctor/v1/mtg/search?q=${encodeURIComponent(query)}&topk=20`);
 
 
 
 
 
 
 
 
1018
  const data = await response.json();
1019
 
1020
  if (data && data.length > 0) {
1021
- enterSearchMode(query, data);
 
1022
  showToast(`Found ${data.length} cards`, 'success');
1023
  } else {
1024
  showToast('No cards found', 'error');
@@ -1107,20 +1423,38 @@
1107
 
1108
  // Hide search results
1109
  searchResultsSection.classList.add('hidden');
 
 
 
1110
  }
1111
 
1112
  function displaySearchResults(results) {
1113
  const searchResultsContainer = $('search-results');
1114
 
1115
  searchResultsContainer.innerHTML = results.map(([card, score]) => `
1116
- <div class="gallery-item" onclick="loadCardFromSearch('${card.id}')">
1117
  <img src="${card.image_uris?.normal || ''}" alt="${card.name}" class="w-full h-full object-cover">
1118
  ${card.prices?.usd ? `<div class="absolute bottom-2 left-2 price-tag text-xs">${parseFloat(card.prices.usd).toFixed(2)}</div>` : ''}
1119
  <div class="gallery-overlay">
1120
- <h4 class="font-semibold text-white line-clamp-2">${card.name}</h4>
1121
- <p class="text-xs text-white/60 mt-1">${card.type_line}</p>
1122
- ${score < 1.0 ? `<p class="text-xs text-white/40 mt-1">${Math.round(score * 100)}% match</p>` :
1123
- score === 1.0 ? `<p class="text-xs text-white/40 mt-1">From decklist</p>` : ''}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1124
  </div>
1125
  </div>
1126
  `).join('');
@@ -1141,6 +1475,7 @@
1141
  searchClear.classList.remove('visible');
1142
 
1143
  window.scrollTo({ top: 0, behavior: 'smooth' });
 
1144
  } catch (error) {
1145
  showToast('Failed to load card', 'error');
1146
  } finally {
@@ -1211,10 +1546,88 @@
1211
  // Update card info
1212
  $('card-name').textContent = face.name || card.name;
1213
  $('card-type').innerHTML = `<i class="fas fa-layer-group mr-2"></i>${face.type_line || card.type_line}`;
1214
- $('card-text').textContent = face.oracle_text || card.oracle_text || '';
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1215
  }
1216
 
1217
  function updateCardInfo(card) {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1218
  // Legalities
1219
  const formats = [
1220
  { key: 'standard', name: 'Standard' },
@@ -1356,9 +1769,18 @@
1356
  try {
1357
  // Build the query string using the same logic as Python
1358
  const query = buildCardQueryString(card);
 
1359
  console.log('Query string:', query); // Debug logging
1360
 
1361
- const response = await fetch(`https://api.deck.doctor/v1/mtg/search?q=${encodeURIComponent(query)}&topk=12`);
 
 
 
 
 
 
 
 
1362
  const data = await response.json();
1363
 
1364
  if (data?.length > 0) {
@@ -1372,6 +1794,10 @@
1372
 
1373
  displayGallery(filteredResults);
1374
  $('gallery-section').classList.remove('hidden');
 
 
 
 
1375
  }
1376
  } catch (error) {
1377
  console.error('Gallery error:', error);
@@ -1380,12 +1806,35 @@
1380
 
1381
  function displayGallery(cards) {
1382
  $('gallery').innerHTML = cards.map(([card, score]) => `
1383
- <div class="gallery-item" onclick="loadCard('${card.id}')">
1384
  <img src="${card.image_uris?.normal || ''}" alt="${card.name}" class="w-full h-full object-cover">
1385
- ${card.prices?.usd ? `<div class="absolute bottom-2 left-2 price-tag text-xs">$${parseFloat(card.prices.usd).toFixed(2)}</div>` : ''}
1386
  <div class="gallery-overlay">
1387
- <h4 class="font-semibold text-white line-clamp-2">${card.name}</h4>
1388
- <p class="text-xs text-white/60">${Math.round(score * 100)}% similar</p>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1389
  </div>
1390
  </div>
1391
  `).join('');
@@ -1398,6 +1847,7 @@
1398
  const card = await response.json();
1399
  displayCard(card);
1400
  window.scrollTo({ top: 0, behavior: 'smooth' });
 
1401
  } catch (error) {
1402
  showToast('Failed to load card', 'error');
1403
  } finally {
@@ -1409,36 +1859,7 @@
1409
  if (!currentCard?.card_faces) return;
1410
  currentFace = (currentFace + direction + currentCard.card_faces.length) % currentCard.card_faces.length;
1411
  displayCardFace(currentCard, currentFace);
1412
- }
1413
-
1414
- function showDetails() {
1415
- if (!currentCard) return;
1416
- $('modal-content').innerHTML = `
1417
- <h3 class="text-2xl font-semibold text-white mb-4">${currentCard.name}</h3>
1418
- <div class="space-y-3">
1419
- <div class="glass p-3 rounded">
1420
- <p class="info-label">Set</p>
1421
- <p class="text-white">${currentCard.set_name} (${currentCard.set.toUpperCase()})</p>
1422
- </div>
1423
- ${currentCard.flavor_text ? `
1424
- <div class="glass p-3 rounded">
1425
- <p class="info-label">Flavor Text</p>
1426
- <p class="italic text-white/70">"${currentCard.flavor_text}"</p>
1427
- </div>
1428
- ` : ''}
1429
- <div class="glass p-3 rounded">
1430
- <p class="info-label">Artist</p>
1431
- <p class="text-white">${currentCard.artist}</p>
1432
- </div>
1433
- </div>
1434
- `;
1435
- $('details-modal').classList.add('active');
1436
- document.body.style.overflow = 'hidden';
1437
- }
1438
-
1439
- function closeModal() {
1440
- $('details-modal').classList.remove('active');
1441
- document.body.style.overflow = '';
1442
  }
1443
 
1444
  // Utilities
@@ -1446,7 +1867,6 @@
1446
  isLoading = state;
1447
  loading.classList.toggle('hidden', !state);
1448
  randomBtn.disabled = state;
1449
- detailsBtn.disabled = state || !currentCard;
1450
  }
1451
 
1452
  function showToast(message, type = 'info') {
 
186
  background: rgba(255, 255, 255, 0.1);
187
  }
188
 
189
+ /* Gallery Header with Filters */
190
+ .gallery-header {
191
+ display: flex;
192
+ align-items: center;
193
+ justify-content: space-between;
194
+ margin-bottom: 24px;
195
+ flex-wrap: wrap;
196
+ gap: 16px;
197
+ }
198
+
199
+ .gallery-title {
200
+ font-size: 1.5rem;
201
+ font-weight: 600;
202
+ color: white;
203
+ display: flex;
204
+ align-items: center;
205
+ gap: 12px;
206
+ }
207
+
208
+ /* Color Filters - Redesigned for gallery section */
209
+ .color-filters-inline {
210
+ display: flex;
211
+ align-items: center;
212
+ gap: 8px;
213
+ }
214
+
215
+ .color-filter-label {
216
+ font-size: 0.875rem;
217
+ font-weight: 500;
218
+ color: var(--text-secondary);
219
+ margin-right: 4px;
220
+ }
221
+
222
+ .color-filter-btn {
223
+ width: 36px;
224
+ height: 36px;
225
+ border-radius: 50%;
226
+ border: 2px solid transparent;
227
+ cursor: pointer;
228
+ transition: var(--transition-fast);
229
+ display: flex;
230
+ align-items: center;
231
+ justify-content: center;
232
+ font-weight: bold;
233
+ font-size: 13px;
234
+ position: relative;
235
+ background: none;
236
+ outline: none;
237
+ }
238
+
239
+ .color-filter-btn:hover {
240
+ transform: scale(1.15);
241
+ box-shadow: 0 0 20px rgba(255, 255, 255, 0.3);
242
+ }
243
+
244
+ .color-filter-btn.active {
245
+ border-color: rgba(255, 255, 255, 0.9);
246
+ box-shadow: 0 0 20px rgba(255, 255, 255, 0.5);
247
+ transform: scale(1.15);
248
+ }
249
+
250
+ .color-filter-btn::after {
251
+ content: '';
252
+ position: absolute;
253
+ inset: 2px;
254
+ border-radius: 50%;
255
+ transition: var(--transition-fast);
256
+ }
257
+
258
+ /* Clear filters button */
259
+ .clear-filters-btn {
260
+ background: rgba(239, 68, 68, 0.2);
261
+ border: 1px solid rgba(239, 68, 68, 0.4);
262
+ color: rgb(248, 113, 113);
263
+ padding: 6px 12px;
264
+ border-radius: 8px;
265
+ font-size: 0.75rem;
266
+ cursor: pointer;
267
+ transition: var(--transition-fast);
268
+ display: none;
269
+ }
270
+
271
+ .clear-filters-btn.visible {
272
+ display: inline-flex;
273
+ align-items: center;
274
+ gap: 4px;
275
+ }
276
+
277
+ .clear-filters-btn:hover {
278
+ background: rgba(239, 68, 68, 0.3);
279
+ border-color: rgba(239, 68, 68, 0.6);
280
+ }
281
+
282
  /* Simplified autocomplete */
283
  .autocomplete-dropdown {
284
  position: absolute;
 
371
  display: flex;
372
  align-items: center;
373
  justify-content: space-between;
374
+ flex-wrap: wrap;
375
+ gap: 12px;
376
  }
377
 
378
  .search-results-title {
 
394
  font-size: 0.875rem;
395
  }
396
 
397
+ /* Search Results Filters */
398
+ .search-results-filters {
399
+ display: flex;
400
+ align-items: center;
401
+ gap: 8px;
402
+ }
403
+
404
  /* Optimized buttons */
405
  .glass-button {
406
  background: var(--glass-bg);
 
431
  cursor: not-allowed;
432
  }
433
 
434
+ .glass-button-small {
435
+ padding: 6px 12px;
436
+ font-size: 0.8rem;
437
+ min-height: 32px;
438
+ }
439
+
440
+ /* Oracle text search buttons */
441
+ .oracle-search-btn {
442
+ background: rgba(102, 126, 234, 0.2);
443
+ border: 1px solid rgba(102, 126, 234, 0.4);
444
+ color: rgba(102, 126, 234, 1);
445
+ padding: 12px 16px;
446
+ border-radius: 12px;
447
+ font-size: 0.85rem;
448
+ cursor: pointer;
449
+ transition: var(--transition-fast);
450
+ display: block;
451
+ margin: 8px 0;
452
+ text-align: left;
453
+ width: 100%;
454
+ word-wrap: break-word;
455
+ line-height: 1.5;
456
+ white-space: normal;
457
+ overflow-wrap: break-word;
458
+ word-break: break-word;
459
+ }
460
+
461
+ .oracle-search-btn:hover {
462
+ background: rgba(102, 126, 234, 0.3);
463
+ border-color: rgba(102, 126, 234, 0.6);
464
+ color: white;
465
+ transform: translateY(-1px);
466
+ }
467
+
468
+ .oracle-search-btn i {
469
+ margin-right: 8px;
470
+ flex-shrink: 0;
471
+ }
472
+
473
+ /* Mobile optimization for oracle search buttons */
474
+ @media (max-width: 768px) {
475
+ .oracle-search-btn {
476
+ font-size: 0.8rem;
477
+ padding: 10px 14px;
478
+ margin: 6px 0;
479
+ line-height: 1.4;
480
+ display: flex;
481
+ align-items: flex-start;
482
+ }
483
+
484
+ .oracle-search-btn i {
485
+ margin-right: 8px;
486
+ font-size: 0.8rem;
487
+ margin-top: 0.1em;
488
+ }
489
+ }
490
+
491
+ @media (max-width: 480px) {
492
+ .oracle-search-btn {
493
+ font-size: 0.75rem;
494
+ padding: 10px 12px;
495
+ margin: 5px 0;
496
+ line-height: 1.4;
497
+ border-radius: 10px;
498
+ }
499
+
500
+ .oracle-search-btn i {
501
+ margin-right: 6px;
502
+ font-size: 0.75rem;
503
+ }
504
+ }
505
+
506
+ @media (max-width: 360px) {
507
+ .oracle-search-btn {
508
+ font-size: 0.7rem;
509
+ padding: 8px 10px;
510
+ margin: 4px 0;
511
+ }
512
+ }
513
+
514
  /* Card container */
515
  .card-3d-container {
516
  perspective: 2000px;
 
581
  text-transform: uppercase;
582
  letter-spacing: 0.05em;
583
  color: var(--text-muted);
584
+ margin-bottom: 0.5rem;
585
  }
586
 
587
  .info-value {
 
653
  color: rgb(248, 113, 113);
654
  }
655
 
656
+ /* Enhanced Gallery */
657
  .gallery-grid {
658
  display: grid;
659
  grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
 
665
  grid-template-columns: repeat(2, 1fr);
666
  gap: 1rem;
667
  }
668
+
669
+ .gallery-header {
670
+ flex-direction: column;
671
+ align-items: flex-start;
672
+ }
673
  }
674
 
675
  @media (max-width: 480px) {
 
684
  border-radius: 12px;
685
  overflow: hidden;
686
  transition: var(--transition-fast);
 
687
  background: var(--glass-bg);
688
  border: 1px solid var(--glass-border);
689
  }
 
692
  transform: translateY(-4px) scale(1.02);
693
  }
694
 
695
+ /* Enhanced gallery overlay */
696
  .gallery-overlay {
697
  position: absolute;
698
  inset: 0;
699
+ background: linear-gradient(to top, rgba(0, 0, 0, 0.95) 0%, rgba(0, 0, 0, 0.7) 50%, transparent 100%);
700
  opacity: 0;
701
  transition: var(--transition-fast);
702
  padding: 1rem;
 
709
  opacity: 1;
710
  }
711
 
712
+ .gallery-overlay-content {
713
+ space-y: 1rem;
 
 
 
 
 
 
 
 
 
 
 
714
  }
715
 
716
+ .gallery-mana-cost {
717
+ display: flex;
718
+ align-items: center;
719
+ gap: 4px;
720
+ margin-bottom: 8px;
721
  }
722
 
723
+ .mana-symbol {
724
+ width: 16px;
725
+ height: 16px;
 
 
 
726
  border-radius: 50%;
727
+ display: inline-flex;
 
 
 
728
  align-items: center;
729
  justify-content: center;
730
+ font-size: 10px;
731
+ font-weight: bold;
732
+ color: white;
733
+ text-shadow: 0 1px 2px rgba(0, 0, 0, 0.5);
734
+ }
735
+
736
+ .gallery-actions {
737
+ display: flex;
738
+ gap: 8px;
739
+ margin-top: 12px;
740
+ }
741
+
742
+ .gallery-btn {
743
+ background: rgba(0, 0, 0, 0.8);
744
+ border: 1px solid rgba(255, 255, 255, 0.2);
745
+ color: white;
746
+ padding: 6px 12px;
747
+ border-radius: 6px;
748
+ font-size: 0.75rem;
749
  cursor: pointer;
750
+ transition: var(--transition-fast);
751
+ text-decoration: none;
752
+ display: inline-flex;
753
+ align-items: center;
754
+ gap: 4px;
755
+ }
756
+
757
+ .gallery-btn:hover {
758
+ background: rgba(102, 126, 234, 0.8);
759
+ border-color: rgba(102, 126, 234, 1);
760
+ }
761
+
762
+ .gallery-btn-view {
763
+ background: rgba(102, 126, 234, 0.8);
764
+ }
765
+
766
+ .gallery-btn-view:hover {
767
+ background: rgba(102, 126, 234, 1);
768
+ transform: translateY(-1px);
769
  }
770
 
771
  /* Loading */
 
872
  <!-- Toast notifications -->
873
  <div id="toast" class="toast"></div>
874
 
 
 
 
 
 
 
 
 
 
 
875
  <div class="container mx-auto py-4 md:py-8 px-4">
876
  <!-- Header -->
877
  <header class="text-center mb-8 md:mb-12">
 
886
  </div>
887
 
888
  <!-- Search -->
889
+ <div class="search-container mt-8">
890
  <i class="fas fa-search search-icon"></i>
891
  <textarea
892
  id="search-input"
 
901
  </button>
902
  <div class="autocomplete-dropdown" id="autocomplete"></div>
903
  </div>
 
 
904
  </header>
905
 
906
  <!-- Main Card Display -->
 
926
  <button id="random-btn" class="glass-button">
927
  <i class="fas fa-shuffle mr-2"></i>Random
928
  </button>
 
 
 
929
  </div>
930
 
931
  <!-- Face Navigation -->
 
946
  <h2 id="card-name" class="text-2xl font-semibold text-white mb-1">No Card Selected</h2>
947
  <div id="card-type" class="text-white/60 mb-4"></div>
948
 
949
+ <!-- Oracle Text Search Section -->
950
+ <div id="oracle-search-section" class="mb-4">
951
+ <h3 class="info-label">Search Card Mechanics</h3>
952
+ <div id="oracle-search-buttons" class="space-y-2"></div>
953
  </div>
954
 
955
  <!-- Legalities -->
 
979
  </div>
980
  </div>
981
 
982
+ <!-- Gallery Section with Filters -->
983
  <div id="gallery-section" class="mt-12 hidden">
984
+ <div class="gallery-header max-w-7xl mx-auto">
985
+ <h2 id="gallery-header" class="gallery-title">Similar Cards</h2>
986
+ <div class="color-filters-inline">
987
+ <span class="color-filter-label">Filter:</span>
988
+ <div id="gallery-color-filters" class="flex gap-2"></div>
989
+ <button id="clear-gallery-filters" class="clear-filters-btn" onclick="clearGalleryFilters()">
990
+ <i class="fas fa-times"></i>
991
+ Clear
992
+ </button>
993
+ </div>
994
+ </div>
995
  <div id="gallery" class="gallery-grid max-w-7xl mx-auto"></div>
996
  </div>
997
 
 
1002
  <i class="fas fa-search"></i>
1003
  <span>Search: <span class="search-query" id="search-query-text"></span></span>
1004
  </div>
1005
+ <div class="flex items-center gap-4">
1006
+ <div class="results-count" id="results-count"></div>
1007
+ <div class="search-results-filters">
1008
+ <div id="search-color-filters" class="flex gap-2"></div>
1009
+ <button id="clear-search-filters" class="clear-filters-btn" onclick="clearSearchFilters()">
1010
+ <i class="fas fa-times"></i>
1011
+ Clear
1012
+ </button>
1013
+ </div>
1014
+ </div>
1015
  </div>
1016
  <div id="search-results" class="gallery-grid max-w-7xl mx-auto"></div>
1017
  </div>
 
1034
  let autocompleteResults = [];
1035
  let isSearchMode = false;
1036
  let lastSearchQuery = '';
1037
+ let galleryColors = new Set();
1038
+ let searchColors = new Set();
1039
+ let currentGalleryQuery = '';
1040
+
1041
+ // Mana color definitions
1042
+ const manaColors = [
1043
+ { symbol: 'W', name: 'White', color: '#FFFBD5', textColor: '#000' },
1044
+ { symbol: 'U', name: 'Blue', color: '#0E68AB', textColor: '#fff' },
1045
+ { symbol: 'B', name: 'Black', color: '#150B00', textColor: '#fff' },
1046
+ { symbol: 'R', name: 'Red', color: '#D3202A', textColor: '#fff' },
1047
+ { symbol: 'G', name: 'Green', color: '#00733E', textColor: '#fff' }
1048
+ ];
1049
 
1050
  // DOM Elements
1051
  const $ = id => document.getElementById(id);
 
1053
  const searchClear = $('search-clear');
1054
  const autocomplete = $('autocomplete');
1055
  const randomBtn = $('random-btn');
 
1056
  const loading = $('loading');
1057
  const toast = $('toast');
1058
  const mainCardSection = $('main-card-section');
 
1062
 
1063
  // Initialize
1064
  document.addEventListener('DOMContentLoaded', () => {
1065
+ createColorFilterButtons('gallery-color-filters', 'gallery');
1066
+ createColorFilterButtons('search-color-filters', 'search');
1067
  fetchRandomCard();
1068
  setupEventListeners();
1069
  });
1070
 
1071
+ // Create color filter buttons for different sections
1072
+ function createColorFilterButtons(containerId, filterType) {
1073
+ const container = $(containerId);
1074
+ if (!container) return;
1075
+
1076
+ container.innerHTML = manaColors.map(color => `
1077
+ <button
1078
+ class="color-filter-btn"
1079
+ data-color="${color.symbol}"
1080
+ data-filter-type="${filterType}"
1081
+ onclick="toggleColorFilter('${color.symbol}', '${filterType}')"
1082
+ style="color: ${color.textColor};"
1083
+ title="Filter by ${color.name}"
1084
+ >
1085
+ <span style="
1086
+ position: absolute;
1087
+ inset: 0;
1088
+ background: ${color.color};
1089
+ border-radius: 50%;
1090
+ z-index: -1;
1091
+ "></span>
1092
+ ${color.symbol}
1093
+ </button>
1094
+ `).join('');
1095
+ }
1096
+
1097
+ // Toggle color filter for specific section
1098
+ function toggleColorFilter(colorSymbol, filterType) {
1099
+ const colorSet = filterType === 'gallery' ? galleryColors : searchColors;
1100
+ const button = document.querySelector(`[data-color="${colorSymbol}"][data-filter-type="${filterType}"]`);
1101
+
1102
+ if (colorSet.has(colorSymbol)) {
1103
+ colorSet.delete(colorSymbol);
1104
+ button.classList.remove('active');
1105
+ } else {
1106
+ colorSet.add(colorSymbol);
1107
+ button.classList.add('active');
1108
+ }
1109
+
1110
+ // Update clear button visibility
1111
+ const clearBtn = filterType === 'gallery' ? $('clear-gallery-filters') : $('clear-search-filters');
1112
+ if (clearBtn) {
1113
+ clearBtn.classList.toggle('visible', colorSet.size > 0);
1114
+ }
1115
+
1116
+ // Apply filters based on type
1117
+ if (filterType === 'gallery' && currentCard) {
1118
+ fetchSimilarCards(currentCard);
1119
+ } else if (filterType === 'search' && isSearchMode) {
1120
+ performDeckDoctorSearch(lastSearchQuery);
1121
+ }
1122
+ }
1123
+
1124
+ // Clear filters for gallery
1125
+ function clearGalleryFilters() {
1126
+ galleryColors.clear();
1127
+ document.querySelectorAll('[data-filter-type="gallery"]').forEach(btn => {
1128
+ btn.classList.remove('active');
1129
+ });
1130
+ $('clear-gallery-filters').classList.remove('visible');
1131
+
1132
+ if (currentCard) {
1133
+ fetchSimilarCards(currentCard);
1134
+ }
1135
+ }
1136
+
1137
+ // Clear filters for search
1138
+ function clearSearchFilters() {
1139
+ searchColors.clear();
1140
+ document.querySelectorAll('[data-filter-type="search"]').forEach(btn => {
1141
+ btn.classList.remove('active');
1142
+ });
1143
+ $('clear-search-filters').classList.remove('visible');
1144
+
1145
+ if (isSearchMode) {
1146
+ performDeckDoctorSearch(lastSearchQuery);
1147
+ }
1148
+ }
1149
+
1150
  // Event Listeners
1151
  function setupEventListeners() {
1152
  searchInput.addEventListener('input', handleSearch);
 
1154
  searchClear.addEventListener('click', clearSearch);
1155
  randomBtn.addEventListener('click', () => {
1156
  exitSearchMode();
1157
+ clearGalleryFilters();
1158
  fetchRandomCard();
1159
  });
 
1160
 
1161
  // Close autocomplete on outside click
1162
  document.addEventListener('click', e => {
 
1172
  searchInput.focus();
1173
  }
1174
  if (e.key === 'Escape') {
 
1175
  searchInput.blur();
1176
  hideAutocomplete();
1177
  }
 
1242
  searchInput.value = '';
1243
  searchClear.classList.remove('visible');
1244
  hideAutocomplete();
1245
+ clearSearchFilters();
1246
  if (isSearchMode) {
1247
  exitSearchMode();
1248
  fetchRandomCard();
 
1315
  }
1316
  }
1317
 
1318
+ // Deck Doctor Search with color filters
1319
  async function performDeckDoctorSearch(query) {
1320
  setLoading(true);
1321
  lastSearchQuery = query;
1322
 
1323
  try {
1324
+ // Build URL with colors if selected
1325
+ let url = `https://api.deck.doctor/v1/mtg/search?q=${encodeURIComponent(query)}&topk=20&price_threshold=0`;
1326
+
1327
+ // Add color filters for search results
1328
+ searchColors.forEach(color => {
1329
+ url += `&colors=${color}`;
1330
+ });
1331
+
1332
+ const response = await fetch(url);
1333
  const data = await response.json();
1334
 
1335
  if (data && data.length > 0) {
1336
+ const colorFilterText = searchColors.size > 0 ? ` (${Array.from(searchColors).join('')})` : '';
1337
+ enterSearchMode(query + colorFilterText, data);
1338
  showToast(`Found ${data.length} cards`, 'success');
1339
  } else {
1340
  showToast('No cards found', 'error');
 
1423
 
1424
  // Hide search results
1425
  searchResultsSection.classList.add('hidden');
1426
+
1427
+ // Clear search filters when exiting search mode
1428
+ clearSearchFilters();
1429
  }
1430
 
1431
  function displaySearchResults(results) {
1432
  const searchResultsContainer = $('search-results');
1433
 
1434
  searchResultsContainer.innerHTML = results.map(([card, score]) => `
1435
+ <div class="gallery-item">
1436
  <img src="${card.image_uris?.normal || ''}" alt="${card.name}" class="w-full h-full object-cover">
1437
  ${card.prices?.usd ? `<div class="absolute bottom-2 left-2 price-tag text-xs">${parseFloat(card.prices.usd).toFixed(2)}</div>` : ''}
1438
  <div class="gallery-overlay">
1439
+ <div class="gallery-overlay-content">
1440
+ <h4 class="font-semibold text-white line-clamp-2">${card.name}</h4>
1441
+ <p class="text-xs text-white/60 mt-1">${card.type_line}</p>
1442
+ ${score < 1.0 ? `<p class="text-xs text-white/40 mt-1">${Math.round(score * 100)}% match</p>` :
1443
+ score === 1.0 ? `<p class="text-xs text-white/40 mt-1">From decklist</p>` : ''}
1444
+
1445
+ <div class="gallery-actions">
1446
+ <button onclick="loadCardFromSearch('${card.id}')" class="gallery-btn gallery-btn-view">
1447
+ <i class="fas fa-eye"></i>
1448
+ View Card
1449
+ </button>
1450
+ ${card.purchase_uris?.tcgplayer ? `
1451
+ <a href="${card.purchase_uris.tcgplayer}" target="_blank" class="gallery-btn">
1452
+ <i class="fas fa-shopping-cart"></i>
1453
+ Buy
1454
+ </a>
1455
+ ` : ''}
1456
+ </div>
1457
+ </div>
1458
  </div>
1459
  </div>
1460
  `).join('');
 
1475
  searchClear.classList.remove('visible');
1476
 
1477
  window.scrollTo({ top: 0, behavior: 'smooth' });
1478
+ showToast('Card loaded successfully', 'success');
1479
  } catch (error) {
1480
  showToast('Failed to load card', 'error');
1481
  } finally {
 
1546
  // Update card info
1547
  $('card-name').textContent = face.name || card.name;
1548
  $('card-type').innerHTML = `<i class="fas fa-layer-group mr-2"></i>${face.type_line || card.type_line}`;
1549
+ }
1550
+
1551
+ // Oracle text search functionality
1552
+ function createOracleSearchButtons(oracleText) {
1553
+ if (!oracleText) return '';
1554
+
1555
+ const cardName = currentCard.name;
1556
+ const shortName = cardName.includes(',') ? cardName.split(',')[0].trim() : cardName;
1557
+
1558
+ // Split oracle text into meaningful lines
1559
+ const lines = oracleText.split('\n').filter(line => line.trim().length > 0);
1560
+ const buttons = [];
1561
+
1562
+ lines.forEach((line) => {
1563
+ let cleanedLine = line.trim();
1564
+ if (cleanedLine.length > 10) {
1565
+ // Replace card names with "this card" (case insensitive)
1566
+ cleanedLine = cleanedLine.replace(new RegExp(cardName, 'gi'), 'this card');
1567
+ if (shortName !== cardName) {
1568
+ cleanedLine = cleanedLine.replace(new RegExp(shortName, 'gi'), 'this card');
1569
+ }
1570
+
1571
+ // Show full text in buttons now
1572
+ buttons.push(`
1573
+ <button class="oracle-search-btn" onclick="searchOracleText('${cleanedLine.replace(/'/g, "\\'")}')">
1574
+ <i class="fas fa-search mr-2"></i>
1575
+ ${cleanedLine}
1576
+ </button>
1577
+ `);
1578
+ }
1579
+ });
1580
+
1581
+ return buttons.join('');
1582
+ }
1583
+
1584
+ async function searchOracleText(searchTerm) {
1585
+ searchInput.value = searchTerm;
1586
+ searchClear.classList.add('visible');
1587
+ await performDeckDoctorSearch(searchTerm);
1588
+ }
1589
+
1590
+ // Mana cost display helper
1591
+ function formatManaCost(manaCost) {
1592
+ if (!manaCost) return '';
1593
+
1594
+ const symbols = manaCost.match(/\{[^}]+\}/g) || [];
1595
+ return symbols.map(symbol => {
1596
+ const clean = symbol.replace(/[{}]/g, '');
1597
+ let color = '';
1598
+ let bgColor = '';
1599
+
1600
+ switch(clean) {
1601
+ case 'W': color = 'white'; bgColor = '#FFFBD5'; break;
1602
+ case 'U': color = 'blue'; bgColor = '#0E68AB'; break;
1603
+ case 'B': color = 'black'; bgColor = '#150B00'; break;
1604
+ case 'R': color = 'red'; bgColor = '#D3202A'; break;
1605
+ case 'G': color = 'green'; bgColor = '#00733E'; break;
1606
+ case 'C': color = 'colorless'; bgColor = '#A89B9A'; break;
1607
+ default: color = 'generic'; bgColor = '#CAC5C0'; break;
1608
+ }
1609
+
1610
+ return `<span class="mana-symbol" style="background-color: ${bgColor}">${clean}</span>`;
1611
+ }).join('');
1612
  }
1613
 
1614
  function updateCardInfo(card) {
1615
+ // Oracle text search buttons
1616
+ const hasFaces = card.card_faces?.length > 1;
1617
+ const face = hasFaces ? card.card_faces[currentFace] : card;
1618
+ const oracleText = face.oracle_text || card.oracle_text || '';
1619
+
1620
+ const oracleSection = $('oracle-search-section');
1621
+ if (oracleText) {
1622
+ $('oracle-search-buttons').innerHTML = createOracleSearchButtons(oracleText);
1623
+ oracleSection.style.display = 'block';
1624
+
1625
+ // Add container class for better mobile layout
1626
+ $('oracle-search-buttons').classList.add('space-y-2');
1627
+ } else {
1628
+ oracleSection.style.display = 'none';
1629
+ }
1630
+
1631
  // Legalities
1632
  const formats = [
1633
  { key: 'standard', name: 'Standard' },
 
1769
  try {
1770
  // Build the query string using the same logic as Python
1771
  const query = buildCardQueryString(card);
1772
+ currentGalleryQuery = query;
1773
  console.log('Query string:', query); // Debug logging
1774
 
1775
+ // Build URL with gallery color filters
1776
+ let url = `https://api.deck.doctor/v1/mtg/search?q=${encodeURIComponent(query)}&topk=12&price_threshold=0`;
1777
+
1778
+ // Add color filters for gallery
1779
+ galleryColors.forEach(color => {
1780
+ url += `&colors=${color}`;
1781
+ });
1782
+
1783
+ const response = await fetch(url);
1784
  const data = await response.json();
1785
 
1786
  if (data?.length > 0) {
 
1794
 
1795
  displayGallery(filteredResults);
1796
  $('gallery-section').classList.remove('hidden');
1797
+
1798
+ // Update header if filters are active
1799
+ const colorFilterText = galleryColors.size > 0 ? ` (${Array.from(galleryColors).join('')})` : '';
1800
+ $('gallery-header').textContent = `Similar Cards${colorFilterText}`;
1801
  }
1802
  } catch (error) {
1803
  console.error('Gallery error:', error);
 
1806
 
1807
  function displayGallery(cards) {
1808
  $('gallery').innerHTML = cards.map(([card, score]) => `
1809
+ <div class="gallery-item">
1810
  <img src="${card.image_uris?.normal || ''}" alt="${card.name}" class="w-full h-full object-cover">
1811
+ ${card.prices?.usd ? `<div class="absolute top-2 left-2 price-tag text-xs">$${parseFloat(card.prices.usd).toFixed(2)}</div>` : ''}
1812
  <div class="gallery-overlay">
1813
+ <div class="gallery-overlay-content">
1814
+ <h4 class="font-semibold text-white line-clamp-2 mb-2">${card.name}</h4>
1815
+ ${card.mana_cost ? `<div class="gallery-mana-cost mb-2">${formatManaCost(card.mana_cost)}</div>` : ''}
1816
+ <p class="text-xs text-white/60 line-clamp-2 mb-2">${card.type_line}</p>
1817
+ ${card.oracle_text ? `<p class="text-xs text-white/50 line-clamp-3 mb-3">${card.oracle_text}</p>` : ''}
1818
+ <p class="text-xs text-white/40 mb-3">${Math.round(score * 100)}% similar</p>
1819
+
1820
+ <div class="gallery-actions">
1821
+ <button onclick="loadCard('${card.id}')" class="gallery-btn gallery-btn-view">
1822
+ <i class="fas fa-eye"></i>
1823
+ View Card
1824
+ </button>
1825
+ ${card.purchase_uris?.tcgplayer ? `
1826
+ <a href="${card.purchase_uris.tcgplayer}" target="_blank" class="gallery-btn">
1827
+ <i class="fas fa-shopping-cart"></i>
1828
+ TCG
1829
+ </a>
1830
+ ` : card.purchase_uris?.cardmarket ? `
1831
+ <a href="${card.purchase_uris.cardmarket}" target="_blank" class="gallery-btn">
1832
+ <i class="fas fa-shopping-cart"></i>
1833
+ CM
1834
+ </a>
1835
+ ` : ''}
1836
+ </div>
1837
+ </div>
1838
  </div>
1839
  </div>
1840
  `).join('');
 
1847
  const card = await response.json();
1848
  displayCard(card);
1849
  window.scrollTo({ top: 0, behavior: 'smooth' });
1850
+ showToast('Card loaded successfully', 'success');
1851
  } catch (error) {
1852
  showToast('Failed to load card', 'error');
1853
  } finally {
 
1859
  if (!currentCard?.card_faces) return;
1860
  currentFace = (currentFace + direction + currentCard.card_faces.length) % currentCard.card_faces.length;
1861
  displayCardFace(currentCard, currentFace);
1862
+ updateCardInfo(currentCard); // Refresh oracle text buttons for the new face
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1863
  }
1864
 
1865
  // Utilities
 
1867
  isLoading = state;
1868
  loading.classList.toggle('hidden', !state);
1869
  randomBtn.disabled = state;
 
1870
  }
1871
 
1872
  function showToast(message, type = 'info') {