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-proof
trust-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-proof

Source

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.

Lab results proof, StoreKit