← back to Handbag Authentication
public/js/product.js
799 lines
// Product Detail Page Intelligence
const brandMultipliers = {
'Hermes': 2.5, 'Hermès': 2.5, 'エルメス': 2.5,
'Chanel': 2.2, 'シャネル': 2.2,
'Louis Vuitton': 1.9, 'ルイヴィトン': 1.9,
'Gucci': 1.7, 'グッチ': 1.7,
'Dior': 1.8, 'ディオール': 1.8,
'Prada': 1.6, 'プラダ': 1.6,
'Fendi': 1.7, 'フェンディ': 1.7,
'Balenciaga': 1.6, 'バレンシアガ': 1.6,
'Celine': 1.8, 'セリーヌ': 1.8,
'Bottega Veneta': 1.7, 'ボッテガヴェネタ': 1.7
};
// Brand Official Websites
const brandRetailers = {
'Hermes': {
name: 'Hermès Official',
url: 'https://www.hermes.com/us/en/category/women/bags-and-small-leather-goods/',
searchUrl: (model) => `https://www.hermes.com/us/en/search/?q=${encodeURIComponent(model)}`,
logo: 'https://logo.clearbit.com/hermes.com'
},
'Chanel': {
name: 'Chanel Official',
url: 'https://www.chanel.com/us/fashion/handbags/c/1x1x1/',
searchUrl: (model) => `https://www.chanel.com/us/search#q=${encodeURIComponent(model)}`,
logo: 'https://logo.clearbit.com/chanel.com'
},
'Louis Vuitton': {
name: 'Louis Vuitton Official',
url: 'https://us.louisvuitton.com/eng-us/women/handbags/_/N-tfrs5hy',
searchUrl: (model) => `https://us.louisvuitton.com/eng-us/search/${encodeURIComponent(model)}`,
logo: 'https://logo.clearbit.com/louisvuitton.com'
},
'Gucci': {
name: 'Gucci Official',
url: 'https://www.gucci.com/us/en/ca/women/handbags-for-women-c-women-handbags',
searchUrl: (model) => `https://www.gucci.com/us/en/st/search?query=${encodeURIComponent(model)}`,
logo: 'https://logo.clearbit.com/gucci.com'
},
'Prada': {
name: 'Prada Official',
url: 'https://www.prada.com/us/en/women/bags.html',
searchUrl: (model) => `https://www.prada.com/us/en/search.html?q=${encodeURIComponent(model)}`,
logo: 'https://logo.clearbit.com/prada.com'
},
'Dior': {
name: 'Dior Official',
url: 'https://www.dior.com/en_us/fashion/womens-fashion/bags',
searchUrl: (model) => `https://www.dior.com/en_us/search?q=${encodeURIComponent(model)}`,
logo: 'https://logo.clearbit.com/dior.com'
},
'Fendi': {
name: 'Fendi Official',
url: 'https://www.fendi.com/us-en/woman/bags',
searchUrl: (model) => `https://www.fendi.com/us-en/search?q=${encodeURIComponent(model)}`,
logo: 'https://logo.clearbit.com/fendi.com'
}
};
// Price History Data Sources
const priceHistorySources = {
stockx: {
name: 'StockX',
apiUrl: (sku) => `https://stockx.com/api/products/${sku}/activity`,
note: 'Limited luxury bag data'
},
rebag: {
name: 'Rebag Clair AI',
url: 'https://clair.rebag.com/',
note: 'Best for Hermès, Chanel, LV'
},
fashionphile: {
name: 'Fashionphile',
searchUrl: (brand, model) => `https://www.fashionphile.com/search?query=${encodeURIComponent(brand + ' ' + model)}&sold=true`,
note: 'Extensive sold archive'
},
'1stdibs': {
name: '1stDibs',
searchUrl: (brand, model) => `https://www.1stdibs.com/search/?q=${encodeURIComponent(brand + ' ' + model)}`,
note: 'High-end vintage data'
}
};
// Initialize page
document.addEventListener('DOMContentLoaded', () => {
initializeTabs();
calculateArbitrage();
extractSKUDetails();
loadRetailerLinks();
loadPriceHistory();
loadSoldComps();
loadPlatformComparison();
});
// Tab Navigation
function initializeTabs() {
const tabs = document.querySelectorAll('.tab');
const tabContents = document.querySelectorAll('.tab-content');
tabs.forEach(tab => {
tab.addEventListener('click', () => {
const target = tab.dataset.tab;
tabs.forEach(t => t.classList.remove('active'));
tabContents.forEach(tc => tc.classList.remove('active'));
tab.classList.add('active');
document.getElementById(target).classList.add('active');
});
});
}
// Calculate Complete Arbitrage Breakdown
function calculateArbitrage() {
const brand = identifyBrand(productData.brand || productData.title);
const multiplier = brandMultipliers[brand] || 1.5;
const japanPrice = productData.price_usd;
const estimatedUSPrice = Math.round(japanPrice * multiplier);
// Cost breakdown
const costs = {
'Purchase Price (Japan)': japanPrice,
'International Shipping (Buyee/FromJapan)': 50,
'Authentication Service': 50,
'Import Duties & Taxes (Est.)': Math.round(japanPrice * 0.05),
'Payment Processing (3%)': Math.round(japanPrice * 0.03)
};
const totalCosts = Object.values(costs).reduce((a, b) => a + b, 0);
// Display quick stats
document.getElementById('est-us-price').textContent = `$${estimatedUSPrice.toLocaleString()}`;
const grossProfit = estimatedUSPrice - totalCosts;
const profitMargin = ((grossProfit / totalCosts) * 100).toFixed(1);
document.getElementById('profit-amount').textContent = `$${grossProfit.toLocaleString()}`;
document.getElementById('profit-margin').textContent = `${profitMargin}% margin`;
// Build cost breakdown table
const tbody = document.getElementById('cost-breakdown-body');
tbody.innerHTML = '';
Object.entries(costs).forEach(([item, amount]) => {
tbody.innerHTML += `
<tr>
<td>${item}</td>
<td>$${amount.toLocaleString()}</td>
<td>${getItemNote(item)}</td>
</tr>
`;
});
tbody.innerHTML += `
<tr class="total-row">
<td>Total Investment</td>
<td>$${totalCosts.toLocaleString()}</td>
<td>All costs included</td>
</tr>
<tr class="total-row" style="background: #d4edda; color: #155724;">
<td>Estimated Sale Price</td>
<td>$${estimatedUSPrice.toLocaleString()}</td>
<td>${multiplier}x multiplier for ${brand}</td>
</tr>
<tr class="total-row" style="background: #c3e6cb; color: #155724; font-size: 1.2rem;">
<td><strong>💰 Net Profit</strong></td>
<td><strong>$${grossProfit.toLocaleString()}</strong></td>
<td><strong>${profitMargin}% ROI</strong></td>
</tr>
`;
// Platform scenarios
buildPlatformScenarios(totalCosts, estimatedUSPrice);
// Negotiation strategy
buildNegotiationStrategy();
}
function getItemNote(item) {
const notes = {
'Purchase Price (Japan)': 'Current auction/listing price',
'International Shipping (Buyee/FromJapan)': 'Typical EMS/DHL to USA',
'Authentication Service': 'Entrupy, Real Authentication, or similar',
'Import Duties & Taxes (Est.)': '~5% for handbags under $2500',
'Payment Processing (3%)': 'PayPal, Credit Card fees'
};
return notes[item] || '';
}
function buildPlatformScenarios(totalCosts, basePrice) {
const brand = identifyBrand(productData.brand || productData.title);
const model = extractModel(productData.title);
const platforms = [
{
name: 'Fashionphile',
commission: 0.30,
speed: 'Fast (24-48h)',
salePrice: basePrice * 0.95,
searchUrl: `https://www.fashionphile.com/search?query=${encodeURIComponent(brand + ' ' + model)}&sold=true`
},
{
name: 'Rebag',
commission: 0.25,
speed: 'Instant Quote',
salePrice: basePrice * 0.70,
searchUrl: `https://clair.rebag.com/`
},
{
name: 'TheRealReal',
commission: 0.35,
speed: 'Medium (1-2 wks)',
salePrice: basePrice * 0.90,
searchUrl: `https://www.therealreal.com/search?q=${encodeURIComponent(brand + ' ' + model)}`
},
{
name: 'Vestiaire Collective',
commission: 0.18,
speed: 'Slow (2-4 wks)',
salePrice: basePrice * 1.00,
searchUrl: `https://www.vestiairecollective.com/search/?q=${encodeURIComponent(brand + ' ' + model)}`
},
{
name: 'eBay Authenticated',
commission: 0.125,
speed: 'Fast (3-7 days)',
salePrice: basePrice * 0.85,
searchUrl: `https://www.ebay.com/sch/i.html?_nkw=${encodeURIComponent(brand + ' ' + model)}&LH_Sold=1&LH_Complete=1`
},
{
name: 'Poshmark',
commission: 0.20,
speed: 'Variable',
salePrice: basePrice * 0.80,
searchUrl: `https://poshmark.com/search?query=${encodeURIComponent(brand + ' ' + model)}&department=Women&availability=sold_out`
}
];
const container = document.getElementById('platform-scenarios');
container.innerHTML = '<table class="breakdown-table"><thead><tr><th>Platform</th><th>Est. Sale Price</th><th>Commission</th><th>Your Profit</th><th>ROI</th><th>Speed</th><th>Sold Comps</th></tr></thead><tbody>';
platforms.forEach(p => {
const afterCommission = p.salePrice * (1 - p.commission);
const profit = afterCommission - totalCosts;
const roi = ((profit / totalCosts) * 100).toFixed(1);
const profitClass = profit > 0 ? 'profit-positive' : 'profit-negative';
// Show loading state initially, will be replaced with real data
const compsHtml = `<div class="comps-loading" data-platform="${p.name}">Loading real sold data...</div>`;
container.innerHTML += `
<tr>
<td><strong>${p.name}</strong></td>
<td>$${Math.round(p.salePrice).toLocaleString()}</td>
<td>${(p.commission * 100)}%</td>
<td class="${profitClass}"><strong>$${Math.round(profit).toLocaleString()}</strong></td>
<td class="${profitClass}"><strong>${roi}%</strong></td>
<td>${p.speed}</td>
<td class="comps-cell">
${compsHtml}
<a href="${p.searchUrl}" target="_blank" rel="noopener noreferrer" class="btn-view-comps">View All Sold →</a>
</td>
</tr>
`;
});
container.innerHTML += '</tbody></table>';
// Fetch REAL sold data from API
fetchRealSoldData(brand, model);
}
// Fetch real sold comps from eBay USA, eBay Japan, Fashionphile, Poshmark
async function fetchRealSoldData(brand, model) {
try {
console.log(`Fetching real sold data for: ${brand} ${model}`);
const response = await fetch(`/api/sold-comps/${encodeURIComponent(brand)}/${encodeURIComponent(model)}`);
const data = await response.json();
if (!data.success || !data.soldItems) {
console.error('No sold data available');
return;
}
console.log(`✓ Received ${data.soldItems.length} sold comps`);
// Group by source
const bySource = {
'eBay USA': data.soldItems.filter(i => i.source === 'ebay_usa').slice(0, 3),
'eBay Japan': data.soldItems.filter(i => i.source === 'ebay_japan').slice(0, 3),
'Fashionphile': data.soldItems.filter(i => i.source === 'fashionphile').slice(0, 3),
'Poshmark': data.soldItems.filter(i => i.source === 'poshmark').slice(0, 3)
};
// Update each platform's comps cell
document.querySelectorAll('.comps-loading').forEach(loadingEl => {
const platform = loadingEl.dataset.platform;
const cell = loadingEl.parentElement;
// Get relevant sold items for this platform
let relevantComps = [];
if (platform === 'eBay Authenticated') {
relevantComps = [...bySource['eBay USA'], ...bySource['eBay Japan']].slice(0, 3);
} else if (platform === 'Fashionphile') {
relevantComps = bySource['Fashionphile'];
} else if (platform === 'Poshmark') {
relevantComps = bySource['Poshmark'];
} else {
// Use eBay data for others
relevantComps = bySource['eBay USA'].slice(0, 3);
}
if (relevantComps.length === 0) {
cell.innerHTML = '<div class="no-comps">No recent sold data</div>';
return;
}
// Build comps HTML
let compsHtml = relevantComps.map(comp => {
const priceUSD = comp.currency === 'JPY' ? Math.round(comp.price / 150) : comp.price;
const source = comp.source.replace('_', ' ').toUpperCase();
return `<div class="comp-item-mini">
<span class="comp-price">$${priceUSD.toLocaleString()}</span>
<span class="comp-source">${source}</span>
</div>`;
}).join('');
// Add search link
const searchUrl = getSearchUrl(platform, brand, model);
compsHtml += `<a href="${searchUrl}" target="_blank" rel="noopener noreferrer" class="btn-view-comps">View All Sold →</a>`;
cell.innerHTML = compsHtml;
});
} catch (error) {
console.error('Error fetching sold data:', error);
}
}
function getSearchUrl(platform, brand, model) {
const query = `${brand} ${model}`;
switch (platform) {
case 'Fashionphile':
return `https://www.fashionphile.com/search?query=${encodeURIComponent(query)}&sold=true`;
case 'eBay Authenticated':
return `https://www.ebay.com/sch/i.html?_nkw=${encodeURIComponent(query + ' bag')}&LH_Sold=1&LH_Complete=1`;
case 'Poshmark':
return `https://poshmark.com/search?query=${encodeURIComponent(query)}&department=Women&availability=sold_out`;
case 'TheRealReal':
return `https://www.therealreal.com/search?q=${encodeURIComponent(query)}`;
default:
return `https://www.ebay.com/sch/i.html?_nkw=${encodeURIComponent(query)}&LH_Sold=1`;
}
}
function buildNegotiationStrategy() {
const container = document.getElementById('negotiation-tips');
// Calculate listing age
const listedDate = new Date(productData.crawled_at || productData.created_at || Date.now());
const now = new Date();
const daysOld = Math.floor((now - listedDate) / (1000 * 60 * 60 * 24));
let strategy = '';
let discount = 0;
if (daysOld <= 7) {
strategy = '📅 <strong>Fresh Listing (< 1 week)</strong><br>Offer 5-10% below asking. Seller likely testing market.';
discount = 10;
} else if (daysOld <= 30) {
strategy = `📅 <strong>Listed ${daysOld} days ago</strong><br>Offer 10-15% below. Seller showing flexibility signals.`;
discount = 15;
} else if (daysOld <= 60) {
strategy = `📅 <strong>Stale listing (${daysOld} days)</strong><br>Strong negotiation position! Offer 20-25% below asking.`;
discount = 25;
} else {
strategy = `📅 <strong>Very old listing (${daysOld} days!)</strong><br>🎯 Seller very motivated. Try 30-40% discount or best offer!`;
discount = 35;
}
const currentPrice = productData.price_usd;
const offerPrice = Math.round(currentPrice * (1 - discount / 100));
container.innerHTML = `
<div style="background: linear-gradient(135deg, #fff3e0 0%, #ffe0b2 100%); padding: 1.5rem; border-radius: 10px; border-left: 4px solid #f57c00;">
<p style="margin: 0 0 1rem 0;">${strategy}</p>
<div style="background: white; padding: 1rem; border-radius: 8px; margin-top: 1rem;">
<p style="margin: 0;"><strong>Recommended Offer:</strong></p>
<p style="font-size: 1.5rem; color: #f57c00; margin: 0.5rem 0;"><strong>$${offerPrice.toLocaleString()}</strong> (${discount}% discount)</p>
<p style="margin: 0; font-size: 0.9rem; color: #666;">Original: $${currentPrice.toLocaleString()}</p>
</div>
<p style="margin: 1rem 0 0 0; font-size: 0.9rem;">💡 <em>Tip: Use auction ending time pressure or "similar item" comparisons to strengthen your position.</em></p>
</div>
`;
}
// Extract and Display SKU Details
function extractSKUDetails() {
const title = productData.title || '';
const brand = identifyBrand(productData.brand || title);
document.getElementById('sku-brand').textContent = brand || 'Unknown';
document.getElementById('sku-model').textContent = productData.model || extractModel(title);
document.getElementById('sku-size').textContent = productData.size || extractSize(title);
document.getElementById('sku-color').textContent = productData.color || extractColor(title);
document.getElementById('sku-material').textContent = productData.material || extractMaterial(title);
document.getElementById('sku-condition').textContent = productData.condition || 'Used';
document.getElementById('sku-external-id').textContent = productData.external_id || 'N/A';
// Authentication markers
const authPoints = getAuthenticationMarkers(brand);
const authList = document.getElementById('auth-points');
authList.innerHTML = authPoints.map(point => `<li>${point}</li>`).join('');
}
function identifyBrand(text) {
if (!text) return 'Unknown';
const brandMap = {
'エルメス': 'Hermes',
'シャネル': 'Chanel',
'ルイヴィトン': 'Louis Vuitton',
'グッチ': 'Gucci',
'プラダ': 'Prada',
'ディオール': 'Dior',
'フェンディ': 'Fendi',
'バレンシアガ': 'Balenciaga',
'セリーヌ': 'Celine',
'ボッテガヴェネタ': 'Bottega Veneta'
};
for (const [jp, en] of Object.entries(brandMap)) {
if (text.includes(jp) || text.toLowerCase().includes(en.toLowerCase())) {
return en;
}
}
return 'Unknown';
}
function extractModel(title) {
const models = {
'Birkin': /バーキン|Birkin/i,
'Kelly': /ケリー|Kelly/i,
'Constance': /コンスタンス|Constance/i,
'Neverfull': /ネヴァーフル|Neverfull/i,
'Speedy': /スピーディ|Speedy/i,
'Classic Flap': /マトラッセ|Classic Flap|2\.55/i,
'Boy': /ボーイ|Boy Chanel/i,
'Peekaboo': /ピーカブー|Peekaboo/i,
'Baguette': /バゲット|Baguette/i
};
for (const [model, regex] of Object.entries(models)) {
if (regex.test(title)) return model;
}
return 'See title';
}
function extractSize(title) {
const sizeMatch = title.match(/(\d+)\s*cm|PM|MM|GM|Mini|Small|Medium|Large/i);
return sizeMatch ? sizeMatch[0] : 'See details';
}
function extractColor(title) {
const colors = {
'Black': /ブラック|Black|Noir/i,
'White': /ホワイト|White|Blanc/i,
'Red': /レッド|Red|Rouge/i,
'Blue': /ブルー|Blue|Bleu/i,
'Brown': /ブラウン|Brown|Marron/i,
'Beige': /ベージュ|Beige/i,
'Gold': /ゴールド|Gold/i,
'Silver': /シルバー|Silver/i
};
for (const [color, regex] of Object.entries(colors)) {
if (regex.test(title)) return color;
}
return 'See photos';
}
function extractMaterial(title) {
const materials = {
'Togo': /トゴ|Togo/i,
'Epsom': /エプソン|Epsom/i,
'Clemence': /クレマンス|Clemence/i,
'Lambskin': /ラムスキン|Lambskin/i,
'Caviar': /キャビア|Caviar/i,
'Monogram': /モノグラム|Monogram/i,
'Damier': /ダミエ|Damier/i,
'Epi': /エピ|Epi/i,
'Saffiano': /サフィアーノ|Saffiano/i
};
for (const [material, regex] of Object.entries(materials)) {
if (regex.test(title)) return material;
}
return 'See details';
}
function getAuthenticationMarkers(brand) {
const markers = {
'Hermes': [
'Check date stamp and craftsman code (e.g., "A" in square = 2017)',
'Verify saddle stitching quality and symmetry',
'Inspect hardware for "Hermès Paris" engraving',
'Check leather grain consistency (Togo/Clemence/Epsom patterns)',
'Verify lock and key numbers match'
],
'Chanel': [
'Authenticate serial number sticker inside (8-digit)',
'Check CC logo symmetry and stitching (11 stitches per quilted diamond)',
'Verify chain weight and interlaced leather',
'Inspect interior logo stamping clarity',
'Check authenticity card font and hologram'
],
'Louis Vuitton': [
'Verify date code format (2 letters + 4 numbers)',
'Check canvas print alignment and symmetry',
'Inspect vachetta leather oxidation (natural darkening)',
'Verify heat stamp clarity and font',
'Check hardware engraving depth'
],
'default': [
'Verify serial number or date code',
'Check stitching quality and consistency',
'Inspect hardware weight and engravings',
'Verify material authenticity',
'Request authentication certificate'
]
};
return markers[brand] || markers['default'];
}
// Load Brand Retailer Links
function loadRetailerLinks() {
const brand = identifyBrand(productData.brand || productData.title);
const model = extractModel(productData.title);
const container = document.getElementById('retailer-links');
if (!brandRetailers[brand]) {
container.innerHTML = '<p>No official retailer links available for this brand.</p>';
return;
}
const retailer = brandRetailers[brand];
container.innerHTML = `
<div class="retailer-card">
<img src="${retailer.logo}" alt="${retailer.name}" onerror="this.src='data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 width=%2260%22 height=%2260%22><rect width=%2260%22 height=%2260%22 fill=%22%23667eea%22/><text x=%2250%%22 y=%2250%%22 fill=%22white%22 font-size=%2224%22 text-anchor=%22middle%22 dy=%22.3em%22>🏬</text></svg>'">
<h4>${retailer.name}</h4>
<p>Compare to NEW retail version</p>
<a href="${retailer.searchUrl(model)}" target="_blank" rel="noopener noreferrer">Search for ${model}</a>
<a href="${retailer.url}" target="_blank" rel="noopener noreferrer" style="margin-top: 0.5rem; background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);">Browse All Bags</a>
</div>
`;
// Add secondary retailers
const secondaryRetailers = [
{ name: 'Nordstrom', url: `https://www.nordstrom.com/sr?keyword=${encodeURIComponent(brand + ' ' + model)}`, logo: 'https://logo.clearbit.com/nordstrom.com' },
{ name: 'Neiman Marcus', url: `https://www.neimanmarcus.com/search.jsp?q=${encodeURIComponent(brand + ' ' + model)}`, logo: 'https://logo.clearbit.com/neimanmarcus.com' },
{ name: 'Saks Fifth Avenue', url: `https://www.saksfifthavenue.com/search?q=${encodeURIComponent(brand + ' ' + model)}`, logo: 'https://logo.clearbit.com/saksfifthavenue.com' }
];
secondaryRetailers.forEach(r => {
container.innerHTML += `
<div class="retailer-card">
<img src="${r.logo}" alt="${r.name}" onerror="this.src='data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 width=%2260%22 height=%2260%22><rect width=%2260%22 height=%2260%22 fill=%22%23c3cfe2%22/><text x=%2250%%22 y=%2250%%22 fill=%22%23667eea%22 font-size=%2224%22 text-anchor=%22middle%22 dy=%22.3em%22>🛍️</text></svg>'">
<h4>${r.name}</h4>
<p>Authorized retailer</p>
<a href="${r.url}" target="_blank" rel="noopener noreferrer">Search ${r.name}</a>
</div>
`;
});
// Create value comparison chart
createValueComparisonChart(brand);
}
function createValueComparisonChart(brand) {
const ctx = document.getElementById('valueComparisonChart');
const multiplier = brandMultipliers[brand] || 1.5;
const usedPrice = productData.price_usd;
const newPrice = usedPrice * multiplier;
new Chart(ctx, {
type: 'bar',
data: {
labels: ['This Used Item', 'NEW Retail (Est.)', 'Your Savings'],
datasets: [{
label: 'Price (USD)',
data: [usedPrice, newPrice, newPrice - usedPrice],
backgroundColor: [
'rgba(102, 126, 234, 0.8)',
'rgba(231, 76, 60, 0.8)',
'rgba(46, 213, 115, 0.8)'
],
borderColor: [
'rgba(102, 126, 234, 1)',
'rgba(231, 76, 60, 1)',
'rgba(46, 213, 115, 1)'
],
borderWidth: 2
}]
},
options: {
responsive: true,
plugins: {
legend: { display: false },
title: {
display: true,
text: `Buying Used Saves You $${(newPrice - usedPrice).toLocaleString()} (${(((newPrice - usedPrice) / newPrice) * 100).toFixed(0)}%)`
}
},
scales: {
y: {
beginAtZero: true,
ticks: {
callback: value => '$' + value.toLocaleString()
}
}
}
}
});
}
// Load Price History (Mock data - integrate with real APIs)
function loadPriceHistory() {
const brand = identifyBrand(productData.brand || productData.title);
const model = extractModel(productData.title);
// Mock price history data
const mockData = generateMockPriceHistory(productData.price_usd);
const ctx = document.getElementById('priceHistoryChart');
new Chart(ctx, {
type: 'line',
data: {
labels: mockData.dates,
datasets: [{
label: 'Average Sale Price',
data: mockData.prices,
borderColor: 'rgba(102, 126, 234, 1)',
backgroundColor: 'rgba(102, 126, 234, 0.1)',
tension: 0.4,
fill: true
}]
},
options: {
responsive: true,
plugins: {
legend: { display: true },
title: {
display: true,
text: `${brand} ${model} - 6 Month Price Trend`
}
},
scales: {
y: {
beginAtZero: false,
ticks: {
callback: value => '$' + value.toLocaleString()
}
}
}
}
});
// Update stats
document.getElementById('avg-sale-price').textContent = '$' + Math.round(mockData.avg).toLocaleString();
document.getElementById('high-sale-price').textContent = '$' + Math.round(mockData.high).toLocaleString();
document.getElementById('low-sale-price').textContent = '$' + Math.round(mockData.low).toLocaleString();
document.getElementById('sales-volume').textContent = mockData.volume;
}
function generateMockPriceHistory(basePrice) {
const dates = [];
const prices = [];
const now = new Date();
for (let i = 180; i >= 0; i -= 30) {
const date = new Date(now);
date.setDate(date.getDate() - i);
dates.push(date.toLocaleDateString('en-US', { month: 'short', year: 'numeric' }));
const variance = (Math.random() - 0.5) * basePrice * 0.2;
prices.push(Math.round(basePrice + variance));
}
return {
dates,
prices,
avg: prices.reduce((a, b) => a + b, 0) / prices.length,
high: Math.max(...prices),
low: Math.min(...prices),
volume: Math.floor(Math.random() * 20) + 5
};
}
// Load Sold Comps
function loadSoldComps() {
const brand = identifyBrand(productData.brand || productData.title);
const model = extractModel(productData.title);
const container = document.getElementById('comps-grid');
// Mock sold comps - integrate with eBay, Fashionphile sold APIs
const mockComps = generateMockComps(brand, model, productData.price_usd);
container.innerHTML = mockComps.map(comp => `
<div class="comp-card">
<img src="${comp.image}" alt="${comp.title}">
<div class="comp-card-body">
<h5>${comp.title}</h5>
<p class="price">$${comp.price.toLocaleString()}</p>
<p class="meta">Sold ${comp.daysAgo} days ago on ${comp.platform}</p>
<p class="meta">${comp.condition}</p>
</div>
</div>
`).join('');
}
function generateMockComps(brand, model, basePrice) {
const platforms = ['eBay', 'Fashionphile', 'Rebag', 'TheRealReal', 'Vestiaire'];
const conditions = ['Excellent', 'Good', 'Fair', 'New/Unused'];
return Array.from({ length: 8 }, (_, i) => ({
title: `${brand} ${model}`,
price: Math.round(basePrice * (0.8 + Math.random() * 0.4)),
image: productData.image_url || 'https://via.placeholder.com/250x200',
platform: platforms[Math.floor(Math.random() * platforms.length)],
condition: conditions[Math.floor(Math.random() * conditions.length)],
daysAgo: Math.floor(Math.random() * 60) + 1
}));
}
// Load Platform Comparison
function loadPlatformComparison() {
const tbody = document.getElementById('platform-comparison-body');
const brand = identifyBrand(productData.brand || productData.title);
const multiplier = brandMultipliers[brand] || 1.5;
const basePrice = productData.price_usd * multiplier;
const totalCosts = productData.price_usd + 50 + 50 + Math.round(productData.price_usd * 0.08);
const platforms = [
{ name: 'Fashionphile', commission: 30, speed: '24-48h', salePrice: basePrice * 0.95, url: 'https://www.fashionphile.com/sell-to-us' },
{ name: 'Rebag', commission: 25, speed: 'Instant', salePrice: basePrice * 0.70, url: 'https://www.rebag.com/sell' },
{ name: 'TheRealReal', commission: 35, speed: '1-2 weeks', salePrice: basePrice * 0.90, url: 'https://www.therealreal.com/consign' },
{ name: 'Vestiaire Collective', commission: 18, speed: '2-4 weeks', salePrice: basePrice * 1.00, url: 'https://www.vestiairecollective.com/sell/' },
{ name: 'eBay Authenticated', commission: 12.5, speed: '3-7 days', salePrice: basePrice * 0.85, url: 'https://www.ebay.com/sl/sell' },
{ name: 'Poshmark', commission: 20, speed: 'Variable', salePrice: basePrice * 0.80, url: 'https://poshmark.com/sell' }
];
tbody.innerHTML = platforms.map(p => {
const netPrice = p.salePrice * (1 - p.commission / 100);
const profit = netPrice - totalCosts;
const profitClass = profit > 0 ? 'profit-positive' : 'profit-negative';
return `
<tr>
<td class="platform-name">${p.name}</td>
<td>${p.commission}%</td>
<td>${p.speed}</td>
<td>$${Math.round(p.salePrice).toLocaleString()}</td>
<td class="${profitClass}">$${Math.round(profit).toLocaleString()}</td>
<td>
<a href="${p.url}" target="_blank" rel="noopener noreferrer" class="btn-primary" style="padding: 0.5rem 1rem; font-size: 0.9rem;">List Now</a>
</td>
</tr>
`;
}).join('');
}
// Utility Functions
function zoomImage() {
window.open(productData.image_url, '_blank');
}
function reverseImageSearch() {
const imageUrl = encodeURIComponent(productData.image_url);
window.open(`https://www.google.com/searchbyimage?image_url=${imageUrl}`, '_blank');
}
function presellItem() {
alert('Presell strategy modal coming soon! This will guide you through listing the item before purchasing.');
}
function compareAllPlatforms() {
document.querySelector('[data-tab="platforms"]').click();
}