{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "preflop-range-explorer",
  "title": "Preflop range explorer",
  "description": "A live solver decision: the acting seat's range as a hand matrix, its overall mix, and strategy and EV for any hand you pick. Reads the solver through your server.",
  "registryDependencies": [
    "https://www.pokerstudy.ai/r/poker-core.json",
    "https://www.pokerstudy.ai/r/hand-matrix.json",
    "https://www.pokerstudy.ai/r/strategy-bar.json",
    "https://www.pokerstudy.ai/r/pokerstudy-api.json"
  ],
  "files": [
    {
      "path": "src/components/pokerstudy/preflop-range-explorer.tsx",
      "content": "\"use client\";\n\nimport { useMemo, useState } from \"react\";\nimport { HandMatrix, type MatrixCell } from \"@/components/pokerstudy/hand-matrix\";\nimport { StrategyBar } from \"@/components/pokerstudy/strategy-bar\";\nimport { PokerStudyStatus, usePokerStudy } from \"@/components/pokerstudy/use-pokerstudy\";\nimport { ALL_HANDS, colorActions, comboCount, type NodeResponse } from \"@/lib/pokerstudy/poker\";\n\n/**\n * A live preflop decision from the Poker Study AI solver: the acting seat's\n * whole range as a 13×13 matrix, the range's overall mix, and the strategy and\n * EV for whichever hand you pick.\n *\n * It reads `endpoint`, which defaults to the `/api/pokerstudy/node` route this\n * item installs. That route adds your key on the server; the browser never\n * sees it. Your key opens every stack and game; without one only 100bb 6-max\n * cash is free. Pass `initialData` to render a node you fetched on the server.\n */\nexport function PreflopRangeExplorer({\n  stack = 100,\n  history,\n  game = \"nl\",\n  initialHand = null,\n  endpoint = \"/api/pokerstudy/node\",\n  initialData,\n}: {\n  /** Effective stack in big blinds. */\n  stack?: number;\n  /** Action path ending in the seat about to act, e.g. \"UTG_60%_HJ_Fold_CO_Fold_BTN\". */\n  history: string;\n  /** \"nl\" (6-max cash), \"nl9\" (9-max cash) or \"nlmtt8\" (8-max MTT). */\n  game?: \"nl\" | \"nl9\" | \"nlmtt8\";\n  initialHand?: string | null;\n  endpoint?: string;\n  initialData?: NodeResponse;\n}) {\n  const [hand, setHand] = useState<string | null>(initialHand);\n  const query = new URLSearchParams({ stack: String(stack), history, ...(game !== \"nl\" ? { game } : {}) });\n  const state = usePokerStudy<NodeResponse>(`${endpoint}?${query}`, { initialData });\n\n  const node = state.status === \"ready\" ? state.data : null;\n  const colored = useMemo(() => (node ? colorActions(node.actions, node.stack) : []), [node]);\n\n  const cells = useMemo<(MatrixCell | null)[]>(\n    () =>\n      ALL_HANDS.map((h) => {\n        const freqs = colored.map(({ action }) => action.weights[h] ?? 0);\n        return freqs.reduce((s, f) => s + f, 0) > 0.001 ? { label: h, freqs } : null;\n      }),\n    [colored],\n  );\n\n  // The range's overall mix, weighting each class by its combo count.\n  const rangeMix = useMemo(\n    () =>\n      colored.map(({ action, label, color }) => ({\n        label,\n        color,\n        freq: ALL_HANDS.reduce((s, h) => s + (action.weights[h] ?? 0) * comboCount(h), 0),\n      })),\n    [colored],\n  );\n\n  const handMix = useMemo(\n    () =>\n      hand\n        ? colored.map(({ action, label, color }) => {\n            const ev = action.evs?.[hand];\n            return {\n              label,\n              color,\n              freq: action.weights[hand] ?? 0,\n              // The API reports EV in small blinds.\n              detail: ev != null ? `${ev >= 0 ? \"+\" : \"\"}${(ev / 2).toFixed(2)}bb` : undefined,\n            };\n          })\n        : [],\n    [colored, hand],\n  );\n\n  if (state.status !== \"ready\") return <PokerStudyStatus state={state} />;\n\n  return (\n    <div className=\"flex w-full max-w-[560px] flex-col gap-5\">\n      <div className=\"flex items-baseline justify-between gap-2 text-sm\">\n        <span className=\"font-medium text-foreground\">\n          {state.data.actor} to act · {state.data.stack}bb\n        </span>\n        <span className=\"font-mono text-xs text-muted-foreground\">\n          {state.data.continuingCombos != null ? `${Math.round(state.data.continuingCombos)} combos continue` : null}\n        </span>\n      </div>\n      <StrategyBar items={rangeMix} />\n      <HandMatrix\n        cells={cells}\n        colors={colored.map((c) => c.color)}\n        legend={colored.map(({ label, color }) => ({ label, color }))}\n        selected={hand}\n        onSelect={setHand}\n      />\n      {hand ? (\n        <div className=\"flex flex-col gap-2\">\n          <span className=\"text-sm font-medium text-foreground\">{hand}</span>\n          {handMix.some((m) => m.freq > 0) ? (\n            <StrategyBar items={handMix} />\n          ) : (\n            <span className=\"text-sm text-muted-foreground\">Not in this seat&apos;s range here.</span>\n          )}\n        </div>\n      ) : (\n        <span className=\"text-sm text-muted-foreground\">Pick a hand for its strategy and EV.</span>\n      )}\n    </div>\n  );\n}\n",
      "type": "registry:component",
      "target": "components/pokerstudy/preflop-range-explorer.tsx"
    }
  ],
  "type": "registry:block"
}