Spaces:
Running
Running
Update index.html
Browse files- index.html +201 -96
index.html
CHANGED
|
@@ -36,6 +36,56 @@
|
|
| 36 |
touch-action: pan-y pinch-zoom;
|
| 37 |
}
|
| 38 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 39 |
/* Swipe indicator overlay */
|
| 40 |
.swipe-indicator {
|
| 41 |
position: absolute;
|
|
@@ -864,6 +914,40 @@
|
|
| 864 |
color: rgba(255, 255, 255, 0.9);
|
| 865 |
font-weight: 500;
|
| 866 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 867 |
</style>
|
| 868 |
</head>
|
| 869 |
<body>
|
|
@@ -875,6 +959,18 @@
|
|
| 875 |
<!-- Touch feedback element -->
|
| 876 |
<div class="touch-feedback" id="touch-feedback"></div>
|
| 877 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 878 |
<div class="container mx-auto py-4 md:py-8">
|
| 879 |
<!-- Header -->
|
| 880 |
<header class="text-center mb-8 md:mb-16 relative px-4">
|
|
@@ -915,8 +1011,8 @@
|
|
| 915 |
</div>
|
| 916 |
</div>
|
| 917 |
|
| 918 |
-
<!-- Card Back -->
|
| 919 |
-
<div class="card-face card-face-back glass-card p-3 md:p-6">
|
| 920 |
<div id="card-details" class="h-full overflow-y-auto">
|
| 921 |
<h3 class="text-lg md:text-xl font-semibold text-white mb-3 md:mb-4">Card Analysis</h3>
|
| 922 |
<div id="detailed-info" class="text-white/80 text-sm md:text-base">
|
|
@@ -933,8 +1029,8 @@
|
|
| 933 |
<i class="fas fa-shuffle mr-2"></i><span class="hidden sm:inline">Random Card</span><span class="sm:hidden">Random</span>
|
| 934 |
</button>
|
| 935 |
|
| 936 |
-
<button id="flip-btn" class="glass-button" aria-label="
|
| 937 |
-
<i class="fas fa-
|
| 938 |
</button>
|
| 939 |
</div>
|
| 940 |
|
|
@@ -961,24 +1057,20 @@
|
|
| 961 |
<div id="card-text" class="text-white/80 leading-relaxed whitespace-pre-line text-xs md:text-sm"></div>
|
| 962 |
</div>
|
| 963 |
|
| 964 |
-
<!--
|
| 965 |
-
<div class="
|
| 966 |
-
<
|
| 967 |
-
|
| 968 |
-
<
|
| 969 |
-
</div>
|
| 970 |
-
|
| 971 |
-
<div class="stat-box bg-black/40 border border-white/5">
|
| 972 |
-
<p class="info-label text-xs">Market Price</p>
|
| 973 |
-
<div id="card-prices" class="info-value text-sm md:text-base"></div>
|
| 974 |
-
</div>
|
| 975 |
-
|
| 976 |
-
<div class="stat-box bg-black/40 border border-white/5">
|
| 977 |
-
<p class="info-label text-xs">Rankings</p>
|
| 978 |
-
<div id="card-rankings" class="info-value text-sm md:text-base"></div>
|
| 979 |
</div>
|
| 980 |
</div>
|
| 981 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 982 |
<!-- Purchase Links Section -->
|
| 983 |
<div class="mb-4 md:mb-6">
|
| 984 |
<h3 class="font-semibold text-white mb-2 text-sm md:text-base">Purchase Options</h3>
|
|
@@ -1053,7 +1145,6 @@
|
|
| 1053 |
const cardName = document.getElementById('card-name');
|
| 1054 |
const cardType = document.getElementById('card-type');
|
| 1055 |
const cardText = document.getElementById('card-text');
|
| 1056 |
-
const cardStats = document.getElementById('card-stats');
|
| 1057 |
const cardLegalities = document.getElementById('card-legalities');
|
| 1058 |
const faceIndicator = document.getElementById('face-indicator');
|
| 1059 |
const cardFaceNav = document.getElementById('card-face-nav');
|
|
@@ -1065,6 +1156,9 @@
|
|
| 1065 |
const touchFeedback = document.getElementById('touch-feedback');
|
| 1066 |
const swipeLeftIndicator = document.getElementById('swipe-left');
|
| 1067 |
const swipeRightIndicator = document.getElementById('swipe-right');
|
|
|
|
|
|
|
|
|
|
| 1068 |
|
| 1069 |
let currentCardFace = 0;
|
| 1070 |
let currentCardData = null;
|
|
@@ -1080,6 +1174,9 @@
|
|
| 1080 |
let lastSwipeTime = 0;
|
| 1081 |
let swipeDebounceTime = 500; // Prevent multiple swipes in quick succession
|
| 1082 |
|
|
|
|
|
|
|
|
|
|
| 1083 |
// Event listeners with debouncing
|
| 1084 |
randomBtn.addEventListener('click', debounce(() => {
|
| 1085 |
if (!isLoading) {
|
|
@@ -1091,6 +1188,14 @@
|
|
| 1091 |
flipBtn.addEventListener('click', handleFlipButton);
|
| 1092 |
prevFaceBtn.addEventListener('click', () => switchFace(-1));
|
| 1093 |
nextFaceBtn.addEventListener('click', () => switchFace(1));
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1094 |
|
| 1095 |
// Keyboard navigation
|
| 1096 |
document.addEventListener('keydown', handleKeydown);
|
|
@@ -1145,6 +1250,12 @@
|
|
| 1145 |
return;
|
| 1146 |
}
|
| 1147 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1148 |
switch(e.key) {
|
| 1149 |
case ' ':
|
| 1150 |
case 'Enter':
|
|
@@ -1152,8 +1263,8 @@
|
|
| 1152 |
e.preventDefault();
|
| 1153 |
if (!isLoading) fetchRandomCard();
|
| 1154 |
break;
|
| 1155 |
-
case '
|
| 1156 |
-
case '
|
| 1157 |
e.preventDefault();
|
| 1158 |
handleFlipButton();
|
| 1159 |
break;
|
|
@@ -1305,12 +1416,31 @@
|
|
| 1305 |
if (currentCardData && currentCardData.card_faces) {
|
| 1306 |
switchFace(1);
|
| 1307 |
} else {
|
| 1308 |
-
|
| 1309 |
-
|
| 1310 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1311 |
}
|
| 1312 |
}
|
| 1313 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1314 |
function switchFace(direction) {
|
| 1315 |
if (!currentCardData || !currentCardData.card_faces) return;
|
| 1316 |
|
|
@@ -1333,6 +1463,7 @@
|
|
| 1333 |
// Reset flip state
|
| 1334 |
card3d.classList.remove('flipped');
|
| 1335 |
flipBtn.querySelector('span').textContent = 'Details';
|
|
|
|
| 1336 |
|
| 1337 |
// Fetch related cards with improved query
|
| 1338 |
fetchRelatedCards(data);
|
|
@@ -1466,46 +1597,9 @@
|
|
| 1466 |
cardName.textContent = name || 'Unknown Card';
|
| 1467 |
cardType.innerHTML = `<i class="fas fa-layer-group mr-2"></i>${typeText || ''}`;
|
| 1468 |
cardText.textContent = oracleText || 'No oracle text available.';
|
| 1469 |
-
|
| 1470 |
-
displayStats(stats, card);
|
| 1471 |
}
|
| 1472 |
|
| 1473 |
function displayCardImage(imageUris, name, card, faceIndex) {
|
| 1474 |
-
let priceLink = '';
|
| 1475 |
-
let priceValue = null;
|
| 1476 |
-
let purchaseLink = null;
|
| 1477 |
-
|
| 1478 |
-
// Check for prices in order of preference: USD, USD Foil, EUR
|
| 1479 |
-
if (card.prices?.usd) {
|
| 1480 |
-
priceValue = parseFloat(card.prices.usd).toFixed(2);
|
| 1481 |
-
} else if (card.prices?.usd_foil) {
|
| 1482 |
-
priceValue = parseFloat(card.prices.usd_foil).toFixed(2);
|
| 1483 |
-
} else if (card.prices?.eur) {
|
| 1484 |
-
priceValue = parseFloat(card.prices.eur).toFixed(2);
|
| 1485 |
-
}
|
| 1486 |
-
|
| 1487 |
-
// Determine purchase link
|
| 1488 |
-
if (card.purchase_uris) {
|
| 1489 |
-
purchaseLink = card.purchase_uris.tcgplayer || card.purchase_uris.cardmarket || card.purchase_uris.cardhoarder;
|
| 1490 |
-
}
|
| 1491 |
-
|
| 1492 |
-
if (priceValue) {
|
| 1493 |
-
if (purchaseLink) {
|
| 1494 |
-
priceLink = `
|
| 1495 |
-
<a href="${purchaseLink}" target="_blank" rel="noopener noreferrer"
|
| 1496 |
-
class="absolute bottom-1 md:bottom-2 left-1 md:left-2 price-tag text-xs no-underline hover:scale-105 transition-transform">
|
| 1497 |
-
${priceValue}
|
| 1498 |
-
</a>
|
| 1499 |
-
`;
|
| 1500 |
-
} else {
|
| 1501 |
-
priceLink = `
|
| 1502 |
-
<div class="absolute bottom-1 md:bottom-2 left-1 md:left-2 price-tag text-xs">
|
| 1503 |
-
${priceValue}
|
| 1504 |
-
</div>
|
| 1505 |
-
`;
|
| 1506 |
-
}
|
| 1507 |
-
}
|
| 1508 |
-
|
| 1509 |
if (imageUris && imageUris.large) {
|
| 1510 |
cardImage.innerHTML = `
|
| 1511 |
<img src="${imageUris.large}"
|
|
@@ -1513,7 +1607,6 @@
|
|
| 1513 |
class="w-full h-full object-contain"
|
| 1514 |
loading="lazy"
|
| 1515 |
onerror="this.onerror=null; this.src='${imageUris.normal || imageUris.small}';">
|
| 1516 |
-
${priceLink}
|
| 1517 |
`;
|
| 1518 |
} else {
|
| 1519 |
cardImage.innerHTML = `
|
|
@@ -1523,75 +1616,86 @@
|
|
| 1523 |
<p class="text-white/60 text-sm md:text-base">Image unavailable</p>
|
| 1524 |
</div>
|
| 1525 |
</div>
|
| 1526 |
-
${priceLink}
|
| 1527 |
`;
|
| 1528 |
}
|
| 1529 |
}
|
| 1530 |
|
| 1531 |
-
function displayStats(stats, card) {
|
| 1532 |
-
let statsHtml = '';
|
| 1533 |
-
|
| 1534 |
-
if (stats.power && stats.toughness) {
|
| 1535 |
-
statsHtml += `<div class="mb-1 font-semibold">${stats.power}/${stats.toughness}</div>`;
|
| 1536 |
-
}
|
| 1537 |
-
if (stats.loyalty) {
|
| 1538 |
-
statsHtml += `<div class="mb-1">Loyalty: ${stats.loyalty}</div>`;
|
| 1539 |
-
}
|
| 1540 |
-
|
| 1541 |
-
statsHtml += `<div class="text-white/70">CMC: ${card.cmc || 0}</div>`;
|
| 1542 |
-
|
| 1543 |
-
if (card.mana_cost) {
|
| 1544 |
-
statsHtml += `<div class="text-xs md:text-sm text-white/60 mt-1">${card.mana_cost}</div>`;
|
| 1545 |
-
}
|
| 1546 |
-
|
| 1547 |
-
cardStats.innerHTML = statsHtml || '<span class="text-white/40">—</span>';
|
| 1548 |
-
}
|
| 1549 |
-
|
| 1550 |
function displayCardBackDetails(card) {
|
| 1551 |
let detailsHtml = `
|
| 1552 |
<h3 class="text-lg md:text-2xl font-semibold mb-3 md:mb-4 text-white">${card.name}</h3>
|
| 1553 |
|
| 1554 |
<div class="mb-4 md:mb-6">
|
| 1555 |
<div class="glass p-2 md:p-3 rounded-lg">
|
| 1556 |
-
<h4 class="info-label">Set</h4>
|
| 1557 |
<p class="text-xs md:text-sm text-white/80">${card.set_name}</p>
|
| 1558 |
<p class="text-xs text-white/60">${card.set.toUpperCase()} • #${card.collector_number || 'N/A'}</p>
|
|
|
|
| 1559 |
</div>
|
| 1560 |
</div>
|
| 1561 |
|
| 1562 |
${card.flavor_text ? `
|
| 1563 |
<div class="glass p-2 md:p-3 rounded-lg mb-3 md:mb-4">
|
| 1564 |
-
<h4 class="info-label">Flavor</h4>
|
| 1565 |
<p class="italic text-xs md:text-sm text-white/70 leading-relaxed">"${card.flavor_text}"</p>
|
| 1566 |
</div>
|
| 1567 |
` : ''}
|
| 1568 |
|
| 1569 |
<div class="glass p-2 md:p-3 rounded-lg">
|
| 1570 |
-
<h4 class="info-label">Artist</h4>
|
| 1571 |
<p class="text-xs md:text-sm text-white/80">${card.artist}</p>
|
|
|
|
| 1572 |
</div>
|
| 1573 |
`;
|
| 1574 |
|
| 1575 |
document.getElementById('detailed-info').innerHTML = detailsHtml;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1576 |
}
|
| 1577 |
|
| 1578 |
function displayPrices(card) {
|
| 1579 |
let pricesHtml = '';
|
| 1580 |
if (card.prices) {
|
|
|
|
|
|
|
| 1581 |
if (card.prices.usd) {
|
| 1582 |
-
|
| 1583 |
-
|
|
|
|
|
|
|
|
|
|
| 1584 |
}
|
|
|
|
| 1585 |
if (card.prices.usd_foil) {
|
| 1586 |
-
|
| 1587 |
-
|
|
|
|
|
|
|
|
|
|
| 1588 |
}
|
|
|
|
| 1589 |
if (card.prices.eur) {
|
| 1590 |
-
|
| 1591 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1592 |
}
|
| 1593 |
} else {
|
| 1594 |
-
pricesHtml = '<
|
| 1595 |
}
|
| 1596 |
document.getElementById('card-prices').innerHTML = pricesHtml;
|
| 1597 |
}
|
|
@@ -1872,12 +1976,12 @@
|
|
| 1872 |
|
| 1873 |
${card.prices?.usd ? `
|
| 1874 |
<div class="absolute bottom-1 md:bottom-2 left-1 md:left-2 price-tag text-xs">
|
| 1875 |
-
${parseFloat(card.prices.usd).toFixed(2)}
|
| 1876 |
</div>
|
| 1877 |
` : ''}
|
| 1878 |
|
| 1879 |
<!-- Similarity Score Badge -->
|
| 1880 |
-
<div class="absolute bottom-1 md:bottom-2
|
| 1881 |
<span class="text-green-400 font-semibold">${Math.round(similarityScore * 100)}%</span>
|
| 1882 |
</div>
|
| 1883 |
|
|
@@ -1926,9 +2030,10 @@
|
|
| 1926 |
// Smooth scroll to top
|
| 1927 |
window.scrollTo({ top: 0, behavior: 'smooth' });
|
| 1928 |
|
| 1929 |
-
// Reset flip state
|
| 1930 |
card3d.classList.remove('flipped');
|
| 1931 |
flipBtn.querySelector('span').textContent = 'Details';
|
|
|
|
| 1932 |
|
| 1933 |
// Announce to screen readers
|
| 1934 |
announceToScreenReader(`Loaded card: ${card.name}`);
|
|
|
|
| 36 |
touch-action: pan-y pinch-zoom;
|
| 37 |
}
|
| 38 |
|
| 39 |
+
/* Details modal for mobile */
|
| 40 |
+
.details-modal {
|
| 41 |
+
position: fixed;
|
| 42 |
+
top: 0;
|
| 43 |
+
left: 0;
|
| 44 |
+
right: 0;
|
| 45 |
+
bottom: 0;
|
| 46 |
+
background: rgba(0, 0, 0, 0.95);
|
| 47 |
+
backdrop-filter: blur(20px);
|
| 48 |
+
-webkit-backdrop-filter: blur(20px);
|
| 49 |
+
z-index: 1000;
|
| 50 |
+
display: none;
|
| 51 |
+
overflow-y: auto;
|
| 52 |
+
-webkit-overflow-scrolling: touch;
|
| 53 |
+
}
|
| 54 |
+
|
| 55 |
+
.details-modal.active {
|
| 56 |
+
display: block;
|
| 57 |
+
}
|
| 58 |
+
|
| 59 |
+
.details-modal-content {
|
| 60 |
+
min-height: 100%;
|
| 61 |
+
padding: 20px;
|
| 62 |
+
padding-top: 60px;
|
| 63 |
+
position: relative;
|
| 64 |
+
}
|
| 65 |
+
|
| 66 |
+
.details-close-btn {
|
| 67 |
+
position: fixed;
|
| 68 |
+
top: 20px;
|
| 69 |
+
right: 20px;
|
| 70 |
+
width: 40px;
|
| 71 |
+
height: 40px;
|
| 72 |
+
border-radius: 50%;
|
| 73 |
+
background: rgba(255, 255, 255, 0.1);
|
| 74 |
+
border: 1px solid rgba(255, 255, 255, 0.2);
|
| 75 |
+
color: white;
|
| 76 |
+
display: flex;
|
| 77 |
+
align-items: center;
|
| 78 |
+
justify-content: center;
|
| 79 |
+
cursor: pointer;
|
| 80 |
+
z-index: 1001;
|
| 81 |
+
transition: all 0.3s;
|
| 82 |
+
}
|
| 83 |
+
|
| 84 |
+
.details-close-btn:hover {
|
| 85 |
+
background: rgba(255, 255, 255, 0.2);
|
| 86 |
+
transform: scale(1.1);
|
| 87 |
+
}
|
| 88 |
+
|
| 89 |
/* Swipe indicator overlay */
|
| 90 |
.swipe-indicator {
|
| 91 |
position: absolute;
|
|
|
|
| 914 |
color: rgba(255, 255, 255, 0.9);
|
| 915 |
font-weight: 500;
|
| 916 |
}
|
| 917 |
+
|
| 918 |
+
/* Consolidated price section */
|
| 919 |
+
.price-section {
|
| 920 |
+
background: rgba(0, 0, 0, 0.6);
|
| 921 |
+
border: 1px solid rgba(255, 255, 255, 0.08);
|
| 922 |
+
border-radius: 12px;
|
| 923 |
+
padding: 1rem;
|
| 924 |
+
margin-bottom: 1rem;
|
| 925 |
+
}
|
| 926 |
+
|
| 927 |
+
.price-grid {
|
| 928 |
+
display: grid;
|
| 929 |
+
grid-template-columns: repeat(auto-fit, minmax(100px, 1fr));
|
| 930 |
+
gap: 0.75rem;
|
| 931 |
+
margin-top: 0.75rem;
|
| 932 |
+
}
|
| 933 |
+
|
| 934 |
+
.price-item {
|
| 935 |
+
text-align: center;
|
| 936 |
+
}
|
| 937 |
+
|
| 938 |
+
.price-value {
|
| 939 |
+
font-size: 1.25rem;
|
| 940 |
+
font-weight: 600;
|
| 941 |
+
color: rgba(255, 255, 255, 0.95);
|
| 942 |
+
}
|
| 943 |
+
|
| 944 |
+
.price-label {
|
| 945 |
+
font-size: 0.75rem;
|
| 946 |
+
color: rgba(255, 255, 255, 0.5);
|
| 947 |
+
text-transform: uppercase;
|
| 948 |
+
letter-spacing: 0.05em;
|
| 949 |
+
margin-top: 0.25rem;
|
| 950 |
+
}
|
| 951 |
</style>
|
| 952 |
</head>
|
| 953 |
<body>
|
|
|
|
| 959 |
<!-- Touch feedback element -->
|
| 960 |
<div class="touch-feedback" id="touch-feedback"></div>
|
| 961 |
|
| 962 |
+
<!-- Details Modal for Mobile -->
|
| 963 |
+
<div class="details-modal" id="details-modal">
|
| 964 |
+
<button class="details-close-btn" id="close-details" aria-label="Close details">
|
| 965 |
+
<i class="fas fa-times"></i>
|
| 966 |
+
</button>
|
| 967 |
+
<div class="details-modal-content">
|
| 968 |
+
<div id="modal-card-details" class="glass-card p-4">
|
| 969 |
+
<!-- Card details will be populated here -->
|
| 970 |
+
</div>
|
| 971 |
+
</div>
|
| 972 |
+
</div>
|
| 973 |
+
|
| 974 |
<div class="container mx-auto py-4 md:py-8">
|
| 975 |
<!-- Header -->
|
| 976 |
<header class="text-center mb-8 md:mb-16 relative px-4">
|
|
|
|
| 1011 |
</div>
|
| 1012 |
</div>
|
| 1013 |
|
| 1014 |
+
<!-- Card Back (for desktop only) -->
|
| 1015 |
+
<div class="card-face card-face-back glass-card p-3 md:p-6 hidden lg:block">
|
| 1016 |
<div id="card-details" class="h-full overflow-y-auto">
|
| 1017 |
<h3 class="text-lg md:text-xl font-semibold text-white mb-3 md:mb-4">Card Analysis</h3>
|
| 1018 |
<div id="detailed-info" class="text-white/80 text-sm md:text-base">
|
|
|
|
| 1029 |
<i class="fas fa-shuffle mr-2"></i><span class="hidden sm:inline">Random Card</span><span class="sm:hidden">Random</span>
|
| 1030 |
</button>
|
| 1031 |
|
| 1032 |
+
<button id="flip-btn" class="glass-button" aria-label="Show Details">
|
| 1033 |
+
<i class="fas fa-info-circle mr-2"></i><span>Details</span>
|
| 1034 |
</button>
|
| 1035 |
</div>
|
| 1036 |
|
|
|
|
| 1057 |
<div id="card-text" class="text-white/80 leading-relaxed whitespace-pre-line text-xs md:text-sm"></div>
|
| 1058 |
</div>
|
| 1059 |
|
| 1060 |
+
<!-- Consolidated Pricing Section -->
|
| 1061 |
+
<div class="price-section">
|
| 1062 |
+
<h3 class="info-label">Market Prices</h3>
|
| 1063 |
+
<div id="card-prices" class="price-grid">
|
| 1064 |
+
<!-- Prices will be inserted here dynamically -->
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1065 |
</div>
|
| 1066 |
</div>
|
| 1067 |
|
| 1068 |
+
<!-- Rankings Box -->
|
| 1069 |
+
<div class="stat-box bg-black/40 border border-white/5 mb-4">
|
| 1070 |
+
<p class="info-label text-xs">Rankings</p>
|
| 1071 |
+
<div id="card-rankings" class="info-value text-sm md:text-base"></div>
|
| 1072 |
+
</div>
|
| 1073 |
+
|
| 1074 |
<!-- Purchase Links Section -->
|
| 1075 |
<div class="mb-4 md:mb-6">
|
| 1076 |
<h3 class="font-semibold text-white mb-2 text-sm md:text-base">Purchase Options</h3>
|
|
|
|
| 1145 |
const cardName = document.getElementById('card-name');
|
| 1146 |
const cardType = document.getElementById('card-type');
|
| 1147 |
const cardText = document.getElementById('card-text');
|
|
|
|
| 1148 |
const cardLegalities = document.getElementById('card-legalities');
|
| 1149 |
const faceIndicator = document.getElementById('face-indicator');
|
| 1150 |
const cardFaceNav = document.getElementById('card-face-nav');
|
|
|
|
| 1156 |
const touchFeedback = document.getElementById('touch-feedback');
|
| 1157 |
const swipeLeftIndicator = document.getElementById('swipe-left');
|
| 1158 |
const swipeRightIndicator = document.getElementById('swipe-right');
|
| 1159 |
+
const detailsModal = document.getElementById('details-modal');
|
| 1160 |
+
const closeDetailsBtn = document.getElementById('close-details');
|
| 1161 |
+
const modalCardDetails = document.getElementById('modal-card-details');
|
| 1162 |
|
| 1163 |
let currentCardFace = 0;
|
| 1164 |
let currentCardData = null;
|
|
|
|
| 1174 |
let lastSwipeTime = 0;
|
| 1175 |
let swipeDebounceTime = 500; // Prevent multiple swipes in quick succession
|
| 1176 |
|
| 1177 |
+
// Check if mobile
|
| 1178 |
+
const isMobile = () => window.innerWidth <= 768;
|
| 1179 |
+
|
| 1180 |
// Event listeners with debouncing
|
| 1181 |
randomBtn.addEventListener('click', debounce(() => {
|
| 1182 |
if (!isLoading) {
|
|
|
|
| 1188 |
flipBtn.addEventListener('click', handleFlipButton);
|
| 1189 |
prevFaceBtn.addEventListener('click', () => switchFace(-1));
|
| 1190 |
nextFaceBtn.addEventListener('click', () => switchFace(1));
|
| 1191 |
+
closeDetailsBtn.addEventListener('click', closeDetailsModal);
|
| 1192 |
+
|
| 1193 |
+
// Close modal on outside click
|
| 1194 |
+
detailsModal.addEventListener('click', (e) => {
|
| 1195 |
+
if (e.target === detailsModal) {
|
| 1196 |
+
closeDetailsModal();
|
| 1197 |
+
}
|
| 1198 |
+
});
|
| 1199 |
|
| 1200 |
// Keyboard navigation
|
| 1201 |
document.addEventListener('keydown', handleKeydown);
|
|
|
|
| 1250 |
return;
|
| 1251 |
}
|
| 1252 |
|
| 1253 |
+
// Close modal on Escape
|
| 1254 |
+
if (e.key === 'Escape' && detailsModal.classList.contains('active')) {
|
| 1255 |
+
closeDetailsModal();
|
| 1256 |
+
return;
|
| 1257 |
+
}
|
| 1258 |
+
|
| 1259 |
switch(e.key) {
|
| 1260 |
case ' ':
|
| 1261 |
case 'Enter':
|
|
|
|
| 1263 |
e.preventDefault();
|
| 1264 |
if (!isLoading) fetchRandomCard();
|
| 1265 |
break;
|
| 1266 |
+
case 'd':
|
| 1267 |
+
case 'D':
|
| 1268 |
e.preventDefault();
|
| 1269 |
handleFlipButton();
|
| 1270 |
break;
|
|
|
|
| 1416 |
if (currentCardData && currentCardData.card_faces) {
|
| 1417 |
switchFace(1);
|
| 1418 |
} else {
|
| 1419 |
+
// On mobile, show details modal instead of flipping
|
| 1420 |
+
if (isMobile()) {
|
| 1421 |
+
showDetailsModal();
|
| 1422 |
+
} else {
|
| 1423 |
+
card3d.classList.toggle('flipped');
|
| 1424 |
+
flipBtn.querySelector('span').textContent =
|
| 1425 |
+
card3d.classList.contains('flipped') ? 'Card' : 'Details';
|
| 1426 |
+
}
|
| 1427 |
}
|
| 1428 |
}
|
| 1429 |
|
| 1430 |
+
function showDetailsModal() {
|
| 1431 |
+
if (!currentCardData) return;
|
| 1432 |
+
|
| 1433 |
+
// Populate modal with card details
|
| 1434 |
+
modalCardDetails.innerHTML = document.getElementById('detailed-info').innerHTML;
|
| 1435 |
+
detailsModal.classList.add('active');
|
| 1436 |
+
document.body.style.overflow = 'hidden';
|
| 1437 |
+
}
|
| 1438 |
+
|
| 1439 |
+
function closeDetailsModal() {
|
| 1440 |
+
detailsModal.classList.remove('active');
|
| 1441 |
+
document.body.style.overflow = '';
|
| 1442 |
+
}
|
| 1443 |
+
|
| 1444 |
function switchFace(direction) {
|
| 1445 |
if (!currentCardData || !currentCardData.card_faces) return;
|
| 1446 |
|
|
|
|
| 1463 |
// Reset flip state
|
| 1464 |
card3d.classList.remove('flipped');
|
| 1465 |
flipBtn.querySelector('span').textContent = 'Details';
|
| 1466 |
+
closeDetailsModal();
|
| 1467 |
|
| 1468 |
// Fetch related cards with improved query
|
| 1469 |
fetchRelatedCards(data);
|
|
|
|
| 1597 |
cardName.textContent = name || 'Unknown Card';
|
| 1598 |
cardType.innerHTML = `<i class="fas fa-layer-group mr-2"></i>${typeText || ''}`;
|
| 1599 |
cardText.textContent = oracleText || 'No oracle text available.';
|
|
|
|
|
|
|
| 1600 |
}
|
| 1601 |
|
| 1602 |
function displayCardImage(imageUris, name, card, faceIndex) {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1603 |
if (imageUris && imageUris.large) {
|
| 1604 |
cardImage.innerHTML = `
|
| 1605 |
<img src="${imageUris.large}"
|
|
|
|
| 1607 |
class="w-full h-full object-contain"
|
| 1608 |
loading="lazy"
|
| 1609 |
onerror="this.onerror=null; this.src='${imageUris.normal || imageUris.small}';">
|
|
|
|
| 1610 |
`;
|
| 1611 |
} else {
|
| 1612 |
cardImage.innerHTML = `
|
|
|
|
| 1616 |
<p class="text-white/60 text-sm md:text-base">Image unavailable</p>
|
| 1617 |
</div>
|
| 1618 |
</div>
|
|
|
|
| 1619 |
`;
|
| 1620 |
}
|
| 1621 |
}
|
| 1622 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1623 |
function displayCardBackDetails(card) {
|
| 1624 |
let detailsHtml = `
|
| 1625 |
<h3 class="text-lg md:text-2xl font-semibold mb-3 md:mb-4 text-white">${card.name}</h3>
|
| 1626 |
|
| 1627 |
<div class="mb-4 md:mb-6">
|
| 1628 |
<div class="glass p-2 md:p-3 rounded-lg">
|
| 1629 |
+
<h4 class="info-label">Set Information</h4>
|
| 1630 |
<p class="text-xs md:text-sm text-white/80">${card.set_name}</p>
|
| 1631 |
<p class="text-xs text-white/60">${card.set.toUpperCase()} • #${card.collector_number || 'N/A'}</p>
|
| 1632 |
+
<p class="text-xs text-white/60">Released: ${card.released_at || 'Unknown'}</p>
|
| 1633 |
</div>
|
| 1634 |
</div>
|
| 1635 |
|
| 1636 |
${card.flavor_text ? `
|
| 1637 |
<div class="glass p-2 md:p-3 rounded-lg mb-3 md:mb-4">
|
| 1638 |
+
<h4 class="info-label">Flavor Text</h4>
|
| 1639 |
<p class="italic text-xs md:text-sm text-white/70 leading-relaxed">"${card.flavor_text}"</p>
|
| 1640 |
</div>
|
| 1641 |
` : ''}
|
| 1642 |
|
| 1643 |
<div class="glass p-2 md:p-3 rounded-lg">
|
| 1644 |
+
<h4 class="info-label">Artist & Rarity</h4>
|
| 1645 |
<p class="text-xs md:text-sm text-white/80">${card.artist}</p>
|
| 1646 |
+
<p class="text-xs text-white/60">${card.rarity ? card.rarity.charAt(0).toUpperCase() + card.rarity.slice(1) : 'Unknown'}</p>
|
| 1647 |
</div>
|
| 1648 |
`;
|
| 1649 |
|
| 1650 |
document.getElementById('detailed-info').innerHTML = detailsHtml;
|
| 1651 |
+
|
| 1652 |
+
// Also update modal content if on mobile
|
| 1653 |
+
if (isMobile()) {
|
| 1654 |
+
modalCardDetails.innerHTML = detailsHtml;
|
| 1655 |
+
}
|
| 1656 |
}
|
| 1657 |
|
| 1658 |
function displayPrices(card) {
|
| 1659 |
let pricesHtml = '';
|
| 1660 |
if (card.prices) {
|
| 1661 |
+
const priceData = [];
|
| 1662 |
+
|
| 1663 |
if (card.prices.usd) {
|
| 1664 |
+
priceData.push({
|
| 1665 |
+
label: 'USD',
|
| 1666 |
+
value: `$${parseFloat(card.prices.usd).toFixed(2)}`,
|
| 1667 |
+
color: 'text-green-400'
|
| 1668 |
+
});
|
| 1669 |
}
|
| 1670 |
+
|
| 1671 |
if (card.prices.usd_foil) {
|
| 1672 |
+
priceData.push({
|
| 1673 |
+
label: 'Foil',
|
| 1674 |
+
value: `$${parseFloat(card.prices.usd_foil).toFixed(2)}`,
|
| 1675 |
+
color: 'text-blue-400'
|
| 1676 |
+
});
|
| 1677 |
}
|
| 1678 |
+
|
| 1679 |
if (card.prices.eur) {
|
| 1680 |
+
priceData.push({
|
| 1681 |
+
label: 'EUR',
|
| 1682 |
+
value: `€${parseFloat(card.prices.eur).toFixed(2)}`,
|
| 1683 |
+
color: 'text-purple-400'
|
| 1684 |
+
});
|
| 1685 |
+
}
|
| 1686 |
+
|
| 1687 |
+
if (priceData.length > 0) {
|
| 1688 |
+
pricesHtml = priceData.map(price => `
|
| 1689 |
+
<div class="price-item">
|
| 1690 |
+
<div class="price-value ${price.color}">${price.value}</div>
|
| 1691 |
+
<div class="price-label">${price.label}</div>
|
| 1692 |
+
</div>
|
| 1693 |
+
`).join('');
|
| 1694 |
+
} else {
|
| 1695 |
+
pricesHtml = '<div class="col-span-full text-center text-white/40">No pricing data available</div>';
|
| 1696 |
}
|
| 1697 |
} else {
|
| 1698 |
+
pricesHtml = '<div class="col-span-full text-center text-white/40">No pricing data available</div>';
|
| 1699 |
}
|
| 1700 |
document.getElementById('card-prices').innerHTML = pricesHtml;
|
| 1701 |
}
|
|
|
|
| 1976 |
|
| 1977 |
${card.prices?.usd ? `
|
| 1978 |
<div class="absolute bottom-1 md:bottom-2 left-1 md:left-2 price-tag text-xs">
|
| 1979 |
+
$${parseFloat(card.prices.usd).toFixed(2)}
|
| 1980 |
</div>
|
| 1981 |
` : ''}
|
| 1982 |
|
| 1983 |
<!-- Similarity Score Badge -->
|
| 1984 |
+
<div class="absolute bottom-1 md:bottom-2 right-1 md:right-2 glass-card px-2 py-1 text-xs">
|
| 1985 |
<span class="text-green-400 font-semibold">${Math.round(similarityScore * 100)}%</span>
|
| 1986 |
</div>
|
| 1987 |
|
|
|
|
| 2030 |
// Smooth scroll to top
|
| 2031 |
window.scrollTo({ top: 0, behavior: 'smooth' });
|
| 2032 |
|
| 2033 |
+
// Reset flip state and close modal if open
|
| 2034 |
card3d.classList.remove('flipped');
|
| 2035 |
flipBtn.querySelector('span').textContent = 'Details';
|
| 2036 |
+
closeDetailsModal();
|
| 2037 |
|
| 2038 |
// Announce to screen readers
|
| 2039 |
announceToScreenReader(`Loaded card: ${card.name}`);
|