/* ============================================================ CryptoPrice.co — App primitives Imported by both UI kits. ============================================================ */ const { useState, useMemo, useEffect, useRef } = React; /* ---------- Icons (Lucide-style, hand-rolled subset for offline) ---------- */ const Icon = ({ d, size = 16, fill = "none", strokeWidth = 1.5, ...rest }) => ( {Array.isArray(d) ? d.map((p, i) => ) : } ); const Icons = { trend: (p) => , star: (p) => , starFill: (p) => , wallet: (p) => , bell: (p) => , search: (p) => , settings: (p) => , plus: (p) => , x: (p) => , arrow: (p) => , sun: (p) => , moon: (p) => , chart: (p) => , grid: (p) => , download: (p) => , filter: (p) => , dot: (p) => , }; /* ---------- Coin logos (deterministic placeholders) ---------- */ const COIN_COLORS = { BTC: "#F7931A", ETH: "#627EEA", SOL: "linear-gradient(135deg,#9945FF,#14F195)", USDT: "#26A17B", BNB: "#F3BA2F", XRP: "#23292F", DOGE: "#C2A633", ADA: "#0033AD", AVAX: "#E84142", MATIC: "#8247E5", DOT: "#E6007A", LINK: "#2A5ADA", LTC: "#345D9D", TON: "#0098EA", TRX: "#FF060A", ATOM: "#2E3148", NEAR: "#000", ARB: "#28A0F0", }; const CoinLogo = ({ symbol, size = 24 }) => { const bg = COIN_COLORS[symbol] || "#2A313B"; const isGrad = bg.startsWith("linear"); return (
{symbol[0]}
); }; /* ---------- Button ---------- */ const Button = ({ variant = "secondary", size = "md", icon, children, onClick, style = {} }) => { const base = { display: "inline-flex", alignItems: "center", gap: 8, fontFamily: "var(--font-sans)", fontWeight: 500, border: "1px solid transparent", borderRadius: 8, cursor: "pointer", transition: "all 120ms var(--ease-out)", height: size === "sm" ? 28 : size === "lg" ? 40 : 36, padding: size === "sm" ? "0 10px" : size === "lg" ? "0 18px" : "0 14px", fontSize: size === "sm" ? 12 : 14, whiteSpace: "nowrap", }; const variants = { primary: { background: "var(--brand-lime)", color: "var(--brand-ink)" }, secondary: { background: "var(--bg-2)", color: "var(--fg-0)", borderColor: "var(--line-2)" }, ghost: { background: "transparent", color: "var(--fg-1)" }, danger: { background: "transparent", color: "var(--down)", borderColor: "rgba(255,90,95,0.3)" }, }; return ( ); }; /* ---------- Delta badge ---------- */ const DeltaBadge = ({ value, size = "md" }) => { const up = value >= 0; const fontSize = size === "sm" ? 11 : 12; return ( {up ? "▲" : "▼"} {up ? "+" : "−"}{Math.abs(value).toFixed(2)}% ); }; /* ---------- Sparkline ---------- */ const Sparkline = ({ data, width = 80, height = 28, up = true, fill = false }) => { const min = Math.min(...data), max = Math.max(...data); const range = max - min || 1; const points = data.map((d, i) => [ (i / (data.length - 1)) * width, height - ((d - min) / range) * (height - 2) - 1, ]); const path = "M" + points.map(p => p.join(" ")).join(" L "); const color = up ? "var(--up)" : "var(--down)"; const id = `sg-${Math.random().toString(36).slice(2, 7)}`; return ( {fill && ( <> )} ); }; /* ---------- Helpers ---------- */ const fmtPrice = (n) => { if (n >= 1) return "$" + n.toLocaleString("en-US", { minimumFractionDigits: 2, maximumFractionDigits: 2 }); return "$" + n.toLocaleString("en-US", { minimumFractionDigits: 4, maximumFractionDigits: 6 }); }; const fmtBig = (n) => { if (n >= 1e12) return "$" + (n / 1e12).toFixed(2) + "T"; if (n >= 1e9) return "$" + (n / 1e9).toFixed(2) + "B"; if (n >= 1e6) return "$" + (n / 1e6).toFixed(1) + "M"; return "$" + n.toLocaleString(); }; const genSpark = (seed, up = true) => { let v = 50; return Array.from({ length: 24 }, (_, i) => { v += Math.sin(seed + i * 0.7) * 4 + (up ? 0.6 : -0.6) + (Math.random() - 0.5) * 2; return v; }); }; Object.assign(window, { Icons, CoinLogo, Button, DeltaBadge, Sparkline, fmtPrice, fmtBig, genSpark });