Spaces:
Running
Running
Add a button to search for similar cards to each card face when you mouse over the faces. Append cards to the list use the /search endpoint and then remove duplicates
Browse files- index.html +105 -21
index.html
CHANGED
|
@@ -1002,18 +1002,20 @@
|
|
| 1002 |
<div class="card-3d-container mb-4">
|
| 1003 |
<div class="card-3d" id="card-3d">
|
| 1004 |
<div class="card-face">
|
| 1005 |
-
<div id="card-image" class="w-full h-full bg-gray-900 flex items-center justify-center">
|
| 1006 |
<div class="text-center glass-card p-8">
|
| 1007 |
<i class="fas fa-cube text-5xl text-white/30 mb-4"></i>
|
| 1008 |
<p class="text-white/60">Initializing...</p>
|
| 1009 |
</div>
|
|
|
|
|
|
|
|
|
|
| 1010 |
</div>
|
| 1011 |
<div id="face-indicator" class="absolute top-4 right-4 price-tag hidden">Face 1/2</div>
|
| 1012 |
</div>
|
| 1013 |
</div>
|
| 1014 |
</div>
|
| 1015 |
-
|
| 1016 |
-
<!-- Controls -->
|
| 1017 |
<div class="flex gap-2 mb-4">
|
| 1018 |
<button id="random-btn" class="glass-button">
|
| 1019 |
<i class="fas fa-shuffle mr-2"></i>Random
|
|
@@ -1613,7 +1615,6 @@
|
|
| 1613 |
// Update gallery header back to "Similar Cards"
|
| 1614 |
$('gallery-header').textContent = 'Similar Cards';
|
| 1615 |
}
|
| 1616 |
-
|
| 1617 |
function displayCardFace(card, faceIndex) {
|
| 1618 |
const hasFaces = card.card_faces?.length > 1;
|
| 1619 |
const face = hasFaces ? card.card_faces[faceIndex] : card;
|
|
@@ -1621,10 +1622,21 @@
|
|
| 1621 |
|
| 1622 |
// Update image
|
| 1623 |
if (imageUris?.large) {
|
| 1624 |
-
$('card-image').innerHTML =
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1625 |
}
|
| 1626 |
-
|
| 1627 |
-
// Update face navigation
|
| 1628 |
if (hasFaces) {
|
| 1629 |
$('face-nav').classList.remove('hidden');
|
| 1630 |
$('face-indicator').classList.remove('hidden');
|
|
@@ -1856,13 +1868,19 @@
|
|
| 1856 |
// Final cleaning
|
| 1857 |
return cardStr.replace(/, ,/g, ',');
|
| 1858 |
}
|
| 1859 |
-
|
| 1860 |
-
async function fetchSimilarCards(card) {
|
| 1861 |
try {
|
| 1862 |
-
|
| 1863 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1864 |
currentGalleryQuery = query;
|
| 1865 |
-
console.log('Query string:', query);
|
| 1866 |
|
| 1867 |
// Build URL with gallery color filters
|
| 1868 |
let url = `https://api.deck.doctor/v1/mtg/search?q=${encodeURIComponent(query)}&topk=12&price_threshold=0`;
|
|
@@ -1878,25 +1896,91 @@
|
|
| 1878 |
if (data?.length > 0) {
|
| 1879 |
// Filter out the current card more robustly
|
| 1880 |
const filteredResults = data.filter(([c]) => {
|
| 1881 |
-
// Check multiple identifiers to ensure we exclude the current card
|
| 1882 |
return c.id !== card.id &&
|
| 1883 |
c.name !== card.name &&
|
| 1884 |
(!c.scryfall_uri || c.scryfall_uri !== card.scryfall_uri);
|
| 1885 |
-
})
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1886 |
|
| 1887 |
-
|
| 1888 |
-
|
|
|
|
|
|
|
| 1889 |
|
| 1890 |
-
|
| 1891 |
-
|
| 1892 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1893 |
}
|
| 1894 |
} catch (error) {
|
| 1895 |
console.error('Gallery error:', error);
|
| 1896 |
}
|
| 1897 |
}
|
| 1898 |
-
|
| 1899 |
-
function
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1900 |
$('gallery').innerHTML = cards.map(([card, score]) => `
|
| 1901 |
<div class="gallery-item">
|
| 1902 |
<img src="${card.image_uris?.normal || ''}" alt="${card.name}" class="w-full h-full object-cover">
|
|
|
|
| 1002 |
<div class="card-3d-container mb-4">
|
| 1003 |
<div class="card-3d" id="card-3d">
|
| 1004 |
<div class="card-face">
|
| 1005 |
+
<div id="card-image" class="w-full h-full bg-gray-900 flex items-center justify-center relative">
|
| 1006 |
<div class="text-center glass-card p-8">
|
| 1007 |
<i class="fas fa-cube text-5xl text-white/30 mb-4"></i>
|
| 1008 |
<p class="text-white/60">Initializing...</p>
|
| 1009 |
</div>
|
| 1010 |
+
<button id="find-similar-btn" class="absolute bottom-4 left-1/2 transform -translate-x-1/2 glass-button hidden" onclick="findSimilarForCurrentFace()">
|
| 1011 |
+
<i class="fas fa-search mr-2"></i>Find Similar
|
| 1012 |
+
</button>
|
| 1013 |
</div>
|
| 1014 |
<div id="face-indicator" class="absolute top-4 right-4 price-tag hidden">Face 1/2</div>
|
| 1015 |
</div>
|
| 1016 |
</div>
|
| 1017 |
</div>
|
| 1018 |
+
<!-- Controls -->
|
|
|
|
| 1019 |
<div class="flex gap-2 mb-4">
|
| 1020 |
<button id="random-btn" class="glass-button">
|
| 1021 |
<i class="fas fa-shuffle mr-2"></i>Random
|
|
|
|
| 1615 |
// Update gallery header back to "Similar Cards"
|
| 1616 |
$('gallery-header').textContent = 'Similar Cards';
|
| 1617 |
}
|
|
|
|
| 1618 |
function displayCardFace(card, faceIndex) {
|
| 1619 |
const hasFaces = card.card_faces?.length > 1;
|
| 1620 |
const face = hasFaces ? card.card_faces[faceIndex] : card;
|
|
|
|
| 1622 |
|
| 1623 |
// Update image
|
| 1624 |
if (imageUris?.large) {
|
| 1625 |
+
$('card-image').innerHTML = `
|
| 1626 |
+
<img src="${imageUris.large}" alt="${face.name}" class="w-full h-full object-contain">
|
| 1627 |
+
<button id="find-similar-btn" class="absolute bottom-4 left-1/2 transform -translate-x-1/2 glass-button opacity-0 transition-opacity duration-200"
|
| 1628 |
+
onclick="findSimilarForCurrentFace()">
|
| 1629 |
+
<i class="fas fa-search mr-2"></i>Find Similar
|
| 1630 |
+
</button>
|
| 1631 |
+
`;
|
| 1632 |
+
|
| 1633 |
+
// Add hover effect to show the button
|
| 1634 |
+
const img = $('card-image').querySelector('img');
|
| 1635 |
+
const btn = $('card-image').querySelector('button');
|
| 1636 |
+
img.addEventListener('mouseenter', () => btn.classList.remove('opacity-0'));
|
| 1637 |
+
img.addEventListener('mouseleave', () => btn.classList.add('opacity-0'));
|
| 1638 |
}
|
| 1639 |
+
// Update face navigation
|
|
|
|
| 1640 |
if (hasFaces) {
|
| 1641 |
$('face-nav').classList.remove('hidden');
|
| 1642 |
$('face-indicator').classList.remove('hidden');
|
|
|
|
| 1868 |
// Final cleaning
|
| 1869 |
return cardStr.replace(/, ,/g, ',');
|
| 1870 |
}
|
| 1871 |
+
async function fetchSimilarCards(card, faceIndex = null) {
|
|
|
|
| 1872 |
try {
|
| 1873 |
+
let query;
|
| 1874 |
+
if (faceIndex !== null && card.card_faces?.[faceIndex]) {
|
| 1875 |
+
// Build query for specific face
|
| 1876 |
+
query = buildCardQueryString(card.card_faces[faceIndex]);
|
| 1877 |
+
} else {
|
| 1878 |
+
// Build query for whole card
|
| 1879 |
+
query = buildCardQueryString(card);
|
| 1880 |
+
}
|
| 1881 |
+
|
| 1882 |
currentGalleryQuery = query;
|
| 1883 |
+
console.log('Query string:', query);
|
| 1884 |
|
| 1885 |
// Build URL with gallery color filters
|
| 1886 |
let url = `https://api.deck.doctor/v1/mtg/search?q=${encodeURIComponent(query)}&topk=12&price_threshold=0`;
|
|
|
|
| 1896 |
if (data?.length > 0) {
|
| 1897 |
// Filter out the current card more robustly
|
| 1898 |
const filteredResults = data.filter(([c]) => {
|
|
|
|
| 1899 |
return c.id !== card.id &&
|
| 1900 |
c.name !== card.name &&
|
| 1901 |
(!c.scryfall_uri || c.scryfall_uri !== card.scryfall_uri);
|
| 1902 |
+
});
|
| 1903 |
+
|
| 1904 |
+
// Get existing gallery cards
|
| 1905 |
+
const existingCards = Array.from($('gallery').querySelectorAll('.gallery-item'))
|
| 1906 |
+
.map(el => el.querySelector('h4')?.textContent)
|
| 1907 |
+
.filter(Boolean);
|
| 1908 |
|
| 1909 |
+
// Filter out duplicates
|
| 1910 |
+
const uniqueResults = filteredResults.filter(([c]) =>
|
| 1911 |
+
!existingCards.includes(c.name)
|
| 1912 |
+
).slice(0, 11); // Limit to 11 new cards
|
| 1913 |
|
| 1914 |
+
if (uniqueResults.length > 0) {
|
| 1915 |
+
// Append new cards to gallery
|
| 1916 |
+
const currentGallery = $('gallery').innerHTML;
|
| 1917 |
+
const newCards = uniqueResults.map(([card, score]) => createGalleryCard(card, score)).join('');
|
| 1918 |
+
$('gallery').innerHTML = currentGallery + newCards;
|
| 1919 |
+
|
| 1920 |
+
if ($('gallery-section').classList.contains('hidden')) {
|
| 1921 |
+
$('gallery-section').classList.remove('hidden');
|
| 1922 |
+
}
|
| 1923 |
+
|
| 1924 |
+
// Update header if filters are active
|
| 1925 |
+
const colorFilterText = galleryColors.size > 0 ? ` (${Array.from(galleryColors).join('')})` : '';
|
| 1926 |
+
$('gallery-header').textContent = `Similar Cards${colorFilterText}`;
|
| 1927 |
+
}
|
| 1928 |
}
|
| 1929 |
} catch (error) {
|
| 1930 |
console.error('Gallery error:', error);
|
| 1931 |
}
|
| 1932 |
}
|
| 1933 |
+
|
| 1934 |
+
function findSimilarForCurrentFace() {
|
| 1935 |
+
if (!currentCard) return;
|
| 1936 |
+
setLoading(true);
|
| 1937 |
+
|
| 1938 |
+
// If card has multiple faces, search for current face
|
| 1939 |
+
if (currentCard.card_faces?.length > 1) {
|
| 1940 |
+
fetchSimilarCards(currentCard, currentFace);
|
| 1941 |
+
} else {
|
| 1942 |
+
fetchSimilarCards(currentCard);
|
| 1943 |
+
}
|
| 1944 |
+
|
| 1945 |
+
setLoading(false);
|
| 1946 |
+
}
|
| 1947 |
+
|
| 1948 |
+
function createGalleryCard(card, score) {
|
| 1949 |
+
return `
|
| 1950 |
+
<div class="gallery-item">
|
| 1951 |
+
<img src="${card.image_uris?.normal || ''}" alt="${card.name}" class="w-full h-full object-cover">
|
| 1952 |
+
${card.prices?.usd ? `<div class="absolute top-2 left-2 price-tag text-xs">${parseFloat(card.prices.usd).toFixed(2)}</div>` : ''}
|
| 1953 |
+
<div class="gallery-overlay">
|
| 1954 |
+
<div class="gallery-overlay-content">
|
| 1955 |
+
<h4 class="font-semibold text-white line-clamp-2 mb-2">${card.name}</h4>
|
| 1956 |
+
${card.mana_cost ? `<div class="gallery-mana-cost mb-2">${formatManaCost(card.mana_cost)}</div>` : ''}
|
| 1957 |
+
<p class="text-xs text-white/60 line-clamp-2 mb-2">${card.type_line}</p>
|
| 1958 |
+
${card.oracle_text ? `<p class="text-xs text-white/50 line-clamp-3 mb-3">${card.oracle_text}</p>` : ''}
|
| 1959 |
+
<p class="text-xs text-white/40 mb-3">${Math.round(score * 100)}% similar</p>
|
| 1960 |
+
|
| 1961 |
+
<div class="gallery-actions">
|
| 1962 |
+
<button onclick="loadCard('${card.id}')" class="gallery-btn gallery-btn-view">
|
| 1963 |
+
<i class="fas fa-eye"></i>
|
| 1964 |
+
View Card
|
| 1965 |
+
</button>
|
| 1966 |
+
${card.purchase_uris?.tcgplayer ? `
|
| 1967 |
+
<a href="${card.purchase_uris.tcgplayer}" target="_blank" class="gallery-btn">
|
| 1968 |
+
<i class="fas fa-shopping-cart"></i>
|
| 1969 |
+
TCG
|
| 1970 |
+
</a>
|
| 1971 |
+
` : card.purchase_uris?.cardmarket ? `
|
| 1972 |
+
<a href="${card.purchase_uris.cardmarket}" target="_blank" class="gallery-btn">
|
| 1973 |
+
<i class="fas fa-shopping-cart"></i>
|
| 1974 |
+
CM
|
| 1975 |
+
</a>
|
| 1976 |
+
` : ''}
|
| 1977 |
+
</div>
|
| 1978 |
+
</div>
|
| 1979 |
+
</div>
|
| 1980 |
+
</div>
|
| 1981 |
+
`;
|
| 1982 |
+
}
|
| 1983 |
+
function displayGallery(cards) {
|
| 1984 |
$('gallery').innerHTML = cards.map(([card, score]) => `
|
| 1985 |
<div class="gallery-item">
|
| 1986 |
<img src="${card.image_uris?.normal || ''}" alt="${card.name}" class="w-full h-full object-cover">
|