← back to Handbag Auth Nextjs
auction-viewer/best-values.html
1221 lines
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Best Value Auctions - LUXVAULT</title>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<style>
:root {
--primary-gradient: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
--bg-color: #f5f7fa;
--card-bg: #ffffff;
--text-primary: #333333;
--text-secondary: #666666;
--border-color: #e0e0e0;
--shadow-sm: 0 2px 8px rgba(0,0,0,0.1);
--shadow-md: 0 10px 30px rgba(0,0,0,0.2);
--shadow-lg: 0 20px 60px rgba(0,0,0,0.3);
--success: #10b981;
--warning: #f59e0b;
--danger: #ef4444;
--info: #3b82f6;
}
[data-theme="dark"] {
--bg-color: #1a1a2e;
--card-bg: #16213e;
--text-primary: #eee;
--text-secondary: #aaa;
--border-color: #2d3748;
--shadow-sm: 0 2px 8px rgba(0,0,0,0.3);
--shadow-md: 0 10px 30px rgba(0,0,0,0.5);
--shadow-lg: 0 20px 60px rgba(0,0,0,0.7);
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
background: var(--bg-color);
min-height: 100vh;
padding: 20px;
transition: background 0.3s ease;
}
body.gradient-bg {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
}
.container {
max-width: 1600px;
margin: 0 auto;
}
/* Header */
.header {
background: var(--card-bg);
padding: 40px;
border-radius: 20px;
box-shadow: var(--shadow-lg);
margin-bottom: 40px;
text-align: center;
position: relative;
overflow: hidden;
}
.header::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
height: 4px;
background: var(--primary-gradient);
}
.header-top {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
}
.header h1 {
font-size: 3em;
background: var(--primary-gradient);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
margin-bottom: 10px;
animation: shimmerText 3s ease-in-out infinite;
}
@keyframes shimmerText {
0%, 100% { filter: brightness(1); }
50% { filter: brightness(1.2); }
}
.header p {
color: var(--text-secondary);
font-size: 1.2em;
}
.theme-toggle {
background: var(--card-bg);
border: 2px solid var(--border-color);
color: var(--text-primary);
width: 50px;
height: 50px;
border-radius: 50%;
cursor: pointer;
font-size: 22px;
transition: all 0.3s ease;
display: flex;
align-items: center;
justify-content: center;
position: absolute;
top: 20px;
right: 20px;
}
.theme-toggle:hover {
transform: rotate(180deg);
border-color: #667eea;
}
/* Search Bar */
.search-container {
position: relative;
max-width: 600px;
margin: 20px auto 0;
}
.search-input {
width: 100%;
padding: 15px 50px 15px 20px;
border: 2px solid var(--border-color);
border-radius: 12px;
font-size: 16px;
background: var(--card-bg);
color: var(--text-primary);
transition: all 0.3s ease;
}
.search-input:focus {
outline: none;
border-color: #667eea;
box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1);
}
.search-icon {
position: absolute;
right: 20px;
top: 50%;
transform: translateY(-50%);
color: var(--text-secondary);
font-size: 18px;
}
/* Stats Banner */
.stats-banner {
background: var(--card-bg);
padding: 25px;
border-radius: 15px;
box-shadow: var(--shadow-md);
margin-bottom: 30px;
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 20px;
}
.stat-box {
text-align: center;
padding: 15px;
border-radius: 10px;
background: rgba(102, 126, 234, 0.05);
transition: all 0.3s ease;
}
.stat-box:hover {
transform: translateY(-3px);
box-shadow: var(--shadow-sm);
}
.stat-number {
font-size: 2.5em;
font-weight: bold;
background: var(--primary-gradient);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
}
.stat-label {
color: var(--text-secondary);
font-size: 0.95em;
text-transform: uppercase;
letter-spacing: 1px;
margin-top: 5px;
}
/* Filter Bar */
.filter-bar {
background: var(--card-bg);
padding: 20px;
border-radius: 15px;
box-shadow: var(--shadow-md);
margin-bottom: 30px;
display: flex;
gap: 15px;
flex-wrap: wrap;
align-items: center;
}
.filter-bar select,
.filter-bar input {
padding: 12px 20px;
border: 2px solid var(--border-color);
border-radius: 10px;
font-size: 16px;
background: var(--card-bg);
color: var(--text-primary);
transition: all 0.3s ease;
}
.filter-bar select:focus,
.filter-bar input:focus {
border-color: #667eea;
outline: none;
box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1);
}
.filter-bar button {
padding: 12px 30px;
background: var(--primary-gradient);
color: white;
border: none;
border-radius: 10px;
font-size: 16px;
font-weight: 600;
cursor: pointer;
transition: all 0.3s ease;
display: inline-flex;
align-items: center;
gap: 8px;
}
.filter-bar button:hover {
transform: translateY(-2px);
box-shadow: 0 5px 15px rgba(102, 126, 234, 0.4);
}
.btn-secondary {
background: var(--card-bg) !important;
color: var(--text-primary) !important;
border: 2px solid var(--border-color) !important;
}
/* View Toggle */
.view-toggle {
display: flex;
gap: 5px;
background: var(--card-bg);
padding: 5px;
border-radius: 10px;
border: 2px solid var(--border-color);
margin-left: auto;
}
.view-toggle button {
padding: 8px 16px;
background: transparent;
border: none;
border-radius: 7px;
cursor: pointer;
color: var(--text-secondary);
transition: all 0.3s ease;
}
.view-toggle button.active {
background: var(--primary-gradient);
color: white;
}
/* Cards Grid */
.cards-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(350px, 1fr));
gap: 25px;
margin-bottom: 40px;
animation: fadeIn 0.5s ease;
}
.list-view .cards-grid {
grid-template-columns: 1fr;
}
@keyframes fadeIn {
from { opacity: 0; transform: translateY(20px); }
to { opacity: 1; transform: translateY(0); }
}
/* Value Card */
.value-card {
background: var(--card-bg);
border-radius: 20px;
overflow: hidden;
box-shadow: var(--shadow-md);
transition: all 0.3s ease;
position: relative;
animation: slideUp 0.3s ease;
}
@keyframes slideUp {
from { opacity: 0; transform: translateY(20px); }
to { opacity: 1; transform: translateY(0); }
}
.value-card:hover {
transform: translateY(-10px);
box-shadow: var(--shadow-lg);
}
.card-badge {
position: absolute;
top: 15px;
right: 15px;
background: linear-gradient(135deg, #10b981 0%, #059669 100%);
color: white;
padding: 8px 16px;
border-radius: 20px;
font-weight: 700;
font-size: 14px;
box-shadow: 0 4px 15px rgba(16, 185, 129, 0.4);
z-index: 10;
animation: bounce 2s ease-in-out infinite;
}
@keyframes bounce {
0%, 100% { transform: translateY(0); }
50% { transform: translateY(-5px); }
}
.card-rank {
position: absolute;
top: 15px;
left: 15px;
background: rgba(255, 255, 255, 0.95);
color: #667eea;
width: 50px;
height: 50px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-weight: bold;
font-size: 20px;
box-shadow: var(--shadow-sm);
z-index: 10;
}
.favorite-badge {
position: absolute;
top: 15px;
left: 75px;
background: rgba(255, 255, 255, 0.95);
width: 40px;
height: 40px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
transition: all 0.3s ease;
z-index: 10;
border: 2px solid transparent;
}
.favorite-badge:hover {
transform: scale(1.1);
border-color: #f59e0b;
}
.favorite-badge.active {
background: #f59e0b;
color: white;
}
.favorite-badge i {
font-size: 18px;
color: #f59e0b;
}
.favorite-badge.active i {
color: white;
}
.card-header {
background: var(--primary-gradient);
color: white;
padding: 80px 25px 25px;
position: relative;
}
.card-title {
font-size: 1.3em;
font-weight: bold;
margin-bottom: 10px;
line-height: 1.4;
}
.card-subtitle {
font-size: 0.95em;
opacity: 0.9;
}
.card-body {
padding: 25px;
}
.price-section {
margin-bottom: 20px;
}
.current-price {
font-size: 2.5em;
font-weight: bold;
color: #667eea;
margin-bottom: 10px;
}
.estimate-price {
display: flex;
justify-content: space-between;
background: rgba(102, 126, 234, 0.05);
padding: 15px;
border-radius: 10px;
margin-bottom: 15px;
}
.estimate-label {
color: var(--text-secondary);
font-size: 0.9em;
}
.estimate-value {
font-weight: 600;
color: var(--text-primary);
}
.savings-section {
background: linear-gradient(135deg, #10b981 0%, #059669 100%);
color: white;
padding: 20px;
border-radius: 15px;
text-align: center;
margin-bottom: 20px;
position: relative;
overflow: hidden;
}
.savings-section::before {
content: '';
position: absolute;
top: -50%;
right: -50%;
width: 200%;
height: 200%;
background: radial-gradient(circle, rgba(255,255,255,0.1) 0%, transparent 70%);
animation: rotate 10s linear infinite;
}
@keyframes rotate {
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
}
.savings-amount {
font-size: 2em;
font-weight: bold;
margin-bottom: 5px;
position: relative;
z-index: 1;
}
.savings-percent {
font-size: 1.2em;
opacity: 0.9;
position: relative;
z-index: 1;
}
.card-details {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 15px;
margin-bottom: 20px;
}
.detail-item {
background: rgba(102, 126, 234, 0.05);
padding: 12px;
border-radius: 10px;
transition: all 0.3s ease;
}
.detail-item:hover {
background: rgba(102, 126, 234, 0.1);
}
.detail-label {
font-size: 0.85em;
color: var(--text-secondary);
margin-bottom: 5px;
}
.detail-value {
font-weight: 600;
color: var(--text-primary);
font-size: 0.95em;
}
.card-footer {
padding: 0 25px 25px;
}
.view-button {
display: block;
width: 100%;
padding: 15px;
background: var(--primary-gradient);
color: white;
text-align: center;
text-decoration: none;
border-radius: 12px;
font-weight: 600;
font-size: 16px;
transition: all 0.3s ease;
}
.view-button:hover {
transform: translateY(-2px);
box-shadow: 0 8px 20px rgba(102, 126, 234, 0.4);
}
/* Empty State */
.empty-state {
background: var(--card-bg);
padding: 60px;
border-radius: 20px;
text-align: center;
box-shadow: var(--shadow-md);
}
.empty-state-icon {
font-size: 80px;
margin-bottom: 20px;
opacity: 0.3;
}
.empty-state h3 {
font-size: 2em;
color: var(--text-primary);
margin-bottom: 15px;
}
.empty-state p {
color: var(--text-secondary);
font-size: 1.1em;
}
/* Loading */
.loading {
text-align: center;
padding: 60px;
color: var(--text-secondary);
}
.loading-spinner {
display: inline-block;
width: 60px;
height: 60px;
border: 5px solid var(--border-color);
border-top-color: #667eea;
border-radius: 50%;
animation: spin 1s linear infinite;
margin-bottom: 20px;
}
@keyframes spin {
to { transform: rotate(360deg); }
}
/* Back Button */
.back-button {
display: inline-flex;
align-items: center;
gap: 8px;
padding: 12px 24px;
background: var(--card-bg);
color: #667eea;
text-decoration: none;
border-radius: 10px;
font-weight: 600;
margin-bottom: 20px;
transition: all 0.3s ease;
box-shadow: var(--shadow-sm);
border: 2px solid var(--border-color);
}
.back-button:hover {
transform: translateY(-2px);
box-shadow: var(--shadow-md);
border-color: #667eea;
}
/* Toast Notifications */
.toast-container {
position: fixed;
top: 20px;
right: 20px;
z-index: 9999;
max-width: 350px;
}
.toast {
background: var(--card-bg);
color: var(--text-primary);
padding: 16px 20px;
border-radius: 12px;
box-shadow: var(--shadow-lg);
margin-bottom: 10px;
display: flex;
align-items: center;
gap: 12px;
animation: slideIn 0.3s ease;
border-left: 4px solid #667eea;
}
.toast.success { border-left-color: var(--success); }
.toast.error { border-left-color: var(--danger); }
.toast.warning { border-left-color: var(--warning); }
.toast.info { border-left-color: var(--info); }
@keyframes slideIn {
from { transform: translateX(400px); opacity: 0; }
to { transform: translateX(0); opacity: 1; }
}
.toast-icon {
font-size: 20px;
}
.toast.success .toast-icon { color: var(--success); }
.toast.error .toast-icon { color: var(--danger); }
.toast.warning .toast-icon { color: var(--warning); }
.toast.info .toast-icon { color: var(--info); }
/* Floating Action Buttons */
.fab-container {
position: fixed;
bottom: 30px;
right: 30px;
display: flex;
flex-direction: column;
gap: 15px;
z-index: 1000;
}
.fab {
width: 60px;
height: 60px;
border-radius: 50%;
background: var(--primary-gradient);
color: white;
border: none;
font-size: 24px;
cursor: pointer;
box-shadow: var(--shadow-lg);
transition: all 0.3s ease;
display: flex;
align-items: center;
justify-content: center;
}
.fab:hover {
transform: scale(1.1) rotate(90deg);
}
.fab-secondary {
background: var(--card-bg);
color: #667eea;
width: 50px;
height: 50px;
font-size: 20px;
}
/* Skeleton Loading */
.skeleton-card {
background: var(--card-bg);
border-radius: 20px;
overflow: hidden;
box-shadow: var(--shadow-md);
}
.skeleton {
background: linear-gradient(90deg, var(--border-color) 25%, rgba(102,126,234,0.1) 50%, var(--border-color) 75%);
background-size: 200% 100%;
animation: shimmer 1.5s infinite;
}
@keyframes shimmer {
0% { background-position: 200% 0; }
100% { background-position: -200% 0; }
}
.skeleton-header {
height: 150px;
}
.skeleton-body {
padding: 25px;
}
.skeleton-text {
height: 20px;
margin-bottom: 10px;
border-radius: 4px;
}
.skeleton-button {
height: 50px;
border-radius: 12px;
}
/* Responsive Design */
@media (max-width: 768px) {
body {
padding: 10px;
}
.header h1 {
font-size: 2em;
}
.filter-bar {
flex-direction: column;
}
.filter-bar select,
.filter-bar input,
.filter-bar button {
width: 100%;
}
.view-toggle {
width: 100%;
margin-left: 0;
}
.cards-grid {
grid-template-columns: 1fr;
}
.stats-banner {
grid-template-columns: 1fr;
}
.fab-container {
bottom: 20px;
right: 20px;
}
}
</style>
</head>
<body class="gradient-bg">
<!-- Toast Container -->
<div class="toast-container" id="toastContainer"></div>
<div class="container">
<a href="/" class="back-button">
<i class="fas fa-arrow-left"></i> Back to Dashboard
</a>
<div class="header">
<button class="theme-toggle" onclick="toggleTheme()" title="Toggle Theme">
<i class="fas fa-moon" id="themeIcon"></i>
</button>
<h1><i class="fas fa-gem"></i> Best Value Auctions</h1>
<p>Top luxury handbag deals based on savings vs. estimate</p>
<div class="search-container">
<input type="text" class="search-input" id="searchInput" placeholder="Search by title, brand, or auction house..." onkeyup="searchDeals()">
<i class="fas fa-search search-icon"></i>
</div>
</div>
<div class="stats-banner" id="statsBanner">
<div class="stat-box">
<div class="stat-number" id="totalDeals">0</div>
<div class="stat-label">Total Deals</div>
</div>
<div class="stat-box">
<div class="stat-number" id="avgSavings">0%</div>
<div class="stat-label">Avg Savings</div>
</div>
<div class="stat-box">
<div class="stat-number" id="maxSavings">$0</div>
<div class="stat-label">Max Savings</div>
</div>
<div class="stat-box">
<div class="stat-number" id="topBrand">-</div>
<div class="stat-label">Top Brand</div>
</div>
<div class="stat-box">
<div class="stat-number" id="favoriteDeals">0</div>
<div class="stat-label">Favorites</div>
</div>
</div>
<div class="filter-bar">
<select id="sortBy" onchange="applyFilters()">
<option value="percent">Sort by % Savings</option>
<option value="dollar">Sort by $ Savings</option>
<option value="price_low">Price: Low to High</option>
<option value="price_high">Price: High to Low</option>
</select>
<select id="brandFilter" onchange="applyFilters()">
<option value="">All Brands</option>
</select>
<input type="number" id="minSavings" placeholder="Min Savings %" min="0" max="100" onchange="applyFilters()">
<input type="number" id="minDollarSavings" placeholder="Min $ Savings" min="0" onchange="applyFilters()">
<input type="number" id="maxPrice" placeholder="Max Price $" min="0" onchange="applyFilters()">
<button onclick="applyFilters()">
<i class="fas fa-filter"></i> Apply Filters
</button>
<button onclick="resetFilters()" class="btn-secondary">
<i class="fas fa-redo"></i> Reset
</button>
<div class="view-toggle">
<button class="active" onclick="toggleView('grid')" title="Grid View">
<i class="fas fa-th"></i>
</button>
<button onclick="toggleView('list')" title="List View">
<i class="fas fa-list"></i>
</button>
</div>
</div>
<div class="cards-grid" id="cardsContainer">
<div class="loading">
<div class="loading-spinner"></div>
<p>Loading best value auctions...</p>
</div>
</div>
</div>
<!-- Floating Action Buttons -->
<div class="fab-container">
<button class="fab fab-secondary" onclick="scrollToTop()" title="Scroll to Top">
<i class="fas fa-arrow-up"></i>
</button>
<button class="fab" onclick="loadBestValues()" title="Refresh Data">
<i class="fas fa-sync-alt"></i>
</button>
</div>
<script>
const API_BASE = window.location.origin;
let allAuctions = [];
let filteredAuctions = [];
let currentSort = 'percent';
let favorites = JSON.parse(localStorage.getItem('luxvault_favorites') || '[]');
let currentView = 'grid';
// Theme Management
function toggleTheme() {
const html = document.documentElement;
const currentTheme = html.getAttribute('data-theme');
const newTheme = currentTheme === 'dark' ? 'light' : 'dark';
html.setAttribute('data-theme', newTheme);
localStorage.setItem('luxvault_theme', newTheme);
const icon = document.getElementById('themeIcon');
icon.className = newTheme === 'dark' ? 'fas fa-sun' : 'fas fa-moon';
showToast(newTheme === 'dark' ? 'Dark mode enabled' : 'Light mode enabled', 'success');
}
// Initialize theme
const savedTheme = localStorage.getItem('luxvault_theme') || 'light';
if (savedTheme === 'dark') {
document.documentElement.setAttribute('data-theme', 'dark');
document.getElementById('themeIcon').className = 'fas fa-sun';
}
// Toast Notifications
function showToast(message, type = 'info', duration = 3000) {
const container = document.getElementById('toastContainer');
const toast = document.createElement('div');
toast.className = `toast ${type}`;
const icons = {
success: 'fa-check-circle',
error: 'fa-exclamation-circle',
warning: 'fa-exclamation-triangle',
info: 'fa-info-circle'
};
toast.innerHTML = `
<i class="fas ${icons[type]} toast-icon"></i>
<span>${message}</span>
`;
container.appendChild(toast);
setTimeout(() => {
toast.style.animation = 'slideIn 0.3s ease reverse';
setTimeout(() => toast.remove(), 300);
}, duration);
}
// Favorites Management
function toggleFavorite(auctionId) {
const index = favorites.indexOf(auctionId);
if (index > -1) {
favorites.splice(index, 1);
showToast('Removed from favorites', 'info');
} else {
favorites.push(auctionId);
showToast('Added to favorites', 'success');
}
localStorage.setItem('luxvault_favorites', JSON.stringify(favorites));
updateFavoriteCount();
// Update badge
const badge = document.querySelector(`[data-favorite-id="${auctionId}"]`);
if (badge) {
badge.classList.toggle('active');
}
}
function updateFavoriteCount() {
const favoriteDeals = filteredAuctions.filter(a => favorites.includes(a.id));
document.getElementById('favoriteDeals').textContent = favoriteDeals.length;
}
// View Toggle
function toggleView(view) {
currentView = view;
const container = document.querySelector('.container');
const buttons = document.querySelectorAll('.view-toggle button');
buttons.forEach(btn => btn.classList.remove('active'));
event.target.classList.add('active');
if (view === 'list') {
container.classList.add('list-view');
} else {
container.classList.remove('list-view');
}
showToast(`Switched to ${view} view`, 'info', 2000);
}
// Search Functionality
function searchDeals() {
const query = document.getElementById('searchInput').value.toLowerCase();
if (!query) {
applyFilters();
return;
}
filteredAuctions = allAuctions.filter(auction => {
return (auction.title && auction.title.toLowerCase().includes(query)) ||
(auction.search_term && auction.search_term.toLowerCase().includes(query)) ||
(auction.auction_house && auction.auction_house.toLowerCase().includes(query));
});
updateStats(filteredAuctions);
renderCards(filteredAuctions);
}
// Load Best Values
async function loadBestValues() {
try {
showToast('Loading auctions...', 'info', 1000);
const res = await fetch(`${API_BASE}/api/best-values`);
const data = await res.json();
allAuctions = data.data;
filteredAuctions = allAuctions;
if (data.count === 0) {
document.getElementById('cardsContainer').innerHTML = `
<div class="empty-state">
<div class="empty-state-icon"><i class="fas fa-gem"></i></div>
<h3>No auction data yet</h3>
<p>The scraper will run daily at 6:00 AM and discover best value deals.</p>
<p style="margin-top: 20px;">Check back after the first scrape completes!</p>
</div>
`;
return;
}
// Populate brand filter
const brands = [...new Set(allAuctions.map(a => a.search_term))].sort();
const brandFilter = document.getElementById('brandFilter');
brandFilter.innerHTML = '<option value="">All Brands</option>';
brands.forEach(brand => {
brandFilter.innerHTML += `<option value="${brand}">${brand}</option>`;
});
// Update stats
updateStats(filteredAuctions);
// Render cards
renderCards(filteredAuctions);
showToast('Data loaded successfully', 'success', 2000);
} catch (error) {
document.getElementById('cardsContainer').innerHTML = `
<div class="empty-state">
<div class="empty-state-icon"><i class="fas fa-exclamation-circle"></i></div>
<h3>Error loading data</h3>
<p>${error.message}</p>
</div>
`;
showToast('Failed to load data', 'error');
}
}
function updateStats(auctions) {
if (auctions.length === 0) {
document.getElementById('totalDeals').textContent = '0';
document.getElementById('avgSavings').textContent = '0%';
document.getElementById('maxSavings').textContent = '$0';
document.getElementById('topBrand').textContent = '-';
updateFavoriteCount();
return;
}
document.getElementById('totalDeals').textContent = auctions.length;
const avgSavings = auctions.reduce((sum, a) => sum + a.savings_percent, 0) / auctions.length;
document.getElementById('avgSavings').textContent = Math.round(avgSavings) + '%';
const maxSavings = Math.max(...auctions.map(a => a.savings_amount));
document.getElementById('maxSavings').textContent = '$' + maxSavings.toLocaleString();
// Top brand by count
const brandCounts = {};
auctions.forEach(a => {
brandCounts[a.search_term] = (brandCounts[a.search_term] || 0) + 1;
});
const topBrand = Object.keys(brandCounts).reduce((a, b) =>
brandCounts[a] > brandCounts[b] ? a : b, '');
document.getElementById('topBrand').textContent = topBrand.split(' ')[0];
updateFavoriteCount();
}
function renderCards(auctions) {
if (auctions.length === 0) {
document.getElementById('cardsContainer').innerHTML = `
<div class="empty-state">
<div class="empty-state-icon"><i class="fas fa-search"></i></div>
<h3>No matches found</h3>
<p>Try adjusting your filters to see more results.</p>
</div>
`;
return;
}
let html = '';
auctions.forEach((auction, index) => {
const savingsPercent = Math.round(auction.savings_percent);
const savingsAmount = Math.round(auction.savings_amount);
const isFavorite = favorites.includes(auction.id);
// Badge shows the primary sort value
const badgeText = currentSort === 'dollar'
? `$${savingsAmount.toLocaleString()} OFF`
: `${savingsPercent}% OFF`;
html += `
<div class="value-card" style="animation-delay: ${index * 0.05}s">
<div class="card-rank">#${index + 1}</div>
<div class="favorite-badge ${isFavorite ? 'active' : ''}"
data-favorite-id="${auction.id}"
onclick="toggleFavorite(${auction.id})">
<i class="fas fa-star"></i>
</div>
<div class="card-badge">${badgeText}</div>
<div class="card-header">
<div class="card-title">${auction.title || 'Luxury Handbag'}</div>
<div class="card-subtitle">${auction.search_term}</div>
</div>
<div class="card-body">
<div class="price-section">
<div class="current-price">$${auction.current_price.toLocaleString()}</div>
<div class="estimate-price">
<div>
<div class="estimate-label">Estimate Low</div>
<div class="estimate-value">$${auction.estimate_low.toLocaleString()}</div>
</div>
${auction.estimate_high ? `
<div>
<div class="estimate-label">Estimate High</div>
<div class="estimate-value">$${auction.estimate_high.toLocaleString()}</div>
</div>
` : ''}
</div>
</div>
<div class="savings-section">
<div class="savings-amount">$${savingsAmount.toLocaleString()}</div>
<div class="savings-percent">${savingsPercent}% Savings</div>
</div>
<div class="card-details">
<div class="detail-item">
<div class="detail-label">Auction House</div>
<div class="detail-value">${auction.auction_house || 'N/A'}</div>
</div>
<div class="detail-item">
<div class="detail-label">End Date</div>
<div class="detail-value">${auction.end_date ? new Date(auction.end_date).toLocaleDateString() : 'N/A'}</div>
</div>
<div class="detail-item">
<div class="detail-label">Lot Number</div>
<div class="detail-value">${auction.lot_number || 'N/A'}</div>
</div>
<div class="detail-item">
<div class="detail-label">Listed</div>
<div class="detail-value">${new Date(auction.created_at).toLocaleDateString()}</div>
</div>
</div>
</div>
<div class="card-footer">
<a href="${auction.url}" target="_blank" class="view-button">
<i class="fas fa-external-link-alt"></i> View Auction
</a>
</div>
</div>
`;
});
document.getElementById('cardsContainer').innerHTML = html;
}
function applyFilters() {
const brand = document.getElementById('brandFilter').value;
const minSavings = parseFloat(document.getElementById('minSavings').value) || 0;
const minDollarSavings = parseFloat(document.getElementById('minDollarSavings').value) || 0;
const maxPrice = parseFloat(document.getElementById('maxPrice').value) || Infinity;
currentSort = document.getElementById('sortBy').value;
filteredAuctions = allAuctions.filter(auction => {
if (brand && auction.search_term !== brand) return false;
if (auction.savings_percent < minSavings) return false;
if (auction.savings_amount < minDollarSavings) return false;
if (auction.current_price > maxPrice) return false;
return true;
});
// Sort based on current selection
switch(currentSort) {
case 'dollar':
filteredAuctions.sort((a, b) => b.savings_amount - a.savings_amount);
break;
case 'price_low':
filteredAuctions.sort((a, b) => a.current_price - b.current_price);
break;
case 'price_high':
filteredAuctions.sort((a, b) => b.current_price - a.current_price);
break;
default: // percent
filteredAuctions.sort((a, b) => b.savings_percent - a.savings_percent);
}
updateStats(filteredAuctions);
renderCards(filteredAuctions);
showToast(`${filteredAuctions.length} deals found`, 'info', 2000);
}
function resetFilters() {
document.getElementById('sortBy').value = 'percent';
document.getElementById('brandFilter').value = '';
document.getElementById('minSavings').value = '';
document.getElementById('minDollarSavings').value = '';
document.getElementById('maxPrice').value = '';
document.getElementById('searchInput').value = '';
currentSort = 'percent';
filteredAuctions = [...allAuctions];
filteredAuctions.sort((a, b) => b.savings_percent - a.savings_percent);
updateStats(filteredAuctions);
renderCards(filteredAuctions);
showToast('Filters reset', 'info');
}
function scrollToTop() {
window.scrollTo({ top: 0, behavior: 'smooth' });
}
// Initialize
loadBestValues();
// Auto-refresh every 60 seconds
setInterval(loadBestValues, 60000);
</script>
</body>
</html>