Macro detail hero

Hard crop into one texture or ingredient with a zoom and focal point, a corner copy card and a mono caption.

blockmacrobeautysupplementsanycta
$npx shadcn@latest add @storekit/hero-macro
hero-macro

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-macro
$pnpm dlx shadcn@latest add @storekit/hero-macro
$yarn dlx shadcn@latest add @storekit/hero-macro
$bunx --bun shadcn@latest add @storekit/hero-macro

Source

components/storekit/hero/hero-macro.tsx
86 lines
"use client"

import { motion, useReducedMotion } from "motion/react"
import { cn } from "@/components/storekit/lib/cn"
import { useSkBlock } from "@/components/storekit/lib/sk-analytics"

/* Macro product hero. The image is cropped hard into one detail (label,
   powder, cap texture) with `zoom` and a `focus` point, and a small copy card
   sits in the lower corner on the paper token so it reads on any photo. A
   mono caption names what the crop shows. Under md the card drops below the
   crop instead of covering it. */

const EASE = [0.23, 1, 0.32, 1] as const

type Cta = { label: string; href: string }

export function HeroMacro({
  image = "/placeholder.webp",
  imageAlt = "",
  zoom = 2.2,
  focus = { x: 50, y: 62 },
  caption = "Detail: Daily Greens, one scoop",
  eyebrow = "Up close",
  headline = "Real greens look like this.",
  subcopy = "Deep color, fine grind, no fillers. It mixes smooth in water with a quick shake.",
  primaryCta = { label: "Shop Daily Greens", href: "#" },
  className,
}: {
  image?: string
  imageAlt?: string
  /** Scale factor for the crop. 1 shows the whole image. */
  zoom?: number
  /** Focal point in percent of the image. */
  focus?: { x: number; y: number }
  caption?: string
  eyebrow?: string
  headline?: string
  subcopy?: string
  primaryCta?: Cta
  className?: string
}) {
  const reduce = useReducedMotion()
  const sk = useSkBlock("hero", "hero-macro")

  return (
    <section ref={sk.ref} {...sk.attrs} className={cn("bg-sk-paper text-sk-ink", className)}>
      <div className="mx-auto w-full max-w-6xl px-4 py-8 sm:px-8 sm:py-12">
        <div className="relative">
          <div className="relative aspect-[4/5] overflow-hidden rounded-sk-card border border-sk-line bg-sk-surface sm:aspect-[16/10] lg:aspect-[16/8]">
            <img
              src={image}
              alt={imageAlt}
              className="absolute inset-0 h-full w-full object-cover"
              style={{ transform: `scale(${zoom})`, transformOrigin: `${focus.x}% ${focus.y}%` }}
            />
            <p className="absolute right-3 top-3 rounded-sk-chip bg-[rgb(0_0_0/0.55)] px-2.5 py-1 font-sk-mono text-[11px] uppercase tracking-[0.14em] text-[rgb(255_255_255)]">
              {zoom.toFixed(1)}x
            </p>
          </div>

          <motion.div
            initial={{ opacity: 0, y: reduce ? 0 : 8 }}
            animate={{ opacity: 1, y: 0 }}
            transition={{ duration: 0.5, ease: EASE, delay: reduce ? 0 : 0.15 }}
            className="relative mt-4 rounded-sk-card border border-sk-line bg-sk-paper p-5 sm:p-6 md:absolute md:bottom-6 md:left-6 md:mt-0 md:max-w-sm"
          >
            <p className="font-sk-mono text-[11px] uppercase tracking-[0.14em] text-sk-muted">{eyebrow}</p>
            <h1 className="mt-3 text-balance font-sk-display text-[28px] font-semibold leading-[1.1] tracking-[-0.02em]">
              {headline}
            </h1>
            <p className="mt-3 text-sm leading-[1.5] text-sk-muted">{subcopy}</p>
            <a
              href={primaryCta.href}
              onClick={() => sk.emit("interact", "cta")}
              className="sk-btn mt-5 inline-flex h-11 w-full items-center justify-center rounded-sk-btn bg-sk-accent px-6 text-sm 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] sm:w-auto"
            >
              {primaryCta.label}
            </a>
          </motion.div>
        </div>
        <p className="mt-3 font-sk-mono text-[11px] uppercase tracking-[0.14em] text-sk-muted md:text-right">{caption}</p>
      </div>
    </section>
  )
}

Usage

Set `zoom` and `focus` to crop into the detail; the card drops below the crop under md.

Macro detail hero, StoreKit