{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "trust-coa-proof",
  "type": "registry:block",
  "title": "Lab results proof",
  "description": "Lab results lookup with a typewriter placeholder beside a third-party test card with a sequenced 44-segment score bar.",
  "author": "StoreKit <hello@storekit.dev>",
  "categories": [
    "trust",
    "supplements",
    "wellness"
  ],
  "dependencies": [
    "motion",
    "lucide-react"
  ],
  "registryDependencies": [
    "https://storekit-eight.vercel.app/r/cn.json",
    "https://storekit-eight.vercel.app/r/button.json",
    "https://storekit-eight.vercel.app/r/input.json",
    "https://storekit-eight.vercel.app/r/sk-analytics.json"
  ],
  "files": [
    {
      "path": "trust/trust-coa-proof.tsx",
      "content": "\"use client\"\n\nimport { useEffect, useRef, useState, type RefObject } from \"react\"\nimport { motion, useInView, useReducedMotion } from \"motion/react\"\nimport { ArrowRight } from \"lucide-react\"\nimport { cn } from \"@/components/storekit/lib/cn\"\nimport { Button } from \"@/components/storekit/ui/button\"\nimport { Input } from \"@/components/storekit/ui/input\"\nimport { useSkBlock } from \"@/components/storekit/lib/sk-analytics\"\n\n/* Proof, not promises. A lab results lookup with a typewriter placeholder\n   beside a third-party test card whose 44-segment score bar fills in\n   sequence the first time it enters the viewport. */\n\nconst EASE = [0.23, 1, 0.32, 1] as const\nconst PREFIX = \"Search \"\n\nfunction useTypewriter(list: string[], paused: boolean) {\n  const reduce = useReducedMotion()\n  const [text, setText] = useState(`${PREFIX}${list[0] ?? \"\"}`)\n  // Depend on the joined string so a fresh array with the same names does\n  // not restart the cycle on every render.\n  const key = list.join(\"|\")\n\n  useEffect(() => {\n    const names = key ? key.split(\"|\") : []\n    if (reduce || paused || names.length === 0) return\n    let word = 0\n    let chars = 0\n    let dir = 1\n    let hold = 0\n    const id = setInterval(() => {\n      const name = names[word]\n      if (hold > 0) {\n        hold--\n        return\n      }\n      chars += dir\n      if (chars >= name.length) {\n        chars = name.length\n        dir = -1\n        hold = 14\n      } else if (chars <= 0) {\n        chars = 0\n        dir = 1\n        word = (word + 1) % names.length\n        hold = 3\n      }\n      setText(PREFIX + names[word].slice(0, chars))\n    }, 90)\n    return () => clearInterval(id)\n  }, [key, paused, reduce])\n\n  return text\n}\n\nexport function TrustCoaProof({\n  eyebrow = \"Proof, not promises\",\n  headline = \"Every pouch traces back to a lab report.\",\n  body = \"Each batch is tested by an independent lab for heavy metals, pesticides and microbials, and the results go up before a single pouch ships. Type a product and read them yourself.\",\n  names = [\"Daily Greens\", \"Electrolytes\", \"Magnesium Night\", \"Protein\"],\n  score = 98.6,\n  scoreLabel = \"Batches passed on first test\",\n  segments = 44,\n  rows = [\n    { label: \"Batch\", value: \"DG-2417\" },\n    { label: \"Screens\", value: \"Metals, micro, pesticides\" },\n    { label: \"Lab\", value: \"Independent, ISO 17025\" },\n    { label: \"Date\", value: \"2026-09-18\" },\n  ],\n  onSearch,\n  className,\n}: {\n  eyebrow?: string\n  headline?: string\n  body?: string\n  names?: string[]\n  score?: number\n  scoreLabel?: string\n  segments?: number\n  rows?: { label: string; value: string }[]\n  onSearch?: (query: string) => void\n  className?: string\n}) {\n  const reduce = useReducedMotion()\n  const sk = useSkBlock(\"trust\", \"trust-coa-proof\")\n  const [query, setQuery] = useState(\"\")\n  const [focused, setFocused] = useState(false)\n  const placeholder = useTypewriter(names, focused || query.length > 0)\n  const barRef = useRef<HTMLDivElement>(null)\n  const inView = useInView(barRef, { once: true, amount: 0.5 })\n  const filled = Math.round((segments * score) / 100)\n\n  return (\n    <div ref={sk.ref as RefObject<HTMLDivElement>} {...sk.attrs} className={cn(\"grid items-center gap-12 lg:grid-cols-2 lg:gap-20\", className)}>\n      <div>\n        <p className=\"font-sk-mono text-[11px] uppercase tracking-[0.14em] text-sk-muted\">{eyebrow}</p>\n        <h2\n          className=\"mt-3 max-w-lg text-balance font-sk-display font-semibold leading-[1.1] tracking-[-0.02em] text-sk-ink\"\n          style={{ fontSize: \"clamp(1.9rem, 3.4vw, 2.6rem)\" }}\n        >\n          {headline}\n        </h2>\n        <p className=\"mt-4 max-w-md text-[15px] leading-[1.5] text-sk-muted\">{body}</p>\n        <form\n          role=\"search\"\n          className=\"mt-8 flex max-w-md flex-col gap-2.5 sm:flex-row\"\n          onSubmit={(e) => {\n            e.preventDefault()\n            sk.emit(\"interact\", \"view_coa\")\n            onSearch?.(query.trim())\n          }}\n        >\n          <div className=\"min-w-0 flex-1\">\n            <Input\n              aria-label=\"Product name\"\n              value={query}\n              onChange={(e) => setQuery(e.target.value)}\n              onFocus={() => setFocused(true)}\n              onBlur={() => setFocused(false)}\n              placeholder={focused ? \"Product name\" : placeholder}\n            />\n          </div>\n          <Button type=\"submit\" className=\"active:scale-[0.97]\">\n            View results\n            <ArrowRight size={16} aria-hidden />\n          </Button>\n        </form>\n      </div>\n\n      <div className=\"w-full max-w-[400px] justify-self-center rounded-sk-card border border-sk-line bg-sk-card p-6 lg:justify-self-end\">\n        <div className=\"flex items-center justify-between gap-4\">\n          <span className=\"text-[15px] font-semibold text-sk-ink\">Lab results</span>\n          <span className=\"inline-flex items-center gap-1.5 text-[13px] font-medium text-sk-savings\">\n            <span className=\"h-1.5 w-1.5 rounded-full bg-sk-savings\" aria-hidden />\n            Passed\n          </span>\n        </div>\n        <div className=\"mt-5 flex flex-wrap items-end gap-x-3 gap-y-1\">\n          <span className=\"font-sk-display text-[46px] font-semibold leading-none tracking-[-0.02em] tabular-nums text-sk-ink\">\n            {score.toFixed(1)}%\n          </span>\n          <span className=\"mb-1 text-[13px] text-sk-muted\">{scoreLabel}</span>\n        </div>\n        <div\n          ref={barRef}\n          role=\"img\"\n          aria-label={`${scoreLabel}: ${score.toFixed(1)} percent`}\n          className=\"mt-5 flex h-5 gap-[3px]\"\n        >\n          {Array.from({ length: segments }).map((_, i) => (\n            <span key={i} className=\"relative h-full flex-1 overflow-hidden rounded-[1px] bg-sk-ink/10\">\n              {i < filled ? (\n                <motion.span\n                  className=\"absolute inset-0 origin-bottom bg-sk-accent\"\n                  initial={{ scaleY: reduce ? 1 : 0, opacity: reduce ? 1 : 0 }}\n                  animate={inView ? { scaleY: 1, opacity: 1 } : undefined}\n                  transition={{ duration: 0.3, ease: EASE, delay: reduce ? 0 : i * 0.018 }}\n                />\n              ) : null}\n            </span>\n          ))}\n        </div>\n        <dl className=\"mt-6 border-t border-sk-line text-[13px]\">\n          {rows.map((r) => (\n            <div key={r.label} className=\"flex items-center justify-between gap-4 border-b border-sk-line py-2.5\">\n              <dt className=\"font-sk-mono text-[11px] uppercase tracking-[0.14em] text-sk-muted\">{r.label}</dt>\n              <dd className=\"text-right font-sk-mono tabular-nums text-sk-ink\">{r.value}</dd>\n            </div>\n          ))}\n        </dl>\n      </div>\n    </div>\n  )\n}\n",
      "type": "registry:block",
      "target": "components/storekit/trust/trust-coa-proof.tsx"
    }
  ],
  "docs": "Wire `onSearch` to your lab results route.",
  "meta": {
    "section": "trust",
    "variant": "coa",
    "niche": [
      "supplements",
      "wellness"
    ],
    "commerce": [
      "search"
    ],
    "previewHeight": 600
  }
}