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 { attachGuidePrices, getCategoryThumbnails, getLatestObservationsFeed } from '@/lib/queries/assets';
import { fmtNum } from '@/lib/format';
import { Skeleton } from '@/components/ui/primitives';
import { getLatestSales, getSiteStats } from '@/lib/queries/site';
import { listIndices } from '@/lib/queries/indices';
import { rankedAssets } from '@/lib/queries/assets';
import { getMarketRows } from '@/lib/queries/markets';
import { getTopSales, listLots, listRadar } from '@/lib/queries/market-lists';
import { LineChart } from '@/components/charts/line-chart';
import { getIndexSeries } from '@/lib/queries/indices';
export const revalidate = 120;
export default async function HomePage() {
const [stats, indices] = await Promise.all([getSiteStats(), listIndices()]);
return (
}>
{Array.from({ length: 6 }).map((_, i) => )}
}>
}>
);
}
async function FlagshipPanel() {
const indices = await listIndices();
const rare = indices.find((i) => i.isFlagship);
const subs = indices.filter((i) => !i.isFlagship && i.latest);
const series = rare ? await getIndexSeries(rare.id, 365) : [];
return (
RARE · RareIndex Global Collectibles Index
Chain-linked, equal-weighted across subindices · base 1,000 on 2024-01-01
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'} />
Subindices
Daily change · latest published value
{subs.length ? (
{subs.slice(0, 10).map((i) => (
-
{i.ticker}
{i.latest!.value.toFixed(2)}
= 0 ? 'text-gain' : 'text-loss'}>{i.change1d === null ? '—' : `${i.change1d >= 0 ? '+' : ''}${(i.change1d * 100).toFixed(2)}%`}
))}
) : (
No subindex has reached its minimum constituent count yet.
See build progress
)}
);
}
async function Discovery() {
const [trending, movers, losers, records, radar, lots, watched, newest, markets, 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('watched', { limit: 8 }),
rankedAssets('newest', { limit: 8 }),
getMarketRows(),
rankedAssets('opportunity', { limit: 8 }),
rankedAssets('newest_priced', { limit: 8 }),
rankedAssets('documented', { limit: 8 }),
rankedAssets('with_sales', { limit: 8 }),
]);
await Promise.all([attachGuidePrices(newest), attachGuidePrices(documented)]);
const families = markets.filter((r) => r.counts.assets > 0);
const thumbs = await getCategoryThumbnails(markets.map((r) => r.node.slug));
// 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 needs momentum history)', items: newestPriced, metric: 'riv' as const, href: '/explore?has=valuation&sort=riv' }
: { title: 'Best-documented assets', subtitle: 'Images plus sales or guide prices — trending publishes once momentum history exists', 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: 'riv' as const, href: '/sales' }
: null;
return (
<>
Browse by category
{fmtNum(families.length)} families with data ·{' '}
all {fmtNum(markets.length)} →
{moversRail ? : null}
{watched.length ? : null}
{opportunities.length ? : null}
>
);
}
async function Latest() {
const items = await getLatestSales(12);
const observations = items.length ? [] : await getLatestObservationsFeed(12);
return (
);
}