{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "pack-details",
  "title": "Pack details",
  "description": "A pokerdata.io preflop pack's Details page: the buy header with the table graphic, price and actions, then the tree, payouts, solver run and file. Pass the details from pokerdata.io's partner API, and your own actions.",
  "registryDependencies": [
    "https://www.pokerstudy.ai/r/pack-table.json",
    "https://www.pokerstudy.ai/r/payouts-dialog.json"
  ],
  "files": [
    {
      "path": "src/components/pokerdata/pack-details.tsx",
      "content": "import Link from \"next/link\";\nimport { Fragment, type ReactNode } from \"react\";\nimport { PackTable, seatsLabel } from \"@/components/pokerdata/pack-table-viz\";\nimport { dollars } from \"@/lib/pokerdata/payouts\";\n\n/**\n * A preflop pack's Details page body: the buy header (game, table, street,\n * stack and rake chips beside the table graphic, then the price and actions),\n * and the technical sections, which are the tree, an ICM pack's payouts, the\n * solver run and the file. pokerdata.io builds `PackDetailsInfo` from its\n * catalog (`GET /api/partner/packs/[id]` serves it); the host supplies the\n * actions, or gets the default Buy and Browse links.\n */\n\nexport type PackDetailsInfo = {\n  id: string;\n  /** \"PLO 9-max Preflop Download\". */\n  title: string;\n  seats: number;\n  /** The first chip (\"PLO\"), ahead of the seats, street, stack and rake. */\n  gameType: string | null;\n  stackBb: number;\n  rake: string;\n  price: string;\n  cta: string;\n  checkoutHref: string;\n  /** Where to read the ranges before buying, or null when no explorer serves them. */\n  browseHref: string | null;\n  sample: { href: string; filename: string } | null;\n  /** The one upsell line: the pack's section or game bundle, when cheaper. */\n  bundle: {\n    question: string;\n    name: string;\n    href: string;\n    price: string;\n    compareAt?: string;\n  } | null;\n  tree: [string, string][];\n  /** ICM packs: the payouts their $EV is measured against. */\n  payouts: {\n    intro: string;\n    rows: { places: string; prize: number; share: number }[];\n  } | null;\n  /** The solver's run record, or null when we don't have it. */\n  solve: [string, string][] | null;\n  file: { format: string; download: string; opensWith: string };\n};\n\nconst card = \"rounded-[var(--rb-r-xl,12px)] border border-neutral-800 bg-neutral-900/60\";\n\nconst focus =\n  \"focus-visible:outline-none focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[var(--rb-accent,oklch(100%_0_0))]\";\n\nconst link = \"text-neutral-200 underline underline-offset-2 transition-colors duration-150 hover:text-white\";\n\n/** The header's buttons, for a host that wants pokerdata.io's own. */\nexport const packDetailsButton = {\n  primary: `inline-flex h-10 items-center justify-center rounded-full bg-neutral-100 px-5 text-sm font-medium text-neutral-900 transition-colors duration-150 hover:bg-white ${focus}`,\n  secondary: `inline-flex h-10 items-center justify-center rounded-full border border-neutral-700 px-5 text-sm font-medium text-neutral-200 transition-colors duration-150 hover:border-neutral-500 hover:text-white ${focus}`,\n};\n\nfunction Section({ title, children }: { title: string; children: ReactNode }) {\n  return (\n    <section className={`${card} p-5 sm:p-6`}>\n      <h2 className=\"mb-4 text-[13px] font-medium uppercase tracking-wide text-neutral-500\">{title}</h2>\n      {children}\n    </section>\n  );\n}\n\nfunction Rows({ rows }: { rows: [string, ReactNode][] }) {\n  return (\n    <dl className=\"grid grid-cols-[max-content_1fr] gap-x-8 gap-y-2.5 text-sm\">\n      {rows.map(([dt, dd]) => (\n        <Fragment key={dt}>\n          <dt className=\"text-neutral-500\">{dt}</dt>\n          <dd className=\"break-words text-neutral-300\">{dd}</dd>\n        </Fragment>\n      ))}\n    </dl>\n  );\n}\n\nexport function PackDetails({\n  details: d,\n  actions,\n}: {\n  details: PackDetailsInfo;\n  /** In place of the Buy and Browse links: the host's own buttons. */\n  actions?: ReactNode;\n}) {\n  const chips = [d.gameType, seatsLabel(d.seats), \"Preflop\", `${d.stackBb}bb`, `Rake ${d.rake}`].filter(\n    (c): c is string => Boolean(c),\n  );\n\n  const file: [string, ReactNode][] = [\n    [\"Format\", d.file.format],\n    [\"Download\", d.file.download],\n    [\"Opens with\", d.file.opensWith],\n  ];\n  if (d.sample) {\n    file.push([\n      \"Sample\",\n      <a\n        key=\"sample\"\n        href={d.sample.href}\n        download={d.sample.filename}\n        className=\"text-neutral-100 underline underline-offset-2 transition-colors duration-150 hover:text-white\"\n      >\n        Download a free sample ({d.sample.filename})\n      </a>,\n    ]);\n  }\n\n  return (\n    <div className=\"flex flex-col gap-6\">\n      <header className=\"border-b border-neutral-800 pb-8\">\n        <div className=\"grid gap-8 lg:grid-cols-[minmax(0,1fr)_280px] lg:items-center lg:gap-12\">\n          <div className=\"flex items-center gap-6\">\n            <PackTable seats={d.seats} className=\"hidden h-auto w-[164px] shrink-0 sm:block\" />\n            <div className=\"flex min-w-0 flex-col gap-3\">\n              <h1 className=\"text-3xl font-semibold leading-tight tracking-tight text-neutral-50\">{d.title}</h1>\n              <div className=\"flex flex-wrap gap-1.5\">\n                {chips.map((chip, i) => (\n                  <span\n                    key={chip}\n                    className={\n                      i === 0\n                        ? \"inline-flex h-6 items-center rounded-full bg-neutral-100 px-2.5 text-xs font-medium text-neutral-900\"\n                        : \"inline-flex h-6 items-center rounded-full border border-neutral-800 bg-neutral-900 px-2.5 font-mono text-xs text-neutral-300\"\n                    }\n                  >\n                    {chip}\n                  </span>\n                ))}\n              </div>\n            </div>\n          </div>\n\n          <div className=\"flex flex-col gap-3 border-t border-neutral-800 pt-6 lg:border-t-0 lg:border-l lg:py-1 lg:pl-12 lg:pt-1\">\n            <div className=\"flex items-baseline gap-2\">\n              <span className=\"text-3xl font-semibold tracking-tight text-neutral-50\">{d.price}</span>\n              <span className=\"text-sm text-neutral-500\">one-time</span>\n            </div>\n            {actions ?? (\n              <>\n                <Link href={d.checkoutHref} className={packDetailsButton.primary}>\n                  {d.cta} →\n                </Link>\n                {d.browseHref ? (\n                  <Link href={d.browseHref} className={packDetailsButton.secondary}>\n                    Browse the solution\n                  </Link>\n                ) : null}\n              </>\n            )}\n            <p className=\"text-xs leading-relaxed text-neutral-500\">\n              Instant download after payment, re-downloadable any time.\n            </p>\n            {d.sample ? (\n              <p className=\"text-xs leading-relaxed text-neutral-500\">\n                Not sure yet?{\" \"}\n                <a href={d.sample.href} download={d.sample.filename} className={link}>\n                  Download a free sample\n                </a>\n                .\n              </p>\n            ) : null}\n            {d.bundle ? (\n              <p className=\"border-t border-neutral-800 pt-3 text-xs leading-relaxed text-neutral-500\">\n                {d.bundle.question} The{\" \"}\n                <Link href={d.bundle.href} className={link}>\n                  {d.bundle.name}\n                </Link>{\" \"}\n                is {d.bundle.price}\n                {d.bundle.compareAt ? (\n                  <>\n                    {\" \"}\n                    instead of <s className=\"text-neutral-600\">{d.bundle.compareAt}</s>\n                  </>\n                ) : null}\n                .\n              </p>\n            ) : null}\n          </div>\n        </div>\n      </header>\n\n      <Section title=\"Tree\">\n        <Rows rows={d.tree} />\n      </Section>\n\n      {d.payouts && (\n        <Section title=\"Payouts\">\n          <p className=\"mb-4 text-sm text-neutral-400\">{d.payouts.intro}</p>\n          <Rows\n            rows={d.payouts.rows.map((p) => [\n              p.places,\n              <span key={p.places} className=\"tabular-nums\">\n                {dollars(p.prize)} <span className=\"text-neutral-500\">· {(p.share * 100).toFixed(1)}%</span>\n              </span>,\n            ])}\n          />\n        </Section>\n      )}\n\n      <Section title=\"Solver run\">\n        {d.solve ? (\n          <Rows rows={d.solve} />\n        ) : (\n          <p className=\"text-sm text-neutral-400\">\n            We don&apos;t have the solver&apos;s run record (iterations, solve time) for this pack yet.\n          </p>\n        )}\n      </Section>\n\n      <Section title=\"File\">\n        <Rows rows={file} />\n      </Section>\n    </div>\n  );\n}\n",
      "type": "registry:component",
      "target": "components/pokerdata/pack-details.tsx"
    }
  ],
  "type": "registry:block"
}