import type { Metadata } from 'next'; import Link from 'next/link'; import { Suspense } from 'react'; import { PageHeader } from '@/components/ui/page-header'; import { FilterBar } from '@/components/ui/filter-bar'; import { Pagination } from '@/components/ui/pagination'; import { Delta, EmptyState, Skeleton, Table, VsRiv, th, td, tdNum } from '@/components/ui/primitives'; import { Thumb } from '@/components/ui/tile-image'; import { screenAssets, type ScreenerRow } from '@/lib/queries/screener'; import { DEFAULT_SORT, PRESETS, SCREENER_KEYS, isPlausibleChange, parseScreenerParams, type ScreenerSort, type SortDir } from '@/lib/screener'; import { catName } from '@/lib/taxonomy'; import { cn, confidenceLabel, fmtMoney, fmtNum, fmtPct } from '@/lib/format'; import { CATEGORIES, GRADERS } from '@rareindex/taxonomy'; import { pick, sp1, withParams, type SP } from '@/lib/search-params'; export const metadata: Metadata = { title: 'Market Screener', description: 'Screen every valued collectible by RareIndex Valuation, confidence, momentum, liquidity, rarity, sales velocity, listing depth, ask spread and drawdown. Presets for the most liquid, most traded, largest drawdowns, near all-time-high, rising volume and potentially underpriced assets.', }; const PCT_TITLE_HELD = 'Implausible move held back (beyond ±500 %): a data or identity artefact, not a market signal.'; interface Col { id: ScreenerSort | 'asset'; label: string; title: string; numeric?: boolean; } const COLUMNS: Col[] = [ { id: 'asset', label: 'Asset', title: 'Canonical asset (category · year · set)' }, { id: 'riv', label: 'RIV', title: 'RareIndex Valuation with confidence label and number of transactions used', numeric: true }, { id: 'change30d', label: '1M', title: 'Change in RIV over 30 days (representative variant)', numeric: true }, { id: 'change1y', label: '1Y', title: 'Change in RIV over 1 year (representative variant)', numeric: true }, { id: 'liquidity', label: 'Liq.', title: 'Liquidity Score 0–100: sales frequency, listing depth, sources, days between sales, ask/sold spread', numeric: true }, { id: 'rarity', label: 'Rarity', title: 'Rarity Score 0–100 from population, production and market appearances (null without a supply signal)', numeric: true }, { id: 'sales30d', label: 'Sales 30D', title: 'Verified sales in the last 30 days', numeric: true }, { id: 'listings', label: 'Listings', title: 'Active listings observed', numeric: true }, { id: 'spread', label: 'Spread', title: 'Lowest ask of the representative variant vs RIV — only for transaction-based valuations (≥ 5 sales, medium+ confidence) within a plausible 0.1×–10× ratio', numeric: true }, { id: 'drawdown', label: 'Drawdown', title: 'RIV (representative variant) vs the all-time-high verified sale of the asset — the ATH may belong to another variant (e.g. a sealed or top-graded copy), so very deep drawdowns often reflect variant spread rather than a price fall', numeric: true }, { id: 'volume30d', label: 'Vol. 30D', title: 'USD volume of verified sales in the last 30 days', numeric: true }, ]; export default async function ScreenerPage({ searchParams }: { searchParams: Promise }) { const sp = await searchParams; const filters = parseScreenerParams(sp); const params = pick(sp, SCREENER_KEYS); const preset = sp1(sp, 'preset') ?? null; const activePreset = PRESETS.find((p) => p.id === preset) ?? null; const categoryOptions = CATEGORIES.map((c) => ({ value: c.slug, label: `${' '.repeat(c.level)}${c.level ? '↳ ' : ''}${c.name}` })); const exportHref = withParams('/screener/export', { ...params, page: null, size: null }); return (
Export CSV JSON } />
All valued {PRESETS.map((p) => ( {p.label} ))}
{activePreset ?

{activePreset.description}

: null} g.slug !== 'raw').map((g) => ({ value: g.slug, label: g.name })), width: 'w-28' }, { name: 'grade', label: 'Grade', type: 'text', placeholder: '10', width: 'w-16' }, { name: 'min', label: 'RIV ≥ $', type: 'number', placeholder: '0', width: 'w-24' }, { name: 'max', label: 'RIV ≤ $', type: 'number', placeholder: '∞', width: 'w-24' }, { name: 'conf', label: 'Confidence', type: 'select', options: [{ value: '0.5', label: 'Medium or better' }, { value: '0.75', label: 'High only' }], placeholder: 'Any', width: 'w-36' }, { name: 'liq', label: 'Liquidity ≥', type: 'number', placeholder: '0–100', width: 'w-20' }, { name: 'rar', label: 'Rarity ≥', type: 'number', placeholder: '0–100', width: 'w-20' }, { name: 'c30min', label: '1M ≥ %', type: 'number', placeholder: '-100…', width: 'w-20' }, { name: 'c1ymin', label: '1Y ≥ %', type: 'number', placeholder: '-100…', width: 'w-20' }, { name: 's30', label: 'Sales 30D ≥', type: 'number', placeholder: '0', width: 'w-20' }, { name: 's1y', label: 'Sales 1Y ≥', type: 'number', placeholder: '0', width: 'w-20' }, { name: 'lst', label: 'Listings ≥', type: 'number', placeholder: '0', width: 'w-20' }, { name: 'spmax', label: 'Spread ≤ %', type: 'number', placeholder: 'e.g. -10', width: 'w-24' }, { name: 'ddmax', label: 'Drawdown ≤ %', type: 'number', placeholder: 'e.g. -30', width: 'w-24' }, { name: 'from', label: 'Year ≥', type: 'number', placeholder: '1900', width: 'w-20' }, { name: 'to', label: 'Year ≤', type: 'number', placeholder: '2026', width: 'w-20' }, ]} />
}>

Percent moves beyond ±500 % and asks outside 0.1×–10× of the valuation are held back as data/identity anomalies (never shown as signals). Spread and “Potentially Underpriced” use only transaction-based valuations with ≥ 5 sales and medium or better confidence. Scores are explained in the{' '} Methodology Center {' '} and coverage in{' '} Data .

); } function SortHeader({ col, filters, params }: { col: Col; filters: ReturnType; params: Record }) { if (col.id === 'asset') { const active = filters.sort === 'name'; return ( {col.label} {active ? {filters.dir === 'asc' ? '↑' : '↓'} : null} ); } const id = col.id as ScreenerSort; const active = filters.sort === id; const nextDir: SortDir = active ? (filters.dir === 'asc' ? 'desc' : 'asc') : DEFAULT_SORT[id]; return ( {col.label} {active ? {filters.dir === 'asc' ? '↑' : '↓'} : null} ); } function Pct({ value, digits = 1 }: { value: number | null; digits?: number }) { if (value === null || value === undefined) return —; if (!isPlausibleChange(value)) return —; return ; } function Score({ value }: { value: number | null }) { if (value === null) return —; const v = Math.round(value); return ( {v} ); } async function Results({ filters, params }: { filters: ReturnType; params: Record }) { const res = await screenAssets(filters); if (!res.items.length) { return Reset screen} />; } const startRank = (res.page - 1) * res.pageSize; return ( <>

{res.total.toLocaleString('en-US')} assets match · sorted by {filters.sort} {filters.dir === 'asc' ? '↑' : '↓'} · page {res.page}

{COLUMNS.map((c) => ( ))} {res.items.map((a, i) => ( ))}
#
); } function Row({ a, rank }: { a: ScreenerRow; rank: number }) { return ( {rank} {a.title} {catName(a.categorySlug)} {a.year ? ` · ${a.year}` : ''} {a.setName ? ` · ${a.setName}` : ''} {fmtMoney(a.rivUsd)} {confidenceLabel(a.rivConfidence)} · n={a.rivSampleSize} {fmtNum(a.sales30d)} {fmtNum(a.sales1y)} / 1Y {fmtNum(a.activeListings)} {a.spread === null ? ( — ) : ( {a.minAskUsd !== null ? ask {fmtMoney(a.minAskUsd)} : null} )} {a.drawdown === null ? ( — ) : ( {fmtPct(a.drawdown, 1, false)} {a.athUsd !== null ? ATH {fmtMoney(a.athUsd)} : null} )} {a.volume30dUsd !== null && a.volume30dUsd > 0 ? fmtMoney(a.volume30dUsd, 'USD', { compact: true }) : —} ); }