heroes /
Product Cutout Hero (Fashion / Ecom)
Bold-color hero with a full-bleed cutout product or model photo, big serif headline with script accent (Caveat), and a two-tone offer badge floating bottom-right. Built from a
Preview
Source
tsx
import { ArrowRight, MoreHorizontal } from "lucide-react";
export interface ProductCutoutProps {
brand?: string;
eyebrow?: string;
headlinePart1?: string;
headlinePart2?: string;
scriptHighlight?: string; // big italic script word, e.g. "Soul"
description?: string;
primaryCta?: { label: string; href: string };
websiteUrl?: string;
/** Cutout product / model photo. Generate with the same bg color as bgColor for cutout illusion. */
imageSrc?: string;
imageAlt?: string;
badgeOne?: { eyebrow: string; value: string };
badgeTwo?: { eyebrow: string; value: string };
/** Default = bold royal blue #1d6cf2 (matches generated model bg). */
bgColor?: string;
/** Default = bright accent orange #ff6b00 for CTA + badges. */
accentColor?: string;
}
export default function ProductCutoutHero({
brand = "TRADE",
eyebrow = "Fashion",
headlinePart1 = "SPIRIT OF",
headlinePart2 = "YOUR",
scriptHighlight = "Soul",
description = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed diam nonumy nibh euismod tincidunt.",
primaryCta = { label: "LEARN MORE", href: "#shop" },
websiteUrl = "www.website.com",
imageSrc = "/heroes/product-cutout/model.webp?v=2",
imageAlt = "Featured product",
badgeOne = { eyebrow: "BEST", value: "OFFER" },
badgeTwo = { eyebrow: "DISCOUNT", value: "30% OFF" },
bgColor = "#1d6cf2",
accentColor = "#ff6b00",
}: ProductCutoutProps) {
return (
<>
{/* Caveat for the script highlight */}
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link
rel="stylesheet"
href="https://fonts.googleapis.com/css2?family=Caveat:wght@600;700&display=swap"
/>
<section
className="relative min-h-[640px] overflow-hidden text-white"
style={{ background: bgColor }}
>
{/* Soft radial gradient lightener behind the model — gives the
background a circle of slightly-lighter blue that fades into
the base color around it. */}
<div
aria-hidden
className="pointer-events-none absolute inset-0"
style={{
background:
"radial-gradient(circle at 72% 55%, rgba(255,255,255,0.22) 0%, rgba(255,255,255,0.10) 28%, transparent 55%)",
}}
/>
{/* Decorative dots scattered */}
<div aria-hidden className="pointer-events-none absolute inset-0">
<span className="absolute top-[18%] left-[25%] h-1.5 w-1.5 rounded-full bg-white/40" />
<span className="absolute top-[35%] left-[12%] h-2 w-2 rounded-full bg-white/30" />
<span className="absolute top-[8%] right-[35%] h-1.5 w-1.5 rounded-full bg-white/40" />
<span className="absolute bottom-[25%] left-[8%] h-2 w-2 rounded-full bg-white/30" />
<span className="absolute bottom-[10%] right-[12%] h-1.5 w-1.5 rounded-full bg-white/40" />
</div>
<div className="relative mx-auto max-w-7xl px-4 sm:px-6 lg:px-8">
{/* Top bar */}
<div className="flex items-center justify-between py-6">
<a href="/" className="flex items-center gap-2">
<BrandIcon />
<span className="text-xl font-bold tracking-wide text-white">
{brand}
</span>
</a>
<button
type="button"
aria-label="Menu"
className="rounded-full p-2 text-white/80 transition-colors hover:bg-white/10"
>
<MoreHorizontal className="h-5 w-5" />
</button>
</div>
{/* Hero grid — image column is bottom-anchored, copy column stays centered */}
<div className="relative grid items-end gap-8 pt-8 lg:grid-cols-[1fr_1.1fr] lg:gap-4 lg:pt-12">
{/* Left: copy (vertically centered + bottom padding) */}
<div className="relative z-10 max-w-md self-center pb-16 lg:pb-24">
<p className="mb-3 text-sm font-medium tracking-wider uppercase text-white/70">
{eyebrow}
</p>
<h1
className="font-extrabold uppercase leading-[0.95] tracking-tight text-white"
style={{ fontSize: "clamp(2.75rem, 6vw, 4.5rem)" }}
>
{headlinePart1}
<br />
{headlinePart2}{" "}
<span
className="inline-block italic normal-case"
style={{
fontFamily: "'Caveat', cursive",
fontWeight: 700,
color: accentColor,
fontSize: "1.05em",
transform: "translateY(0.05em)",
}}
>
{scriptHighlight}
</span>
</h1>
<p className="mt-6 max-w-sm text-sm leading-relaxed text-white/80">
{description}
</p>
<a
href={primaryCta.href}
className="mt-7 inline-flex items-center gap-2 rounded-full px-7 py-3 text-xs font-bold uppercase tracking-wider text-white shadow-lg transition-all hover:-translate-y-0.5"
style={{ background: accentColor, boxShadow: `0 8px 22px ${accentColor}55` }}
>
{primaryCta.label}
<ArrowRight className="h-4 w-4" />
</a>
<p className="mt-12 text-xs text-white/60">{websiteUrl}</p>
</div>
{/* Right: cutout product image — bottom-anchored, fills column height */}
<div className="relative self-end flex justify-center items-end min-h-[560px] lg:min-h-[640px]">
<img
src={imageSrc}
alt={imageAlt}
width={900}
height={1400}
loading="eager"
fetchPriority="high"
decoding="async"
className="relative z-10 block h-full max-h-[640px] lg:max-h-[720px] w-auto object-contain object-bottom"
/>
{/* Offer badges card — bottom-right, two-tone */}
<div className="absolute bottom-0 right-0 z-20 flex overflow-hidden rounded-2xl bg-white shadow-2xl">
<div
className="flex flex-col justify-center px-5 py-4 text-white"
style={{ background: accentColor }}
>
<span className="text-xs font-bold uppercase tracking-wider opacity-90">
{badgeOne.eyebrow}
</span>
<span className="text-2xl font-extrabold leading-tight">
{badgeOne.value}
</span>
</div>
<div className="flex flex-col justify-center px-5 py-4">
<span
className="text-xs font-bold uppercase tracking-wider"
style={{ color: accentColor }}
>
{badgeTwo.eyebrow}
</span>
<span className="text-2xl font-extrabold leading-tight text-slate-900">
{badgeTwo.value}
</span>
</div>
</div>
</div>
</div>
</div>
</section>
</>
);
}
function BrandIcon() {
return (
<svg viewBox="0 0 24 24" className="h-6 w-6" fill="none" aria-hidden>
<path
d="M4 4 L 12 12 L 4 20 L 12 12 L 20 4 L 12 12 L 20 20"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
className="text-white"
/>
<circle cx="12" cy="12" r="2" fill="currentColor" className="text-white" />
</svg>
);
} Claude Code Instructions
CLI Install
npx innovations add product-cutoutWhere to use it
Use this for ecommerce, fashion, beauty, lifestyle products, drops — anywhere you've got a great product/model shot and want to lead with the offer.
In Astro:
---
import ProductCutoutHero from '../components/innovations/heroes/product-cutout';
---
<ProductCutoutHero />
In Next.js:
import ProductCutoutHero from '@/components/innovations/heroes/product-cutout';
No client:* required — pure presentation.
CRITICAL — IMAGE PREP:
The "cutout" effect works because the model photo's background is the SAME color as the section background (default royal blue #1d6cf2). When generating a replacement product photo:
- Use a solid background that matches bgColor exactly
- OR use a transparent-PNG cutout (if your image tool supports it)
- object-contain ensures the figure isn't cropped
If the photo background doesn't match, you'll see a visible square edge.
CUSTOMIZATION:
<ProductCutoutHero
brand="YOUR BRAND"
eyebrow="Collection"
headlinePart1="MAKE IT"
headlinePart2="YOUR"
scriptHighlight="Story" /* big Caveat script accent */
description="..."
primaryCta={{ label: "SHOP NOW", href: "/shop" }}
websiteUrl="www.yourbrand.com"
imageSrc="/your-product.webp"
badgeOne={{ eyebrow: "NEW", value: "DROP" }}
badgeTwo={{ eyebrow: "FREE", value: "SHIPPING" }}
bgColor="#1d6cf2"
accentColor="#ff6b00"
/>
PALETTE: blue + orange. Both are exposed as props. The script accent uses accentColor.
FONT: Caveat shipped via baked-in Google Fonts <link>.