{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "eased-crypto-core",
  "title": "Crypto core primitive",
  "description": "Shared pieces for Eased crypto blocks: a monochrome token glyph, chain tints as tokens with a ≤10% glow, address truncation, and USD price, token amount and signed change formats.",
  "dependencies": [],
  "registryDependencies": [
    "https://eased.dev/r/eased-tokens.json"
  ],
  "files": [
    {
      "path": "src/registry/eased/_primitives/crypto-core/crypto-core.tsx",
      "content": "/**\n * crypto-core: the small pieces every Eased crypto block shares, so a wallet, a swap and an\n * order book read as one product: the token glyph, chain tints, address truncation and the\n * price and amount formats. Import from here.\n */\nimport type { CSSProperties } from \"react\";\nimport { minusFormatter } from \"../../lib/format\";\n\n/* ---------- Token glyph ------------------------------------------------------------------ */\n\n/**\n * Monochrome marks for the common symbols. No brand logos: a disc with a glyph reads as\n * \"a token\" at 20px, sits on any theme and never clashes with the page's accent.\n */\nconst GLYPHS: Record<string, string> = {\n  BTC: \"₿\",\n  ETH: \"Ξ\",\n  SOL: \"◎\",\n  USDC: \"$\",\n  USDT: \"₮\",\n  DAI: \"◈\",\n  LINK: \"⬢\",\n};\n\nexport type TokenGlyphProps = {\n  symbol: string;\n  /** Pixel size of the disc. Default 20; the glyph scales with it. */\n  size?: number;\n  className?: string;\n};\n\n/** A token's disc: sunken fill, the alpha image ring, and a glyph (or the symbol's first letter). */\nexport function TokenGlyph({ symbol, size = 20, className = \"\" }: TokenGlyphProps) {\n  const glyph = GLYPHS[symbol.toUpperCase()] ?? symbol.slice(0, 1).toUpperCase();\n  return (\n    <span\n      aria-hidden\n      style={{ width: size, height: size, fontSize: Math.round(size * 0.55) } as CSSProperties}\n      className={`relative inline-flex shrink-0 items-center justify-center rounded-full bg-surface-active font-medium leading-none text-text-secondary image-ring select-none ${className}`}\n    >\n      {glyph}\n    </span>\n  );\n}\n\n/* ---------- Chain tints ------------------------------------------------------------------ */\n\nexport type ChainTone = \"info\" | \"brand\" | \"success\" | \"warning\" | \"neutral\";\n\n/** A chain's tint as a token, for washes and dots. Never decoration beyond one glow (rule 35/36). */\nexport const CHAIN_TONE_VAR: Record<ChainTone, string> = {\n  info: \"var(--color-info)\",\n  brand: \"var(--color-brand)\",\n  success: \"var(--color-success)\",\n  warning: \"var(--color-warning)\",\n  neutral: \"var(--color-text-muted)\",\n};\n\n/** A ≤ 10%-alpha radial glow of a chain's tone, for the top of a card (rule 36). */\nexport const chainGlow = (tone: ChainTone, alpha = 10) =>\n  `radial-gradient(120% 80% at 0% 0%, color-mix(in oklch, ${CHAIN_TONE_VAR[tone]} ${alpha}%, transparent), transparent 70%)`;\n\n/* ---------- Formats ---------------------------------------------------------------------- */\n\n/** Default locale for every crypto format: prices read `$67,321.50`, never `US$`. Pass your own. */\nexport const CRYPTO_LOCALE = \"en-US\";\n\n/** `0x7a3F…Bf62`: the head and tail a person actually checks. */\nexport function truncateAddress(address: string, head = 6, tail = 4) {\n  if (address.length <= head + tail + 1) return address;\n  return `${address.slice(0, head)}…${address.slice(-tail)}`;\n}\n\n/** Fraction digits for a USD price: cents above $1, more below, so small tokens stay legible. */\nexport function priceDecimals(price: number) {\n  const p = Math.abs(price);\n  if (p >= 1000) return 2;\n  if (p >= 1) return p >= 10 ? 2 : 3;\n  if (p >= 0.01) return 4;\n  return 6;\n}\n\n/** Intl options for a USD price with `priceDecimals`, ready for NumberRoll's `format`. */\nexport const usdFormat = (price: number, decimals = priceDecimals(price)): Intl.NumberFormatOptions => ({\n  style: \"currency\",\n  currency: \"USD\",\n  minimumFractionDigits: decimals,\n  maximumFractionDigits: decimals,\n});\n\n/** Significant decimals for a token amount: fewer for big holdings, more for fractions of a coin. */\nexport function amountDecimals(amount: number) {\n  const a = Math.abs(amount);\n  if (a === 0) return 2;\n  if (a >= 1000) return 2;\n  if (a >= 1) return 4;\n  return 6;\n}\n\n/** `4.2817`, `12,480.55`, `0.002914` — token amounts at a sensible precision. */\nexport const formatAmount = (amount: number, { locale = CRYPTO_LOCALE, decimals = amountDecimals(amount) }: { locale?: Intl.LocalesArgument; decimals?: number } = {}) =>\n  new Intl.NumberFormat(locale, { minimumFractionDigits: Math.min(2, decimals), maximumFractionDigits: decimals }).format(amount);\n\n/** `$12,480.55` (or `$4.2K` compact). */\nexport const formatUsd = (value: number, { locale = CRYPTO_LOCALE, decimals, compact = false }: { locale?: Intl.LocalesArgument; decimals?: number; compact?: boolean } = {}) =>\n  new Intl.NumberFormat(locale, {\n    style: \"currency\",\n    currency: \"USD\",\n    notation: compact ? \"compact\" : \"standard\",\n    minimumFractionDigits: decimals ?? (compact ? 0 : 2),\n    maximumFractionDigits: decimals ?? (compact ? 1 : 2),\n  }).format(value);\n\n/** `+1.84%` / `−0.93%`, always signed, one decimal unless told otherwise. */\nexport const formatChange = (share: number, { locale = CRYPTO_LOCALE, decimals = 2 }: { locale?: Intl.LocalesArgument; decimals?: number } = {}) =>\n  minusFormatter(locale, { style: \"percent\", minimumFractionDigits: decimals, maximumFractionDigits: decimals, signDisplay: \"exceptZero\" })(share);\n\n/**\n * The market colour of a move: up reads success, down reads danger (trading convention), flat is\n * muted. Only for market moves (prices, rates, P&L). A fall in a metric the user owns, like\n * spend or a balance, reads warning instead (rule 119).\n */\nexport const moveTone = (delta: number) => (delta > 0 ? \"text-success-text\" : delta < 0 ? \"text-danger-text\" : \"text-text-muted\");\n",
      "type": "registry:ui",
      "target": "@components/eased/_primitives/crypto-core/crypto-core.tsx"
    }
  ],
  "type": "registry:ui"
}