/* ============================================================
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 }) => (
);
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 (
);
};
/* ---------- 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 });