Workspace / block / hero
Editorial Hero
paxfx add blocks/block-hero-editorial-01local onlyTier: low. Reduced motion: Headline and copy render in place without the entrance stagger.
"use client";
import { cn, useInView, useReducedMotion, type BlockCopy } from "@pax-sh/blocks";
export interface HeroEditorialProps {
copy?: BlockCopy;
className?: string;
/** "h1" when the hero opens the page (default); "h2" when embedded below one. */
headingLevel?: "h1" | "h2";
}
const DEFAULT_COPY: BlockCopy = {
eyebrow: "Issue 01",
title: "Design is the part your users actually see.",
description:
"Pax gives coding agents real creative range: motion, canvas, and layout that hold up next to work done by hand. This page is set the way a magazine opener is set, because that is the standard.",
primaryCta: { label: "Start with the docs", href: "/docs/installation" },
secondaryCta: { label: "Browse the blocks", href: "/blocks" },
};
/**
* A magazine-opener hero: oversized headline with a hanging eyebrow rule,
* one measured column of standfirst copy, and a single confident CTA pair.
* The entrance is a short typographic settle, skipped under reduced motion.
*/
export function HeroEditorial({
copy = DEFAULT_COPY,
className,
headingLevel = "h1",
}: HeroEditorialProps) {
const Heading = headingLevel;
const reduced = useReducedMotion();
const [ref, inView] = useInView<HTMLDivElement>({ threshold: 0.25 });
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-20 text-[var(--pax-ink,#12110f)] md:px-10 md:py-28",
className,
)}
data-pax-asset="block-hero-editorial-01"
>
<div ref={ref} className="mx-auto max-w-4xl">
{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}
<Heading
className="mt-6 text-4xl font-semibold leading-[1.04] tracking-tight [text-wrap:balance] md:text-6xl"
style={enter(1)}
>
{copy.title}
</Heading>
{copy.description ? (
<p
className="mt-6 max-w-2xl 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-10 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>
</section>
);
}