Add Three.js page graphics and GSAP site-wide motion system
- Home hero: full-height, with an interactive 3D wireframe of my face (MediaPipe landmarks from my-photo.png) rendered as a network graph with packets, scan sweeps and an assemble-on-load entry animation - Inner pages: shared PageHero with a themed scene per page (About helix, Skills geodesic sphere, Work infra stack, Blog signal waves, Contact globe with connection arcs) - Shared Three.js stage (framing, GSAP entry, pointer, off-screen pausing, reduced motion) with lazily loaded per-scene chunks - GSAP motion system: data-reveal / data-reveal-stagger / data-parallax attributes, SplitText headings, divider draw-in, page exit fade, no-JS and reduced-motion fallbacks - Mobile: graphic stacks above the text Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
@@ -0,0 +1,85 @@
|
||||
import React from "react";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
import H1 from "@/src/components/lib/layout/H1";
|
||||
import Section from "@/src/components/lib/layout/Section";
|
||||
import Span from "@/src/components/lib/layout/Span";
|
||||
import Stack from "@/src/components/lib/layout/Stack";
|
||||
import SceneCanvas, { SceneName } from "@/src/components/lib/three/SceneCanvas";
|
||||
|
||||
type Props = {
|
||||
title: React.ReactNode;
|
||||
subtitle?: React.ReactNode;
|
||||
scene: SceneName;
|
||||
/** Small label above the title. */
|
||||
eyebrow?: React.ReactNode;
|
||||
/** Extra content under the subtitle (CTAs, contact details...). */
|
||||
children?: React.ReactNode;
|
||||
className?: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* Inner-page hero: animated title block on the left, the page's 3D graphic
|
||||
* on the right (behind the text, dimmed, on smaller screens).
|
||||
*/
|
||||
export default function PageHero({
|
||||
title,
|
||||
subtitle,
|
||||
scene,
|
||||
eyebrow,
|
||||
children,
|
||||
className,
|
||||
}: Props) {
|
||||
return (
|
||||
<Section
|
||||
className={twMerge(
|
||||
"relative overflow-hidden justify-start xl:justify-center",
|
||||
"xl:min-h-[clamp(440px,64svh,760px)]",
|
||||
"pt-4! xl:pt-20!",
|
||||
className,
|
||||
)}
|
||||
>
|
||||
<div
|
||||
className={twMerge(
|
||||
"relative w-full h-[38svh] min-h-[260px] max-h-[440px] mb-4",
|
||||
"xl:absolute xl:inset-y-0 xl:right-0 xl:left-auto",
|
||||
"xl:w-1/2 xl:h-auto xl:max-h-none xl:mb-0",
|
||||
"xl:[mask-image:linear-gradient(to_right,transparent,black_18%)]",
|
||||
)}
|
||||
data-parallax="0.4"
|
||||
>
|
||||
<SceneCanvas scene={scene} className="w-full h-full" />
|
||||
</div>
|
||||
<Stack className="relative z-10 w-full max-w-full xl:max-w-[50vw] gap-6">
|
||||
{eyebrow && (
|
||||
<Span
|
||||
className="text-sm uppercase tracking-[0.2em] dark:text-emerald-400/80"
|
||||
data-reveal="fade"
|
||||
>
|
||||
{eyebrow}
|
||||
</Span>
|
||||
)}
|
||||
<H1 className="leading-snug m-0!" data-reveal="text">
|
||||
{title}
|
||||
</H1>
|
||||
{subtitle && (
|
||||
<Span
|
||||
className="text-xl"
|
||||
data-reveal="up"
|
||||
data-reveal-delay="0.35"
|
||||
>
|
||||
{subtitle}
|
||||
</Span>
|
||||
)}
|
||||
{children && (
|
||||
<div
|
||||
className="w-full"
|
||||
data-reveal="up"
|
||||
data-reveal-delay="0.5"
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
)}
|
||||
</Stack>
|
||||
</Section>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user