{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "run-receipt",
  "title": "Run Receipt",
  "description": "A tear-off receipt of a run's tokens, cost and tools that prints line by line.",
  "dependencies": [
    "animejs"
  ],
  "registryDependencies": [
    "https://spotcheckui.com/r/spotcheck-base.json",
    "https://spotcheckui.com/r/use-in-view.json",
    "https://spotcheckui.com/r/use-reduced-motion.json"
  ],
  "files": [
    {
      "path": "registry/spotcheck/components/spotcheck/run-receipt.tsx",
      "content": "\"use client\"\n\nimport * as React from \"react\"\nimport { animate, steps } from \"animejs\"\n\nimport { cn } from \"@/lib/utils\"\nimport { useInView } from \"@/registry/spotcheck/hooks/use-in-view\"\nimport { useReducedMotion } from \"@/registry/spotcheck/hooks/use-reduced-motion\"\n\nconst MS_PER_LINE = 110\nconst TEETH = 16\nconst TOOTH_DEPTH = 7\n\n/** A zigzag bottom edge, as a clip-path. */\nexport function tornEdge(teeth = TEETH, depth = TOOTH_DEPTH) {\n  const points = Array.from({ length: teeth * 2 + 1 }, (_, index) => {\n    const x = (index / (teeth * 2)) * 100\n    return `${100 - x}% ${index % 2 === 0 ? \"100%\" : `calc(100% - ${depth}px)`}`\n  })\n  return `polygon(0 0, 100% 0, ${points.join(\", \")})`\n}\n\nexport type ReceiptLine = { label: string; value: string }\n\ntype RunReceiptProps = {\n  /** The run's name, printed at the top. */\n  title: string\n  /** A second line, such as the model and time. */\n  meta?: string\n  lines: ReceiptLine[]\n  total: ReceiptLine\n  /** Printed at the very bottom. */\n  footer?: string\n  className?: string\n}\n\nexport function RunReceipt({ title, meta, lines, total, footer = \"Thank you for running\", className }: RunReceiptProps) {\n  const host = React.useRef<HTMLDivElement>(null)\n  const paper = React.useRef<HTMLDivElement>(null)\n  const inView = useInView(host)\n  const reduced = useReducedMotion()\n  const printed = React.useRef(false)\n  const count = lines.length + 4\n\n  React.useEffect(() => {\n    if (!inView || reduced || printed.current || !paper.current) return\n    printed.current = true\n    animate(paper.current, {\n      translateY: [\"-100%\", \"0%\"],\n      duration: count * MS_PER_LINE,\n      ease: steps(count),\n    })\n  }, [count, inView, reduced])\n\n  return (\n    <div ref={host} className={cn(\"w-full max-w-80 font-(family-name:--f-body)\", className)}>\n      <div aria-hidden className=\"border-ink bg-ink relative z-10 h-2 border-(length:--rule)\" />\n      <div className=\"-mx-px overflow-hidden px-px pr-1.5 pb-1.5\">\n        <div ref={paper} data-testid=\"receipt-paper\" className=\"drop-shadow-[4px_4px_0_var(--ink)]\">\n          <div\n            className=\"bg-raise px-4 pt-4 pb-6 font-(family-name:--f-mono) text-xs\"\n            style={{ clipPath: tornEdge() }}\n          >\n            <div className=\"spotcheck-display text-center font-(family-name:--f-display) text-[26px] leading-none\">\n              {title}\n            </div>\n            {meta && <div className=\"spotcheck-label mt-1 text-center\">{meta}</div>}\n            <dl className=\"mt-3 border-y border-dashed border-[color-mix(in_srgb,var(--ink)_40%,transparent)] py-2\">\n              {lines.map((line) => (\n                <div key={line.label} className=\"flex items-baseline justify-between gap-3 py-0.5\">\n                  <dt>{line.label}</dt>\n                  <dd className=\"m-0 tabular-nums\">{line.value}</dd>\n                </div>\n              ))}\n            </dl>\n            <div className=\"mt-2 flex items-baseline justify-between gap-3\">\n              <span className=\"font-semibold uppercase\">{total.label}</span>\n              <b className=\"spotcheck-display text-[30px] leading-none tabular-nums\">{total.value}</b>\n            </div>\n            <div className=\"spotcheck-label mt-3 text-center\">{footer}</div>\n          </div>\n        </div>\n      </div>\n    </div>\n  )\n}\n",
      "type": "registry:component"
    }
  ],
  "categories": [
    "ai"
  ],
  "type": "registry:component"
}