Files
dark-lord/app/market/analytics/page.tsx
drjones 78a071ba02 Harden onion boot flow and deepen site surfaces
Add persistent onion key backup and restore, improve startup resilience, and flesh out the major site verticals with richer navigation, search coverage, and operator documentation.

Made-with: Cursor
2026-04-07 21:35:52 -07:00

120 lines
5.4 KiB
TypeScript

"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<ShopCurrency, number> = { 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 (
<div className="space-y-10 font-mono text-[#c8ffd8]">
<header>
<p className="text-[10px] uppercase tracking-[0.45em] text-[#00ff41]/45">Layer 2 · floor telemetry</p>
<h1 className="mt-2 text-2xl font-black tracking-tight text-[#7af598] md:text-3xl">Analytics</h1>
<p className="mt-3 max-w-3xl text-sm leading-relaxed text-[#5a8f6a]">
Derived from the live catalog in <code className="text-[#7af598]">shopCatalog</code> 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.
</p>
</header>
<section className="grid gap-6 md:grid-cols-3">
<div className="rounded border border-[#00ff41]/25 bg-[#050a08]/90 p-5">
<p className="text-[10px] uppercase tracking-widest text-[#00ff41]/50">SKUs</p>
<p className="mt-1 text-3xl font-black text-[#b4ffcc]">{SHOP_PRODUCTS.length}</p>
</div>
<div className="rounded border border-[#00ff41]/25 bg-[#050a08]/90 p-5">
<p className="text-[10px] uppercase tracking-widest text-[#00ff41]/50">Limited drops</p>
<p className="mt-1 text-3xl font-black text-[#ff9a9a]">{limitedCount}</p>
</div>
<div className="rounded border border-[#00ff41]/25 bg-[#050a08]/90 p-5">
<p className="text-[10px] uppercase tracking-widest text-[#00ff41]/50">Avg notional (rough USD)</p>
<p className="mt-1 text-3xl font-black text-[#7af598]">
${avgPriceUsdish.toFixed(0)}
</p>
<p className="mt-2 text-[9px] text-[#4a6b55]">BTC/ETH/XMR converted with static fiction rates for display only.</p>
</div>
</section>
<section className="rounded border border-[#00ff41]/20 p-6">
<h2 className="text-xs font-black uppercase tracking-widest text-[#00ff41]/70">Category mix</h2>
<ul className="mt-4 space-y-3">
{byCategory.map((c) => (
<li key={c.name} className="flex items-center gap-3 text-xs">
<span className="w-8 shrink-0 text-lg">{c.icon}</span>
<span className="w-36 shrink-0 font-bold text-[#b4ffcc]">{c.name}</span>
<div className="h-2 min-w-0 flex-1 overflow-hidden rounded bg-[#0a120d]">
<div className="h-full bg-gradient-to-r from-[#00ff41]/20 to-[#7af598]" style={{ width: `${c.pct}%` }} />
</div>
<span className="w-10 shrink-0 text-right text-[#6a9b7a]">{c.count}</span>
</li>
))}
</ul>
</section>
<aside className="rounded border border-dashed border-[#00ff41]/25 p-4 text-[10px] leading-relaxed text-[#4a6b55]">
<strong className="text-[#5a8f6a]">Telemetry vocabulary:</strong> heat is relative SKU density per category, mix is currency tag share, notional is a
fiction USD bridge using static BTC/ETH/XMR anchors useful for comparing stalls, not for pricing legal tender. All terms index the{" "}
<strong className="text-[#6a9b7a]">Void crawler</strong> when mirrored on search-enabled onions.
</aside>
<section className="rounded border border-[#00ff41]/20 p-6">
<h2 className="text-xs font-black uppercase tracking-widest text-[#00ff41]/70">Currency mix</h2>
<ul className="mt-4 space-y-3">
{byCurrency.map(({ k, v, pct }) => (
<li key={k} className="flex items-center gap-3 text-xs">
<span className="w-10 shrink-0 font-bold text-[#9dffc4]">
{currencySymbol(k)} {k}
</span>
<div className="h-2 min-w-0 flex-1 overflow-hidden rounded bg-[#0a120d]">
<div className="h-full bg-[#00ff41]/35" style={{ width: `${pct}%` }} />
</div>
<span className="w-10 shrink-0 text-right text-[#6a9b7a]">{v}</span>
</li>
))}
</ul>
</section>
</div>
);
}