feat: binary micro-interactions across the page
Section headlines are written out behind a column of digits, mono labels resolve from binary, the portrait assembles itself from bits, section indexes rattle and keep one bit alive, cards get corner marks, the header shows the scroll position as an eight-bit number, pressed links and buttons shed a few digits, and the work stage leans towards the pointer. All effects respect reduced motion and leave the content readable without JavaScript. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -2,7 +2,7 @@
|
||||
|
||||
import * as React from "react";
|
||||
import Image from "next/image";
|
||||
import { motion } from "framer-motion";
|
||||
import { motion, useMotionValue, useSpring } from "framer-motion";
|
||||
|
||||
export type WorkItem = {
|
||||
readonly name: string;
|
||||
@@ -88,10 +88,26 @@ const WorkStage: React.FC<{ item: WorkItem }> = ({ item }) => {
|
||||
|
||||
const active = inView && !still;
|
||||
|
||||
// The frames lean a few degrees towards the pointer.
|
||||
const tiltX = useSpring(useMotionValue(0), { stiffness: 120, damping: 18 });
|
||||
const tiltY = useSpring(useMotionValue(0), { stiffness: 120, damping: 18 });
|
||||
const onPointerMove = (event: React.PointerEvent<HTMLDivElement>) => {
|
||||
if (still || event.pointerType !== "mouse") return;
|
||||
const box = event.currentTarget.getBoundingClientRect();
|
||||
tiltY.set(((event.clientX - box.left) / box.width - 0.5) * 7);
|
||||
tiltX.set(-((event.clientY - box.top) / box.height - 0.5) * 5);
|
||||
};
|
||||
const onPointerLeave = () => {
|
||||
tiltX.set(0);
|
||||
tiltY.set(0);
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={ref}
|
||||
className="relative overflow-hidden rounded-3xl bg-slate-900 px-5 pb-0 pt-8 md:px-12 md:pt-14"
|
||||
onPointerMove={onPointerMove}
|
||||
onPointerLeave={onPointerLeave}
|
||||
className="relative overflow-hidden rounded-3xl bg-slate-900 px-5 pb-0 pt-8 [perspective:1400px] md:px-12 md:pt-14"
|
||||
>
|
||||
<div
|
||||
className="pointer-events-none absolute inset-0 opacity-60"
|
||||
@@ -102,7 +118,14 @@ const WorkStage: React.FC<{ item: WorkItem }> = ({ item }) => {
|
||||
aria-hidden="true"
|
||||
/>
|
||||
|
||||
<div className="relative mx-auto max-w-4xl">
|
||||
<motion.div
|
||||
className="relative mx-auto max-w-4xl"
|
||||
style={{
|
||||
rotateX: tiltX,
|
||||
rotateY: tiltY,
|
||||
transformStyle: "preserve-3d",
|
||||
}}
|
||||
>
|
||||
{/* Desktop */}
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 40 }}
|
||||
@@ -150,7 +173,7 @@ const WorkStage: React.FC<{ item: WorkItem }> = ({ item }) => {
|
||||
/>
|
||||
</div>
|
||||
</motion.div>
|
||||
</div>
|
||||
</motion.div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user