"use client"; import { useMemo } from "react"; import { SHOP_PRODUCTS, shopCategoryCounts, type ShopCurrency } from "@/lib/shopCatalog"; function currencySymbol(c: ShopCurrency): string { if (c === "BTC") return "₿"; if (c === "ETH") return "Ξ"; if (c === "XMR") return "⏣"; return "$"; } export default function MarketAnalyticsPage() { const { byCategory, byCurrency, avgPriceUsdish, limitedCount } = useMemo(() => { const cats = shopCategoryCounts(); const cur: Record = { BTC: 0, ETH: 0, USD: 0, XMR: 0 }; let usdSum = 0; let n = 0; let lim = 0; for (const p of SHOP_PRODUCTS) { cur[p.currency]++; if (p.currency === "USD") { usdSum += p.price; n++; } else if (p.currency === "BTC") { usdSum += p.price * 65_000; n++; } else if (p.currency === "ETH") { usdSum += p.price * 3_500; n++; } else { usdSum += p.price * 165; n++; } if (p.limited) lim++; } const maxCat = Math.max(1, ...cats.map((c) => c.count)); const maxCur = Math.max(1, ...Object.values(cur)); return { byCategory: cats.map((c) => ({ ...c, pct: Math.round((c.count / maxCat) * 100) })), byCurrency: (Object.entries(cur) as [ShopCurrency, number][]).map(([k, v]) => ({ k, v, pct: Math.round((v / maxCur) * 100), })), avgPriceUsdish: n ? usdSum / n : 0, limitedCount: lim, }; }, []); return (

Layer 2 · floor telemetry

Analytics

Derived from the live catalog in shopCatalog — not a third-party charting plugin. Bars are CSS; numbers update when SKUs change. Use this view to explain category heat and currency mix on vendor calls or mirror status pages.

SKUs

{SHOP_PRODUCTS.length}

Limited drops

{limitedCount}

Avg notional (rough USD)

${avgPriceUsdish.toFixed(0)}

BTC/ETH/XMR converted with static fiction rates for display only.

Category mix

    {byCategory.map((c) => (
  • {c.icon} {c.name}
    {c.count}
  • ))}

Currency mix

    {byCurrency.map(({ k, v, pct }) => (
  • {currencySymbol(k)} {k}
    {v}
  • ))}
); }