Workspace / block / hero
Split Hero
paxfx add blocks/block-hero-split-02local onlyTier: medium. Reduced motion: Both columns render settled; the stage shows its static frame.
"use client";
import { cn, useInView, useReducedMotion, type BlockCopy } from "@pax-sh/blocks";
export interface HeroSplitProps {
copy?: BlockCopy;
className?: string;
}
const DEFAULT_COPY: BlockCopy = {
eyebrow: "Pax effects",
title: "The stage is half the story.",
description:
"Copy earns attention, the visual keeps it. Pax pairs every layout with a live stage you can swap for canvas work, product shots, or motion, without touching the grid.",
primaryCta: { label: "Install the CLI", href: "/docs/installation" },
secondaryCta: { label: "See the stages", href: "/blocks" },
};
/**
* Copy column on the left, a layered visual stage on the right: stacked
* glass panels floating over a slowly drifting acid gradient, all CSS.
* The two columns sit on a strict grid that collapses cleanly on mobile.
* Reduced motion freezes the gradient and renders both columns settled.
*/
export function HeroSplit({ copy = DEFAULT_COPY, className }: HeroSplitProps) {
const reduced = useReducedMotion();
const [ref, inView] = useInView<HTMLDivElement>({ threshold: 0.2 });
const settled = reduced || inView;
const enter = (step: number) =>
reduced
? undefined
: ({
opacity: settled ? 1 : 0,
transform: settled ? "translateY(0)" : "translateY(14px)",
transition: `opacity 0.55s ease ${step * 0.09}s, transform 0.6s cubic-bezier(0.22, 1, 0.36, 1) ${step * 0.09}s`,
} as const);
return (
<section
className={cn(
"relative overflow-hidden bg-[var(--pax-paper,#f6f2ea)] px-6 py-16 text-[var(--pax-ink,#12110f)] md:px-10 md:py-24",
className,
)}
data-pax-asset="block-hero-split-02"
>
<style>{`
@keyframes pax-split-drift {
0% { transform: translate3d(-6%, -4%, 0) scale(1); }
50% { transform: translate3d(6%, 5%, 0) scale(1.12); }
100% { transform: translate3d(-6%, -4%, 0) scale(1); }
}
`}</style>
<div
ref={ref}
className="mx-auto grid max-w-6xl items-center gap-12 md:grid-cols-2 md:gap-10"
>
<div>
{copy.eyebrow ? (
<div className="flex items-center gap-4" style={enter(0)}>
<span className="h-px w-10 bg-current opacity-40" aria-hidden="true" />
<p className="m-0 text-xs font-medium uppercase tracking-[0.22em] text-[var(--pax-mute,#6b645c)]">
{copy.eyebrow}
</p>
</div>
) : null}
<h1
className="mt-6 text-4xl font-semibold leading-[1.05] tracking-tight [text-wrap:balance] md:text-5xl"
style={enter(1)}
>
{copy.title}
</h1>
{copy.description ? (
<p
className="mt-5 max-w-xl text-base leading-relaxed text-[var(--pax-mute,#6b645c)] md:text-lg"
style={enter(2)}
>
{copy.description}
</p>
) : null}
{copy.primaryCta || copy.secondaryCta ? (
<div className="mt-9 flex flex-wrap items-center gap-4" style={enter(3)}>
{copy.primaryCta ? (
<a
href={copy.primaryCta.href}
className="rounded-full bg-[var(--pax-ink,#12110f)] px-6 py-3 text-sm font-semibold text-[var(--pax-paper,#f6f2ea)] outline-offset-4 transition-colors hover:bg-[var(--pax-night,#0b0d10)] focus-visible:outline focus-visible:outline-2"
{...(copy.primaryCta.goal ? { "data-fast-goal": copy.primaryCta.goal } : {})}
>
{copy.primaryCta.label}
</a>
) : null}
{copy.secondaryCta ? (
<a
href={copy.secondaryCta.href}
className="text-sm font-semibold underline decoration-[var(--pax-acid,#c8ff00)] decoration-2 underline-offset-4 outline-offset-4 focus-visible:outline focus-visible:outline-2"
{...(copy.secondaryCta.goal ? { "data-fast-goal": copy.secondaryCta.goal } : {})}
>
{copy.secondaryCta.label}
</a>
) : null}
</div>
) : null}
</div>
<div
aria-hidden="true"
className="relative isolate h-72 overflow-hidden rounded-2xl border border-[var(--pax-border,rgba(18,17,15,0.12))] bg-[var(--pax-night,#0b0d10)] md:h-96"
style={enter(2)}
>
{/* Drifting acid gradient field behind the panels. */}
<div
className="absolute -inset-1/4"
style={{
background:
"radial-gradient(42% 46% at 32% 36%, var(--pax-acid,#c8ff00) 0%, transparent 62%), radial-gradient(38% 42% at 70% 68%, var(--pax-mute,#6b645c) 0%, transparent 66%)",
opacity: 0.85,
filter: "blur(28px)",
animation: reduced ? "none" : "pax-split-drift 14s ease-in-out infinite",
}}
/>
{/* Stacked glass panels. */}
<div
className="absolute left-[12%] top-[16%] h-[54%] w-[62%] rounded-xl border border-[rgba(255,255,255,0.22)] backdrop-blur-md"
style={{
background: "linear-gradient(160deg, rgba(255,255,255,0.16), rgba(255,255,255,0.05))",
boxShadow: "inset 0 1px 0 rgba(255,255,255,0.35)",
}}
/>
<div
className="absolute left-[30%] top-[38%] h-[46%] w-[56%] rounded-xl border border-[rgba(255,255,255,0.28)] backdrop-blur-md"
style={{
background: "linear-gradient(160deg, rgba(255,255,255,0.22), rgba(255,255,255,0.07))",
boxShadow: "inset 0 1px 0 rgba(255,255,255,0.45)",
}}
>
<span className="absolute left-4 top-4 h-2 w-2 rounded-full bg-[var(--pax-acid,#c8ff00)]" />
<span className="absolute left-4 top-4 mt-5 block h-1.5 w-2/3 rounded-full bg-[rgba(255,255,255,0.3)]" />
<span className="absolute left-4 top-4 mt-9 block h-1.5 w-1/2 rounded-full bg-[rgba(255,255,255,0.2)]" />
</div>
<div
className="absolute left-[8%] top-[62%] h-[30%] w-[38%] rounded-xl border border-[rgba(255,255,255,0.18)] backdrop-blur-md"
style={{
background: "linear-gradient(160deg, rgba(255,255,255,0.12), rgba(255,255,255,0.04))",
boxShadow: "inset 0 1px 0 rgba(255,255,255,0.3)",
}}
/>
</div>
</div>
</section>
);
}