{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "author": "Lopifi (https://lopifi.com)",
  "categories": [
    "cards",
    "finance",
    "accounting"
  ],
  "dependencies": [],
  "description": "Months of runway with a burn line.",
  "files": [
    {
      "content": "\"use client\";\n\nimport * as React from \"react\";\nimport { cn } from \"@/lib/ledger/cn\";\nimport { formatDate } from \"@/lib/ledger/fiscal-period\";\nimport type { MoneyValue } from \"@/lib/ledger/money.types\";\nimport { Card } from \"@/components/ledger/card\";\nimport { Money } from \"@/components/ledger/money\";\nimport { StatusPill } from \"@/components/ledger/status-pill\";\n\nexport interface RunwayCardProps extends React.ComponentPropsWithoutRef<\"div\"> {\n  /** Actual burn history, then the projection. */\n  actual: number[];\n  cash: MoneyValue;\n  criticalMonths?: number;\n  monthlyBurn: MoneyValue;\n  months: number;\n  projected: number[];\n  /** A business judgment, not a universal truth. */\n  warningMonths?: number;\n  zeroDate: Date;\n}\n\n/**\n * Burn is `polarity=\"inverted\"` — an increase is not good news.\n * The projection segment is dashed, because a projection is not a fact.\n */\nexport const RunwayCard = React.forwardRef<HTMLDivElement, RunwayCardProps>(\n  (\n    {\n      className,\n      months,\n      cash,\n      monthlyBurn,\n      zeroDate,\n      actual,\n      projected,\n      warningMonths = 6,\n      criticalMonths = 3,\n      ...props\n    },\n    ref,\n  ) => {\n    const severity = months < criticalMonths ? \"critical\" : months < warningMonths ? \"warning\" : \"ok\";\n\n    return (\n      <Card className={cn(\"flex flex-col\", className)} ref={ref} {...props}>\n        <div className=\"flex items-start justify-between gap-4\">\n          <span className=\"text-label font-medium text-text-secondary\">Runway</span>\n          {severity !== \"ok\" ? (\n            <StatusPill entity=\"Runway\" status={severity === \"critical\" ? \"overdue\" : \"needs-review\"} />\n          ) : null}\n        </div>\n\n        <p\n          className={cn(\n            \"numeric mt-2 text-figure-hero font-medium leading-none\",\n            severity === \"critical\" && \"text-negative\",\n            severity === \"warning\" && \"text-warning\",\n          )}\n        >\n          {months.toFixed(1)}\n          <span className=\"ml-2 text-title-md font-normal text-text-secondary\">months</span>\n        </p>\n\n        <p className=\"mt-2 text-caption text-text-secondary\">\n          Cash <Money size=\"xs\" tone=\"inherit\" value={cash} /> · burn{\" \"}\n          <Money size=\"xs\" tone=\"inherit\" value={monthlyBurn} />\n          /mo · zero {formatDate(zeroDate)}\n        </p>\n\n        <BurnLine actual={actual} className=\"mt-4\" projected={projected} />\n      </Card>\n    );\n  },\n);\nRunwayCard.displayName = \"RunwayCard\";\n\nfunction BurnLine({\n  actual,\n  projected,\n  className,\n}: {\n  actual: number[];\n  projected: number[];\n  className?: string;\n}) {\n  const all = [...actual, ...projected];\n  const max = Math.max(...all, 1);\n  const width = 240;\n  const height = 56;\n  const step = width / (all.length - 1);\n  const point = (v: number, i: number) =>\n    `${(i * step).toFixed(1)},${(height - (v / max) * height).toFixed(1)}`;\n\n  const actualPath = actual.map((v, i) => `${i === 0 ? \"M\" : \"L\"}${point(v, i)}`).join(\" \");\n  const projectedPath = projected\n    .map((v, i) => `${i === 0 ? \"M\" : \"L\"}${point(v, actual.length - 1 + i)}`)\n    .join(\" \");\n  const bridge = `M${point(actual[actual.length - 1], actual.length - 1)} L${point(projected[0], actual.length - 1)}`;\n\n  return (\n    <svg\n      aria-label=\"Cash balance, actual then projected to zero\"\n      className={cn(\"h-14 w-full\", className)}\n      preserveAspectRatio=\"none\"\n      role=\"img\"\n      viewBox={`0 0 ${width} ${height}`}\n    >\n      <line\n        stroke=\"var(--border-strong)\"\n        strokeWidth=\"1\"\n        vectorEffect=\"non-scaling-stroke\"\n        x1=\"0\"\n        x2={width}\n        y1={height}\n        y2={height}\n      />\n      <path\n        d={actualPath}\n        fill=\"none\"\n        stroke=\"var(--viz-1)\"\n        strokeWidth=\"1.5\"\n        vectorEffect=\"non-scaling-stroke\"\n      />\n      <path\n        d={bridge}\n        fill=\"none\"\n        stroke=\"var(--viz-1)\"\n        strokeDasharray=\"4 3\"\n        strokeWidth=\"1.5\"\n        vectorEffect=\"non-scaling-stroke\"\n      />\n      <path\n        d={projectedPath}\n        fill=\"none\"\n        opacity=\"0.8\"\n        stroke=\"var(--viz-1)\"\n        strokeDasharray=\"4 3\"\n        strokeWidth=\"1.5\"\n        vectorEffect=\"non-scaling-stroke\"\n      />\n    </svg>\n  );\n}\n",
      "path": "components/ledger/runway-card.tsx",
      "target": "components/ledger/runway-card.tsx",
      "type": "registry:component"
    }
  ],
  "meta": {
    "license": "MIT"
  },
  "name": "runway-card",
  "registryDependencies": [
    "@lopifi/theme",
    "@lopifi/card",
    "@lopifi/cn",
    "@lopifi/fiscal-period",
    "@lopifi/money",
    "@lopifi/money.types",
    "@lopifi/status-pill"
  ],
  "title": "RunwayCard",
  "type": "registry:component"
}
