Founder or creator endorsement hero
Portrait card with a product chip beside a large pull quote, name, role, credentials and an FTC disclosure line for paid partners.
blockendorsementanycta
$
npx shadcn@latest add @storekit/hero-endorsementhero-endorsement
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/hero-endorsement$
pnpm dlx shadcn@latest add @storekit/hero-endorsement$
yarn dlx shadcn@latest add @storekit/hero-endorsement$
bunx --bun shadcn@latest add @storekit/hero-endorsementSource
components/storekit/hero/hero-endorsement.tsx
132 lines
"use client"
import { motion, useReducedMotion } from "motion/react"
import { Quote } from "lucide-react"
import { cn } from "@/components/storekit/lib/cn"
import { useSkBlock } from "@/components/storekit/lib/sk-analytics"
/* Creator or founder endorsement hero. A portrait card on one side, a large
pull quote on the other, then the person's name, role and the credentials
that make the quote count. `kind` switches the small label between a
founder note and a creator or expert partner, and a disclosure line is
rendered for paid partnerships (FTC). The product they are holding links
from a chip pinned to the portrait. Stacks portrait first on mobile. */
const EASE = [0.23, 1, 0.32, 1] as const
type Cta = { label: string; href: string }
const PRIMARY =
"sk-btn inline-flex min-h-12 items-center justify-center py-2.5 text-center leading-tight rounded-sk-btn bg-sk-accent px-7 text-[15px] font-medium text-sk-accent-ink outline-none transition-[filter,transform] duration-200 ease-sk-out hover:brightness-[1.06] focus-visible:ring-2 focus-visible:ring-sk-accent focus-visible:ring-offset-2 focus-visible:ring-offset-sk-paper active:scale-[0.97]"
const SECONDARY =
"sk-btn inline-flex min-h-12 items-center justify-center py-2.5 text-center leading-tight rounded-sk-btn border border-sk-line px-7 text-[15px] font-medium text-sk-ink outline-none transition-[background-color,transform] duration-200 ease-sk-out hover:bg-sk-ink/[0.04] focus-visible:ring-2 focus-visible:ring-sk-accent focus-visible:ring-offset-2 focus-visible:ring-offset-sk-paper active:scale-[0.97]"
export function HeroEndorsement({
kind = "founder",
headline = "Why we make it",
portrait = "/placeholder.webp",
portraitAlt = "",
portraitFocus = "center",
quote = "I built this because I wanted one thing in my morning I never had to think about. Every batch still crosses my desk before it ships.",
name = "Jordan Ellis",
role = "Founder",
credentials = ["Registered dietitian", "12 years in clinical nutrition"],
product,
disclosure,
primaryCta = { label: "Shop the routine", href: "#" },
secondaryCta = { label: "Read the full story", href: "#" },
className,
}: {
kind?: "founder" | "creator" | "expert"
/** Page heading for assistive tech; the quote is the visual headline. */
headline?: string
portrait?: string
portraitAlt?: string
portraitFocus?: string
quote?: string
name?: string
role?: string
credentials?: string[]
/** Product in the shot, shown as a chip on the portrait. */
product?: { title: string; image?: string; href: string }
/** Paid partnership or gifted product note. Required by the FTC for creator deals. */
disclosure?: string
primaryCta?: Cta
secondaryCta?: Cta
className?: string
}) {
const reduce = useReducedMotion()
const sk = useSkBlock("hero", "hero-endorsement")
const label = kind === "founder" ? "A note from our founder" : kind === "expert" ? "Expert pick" : "Creator pick"
const rise = (i: number) => ({
initial: { opacity: 0, y: reduce ? 0 : 8 },
animate: { opacity: 1, y: 0 },
transition: { duration: 0.5, ease: EASE, delay: reduce ? 0 : 0.12 + i * 0.07 },
})
return (
<section ref={sk.ref} {...sk.attrs} className={cn("bg-sk-paper text-sk-ink", className)}>
<div className="mx-auto grid w-full max-w-6xl items-center gap-8 px-4 py-10 sm:px-8 sm:py-14 lg:grid-cols-[0.85fr_1.15fr] lg:gap-16 lg:py-16">
<motion.figure
initial={{ opacity: 0, scale: reduce ? 1 : 0.97 }}
animate={{ opacity: 1, scale: 1 }}
transition={{ duration: 0.6, ease: EASE }}
className="relative mx-auto w-full max-w-[440px] lg:max-w-none"
>
<div className="relative aspect-[4/5] overflow-hidden rounded-sk-card border border-sk-line bg-sk-surface">
<img src={portrait} alt={portraitAlt} className="absolute inset-0 h-full w-full object-cover" style={{ objectPosition: portraitFocus }} />
</div>
{product ? (
<a
href={product.href}
onClick={() => sk.emit("interact", "product_chip")}
className="absolute -bottom-4 right-4 flex items-center gap-3 rounded-sk-card border border-sk-line bg-sk-card py-2 pl-2 pr-4 text-sk-ink shadow-[0_24px_48px_-24px_rgb(var(--sk-ink)/0.35)] outline-none transition-transform duration-300 ease-sk-out hover:-translate-y-0.5 focus-visible:ring-2 focus-visible:ring-sk-accent"
>
{product.image ? <img src={product.image} alt="" className="h-11 w-11 rounded-sk-chip bg-sk-surface object-cover" /> : null}
<span className="flex flex-col leading-tight">
<span className="font-sk-mono text-[10px] uppercase tracking-[0.14em] text-sk-muted">In the shot</span>
<span className="text-sm font-medium">{product.title}</span>
</span>
</a>
) : null}
</motion.figure>
<div className="pt-4 lg:pt-0">
<h1 className="sr-only">{headline}</h1>
<motion.p {...rise(0)} className="font-sk-mono text-[11px] uppercase tracking-[0.14em] text-sk-muted">
{label}
</motion.p>
<motion.blockquote {...rise(1)} className="relative mt-5">
<Quote aria-hidden size={28} className="mb-3 text-sk-accent" />
<p className="text-balance font-sk-display font-semibold leading-[1.15] tracking-[-0.015em]" style={{ fontSize: "clamp(1.6rem, 3.2vw, 2.6rem)" }}>
{quote}
</p>
</motion.blockquote>
<motion.div {...rise(2)} className="mt-7 flex flex-col gap-1">
<p className="text-base font-semibold">{name}</p>
<p className="text-sm text-sk-muted">{role}</p>
{credentials.length ? (
<ul className="mt-3 flex flex-wrap gap-2">
{credentials.map((c) => (
<li key={c} className="rounded-sk-chip border border-sk-line px-3 py-1 text-[12px] text-sk-muted">
{c}
</li>
))}
</ul>
) : null}
</motion.div>
<motion.div {...rise(3)} className="mt-8 flex flex-col gap-3 sm:flex-row">
<a href={primaryCta.href} onClick={() => sk.emit("interact", "cta")} className={PRIMARY}>
{primaryCta.label}
</a>
<a href={secondaryCta.href} onClick={() => sk.emit("interact", "cta_secondary")} className={SECONDARY}>
{secondaryCta.label}
</a>
</motion.div>
{disclosure ? <p className="mt-5 text-[12px] text-sk-muted">{disclosure}</p> : null}
</div>
</div>
</section>
)
}
Usage
Set `kind` to founder, creator or expert. Always pass `disclosure` for paid or gifted creator deals.

