Spaces:
Running
Running
Update index.html
Browse files- index.html +97 -16
index.html
CHANGED
@@ -680,7 +680,7 @@
|
|
680 |
Deck Doctor
|
681 |
</h1>
|
682 |
<div class="flex justify-center gap-5 md:gap-8 text-xs md:text-sm flex-wrap">
|
683 |
-
<span class="text-white/40"><i class="fas fa-shield-alt mr-2"></i>
|
684 |
<span class="text-white/40"><i class="fas fa-chart-line mr-2"></i>Deck Analysis</span>
|
685 |
<span class="text-white/40"><i class="fas fa-search mr-2"></i>Smart Discovery</span>
|
686 |
</div>
|
@@ -688,13 +688,14 @@
|
|
688 |
<!-- Search -->
|
689 |
<div class="search-container">
|
690 |
<i class="fas fa-search search-icon"></i>
|
691 |
-
<
|
692 |
-
type="text"
|
693 |
id="search-input"
|
694 |
class="search-input"
|
695 |
-
placeholder="Search for cards or
|
696 |
autocomplete="off"
|
697 |
-
|
|
|
|
|
698 |
<button class="search-clear" id="search-clear" aria-label="Clear">
|
699 |
<i class="fas fa-times"></i>
|
700 |
</button>
|
@@ -870,12 +871,28 @@
|
|
870 |
});
|
871 |
}
|
872 |
|
|
|
|
|
|
|
|
|
|
|
|
|
873 |
// Search Functions
|
874 |
function handleSearch(e) {
|
875 |
const query = e.target.value.trim();
|
876 |
searchClear.classList.toggle('visible', query.length > 0);
|
877 |
|
878 |
clearTimeout(searchTimeout);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
879 |
if (query.length < 2) {
|
880 |
hideAutocomplete();
|
881 |
return;
|
@@ -886,18 +903,30 @@
|
|
886 |
|
887 |
function handleSearchKey(e) {
|
888 |
const query = searchInput.value.trim();
|
|
|
889 |
|
890 |
if (e.key === 'Enter' && query.length > 0) {
|
891 |
-
|
892 |
-
|
|
|
|
|
|
|
|
|
|
|
893 |
|
894 |
-
//
|
895 |
-
if (
|
896 |
-
|
897 |
-
|
898 |
-
|
899 |
-
//
|
900 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
901 |
}
|
902 |
}
|
903 |
}
|
@@ -1000,6 +1029,57 @@
|
|
1000 |
setLoading(false);
|
1001 |
}
|
1002 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1003 |
|
1004 |
// Search Mode Management
|
1005 |
function enterSearchMode(query, results) {
|
@@ -1034,11 +1114,12 @@
|
|
1034 |
searchResultsContainer.innerHTML = results.map(([card, score]) => `
|
1035 |
<div class="gallery-item" onclick="loadCardFromSearch('${card.id}')">
|
1036 |
<img src="${card.image_uris?.normal || ''}" alt="${card.name}" class="w-full h-full object-cover">
|
1037 |
-
${card.prices?.usd ? `<div class="absolute bottom-2 left-2 price-tag text-xs"
|
1038 |
<div class="gallery-overlay">
|
1039 |
<h4 class="font-semibold text-white line-clamp-2">${card.name}</h4>
|
1040 |
<p class="text-xs text-white/60 mt-1">${card.type_line}</p>
|
1041 |
-
<p class="text-xs text-white/40 mt-1">${Math.round(score * 100)}% match</p
|
|
|
1042 |
</div>
|
1043 |
</div>
|
1044 |
`).join('');
|
|
|
680 |
Deck Doctor
|
681 |
</h1>
|
682 |
<div class="flex justify-center gap-5 md:gap-8 text-xs md:text-sm flex-wrap">
|
683 |
+
<span class="text-white/40"><i class="fas fa-shield-alt mr-2"></i>Card Search</span>
|
684 |
<span class="text-white/40"><i class="fas fa-chart-line mr-2"></i>Deck Analysis</span>
|
685 |
<span class="text-white/40"><i class="fas fa-search mr-2"></i>Smart Discovery</span>
|
686 |
</div>
|
|
|
688 |
<!-- Search -->
|
689 |
<div class="search-container">
|
690 |
<i class="fas fa-search search-icon"></i>
|
691 |
+
<textarea
|
|
|
692 |
id="search-input"
|
693 |
class="search-input"
|
694 |
+
placeholder="Search for cards, terms or paste a deck list..."
|
695 |
autocomplete="off"
|
696 |
+
rows="1"
|
697 |
+
oninput="autoResize(this)"
|
698 |
+
></textarea>
|
699 |
<button class="search-clear" id="search-clear" aria-label="Clear">
|
700 |
<i class="fas fa-times"></i>
|
701 |
</button>
|
|
|
871 |
});
|
872 |
}
|
873 |
|
874 |
+
// Auto-resize textarea
|
875 |
+
function autoResize(textarea) {
|
876 |
+
textarea.style.height = 'auto';
|
877 |
+
textarea.style.height = Math.min(textarea.scrollHeight, 200) + 'px';
|
878 |
+
}
|
879 |
+
|
880 |
// Search Functions
|
881 |
function handleSearch(e) {
|
882 |
const query = e.target.value.trim();
|
883 |
searchClear.classList.toggle('visible', query.length > 0);
|
884 |
|
885 |
clearTimeout(searchTimeout);
|
886 |
+
|
887 |
+
// Check if this looks like a decklist (multiple lines)
|
888 |
+
const lines = query.split('\n').filter(line => line.trim().length > 0);
|
889 |
+
|
890 |
+
if (lines.length > 5) {
|
891 |
+
// This is likely a decklist - don't show autocomplete
|
892 |
+
hideAutocomplete();
|
893 |
+
return;
|
894 |
+
}
|
895 |
+
|
896 |
if (query.length < 2) {
|
897 |
hideAutocomplete();
|
898 |
return;
|
|
|
903 |
|
904 |
function handleSearchKey(e) {
|
905 |
const query = searchInput.value.trim();
|
906 |
+
const lines = query.split('\n').filter(line => line.trim().length > 0);
|
907 |
|
908 |
if (e.key === 'Enter' && query.length > 0) {
|
909 |
+
// Handle decklist if more than 5 lines
|
910 |
+
if (lines.length > 5) {
|
911 |
+
e.preventDefault();
|
912 |
+
hideAutocomplete();
|
913 |
+
processDecklist(query);
|
914 |
+
return;
|
915 |
+
}
|
916 |
|
917 |
+
// Handle single card search
|
918 |
+
if (!e.shiftKey) { // Allow Shift+Enter for newlines in regular searches
|
919 |
+
e.preventDefault();
|
920 |
+
hideAutocomplete();
|
921 |
+
|
922 |
+
// Check if user selected from autocomplete or is typing a custom query
|
923 |
+
if (autocompleteResults.length > 0 && autocompleteResults.includes(query)) {
|
924 |
+
// Exact match from autocomplete
|
925 |
+
selectCard(query);
|
926 |
+
} else {
|
927 |
+
// Custom search query - use Deck Doctor search
|
928 |
+
performDeckDoctorSearch(query);
|
929 |
+
}
|
930 |
}
|
931 |
}
|
932 |
}
|
|
|
1029 |
setLoading(false);
|
1030 |
}
|
1031 |
}
|
1032 |
+
|
1033 |
+
// Process decklist
|
1034 |
+
async function processDecklist(decklistText) {
|
1035 |
+
setLoading(true);
|
1036 |
+
showToast('Processing decklist...', 'success');
|
1037 |
+
|
1038 |
+
try {
|
1039 |
+
// Parse decklist - extract card names (simple parsing)
|
1040 |
+
const lines = decklistText.split('\n')
|
1041 |
+
.filter(line => line.trim().length > 0)
|
1042 |
+
.map(line => {
|
1043 |
+
// Remove quantity numbers and set codes if present
|
1044 |
+
return line.replace(/^\d+\s*/, '') // Remove leading numbers
|
1045 |
+
.replace(/\s*\(.*?\)\s*$/, '') // Remove set codes in parentheses
|
1046 |
+
.replace(/\s*\*.*?\*\s*$/, '') // Remove any *footnotes*
|
1047 |
+
.trim();
|
1048 |
+
})
|
1049 |
+
.filter(name => name.length > 0);
|
1050 |
+
|
1051 |
+
if (lines.length === 0) {
|
1052 |
+
showToast('No valid card names found in decklist', 'error');
|
1053 |
+
return;
|
1054 |
+
}
|
1055 |
+
|
1056 |
+
// Search for each card in the decklist
|
1057 |
+
const deckCards = [];
|
1058 |
+
for (const cardName of lines) {
|
1059 |
+
try {
|
1060 |
+
const response = await fetch(`https://api.scryfall.com/cards/named?exact=${encodeURIComponent(cardName)}`);
|
1061 |
+
if (response.ok) {
|
1062 |
+
const card = await response.json();
|
1063 |
+
deckCards.push([card, 1.0]); // Use 1.0 as similarity score for decklist items
|
1064 |
+
}
|
1065 |
+
} catch (error) {
|
1066 |
+
console.warn(`Could not find card: ${cardName}`);
|
1067 |
+
}
|
1068 |
+
}
|
1069 |
+
|
1070 |
+
if (deckCards.length > 0) {
|
1071 |
+
enterSearchMode(`Decklist (${deckCards.length} cards)`, deckCards);
|
1072 |
+
showToast(`Loaded ${deckCards.length} cards from decklist`, 'success');
|
1073 |
+
} else {
|
1074 |
+
showToast('No valid cards found in decklist', 'error');
|
1075 |
+
}
|
1076 |
+
} catch (error) {
|
1077 |
+
console.error('Decklist processing error:', error);
|
1078 |
+
showToast('Failed to process decklist', 'error');
|
1079 |
+
} finally {
|
1080 |
+
setLoading(false);
|
1081 |
+
}
|
1082 |
+
}
|
1083 |
|
1084 |
// Search Mode Management
|
1085 |
function enterSearchMode(query, results) {
|
|
|
1114 |
searchResultsContainer.innerHTML = results.map(([card, score]) => `
|
1115 |
<div class="gallery-item" onclick="loadCardFromSearch('${card.id}')">
|
1116 |
<img src="${card.image_uris?.normal || ''}" alt="${card.name}" class="w-full h-full object-cover">
|
1117 |
+
${card.prices?.usd ? `<div class="absolute bottom-2 left-2 price-tag text-xs">${parseFloat(card.prices.usd).toFixed(2)}</div>` : ''}
|
1118 |
<div class="gallery-overlay">
|
1119 |
<h4 class="font-semibold text-white line-clamp-2">${card.name}</h4>
|
1120 |
<p class="text-xs text-white/60 mt-1">${card.type_line}</p>
|
1121 |
+
${score < 1.0 ? `<p class="text-xs text-white/40 mt-1">${Math.round(score * 100)}% match</p>` :
|
1122 |
+
score === 1.0 ? `<p class="text-xs text-white/40 mt-1">From decklist</p>` : ''}
|
1123 |
</div>
|
1124 |
</div>
|
1125 |
`).join('');
|