Lab results proof
Lab results lookup with a typewriter placeholder beside a third-party test card with a sequenced 44-segment score bar.
blockcoasupplementswellnesssearch
$
npx shadcn@latest add @storekit/trust-coa-prooftrust-coa-proof
Live and interactive. Switch the brand in the frame header to see the same source under another token file.
Installation
$
npx shadcn@latest add @storekit/trust-coa-proof$
pnpm dlx shadcn@latest add @storekit/trust-coa-proof$
yarn dlx shadcn@latest add @storekit/trust-coa-proof$
bunx --bun shadcn@latest add @storekit/trust-coa-proofSource
components/storekit/trust/trust-coa-proof.tsx
176 lines
"use client"
import { useEffect, useRef, useState, type RefObject } from "react"
import { motion, useInView, useReducedMotion } from "motion/react"
import { ArrowRight } from "lucide-react"
import { cn } from "@/components/storekit/lib/cn"
import { Button } from "@/components/storekit/ui/button"
import { Input } from "@/components/storekit/ui/input"
import { useSkBlock } from "@/components/storekit/lib/sk-analytics"
/* Proof, not promises. A lab results lookup with a typewriter placeholder
beside a third-party test card whose 44-segment score bar fills in
sequence the first time it enters the viewport. */
const EASE = [0.23, 1, 0.32, 1] as const
const PREFIX = "Search "
function useTypewriter(list: string[], paused: boolean) {
const reduce = useReducedMotion()
const [text, setText] = useState(`${PREFIX}${list[0] ?? ""}`)
// Depend on the joined string so a fresh array with the same names does
// not restart the cycle on every render.
const key = list.join("|")
useEffect(() => {
const names = key ? key.split("|") : []
if (reduce || paused || names.length === 0) return
let word = 0
let chars = 0
let dir = 1
let hold = 0
const id = setInterval(() => {
const name = names[word]
if (hold > 0) {
hold--
return
}
chars += dir
if (chars >= name.length) {
chars = name.length
dir = -1
hold = 14
} else if (chars <= 0) {
chars = 0
dir = 1
word = (word + 1) % names.length
hold = 3
}
setText(PREFIX + names[word].slice(0, chars))
}, 90)
return () => clearInterval(id)
}, [key, paused, reduce])
return text
}
export function TrustCoaProof({
eyebrow = "Proof, not promises",
headline = "Every pouch traces back to a lab report.",
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.",
names = ["Daily Greens", "Electrolytes", "Magnesium Night", "Protein"],
score = 98.6,
scoreLabel = "Batches passed on first test",
segments = 44,
rows = [
{ label: "Batch", value: "DG-2417" },
{ label: "Screens", value: "Metals, micro, pesticides" },
{ label: "Lab", value: "Independent, ISO 17025" },
{ label: "Date", value: "2026-09-18" },
],
onSearch,
className,
}: {
eyebrow?: string
headline?: string
body?: string
names?: string[]
score?: number
scoreLabel?: string
segments?: number
rows?: { label: string; value: string }[]
onSearch?: (query: string) => void
className?: string
}) {
const reduce = useReducedMotion()
const sk = useSkBlock("trust", "trust-coa-proof")
const [query, setQuery] = useState("")
const [focused, setFocused] = useState(false)
const placeholder = useTypewriter(names, focused || query.length > 0)
const barRef = useRef<HTMLDivElement>(null)
const inView = useInView(barRef, { once: true, amount: 0.5 })
const filled = Math.round((segments * score) / 100)
return (
<div ref={sk.ref as RefObject<HTMLDivElement>} {...sk.attrs} className={cn("grid items-center gap-12 lg:grid-cols-2 lg:gap-20", className)}>
<div>
<p className="font-sk-mono text-[11px] uppercase tracking-[0.14em] text-sk-muted">{eyebrow}</p>
<h2
className="mt-3 max-w-lg text-balance font-sk-display font-semibold leading-[1.1] tracking-[-0.02em] text-sk-ink"
style={{ fontSize: "clamp(1.9rem, 3.4vw, 2.6rem)" }}
>
{headline}
</h2>
<p className="mt-4 max-w-md text-[15px] leading-[1.5] text-sk-muted">{body}</p>
<form
role="search"
className="mt-8 flex max-w-md flex-col gap-2.5 sm:flex-row"
onSubmit={(e) => {
e.preventDefault()
sk.emit("interact", "view_coa")
onSearch?.(query.trim())
}}
>
<div className="min-w-0 flex-1">
<Input
aria-label="Product name"
value={query}
onChange={(e) => setQuery(e.target.value)}
onFocus={() => setFocused(true)}
onBlur={() => setFocused(false)}
placeholder={focused ? "Product name" : placeholder}
/>
</div>
<Button type="submit" className="active:scale-[0.97]">
View results
<ArrowRight size={16} aria-hidden />
</Button>
</form>
</div>
<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">
<div className="flex items-center justify-between gap-4">
<span className="text-[15px] font-semibold text-sk-ink">Lab results</span>
<span className="inline-flex items-center gap-1.5 text-[13px] font-medium text-sk-savings">
<span className="h-1.5 w-1.5 rounded-full bg-sk-savings" aria-hidden />
Passed
</span>
</div>
<div className="mt-5 flex flex-wrap items-end gap-x-3 gap-y-1">
<span className="font-sk-display text-[46px] font-semibold leading-none tracking-[-0.02em] tabular-nums text-sk-ink">
{score.toFixed(1)}%
</span>
<span className="mb-1 text-[13px] text-sk-muted">{scoreLabel}</span>
</div>
<div
ref={barRef}
role="img"
aria-label={`${scoreLabel}: ${score.toFixed(1)} percent`}
className="mt-5 flex h-5 gap-[3px]"
>
{Array.from({ length: segments }).map((_, i) => (
<span key={i} className="relative h-full flex-1 overflow-hidden rounded-[1px] bg-sk-ink/10">
{i < filled ? (
<motion.span
className="absolute inset-0 origin-bottom bg-sk-accent"
initial={{ scaleY: reduce ? 1 : 0, opacity: reduce ? 1 : 0 }}
animate={inView ? { scaleY: 1, opacity: 1 } : undefined}
transition={{ duration: 0.3, ease: EASE, delay: reduce ? 0 : i * 0.018 }}
/>
) : null}
</span>
))}
</div>
<dl className="mt-6 border-t border-sk-line text-[13px]">
{rows.map((r) => (
<div key={r.label} className="flex items-center justify-between gap-4 border-b border-sk-line py-2.5">
<dt className="font-sk-mono text-[11px] uppercase tracking-[0.14em] text-sk-muted">{r.label}</dt>
<dd className="text-right font-sk-mono tabular-nums text-sk-ink">{r.value}</dd>
</div>
))}
</dl>
</div>
</div>
)
}
Usage
Wire `onSearch` to your lab results route.