mirror of
https://github.com/Such-Software/such.software.git
synced 2026-10-19 05:48:33 +02:00
Add scroll-reveal animations across home, services, portfolio, about, apps
New ScrollReveal client component wraps section-containers and cards with a subtle opacity + y-translate fade-in triggered by IntersectionObserver. Bails out to an unanimated render when prefers-reduced-motion is set. - home: Selected Works, Inclusive by Design, Start a Project - services: each of the four verticals and the Recent Work card - portfolio: each product card, slight stagger by column index - about: Leadership section and Get in Touch - apps: each app card, stagger by index
This commit is contained in:
@@ -0,0 +1,77 @@
|
||||
'use client';
|
||||
|
||||
import { motion, useReducedMotion, type Variants } from 'framer-motion';
|
||||
import { createElement, type ReactNode } from 'react';
|
||||
|
||||
type Direction = 'up' | 'down' | 'left' | 'right' | 'none';
|
||||
type Tag = 'div' | 'section' | 'article' | 'li';
|
||||
|
||||
interface ScrollRevealProps {
|
||||
children: ReactNode;
|
||||
direction?: Direction;
|
||||
delay?: number;
|
||||
duration?: number;
|
||||
distance?: number;
|
||||
amount?: number;
|
||||
className?: string;
|
||||
as?: Tag;
|
||||
}
|
||||
|
||||
const axisFor = (direction: Direction, distance: number) => {
|
||||
switch (direction) {
|
||||
case 'up': return { x: 0, y: distance };
|
||||
case 'down': return { x: 0, y: -distance };
|
||||
case 'left': return { x: distance, y: 0 };
|
||||
case 'right': return { x: -distance, y: 0 };
|
||||
case 'none': return { x: 0, y: 0 };
|
||||
}
|
||||
};
|
||||
|
||||
export function ScrollReveal({
|
||||
children,
|
||||
direction = 'up',
|
||||
delay = 0,
|
||||
duration = 0.6,
|
||||
distance = 24,
|
||||
amount = 0.2,
|
||||
className,
|
||||
as = 'div',
|
||||
}: ScrollRevealProps) {
|
||||
const prefersReducedMotion = useReducedMotion();
|
||||
|
||||
if (prefersReducedMotion) {
|
||||
return createElement(as, { className }, children);
|
||||
}
|
||||
|
||||
const offset = axisFor(direction, distance);
|
||||
const variants: Variants = {
|
||||
hidden: { opacity: 0, ...offset },
|
||||
visible: {
|
||||
opacity: 1,
|
||||
x: 0,
|
||||
y: 0,
|
||||
transition: {
|
||||
duration,
|
||||
delay,
|
||||
ease: [0.16, 1, 0.3, 1],
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const common = {
|
||||
className,
|
||||
initial: 'hidden',
|
||||
whileInView: 'visible',
|
||||
viewport: { once: true, amount },
|
||||
variants,
|
||||
children,
|
||||
};
|
||||
|
||||
const Component =
|
||||
as === 'section' ? motion.section :
|
||||
as === 'article' ? motion.article :
|
||||
as === 'li' ? motion.li :
|
||||
motion.div;
|
||||
|
||||
return createElement(Component as never, common);
|
||||
}
|
||||
Reference in New Issue
Block a user