pearsonkyle commited on
Commit
fcb47dc
·
verified ·
1 Parent(s): 2def885

Update index.html

Browse files
Files changed (1) hide show
  1. index.html +200 -17
index.html CHANGED
@@ -3,7 +3,7 @@
3
  <head>
4
  <meta charset="UTF-8">
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
- <title>MTG Deck Doctor - Card Explorer</title>
7
  <meta name="description" content="Advanced MTG card database with similarity search and market prices">
8
  <script src="https://cdn.tailwindcss.com"></script>
9
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
@@ -47,8 +47,8 @@
47
  position: fixed;
48
  inset: 0;
49
  background: rgba(0, 0, 0, 0.7);
50
- backdrop-filter: blur(10px);
51
- -webkit-backdrop-filter: blur(10px);
52
  z-index: 0;
53
  }
54
 
@@ -234,6 +234,17 @@
234
  font-size: 14px;
235
  }
236
 
 
 
 
 
 
 
 
 
 
 
 
237
  /* Headers */
238
  .header-text {
239
  font-family: 'Space Grotesk', sans-serif;
@@ -255,6 +266,38 @@
255
  font-size: clamp(0.75rem, 2vw, 0.875rem);
256
  }
257
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
258
  /* Optimized buttons */
259
  .glass-button {
260
  background: var(--glass-bg);
@@ -632,7 +675,6 @@
632
  <div class="container mx-auto py-4 md:py-8 px-4">
633
  <!-- Header -->
634
  <header class="text-center mb-8 md:mb-12">
635
- <p class="subheader-text mb-2">Advanced Card Database</p>
636
  <h1 class="header-text mb-6">Deck Doctor</h1>
637
 
638
  <!-- Search -->
@@ -642,7 +684,7 @@
642
  type="text"
643
  id="search-input"
644
  class="search-input"
645
- placeholder="Search for cards..."
646
  autocomplete="off"
647
  >
648
  <button class="search-clear" id="search-clear" aria-label="Clear">
@@ -652,7 +694,8 @@
652
  </div>
653
  </header>
654
 
655
- <div class="responsive-grid max-w-7xl mx-auto">
 
656
  <!-- Card Display -->
657
  <div class="flex flex-col items-center">
658
  <div class="card-3d-container mb-4">
@@ -692,7 +735,7 @@
692
  </div>
693
 
694
  <!-- Info Panel -->
695
- <div class="space-y-4">
696
  <div class="glass-card p-4 md:p-5">
697
  <h2 id="card-name" class="text-2xl font-semibold text-white mb-1">No Card Selected</h2>
698
  <div id="card-type" class="text-white/60 mb-4"></div>
@@ -728,12 +771,24 @@
728
  </div>
729
  </div>
730
 
731
- <!-- Gallery -->
732
  <div id="gallery-section" class="mt-12 hidden">
733
- <h2 class="text-2xl font-bold text-center text-white mb-6">Similar Cards</h2>
734
  <div id="gallery" class="gallery-grid max-w-7xl mx-auto"></div>
735
  </div>
736
 
 
 
 
 
 
 
 
 
 
 
 
 
737
  <!-- Loading -->
738
  <div id="loading" class="fixed inset-0 bg-black/60 backdrop-blur-sm flex items-center justify-center hidden z-50">
739
  <div class="glass-card p-6 text-center">
@@ -750,6 +805,8 @@
750
  let isLoading = false;
751
  let searchTimeout = null;
752
  let autocompleteResults = [];
 
 
753
 
754
  // DOM Elements
755
  const $ = id => document.getElementById(id);
@@ -760,6 +817,10 @@
760
  const detailsBtn = $('details-btn');
761
  const loading = $('loading');
762
  const toast = $('toast');
 
 
 
 
763
 
764
  // Initialize
765
  document.addEventListener('DOMContentLoaded', () => {
@@ -772,7 +833,10 @@
772
  searchInput.addEventListener('input', handleSearch);
773
  searchInput.addEventListener('keydown', handleSearchKey);
774
  searchClear.addEventListener('click', clearSearch);
775
- randomBtn.addEventListener('click', () => fetchRandomCard());
 
 
 
776
  detailsBtn.addEventListener('click', showDetails);
777
 
778
  // Close autocomplete on outside click
@@ -791,6 +855,7 @@
791
  if (e.key === 'Escape') {
792
  closeModal();
793
  searchInput.blur();
 
794
  }
795
  });
796
  }
@@ -810,8 +875,20 @@
810
  }
811
 
812
  function handleSearchKey(e) {
813
- if (e.key === 'Enter' && autocompleteResults.length > 0) {
814
- selectCard(autocompleteResults[0]);
 
 
 
 
 
 
 
 
 
 
 
 
815
  }
816
  }
817
 
@@ -819,6 +896,10 @@
819
  searchInput.value = '';
820
  searchClear.classList.remove('visible');
821
  hideAutocomplete();
 
 
 
 
822
  }
823
 
824
  async function fetchAutocomplete(query) {
@@ -827,21 +908,31 @@
827
  const response = await fetch(`https://api.scryfall.com/cards/autocomplete?q=${encodeURIComponent(query)}`);
828
  const data = await response.json();
829
  autocompleteResults = data.data || [];
830
- displayAutocomplete(autocompleteResults);
831
  } catch (error) {
832
  console.error('Autocomplete error:', error);
833
  hideAutocomplete();
834
  }
835
  }
836
 
837
- function displayAutocomplete(results) {
 
 
838
  if (!results.length) {
839
- autocomplete.innerHTML = '<div class="autocomplete-empty">No cards found</div>';
840
  } else {
841
- autocomplete.innerHTML = results.map(name =>
842
  `<div class="autocomplete-item" onclick="selectCard('${name.replace(/'/g, "\\'")}')">${name}</div>`
843
  ).join('');
844
  }
 
 
 
 
 
 
 
 
845
  showAutocomplete();
846
  }
847
 
@@ -863,6 +954,7 @@
863
  searchInput.value = name;
864
  searchClear.classList.add('visible');
865
  setLoading(true);
 
866
 
867
  try {
868
  const response = await fetch(`https://api.scryfall.com/cards/named?exact=${encodeURIComponent(name)}`);
@@ -876,6 +968,94 @@
876
  }
877
  }
878
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
879
  // Card Functions
880
  async function fetchRandomCard() {
881
  if (isLoading) return;
@@ -910,6 +1090,9 @@
910
 
911
  // Fetch similar cards
912
  fetchSimilarCards(card);
 
 
 
913
  }
914
 
915
  function displayCardFace(card, faceIndex) {
@@ -1163,7 +1346,7 @@
1163
  isLoading = state;
1164
  loading.classList.toggle('hidden', !state);
1165
  randomBtn.disabled = state;
1166
- detailsBtn.disabled = state;
1167
  }
1168
 
1169
  function showToast(message, type = 'info') {
 
3
  <head>
4
  <meta charset="UTF-8">
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Deck Doctor</title>
7
  <meta name="description" content="Advanced MTG card database with similarity search and market prices">
8
  <script src="https://cdn.tailwindcss.com"></script>
9
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
 
47
  position: fixed;
48
  inset: 0;
49
  background: rgba(0, 0, 0, 0.7);
50
+ backdrop-filter: blur(20px);
51
+ -webkit-backdrop-filter: blur(20px);
52
  z-index: 0;
53
  }
54
 
 
234
  font-size: 14px;
235
  }
236
 
237
+ .autocomplete-hint {
238
+ padding: 12px 16px;
239
+ background: rgba(102, 126, 234, 0.1);
240
+ border-top: 1px solid rgba(255, 255, 255, 0.1);
241
+ color: var(--text-secondary);
242
+ font-size: 13px;
243
+ display: flex;
244
+ align-items: center;
245
+ gap: 8px;
246
+ }
247
+
248
  /* Headers */
249
  .header-text {
250
  font-family: 'Space Grotesk', sans-serif;
 
266
  font-size: clamp(0.75rem, 2vw, 0.875rem);
267
  }
268
 
269
+ /* Search Results Header */
270
+ .search-results-header {
271
+ background: var(--glass-bg-dark);
272
+ backdrop-filter: blur(20px);
273
+ border: 1px solid var(--glass-border);
274
+ border-radius: 12px;
275
+ padding: 16px 20px;
276
+ margin-bottom: 24px;
277
+ display: flex;
278
+ align-items: center;
279
+ justify-content: space-between;
280
+ }
281
+
282
+ .search-results-title {
283
+ font-size: 1.25rem;
284
+ font-weight: 600;
285
+ color: white;
286
+ display: flex;
287
+ align-items: center;
288
+ gap: 12px;
289
+ }
290
+
291
+ .search-query {
292
+ color: rgba(102, 126, 234, 1);
293
+ font-weight: 400;
294
+ }
295
+
296
+ .results-count {
297
+ color: var(--text-muted);
298
+ font-size: 0.875rem;
299
+ }
300
+
301
  /* Optimized buttons */
302
  .glass-button {
303
  background: var(--glass-bg);
 
675
  <div class="container mx-auto py-4 md:py-8 px-4">
676
  <!-- Header -->
677
  <header class="text-center mb-8 md:mb-12">
 
678
  <h1 class="header-text mb-6">Deck Doctor</h1>
679
 
680
  <!-- Search -->
 
684
  type="text"
685
  id="search-input"
686
  class="search-input"
687
+ placeholder="Search for cards or terms..."
688
  autocomplete="off"
689
  >
690
  <button class="search-clear" id="search-clear" aria-label="Clear">
 
694
  </div>
695
  </header>
696
 
697
+ <!-- Main Card Display -->
698
+ <div id="main-card-section" class="responsive-grid max-w-7xl mx-auto">
699
  <!-- Card Display -->
700
  <div class="flex flex-col items-center">
701
  <div class="card-3d-container mb-4">
 
735
  </div>
736
 
737
  <!-- Info Panel -->
738
+ <div id="info-panel" class="space-y-4">
739
  <div class="glass-card p-4 md:p-5">
740
  <h2 id="card-name" class="text-2xl font-semibold text-white mb-1">No Card Selected</h2>
741
  <div id="card-type" class="text-white/60 mb-4"></div>
 
771
  </div>
772
  </div>
773
 
774
+ <!-- Gallery Section -->
775
  <div id="gallery-section" class="mt-12 hidden">
776
+ <h2 id="gallery-header" class="text-2xl font-bold text-center text-white mb-6">Similar Cards</h2>
777
  <div id="gallery" class="gallery-grid max-w-7xl mx-auto"></div>
778
  </div>
779
 
780
+ <!-- Search Results Section (Initially Hidden) -->
781
+ <div id="search-results-section" class="hidden">
782
+ <div class="search-results-header max-w-7xl mx-auto">
783
+ <div class="search-results-title">
784
+ <i class="fas fa-search"></i>
785
+ <span>Search: <span class="search-query" id="search-query-text"></span></span>
786
+ </div>
787
+ <div class="results-count" id="results-count"></div>
788
+ </div>
789
+ <div id="search-results" class="gallery-grid max-w-7xl mx-auto"></div>
790
+ </div>
791
+
792
  <!-- Loading -->
793
  <div id="loading" class="fixed inset-0 bg-black/60 backdrop-blur-sm flex items-center justify-center hidden z-50">
794
  <div class="glass-card p-6 text-center">
 
805
  let isLoading = false;
806
  let searchTimeout = null;
807
  let autocompleteResults = [];
808
+ let isSearchMode = false;
809
+ let lastSearchQuery = '';
810
 
811
  // DOM Elements
812
  const $ = id => document.getElementById(id);
 
817
  const detailsBtn = $('details-btn');
818
  const loading = $('loading');
819
  const toast = $('toast');
820
+ const mainCardSection = $('main-card-section');
821
+ const infoPanel = $('info-panel');
822
+ const gallerySection = $('gallery-section');
823
+ const searchResultsSection = $('search-results-section');
824
 
825
  // Initialize
826
  document.addEventListener('DOMContentLoaded', () => {
 
833
  searchInput.addEventListener('input', handleSearch);
834
  searchInput.addEventListener('keydown', handleSearchKey);
835
  searchClear.addEventListener('click', clearSearch);
836
+ randomBtn.addEventListener('click', () => {
837
+ exitSearchMode();
838
+ fetchRandomCard();
839
+ });
840
  detailsBtn.addEventListener('click', showDetails);
841
 
842
  // Close autocomplete on outside click
 
855
  if (e.key === 'Escape') {
856
  closeModal();
857
  searchInput.blur();
858
+ hideAutocomplete();
859
  }
860
  });
861
  }
 
875
  }
876
 
877
  function handleSearchKey(e) {
878
+ const query = searchInput.value.trim();
879
+
880
+ if (e.key === 'Enter' && query.length > 0) {
881
+ e.preventDefault();
882
+ hideAutocomplete();
883
+
884
+ // Check if user selected from autocomplete or is typing a custom query
885
+ if (autocompleteResults.length > 0 && autocompleteResults.includes(query)) {
886
+ // Exact match from autocomplete
887
+ selectCard(query);
888
+ } else {
889
+ // Custom search query - use Deck Doctor search
890
+ performDeckDoctorSearch(query);
891
+ }
892
  }
893
  }
894
 
 
896
  searchInput.value = '';
897
  searchClear.classList.remove('visible');
898
  hideAutocomplete();
899
+ if (isSearchMode) {
900
+ exitSearchMode();
901
+ fetchRandomCard();
902
+ }
903
  }
904
 
905
  async function fetchAutocomplete(query) {
 
908
  const response = await fetch(`https://api.scryfall.com/cards/autocomplete?q=${encodeURIComponent(query)}`);
909
  const data = await response.json();
910
  autocompleteResults = data.data || [];
911
+ displayAutocomplete(autocompleteResults, query);
912
  } catch (error) {
913
  console.error('Autocomplete error:', error);
914
  hideAutocomplete();
915
  }
916
  }
917
 
918
+ function displayAutocomplete(results, query) {
919
+ let html = '';
920
+
921
  if (!results.length) {
922
+ html = '';
923
  } else {
924
+ html = results.map(name =>
925
  `<div class="autocomplete-item" onclick="selectCard('${name.replace(/'/g, "\\'")}')">${name}</div>`
926
  ).join('');
927
  }
928
+
929
+ // Add hint for custom search
930
+ html += `<div class="autocomplete-hint">
931
+ <i class="fas fa-lightbulb"></i>
932
+ <span>Press Enter to search cards for "${query}".</span>
933
+ </div>`;
934
+
935
+ autocomplete.innerHTML = html;
936
  showAutocomplete();
937
  }
938
 
 
954
  searchInput.value = name;
955
  searchClear.classList.add('visible');
956
  setLoading(true);
957
+ exitSearchMode();
958
 
959
  try {
960
  const response = await fetch(`https://api.scryfall.com/cards/named?exact=${encodeURIComponent(name)}`);
 
968
  }
969
  }
970
 
971
+ // Deck Doctor Search
972
+ async function performDeckDoctorSearch(query) {
973
+ setLoading(true);
974
+ lastSearchQuery = query;
975
+
976
+ try {
977
+ const response = await fetch(`https://api.deck.doctor/v1/mtg/search?q=${encodeURIComponent(query)}&topk=20`);
978
+ const data = await response.json();
979
+
980
+ if (data && data.length > 0) {
981
+ enterSearchMode(query, data);
982
+ showToast(`Found ${data.length} cards`, 'success');
983
+ } else {
984
+ showToast('No cards found', 'error');
985
+ }
986
+ } catch (error) {
987
+ console.error('Search error:', error);
988
+ showToast('Search failed', 'error');
989
+ } finally {
990
+ setLoading(false);
991
+ }
992
+ }
993
+
994
+ // Search Mode Management
995
+ function enterSearchMode(query, results) {
996
+ isSearchMode = true;
997
+
998
+ // Hide main card display
999
+ mainCardSection.classList.add('hidden');
1000
+ gallerySection.classList.add('hidden');
1001
+
1002
+ // Show search results
1003
+ searchResultsSection.classList.remove('hidden');
1004
+ $('search-query-text').textContent = query;
1005
+ $('results-count').textContent = `${results.length} results`;
1006
+
1007
+ // Display results
1008
+ displaySearchResults(results);
1009
+ }
1010
+
1011
+ function exitSearchMode() {
1012
+ isSearchMode = false;
1013
+
1014
+ // Show main card display
1015
+ mainCardSection.classList.remove('hidden');
1016
+
1017
+ // Hide search results
1018
+ searchResultsSection.classList.add('hidden');
1019
+ }
1020
+
1021
+ function displaySearchResults(results) {
1022
+ const searchResultsContainer = $('search-results');
1023
+
1024
+ searchResultsContainer.innerHTML = results.map(([card, score]) => `
1025
+ <div class="gallery-item" onclick="loadCardFromSearch('${card.id}')">
1026
+ <img src="${card.image_uris?.normal || ''}" alt="${card.name}" class="w-full h-full object-cover">
1027
+ ${card.prices?.usd ? `<div class="absolute bottom-2 left-2 price-tag text-xs">$${parseFloat(card.prices.usd).toFixed(2)}</div>` : ''}
1028
+ <div class="gallery-overlay">
1029
+ <h4 class="font-semibold text-white line-clamp-2">${card.name}</h4>
1030
+ <p class="text-xs text-white/60 mt-1">${card.type_line}</p>
1031
+ <p class="text-xs text-white/40 mt-1">${Math.round(score * 100)}% match</p>
1032
+ </div>
1033
+ </div>
1034
+ `).join('');
1035
+ }
1036
+
1037
+ async function loadCardFromSearch(id) {
1038
+ setLoading(true);
1039
+ try {
1040
+ const response = await fetch(`https://api.scryfall.com/cards/${id}`);
1041
+ const card = await response.json();
1042
+
1043
+ // Exit search mode and display the card
1044
+ exitSearchMode();
1045
+ displayCard(card);
1046
+
1047
+ // Clear search input
1048
+ searchInput.value = '';
1049
+ searchClear.classList.remove('visible');
1050
+
1051
+ window.scrollTo({ top: 0, behavior: 'smooth' });
1052
+ } catch (error) {
1053
+ showToast('Failed to load card', 'error');
1054
+ } finally {
1055
+ setLoading(false);
1056
+ }
1057
+ }
1058
+
1059
  // Card Functions
1060
  async function fetchRandomCard() {
1061
  if (isLoading) return;
 
1090
 
1091
  // Fetch similar cards
1092
  fetchSimilarCards(card);
1093
+
1094
+ // Update gallery header back to "Similar Cards"
1095
+ $('gallery-header').textContent = 'Similar Cards';
1096
  }
1097
 
1098
  function displayCardFace(card, faceIndex) {
 
1346
  isLoading = state;
1347
  loading.classList.toggle('hidden', !state);
1348
  randomBtn.disabled = state;
1349
+ detailsBtn.disabled = state || !currentCard;
1350
  }
1351
 
1352
  function showToast(message, type = 'info') {