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('');
|