Workspace / block / hero

Editorial Hero

A magazine-opener hero: oversized serif-weight headline on paper grain, a single column of measured copy, and one confident call to action.

Workspaceblockhero

paxfx add blocks/block-hero-editorial-01local only

Tier: 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>
  );
}