nicholais-website/app/sections/WorkSection.tsx

168 lines
6.1 KiB
TypeScript

"use client";
import React from "react";
import { motion, useTransform, type MotionValue } from "motion/react";
import { Pin } from "@/components/parallax/Pin";
import { Parallax } from "@/components/parallax/Parallax";
import { TRANSITIONS } from "@/lib/animation";
export function WorkSection() {
return (
<section id="work" aria-label="Selected Work" className="relative w-full">
{/* Subtle ambient background */}
<Parallax speed={0.04} className="pointer-events-none absolute inset-0 -z-10">
<div className="absolute inset-0 bg-[radial-gradient(1200px_600px_at_10%_20%,rgba(255,255,255,0.06),transparent_70%)]" />
</Parallax>
<Pin heightVH={400} className="w-full">
{(progress) => <WorkPinnedContent progress={progress} />}
</Pin>
</section>
);
}
function WorkPinnedContent({ progress }: { progress: MotionValue<number> }) {
// Safe to use React hooks here (top-level of a component)
const x = useTransform(progress, [0, 1], ["0%", "-400%"]);
return (
<div className="relative h-full w-full overflow-hidden">
{/* Section header overlays the sticky area */}
<div className="pointer-events-none absolute left-1/2 top-10 z-20 -translate-x-1/2 text-center">
<motion.h2
className="text-balance text-2xl font-semibold tracking-tight text-neutral-100 sm:text-3xl md:text-4xl"
initial={{ opacity: 0, y: 10 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={TRANSITIONS.base}
>
Highlights
</motion.h2>
<motion.p
className="mt-2 text-sm text-neutral-400"
initial={{ opacity: 0, y: 6 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ ...TRANSITIONS.base, delay: 0.05 }}
>
A scroll-pinned horizontal showcase powered by Lenis + Framer Motion.
</motion.p>
</div>
{/* Horizontal track */}
<motion.div
style={{ x }}
className="absolute inset-0 flex h-full w-[500vw] items-center gap-[5vw] px-[10vw]"
>
<WorkCard
title="Cinematic Portfolio"
subtitle="Parallax-first hero and narrative scroll"
/>
<WorkCard
title="Interactive Showcase"
subtitle="Pinned scenes and composited layers"
/>
<WorkCard
title="Motion System"
subtitle="Variants, micro-interactions, and rhythm"
/>
<WorkCard
title="A11y + Performance"
subtitle="Prefers-reduced-motion, optimized images"
/>
<WorkCard
title="Design Polish"
subtitle="Grain, gradients, light bloom and depth"
/>
</motion.div>
{/* Edge gradient masks for an infinite feel */}
<div className="pointer-events-none absolute inset-y-0 left-0 w-24 bg-gradient-to-r from-black to-transparent" />
<div className="pointer-events-none absolute inset-y-0 right-0 w-24 bg-gradient-to-l from-black to-transparent" />
</div>
);
}
function WorkCard({
title,
subtitle,
}: {
title: string;
subtitle: string;
}) {
return (
<motion.article
className="group relative h-[66vh] w-[80vw] max-w-[720px] overflow-hidden rounded-3xl glass-strong p-6"
initial={{ opacity: 0, y: 24, scale: 0.98 }}
whileInView={{ opacity: 1, y: 0, scale: 1 }}
viewport={{ once: true, margin: "0px 0px -10% 0px" }}
transition={TRANSITIONS.base}
whileHover={{ scale: 1.02 }}
>
{/* Accent background layers with parallax depth */}
<Parallax speed={0.08} className="pointer-events-none absolute -inset-20 -z-10">
<div className="absolute inset-0 bg-[radial-gradient(600px_300px_at_70%_30%,rgba(255,255,255,0.08),transparent_70%)] blur-2xl" />
</Parallax>
<Parallax speed={-0.06} className="pointer-events-none absolute inset-0 -z-10">
<div className="absolute inset-0 bg-[radial-gradient(600px_300px_at_70%_30%,rgba(255,255,255,0.08),transparent_70%)]" />
</Parallax>
{/* Content */}
<div className="flex h-full flex-col justify-between">
<div>
<motion.h3
className="text-xl font-semibold tracking-tight text-neutral-100"
initial={{ opacity: 0, y: 8 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={TRANSITIONS.base}
>
{title}
</motion.h3>
<motion.p
className="mt-2 max-w-[48ch] text-sm text-neutral-300"
initial={{ opacity: 0, y: 6 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ ...TRANSITIONS.base, delay: 0.05 }}
>
{subtitle}
</motion.p>
</div>
{/* Placeholder visual block; replace with Next/Image for real work later */}
<motion.div
className="relative mt-6 flex flex-1 items-center justify-center overflow-hidden rounded-2xl glass-strong"
initial={{ opacity: 0, y: 10 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ ...TRANSITIONS.base, delay: 0.08 }}
>
<div className="absolute inset-0 bg-[linear-gradient(180deg,transparent,rgba(255,255,255,0.06)_40%,transparent_80%)]" />
<div className="text-xs text-neutral-400">Project visual placeholder</div>
</motion.div>
{/* Footer */}
<div className="mt-4 flex items-center justify-between">
<motion.span
className="text-xs text-neutral-400"
initial={{ opacity: 0 }}
whileInView={{ opacity: 1 }}
viewport={{ once: true }}
transition={TRANSITIONS.base}
>
Scroll to explore
</motion.span>
<motion.button
className="rounded-full glass px-3 py-1 text-xs font-medium transition-colors hover:opacity-95"
whileHover={{ scale: 1.03 }}
whileTap={{ scale: 0.97 }}
>
View case
</motion.button>
</div>
</div>
</motion.article>
);
}