import { Suspense } from 'react';
import Link from 'next/link';
import { Hero } from '@/components/home/hero';
import { IndexTape } from '@/components/market/index-strip';
import { AssetRail, LatestTransactions, LotsRail, MarketInsights, RadarRail, SalesRail } from '@/components/home/sections';
import { CategoryGrid } from '@/components/market/category-grid';
import { TileSkeleton } from '@/components/market/asset-tile';
import { attachGuidePrices, getCategoryThumbnails, getLatestObservationsFeed, rankedAssets } from '@/lib/queries/assets';
import { fmtNum } from '@/lib/format';
import { Card, Delta, Skeleton, TableSkeleton } from '@/components/ui/primitives';
import { getLatestSales, getSiteStats } from '@/lib/queries/site';
import { getIndexSeries, listIndices } from '@/lib/queries/indices';
import { getCategorySparklines, getMarketRows } from '@/lib/queries/markets';
import { getTopSales, listLots, listRadar } from '@/lib/queries/market-lists';
import { LineChart } from '@/components/charts/line-chart';
export const revalidate = 120;
export default async function HomePage() {
const [stats, indices] = await Promise.all([getSiteStats(), listIndices()]);
return (
{/* §195 hierarchy: hero + search → live stats (in hero) → RARE tape → global index → market snapshot →
trending → record sales → Rare Radar → value opportunities → ending auctions → latest sales → categories → long tail */}
}>
}>
}>
}>
}>
}>
);
}
function RailFallback() {
return (
{[0, 1].map((k) => (
{Array.from({ length: 8 }).map((_, i) => (
))}
))}
);
}
async function Categories() {
const markets = await getMarketRows();
const families = markets.filter((r) => r.counts.assets > 0);
const [thumbs, sparklines] = await Promise.all([getCategoryThumbnails(markets.map((r) => r.node.slug)), getCategorySparklines(markets.map((r) => r.node.slug), 90)]);
return (
Browse by category
Live counts from the canonical tables · 90-day category index where published
{fmtNum(families.length)} of {fmtNum(markets.length)} families with data →
);
}
async function FlagshipPanel() {
const indices = await listIndices();
const rare = indices.find((i) => i.isFlagship);
const subs = indices.filter((i) => !i.isFlagship);
const published = subs.filter((i) => i.latest);
const building = subs.filter((i) => !i.latest).sort((a, b) => b.pricedAssets / b.minConstituents - a.pricedAssets / a.minConstituents);
const series = rare ? await getIndexSeries(rare.id, 365) : [];
return (
RARE · Global Collectibles Index
{rare?.latest ? (
{rare.latest.value.toFixed(2)}
1D
30D
) : (
Transaction-weighted across published subindices · base 1,000 on 2024-01-01 · methodology
)}
Index detail →
({ x: p.date, y: p.value })) }]} emptyLabel={rare ? `Index building — publishes once ${rare.minConstituents} priced constituents exist (${rare.pricedAssets} today)` : 'Index not configured'} />
{published.length ? (
{published.slice(0, 8).map((i) => (
-
{i.ticker}
{i.name.replace('RareIndex ', '')}
{i.latest!.value.toFixed(2)}
))}
) : null}
{building.length ? (
In development · priced constituents vs. minimumWhy →
{building.slice(0, 8).map((i) => {
const pct = Math.min(100, Math.round((i.pricedAssets / Math.max(1, i.minConstituents)) * 100));
return (
-
{i.ticker.replace('RARE-', '')}
{i.pricedAssets}/{i.minConstituents}
);
})}
) : null}
);
}
/** Market snapshot (§195): the category table right under the global index. */
async function Snapshot() {
const markets = await getMarketRows();
return ;
}
/** Long tail below the fold: catalogue additions and community signals. */
async function LongTail() {
const [newest, watched] = await Promise.all([rankedAssets('newest', { limit: 8 }), rankedAssets('watched', { limit: 8 })]);
await attachGuidePrices(newest);
return (
<>
{watched.length ? : null}
>
);
}
async function Discovery() {
const [trending, movers, losers, records, radar, lots, opportunities, newestPriced, documented, withSales] = await Promise.all([
rankedAssets('trending', { limit: 8 }),
rankedAssets('gainers', { limit: 8, window: '7d' }),
rankedAssets('losers', { limit: 8, window: '7d' }),
getTopSales(8),
listRadar({ limit: 8 }),
listLots({ endingWithinHours: 72, status: null, limit: 8 }),
rankedAssets('opportunity', { limit: 8 }),
rankedAssets('newest_priced', { limit: 8 }),
rankedAssets('documented', { limit: 8 }),
rankedAssets('with_sales', { limit: 8 }),
]);
await Promise.all([attachGuidePrices(documented), attachGuidePrices(trending), attachGuidePrices(newestPriced)]);
// Honest fallbacks while valuations are still being computed: each rail states what it shows.
const trendingRail = trending.length
? { title: 'Trending Now', subtitle: 'Price × volume × listing momentum', items: trending, metric: 'trending' as const, href: '/trending' }
: newestPriced.length
? { title: 'Recently valued', subtitle: 'Newest RareIndex Valuations — trending publishes once momentum history exists', items: newestPriced, metric: 'riv' as const, href: '/explore?has=valuation&sort=riv' }
: { title: 'Best-documented assets', subtitle: 'Images plus sales or guide prices', items: documented, metric: 'guide' as const, href: '/explore?has=observations' };
const moversItems = movers.length ? movers : losers;
const moversRail = moversItems.length
? { title: 'Biggest Movers', subtitle: '7-day change in RareIndex Valuation', items: moversItems, metric: 'change7d' as const, href: '/explore?sort=change7d' }
: withSales.length
? { title: 'Latest sold assets', subtitle: 'Most recent verified sale per asset — movers need a 7-day valuation history', items: withSales, metric: 'latestSale' as const, href: '/sales' }
: null;
return (
<>
{moversRail ? : null}
{opportunities.length ? : null}
>
);
}
async function Latest() {
const items = await getLatestSales(12);
const observations = items.length ? [] : await getLatestObservationsFeed(12);
return ;
}