diff --git a/api.js b/api.js
index 9eaccc1..9b0c5a6 100644
--- a/api.js
+++ b/api.js
@@ -12,7 +12,7 @@ import { searchMagi } from "./lib/sources/magi.js";
import { searchYahooAuctions } from "./lib/sources/yahooauctions.js";
import { getPsaGradingSignal } from "./lib/grading/psa.js";
import { gradeImage } from "./lib/grading/grading.js";
-import { parseListingLanguagesFromInput, filterByCondition, detectCondition } from "./lib/search/filters.js";
+import { parseListingLanguagesFromInput, filterByCondition, detectCondition, flagPriceOutliers } from "./lib/search/filters.js";
import { buildEbaySearchQuery } from "./lib/search/listingQuery.js";
import { EBAY_CATEGORY_TCG_SINGLE_CARDS_US } from "./lib/search/ebayCategories.js";
import { getRedisStatus, sha256 } from "./lib/data/redis-cache.js";
@@ -224,9 +224,9 @@ app.get("/api/search", apiAuthMiddleware, (req, res, next) => { req._errorType =
if (wantDemo) {
const demoResult = getDemoSearchResult(q, { source: req.query.source, condition: req.query.condition });
for (const country of Object.keys(demoResult.activeByCountry || {})) {
- demoResult.activeByCountry[country] = demoResult.activeByCountry[country].map(item => ({
+ demoResult.activeByCountry[country] = flagPriceOutliers(demoResult.activeByCountry[country].map(item => ({
...item, detectedCondition: item.detectedCondition || detectCondition(item),
- }));
+ })));
}
if (demoResult.sold?.length) recordSoldPrices(q, demoResult.sold, demoResult.source).catch(() => {});
return res.json(demoResult);
@@ -278,12 +278,12 @@ app.get("/api/search", apiAuthMiddleware, (req, res, next) => { req._errorType =
}
}
- // Add detected condition to each listing
+ // Add detected condition + flag outliers
for (const country of Object.keys(result.activeByCountry || {})) {
- result.activeByCountry[country] = result.activeByCountry[country].map(item => ({
+ result.activeByCountry[country] = flagPriceOutliers(result.activeByCountry[country].map(item => ({
...item,
detectedCondition: item.detectedCondition || detectCondition(item),
- }));
+ })));
}
// Filter by condition if requested
diff --git a/lib/data/demo.js b/lib/data/demo.js
index 81f302f..f54cb27 100644
--- a/lib/data/demo.js
+++ b/lib/data/demo.js
@@ -110,7 +110,7 @@ const DEMO_CARDS = {
{
itemId: "v1|178048869261|0", itemWebUrl: "https://www.ebay.com/itm/178048869261",
title: "Pokemon Indonesia 2025 Umbreon Ex sv8a 217/187 SAR Special Art Rare",
- price: 379.90, priceCurrency: "USD", shippingLabel: "$19.00", totalCost: 398.90, condition: "Ungraded",
+ price: 379.90, priceCurrency: "USD", shippingLabel: "$19.00", totalCost: 398.90, condition: "Ungraded", detectedCondition: "LP",
imageUrl: "https://i.ebayimg.com/images/g/dvIAAeSwCB9p3n5z/s-l500.jpg",
additionalImages: [{ imageUrl: "https://i.ebayimg.com/images/g/WUIAAeSwmlFp3n58/s-l500.jpg" }],
grade: { overall: 7.5, centering: 7.5, corners: 9.0, edges: 9.0, surface: 9.0, confidence: 0.70, mode: "llm-detailed", notes: "Grade limiter: centering — Back centering off, bottom border wider than top ~63/37.", limitations: "", subgradeDetails: { centering: { score: 7.5, confidence: 0.75, detail: "Front ~54/46 acceptable. Back noticeably off — bottom wider than top ~63/37." }, corners: { score: 9.0, confidence: 0.65, detail: "Corners appear clean. No close-up available — reduced confidence." }, edges: { score: 9.0, confidence: 0.62, detail: "Edges look clean from full shots. No close-up detail available." }, surface: { score: 9.0, confidence: 0.78, detail: "Front surface clean, no scratches or print defects visible." } } },
@@ -118,7 +118,7 @@ const DEMO_CARDS = {
{
itemId: "v1|397899646795|0", itemWebUrl: "https://www.ebay.com/itm/397899646795",
title: "Umbreon ex SAR 217/187 SV8a TERASTAL FEST EX Japanese Pokemon TCG",
- price: 400, priceCurrency: "USD", shippingLabel: "Free", totalCost: 400, condition: "Ungraded",
+ price: 400, priceCurrency: "USD", shippingLabel: "Free", totalCost: 400, condition: "Ungraded", detectedCondition: "NM",
imageUrl: "https://i.ebayimg.com/images/g/XYkAAeSw8fBp9JS-/s-l500.jpg",
additionalImages: [
{ imageUrl: "https://i.ebayimg.com/images/g/PcwAAeSw5zhp9JS~/s-l500.jpg" },
@@ -130,7 +130,7 @@ const DEMO_CARDS = {
{
itemId: "v1|177832326093|0", itemWebUrl: "https://www.ebay.com/itm/177832326093",
title: "Umbreon ex SAR 217/187 Terastal Festival sv8a 2024 Pokemon Card Japanese NM",
- price: 400, priceCurrency: "USD", shippingLabel: "Free", totalCost: 400, condition: "Ungraded",
+ price: 400, priceCurrency: "USD", shippingLabel: "Free", totalCost: 400, condition: "Ungraded", detectedCondition: "NM",
imageUrl: "https://i.ebayimg.com/images/g/FTcAAeSwiLRpgfeC/s-l500.jpg",
additionalImages: [{ imageUrl: "https://i.ebayimg.com/images/g/7NIAAeSw8s9pgfeE/s-l500.jpg" }],
grade: { overall: 8.5, centering: 9.0, corners: 9.0, edges: 9.5, surface: 8.5, confidence: 0.74, mode: "llm-detailed", notes: "Grade limiter: surface — Faint holo texture disruption, likely print variation.", limitations: "", subgradeDetails: { centering: { score: 9.0, confidence: 0.76, detail: "Centering solid ~53/47 front. Back appears centered from single photo." }, corners: { score: 9.0, confidence: 0.70, detail: "Corners look clean. Single back photo limits full assessment." }, edges: { score: 9.5, confidence: 0.72, detail: "Edges clean with no whitening visible from available angles." }, surface: { score: 8.5, confidence: 0.78, detail: "Faint holo texture disruption under light. Likely print variation but could cost half a point." } } },
@@ -138,7 +138,7 @@ const DEMO_CARDS = {
{
itemId: "v1|397643034526|0", itemWebUrl: "https://www.ebay.com/itm/397643034526",
title: "With tracking Umbreon ex SAR 217/187 Terastal Festival sv8a 2024 Pokemon Card",
- price: 415.31, priceCurrency: "USD", shippingLabel: "Free", totalCost: 415.31, condition: "Ungraded",
+ price: 415.31, priceCurrency: "USD", shippingLabel: "Free", totalCost: 415.31, condition: "Ungraded", detectedCondition: "NM",
imageUrl: "https://i.ebayimg.com/images/g/8fIAAeSwny1pnSPT/s-l500.jpg",
additionalImages: [
{ imageUrl: "https://i.ebayimg.com/images/g/2iYAAeSwV0hpnSPT/s-l500.jpg" },
@@ -150,7 +150,7 @@ const DEMO_CARDS = {
{
itemId: "v1|397467499018|0", itemWebUrl: "https://www.ebay.com/itm/397467499018",
title: "With tracking Umbreon ex SAR 217/187 Terastal Festival sv8a 2024 Pokemon Card",
- price: 425.65, priceCurrency: "USD", shippingLabel: "Free", totalCost: 425.65, condition: "Ungraded",
+ price: 425.65, priceCurrency: "USD", shippingLabel: "Free", totalCost: 425.65, condition: "Ungraded", detectedCondition: "LP",
imageUrl: "https://i.ebayimg.com/images/g/MK8AAeSwqEVpXH5z/s-l500.jpg",
additionalImages: [
{ imageUrl: "https://i.ebayimg.com/images/g/xrYAAeSwMW1pXH5z/s-l500.jpg" },
diff --git a/public/app.js b/public/app.js
index fa08846..4ba4dcb 100644
--- a/public/app.js
+++ b/public/app.js
@@ -21,6 +21,7 @@ let allItems = [];
let allActive = [];
let allSold = [];
let activeSourceFilter = "all";
+let currentSort = "price-asc";
let currentPsaSignal = null;
document.querySelectorAll(".hint").forEach(h => {
@@ -43,6 +44,21 @@ document.querySelectorAll(".list-tab").forEach(tab => {
});
});
+document.getElementById("sort-select").addEventListener("change", (e) => {
+ currentSort = e.target.value;
+ applySourceFilter();
+});
+
+function sortItems(items) {
+ const sorted = [...items];
+ if (currentSort === "price-desc") {
+ sorted.sort((a, b) => (b.totalCost || b.price) - (a.totalCost || a.price));
+ } else {
+ sorted.sort((a, b) => (a.totalCost || a.price) - (b.totalCost || b.price));
+ }
+ return sorted;
+}
+
form.addEventListener("submit", async (e) => {
e.preventDefault();
const q = input.value.trim();
@@ -117,15 +133,21 @@ function render(data) {
allActive = active;
allSold = sold;
activeSourceFilter = "all";
+ currentSort = "price-asc";
+ document.getElementById("sort-select").value = "price-asc";
const isMulti = data.source === "multi";
const sources = isMulti ? detectSources(active, sold) : [];
renderSourceFilters(sources);
- renderList(activeList, active);
- renderList(soldList, sold);
+ renderList(activeList, sortItems(active));
+ renderList(soldList, sortItems(sold));
+ const activeTab = document.querySelector('.list-tab[data-tab="active"]');
const soldTab = document.querySelector('.list-tab[data-tab="sold"]');
+ activeTab.textContent = `Active (${active.length})`;
+ soldTab.textContent = `Sold (${sold.length})`;
+
if (hasGrades && soldTotal === 0) {
soldTab.classList.add("hidden");
} else {
@@ -187,13 +209,16 @@ function applySourceFilter() {
? () => true
: (item) => itemSource(item.itemWebUrl) === activeSourceFilter;
- const filteredActive = allActive.filter(filterFn);
- const filteredSold = allSold.filter(filterFn);
+ const filteredActive = sortItems(allActive.filter(filterFn));
+ const filteredSold = sortItems(allSold.filter(filterFn));
allItems = [...filteredActive, ...filteredSold];
renderList(activeList, filteredActive);
renderList(soldList, filteredSold);
+ document.querySelector('.list-tab[data-tab="active"]').textContent = `Active (${filteredActive.length})`;
+ document.querySelector('.list-tab[data-tab="sold"]').textContent = `Sold (${filteredSold.length})`;
+
detailPanel.innerHTML = '
Click a listing to inspect
';
if (filteredActive.length) selectItem(filteredActive[0].itemId);
}
@@ -242,7 +267,26 @@ function renderList(container, items) {
const price = formatPrice(item.price, item.priceCurrency);
const imgSrc = item.imageUrl && !item.imageUrl.includes("placeholder") ? item.imageUrl : "";
const imgHtml = imgSrc ? `
` : ``;
- const condition = item.condition ? `${esc(item.condition)}` : "";
+
+ let displayCond = item.condition || "";
+ if (!item.listingGradeLabel && item.detectedCondition) {
+ displayCond = item.detectedCondition;
+ } else if (displayCond === "Ungraded" || displayCond === "ungraded") {
+ displayCond = item.detectedCondition || "";
+ }
+ const useBadge = !item.condition && item.detectedCondition;
+ const conditionHtml = displayCond
+ ? `${esc(displayCond)}`
+ : "";
+
+ const shippingHtml = item.shippingLabel === "Free" || item.shippingLabel === "free"
+ ? 'Free shipping'
+ : item.shippingLabel && item.shippingLabel !== "—"
+ ? `+ ${esc(item.shippingLabel)}`
+ : "";
+
+ const outlierHtml = item._priceOutlier ? 'Price outlier' : "";
+
const gradeChip = item.grade && !item.grade.error
? `${item.grade.overall.toFixed(1)}`
: item.listingGradeLabel
@@ -258,9 +302,11 @@ function renderList(container, items) {
${price}
${gradeChip}
+ ${shippingHtml}
${srcTag}
- ${condition}
+ ${conditionHtml}
+ ${outlierHtml}
`;
@@ -319,10 +365,14 @@ function selectItem(itemId) {
`;
const fields = [];
- const condVal = item.condition
- ? (slabLabel ? `${item.condition} Graded` : item.condition)
- : (slabLabel ? `Graded` : "");
- if (condVal) fields.push({ label: "Condition", value: condVal, raw: true });
+ if (slabLabel) {
+ fields.push({ label: "Condition", value: `Graded`, raw: true });
+ } else {
+ const condVal = (item.condition === "Ungraded" || item.condition === "ungraded")
+ ? (item.detectedCondition || "")
+ : (item.detectedCondition || item.condition || "");
+ if (condVal) fields.push({ label: "Condition", value: condVal });
+ }
if (item.soldDate || item.endedDate) fields.push({ label: "Sold", value: item.soldDate || item.endedDate });
if (item.priceJPY) fields.push({ label: "Price (JPY)", value: `¥${item.priceJPY.toLocaleString()}` });
if (item.totalCost && item.totalCost !== item.price) fields.push({ label: "Item Price", value: formatPrice(item.price, item.priceCurrency) });
@@ -351,8 +401,8 @@ function selectItem(itemId) {
${summaryHtml}
+
${hasGrade ? `
` : ""}
@@ -365,7 +415,7 @@ function selectItem(itemId) {
@@ -412,14 +462,13 @@ async function loadCardIdentity(query) {
).join("");
const setName = card.setName || "";
- container.innerHTML = `
-
- ${esc(card.cardId)}
- ${card.rarity ? `${esc(card.rarity)}` : ""}
- ${setName ? `${esc(setName)}` : ""}
-
- ${nameHtml ? `${nameHtml}
` : ""}
- `;
+ const parts = [
+ `${esc(card.cardId)}`,
+ card.rarity ? `${esc(card.rarity)}` : "",
+ setName ? `${esc(setName)}` : "",
+ nameHtml ? `${nameHtml}` : "",
+ ].filter(Boolean).join("");
+ container.innerHTML = parts;
} catch {}
}
@@ -437,10 +486,19 @@ async function loadArbitrage(query) {
container.classList.remove("hidden");
const arb = data.arbitrage;
+ const sorted = names.sort((a, b) => sources[a].lowest - sources[b].lowest);
+ const savingsHtml = arb ? (() => {
+ const match = arb.summary.match(/\$[\d,.]+/);
+ const pctMatch = arb.summary.match(/(\d+%)\s*spread/);
+ const savings = match ? match[0] : "";
+ const spread = pctMatch ? pctMatch[1] : "";
+ return `${savings} cheaper on ${esc(arb.cheapest.source)}${spread ? `${spread} spread` : ""}
`;
+ })() : "";
+
container.innerHTML = `
Cross-Source Prices
- ${names.sort((a, b) => sources[a].lowest - sources[b].lowest).map(s => {
+ ${sorted.map(s => {
const d = sources[s];
const isCheapest = arb && s === arb.cheapest.source;
return `
@@ -448,10 +506,11 @@ async function loadArbitrage(query) {
${formatPrice(d.lowest, d.currency)}
${d.priceJPY ? `
¥${d.priceJPY.toLocaleString()}
` : ""}
${d.count} listing${d.count !== 1 ? "s" : ""}
+ ${isCheapest ? `
Best Price` : ""}
`;
}).join("")}
- ${arb ? `${esc(arb.summary)}
` : ""}
+ ${savingsHtml}
`;
} catch {}
}
@@ -481,7 +540,7 @@ async function loadPriceChart(query) {
if (data.stats) {
statsEl.innerHTML = `
Low: ${formatPrice(data.stats.min, "USD")}
- Avg: ${formatPrice(data.stats.avg, "USD")}
+ Avg: ${formatPrice(data.stats.avg, "USD")}
High: ${formatPrice(data.stats.max, "USD")}
${data.stats.count} sales
`;
@@ -492,7 +551,7 @@ async function loadPriceChart(query) {
function drawPriceChart(canvas, points) {
const ctx = canvas.getContext("2d");
const w = canvas.parentElement.clientWidth;
- const h = 120;
+ const h = 140;
canvas.width = w;
canvas.height = h;
canvas.style.width = w + "px";
@@ -502,7 +561,7 @@ function drawPriceChart(canvas, points) {
const max = Math.max(...prices) * 1.05;
const range = max - min || 1;
- const pad = { top: 10, right: 10, bottom: 20, left: 50 };
+ const pad = { top: 10, right: 10, bottom: 32, left: 50 };
const cw = w - pad.left - pad.right;
const ch = h - pad.top - pad.bottom;
@@ -556,6 +615,19 @@ function drawPriceChart(canvas, points) {
ctx.arc(x, y, 3, 0, Math.PI * 2);
ctx.fill();
});
+
+ // X-axis date labels
+ const fmt = (d) => { const dt = new Date(d); return `${dt.getMonth() + 1}/${dt.getDate()}`; };
+ ctx.fillStyle = "rgba(138,138,154,0.6)";
+ ctx.font = "10px 'JetBrains Mono', monospace";
+ ctx.textAlign = "center";
+ const maxLabels = Math.min(points.length, 5);
+ const step = maxLabels > 1 ? (points.length - 1) / (maxLabels - 1) : 0;
+ for (let i = 0; i < maxLabels; i++) {
+ const idx = Math.round(i * step);
+ const x = pad.left + (idx / (points.length - 1 || 1)) * cw;
+ ctx.fillText(fmt(points[idx].recordedAt), x, h - 6);
+ }
}
function renderPsaInline(psa) {
diff --git a/public/index.html b/public/index.html
index c484e6c..4580a6f 100644
--- a/public/index.html
+++ b/public/index.html
@@ -51,6 +51,10 @@ Research any Pokemon card
in seconds
+
diff --git a/public/style.css b/public/style.css
index 9bc13f4..db02c7e 100644
--- a/public/style.css
+++ b/public/style.css
@@ -310,6 +310,22 @@ main {
color: var(--gold);
background: var(--gold-dim);
}
+.sort-select {
+ margin-left: auto;
+ background: var(--inset);
+ color: var(--muted);
+ border: 1px solid var(--border);
+ border-radius: 6px;
+ padding: 4px 10px;
+ font-family: 'JetBrains Mono', monospace;
+ font-size: 11px;
+ cursor: pointer;
+ outline: none;
+ appearance: none;
+ -webkit-appearance: none;
+}
+.sort-select:focus { border-color: rgba(217, 182, 118, 0.3); color: var(--text); }
+.sort-select option { background: var(--panel); color: var(--text); }
.source-filters {
display: flex;
@@ -651,11 +667,12 @@ main {
}
.gem-bar-track {
flex: 1;
- height: 5px;
+ height: 6px;
background: rgba(255,255,255,0.06);
border-radius: 3px;
overflow: hidden;
- max-width: 60px;
+ min-width: 40px;
+ max-width: 80px;
}
.gem-bar-fill {
height: 100%;
@@ -663,34 +680,59 @@ main {
border-radius: 3px;
}
-.card-identity { display: flex; align-items: center; gap: 6px; margin-left: auto; }
-.card-id-row { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; }
+.card-identity {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ flex-wrap: wrap;
+ background: var(--inset);
+ border: 1px solid var(--border);
+ border-radius: 8px;
+ padding: 10px 14px;
+ margin-bottom: 14px;
+}
.card-id-badge { font-family: 'JetBrains Mono', monospace; font-size: 12px; color: var(--gold); background: var(--gold-dim); padding: 3px 8px; border-radius: 4px; font-weight: 600; }
-.card-id-rarity { font-family: 'Space Grotesk', system-ui, sans-serif; font-size: 11px; font-weight: 700; color: var(--text); background: var(--inset); border: 1px solid var(--border); padding: 2px 6px; border-radius: 3px; }
+.card-id-rarity { font-family: 'Space Grotesk', system-ui, sans-serif; font-size: 11px; font-weight: 700; color: var(--text); background: var(--panel); border: 1px solid var(--border); padding: 2px 6px; border-radius: 3px; }
.card-id-set { font-size: 11px; color: var(--muted); }
-.card-id-num { font-size: 11px; color: var(--muted); font-family: 'JetBrains Mono', monospace; }
-.card-id-names { margin-top: 4px; display: flex; gap: 10px; }
.card-id-name { font-size: 11px; color: var(--text); }
+.card-id-sep { width: 1px; height: 14px; background: var(--border); flex-shrink: 0; }
.card-id-lang { font-size: 9px; color: var(--muted); text-transform: uppercase; letter-spacing: 0.04em; margin-right: 2px; }
-.arbitrage-container { background: var(--inset); border: 1px solid var(--border); border-radius: 8px; padding: 12px 14px; margin-bottom: 12px; }
+.arbitrage-container { background: var(--inset); border: 1px solid var(--border); border-radius: 8px; padding: 14px 16px; margin-bottom: 14px; }
.arbitrage-sources { display: flex; gap: 8px; margin-top: 8px; }
.arb-source { flex: 1; background: var(--panel); border: 1px solid var(--border); border-radius: 6px; padding: 10px; text-align: center; }
-.arb-source.arb-cheapest { border-color: var(--green); background: rgba(124, 224, 168, 0.05); }
+.arb-source.arb-cheapest { border-color: var(--green); background: rgba(124, 224, 168, 0.08); }
+.arb-best-chip { font-family: 'JetBrains Mono', monospace; font-size: 9px; font-weight: 600; text-transform: uppercase; letter-spacing: 0.06em; color: var(--green); background: rgba(124, 224, 168, 0.12); border: 1px solid rgba(124, 224, 168, 0.3); padding: 2px 7px; border-radius: 3px; margin-top: 6px; display: inline-block; }
.arb-source-name { font-family: 'JetBrains Mono', monospace; font-size: 10px; font-weight: 600; color: var(--muted); text-transform: uppercase; letter-spacing: 0.08em; margin-bottom: 6px; }
.arb-cheapest .arb-source-name { color: var(--green); }
.arb-source-price { font-family: 'Space Grotesk', system-ui, sans-serif; font-size: 18px; font-weight: 700; color: var(--text); }
.arb-cheapest .arb-source-price { color: var(--green); }
.arb-source-jpy { font-size: 10px; color: var(--muted); }
.arb-source-count { font-size: 10px; color: var(--muted); margin-top: 2px; }
-.arb-summary { margin-top: 8px; font-size: 11px; color: var(--gold); text-align: center; }
+.arb-summary {
+ margin-top: 10px;
+ padding-top: 10px;
+ border-top: 1px solid var(--border);
+ font-family: 'Space Grotesk', system-ui, sans-serif;
+ font-size: 13px;
+ font-weight: 600;
+ color: var(--green);
+ text-align: center;
+}
+.arb-summary-spread {
+ font-family: 'JetBrains Mono', monospace;
+ font-size: 11px;
+ font-weight: 500;
+ color: var(--muted);
+ margin-left: 6px;
+}
.price-chart-container {
background: var(--inset);
border: 1px solid var(--border);
border-radius: 8px;
padding: 14px 16px;
- margin-bottom: 12px;
+ margin-bottom: 14px;
}
.price-chart-container canvas {
width: 100%;
@@ -699,7 +741,9 @@ main {
.price-chart-stats {
display: flex;
gap: 16px;
- margin-top: 8px;
+ margin-top: 10px;
+ padding-top: 10px;
+ border-top: 1px solid var(--border);
font-size: 11px;
color: var(--muted);
}
@@ -707,24 +751,31 @@ main {
color: var(--text);
font-family: 'Space Grotesk', system-ui, sans-serif;
}
+.price-chart-stats .stat-avg b {
+ color: var(--gold);
+ font-size: 13px;
+}
.detail-actions {
display: flex;
gap: 10px;
- margin-top: 4px;
+ margin-top: 8px;
}
.detail-actions a {
- display: inline-block;
+ display: inline-flex;
+ align-items: center;
+ gap: 6px;
padding: 8px 16px;
- background: var(--gold);
- color: var(--bg);
+ background: transparent;
+ color: var(--gold);
+ border: 1px solid rgba(217, 182, 118, 0.25);
border-radius: 6px;
font-family: 'Space Grotesk', system-ui, sans-serif;
font-size: 13px;
- font-weight: 600;
- transition: opacity 0.2s;
+ font-weight: 500;
+ transition: all 0.2s;
}
-.detail-actions a:hover { opacity: 0.85; text-decoration: none; }
+.detail-actions a:hover { border-color: var(--gold); background: rgba(217, 182, 118, 0.06); text-decoration: none; }
.listing-card .thumb {
width: 72px;
@@ -774,6 +825,33 @@ main {
font-size: 12px;
color: var(--muted);
}
+.condition-badge {
+ font-family: 'JetBrains Mono', monospace;
+ font-size: 10px;
+ font-weight: 600;
+ text-transform: uppercase;
+ letter-spacing: 0.04em;
+ color: var(--muted);
+ background: var(--inset);
+ border: 1px solid var(--border);
+ padding: 1px 6px;
+ border-radius: 3px;
+}
+.price-outlier {
+ font-family: 'JetBrains Mono', monospace;
+ font-size: 10px;
+ font-weight: 600;
+ text-transform: uppercase;
+ letter-spacing: 0.04em;
+ color: var(--red);
+ background: rgba(255, 93, 93, 0.08);
+ border: 1px solid rgba(255, 93, 93, 0.25);
+ padding: 1px 6px;
+ border-radius: 3px;
+ display: inline-block;
+ margin-top: 4px;
+}
+.shipping-free { color: var(--green); }
.listing-card .sold-date {
font-size: 11px;