Initial commit gov-llm-v2
This commit is contained in:
207
components/SectionWrapper.tsx
Executable file
207
components/SectionWrapper.tsx
Executable file
@@ -0,0 +1,207 @@
|
||||
|
||||
import React, { useRef } from 'react';
|
||||
import { motion, useScroll, useTransform, Variants, useInView } from 'framer-motion';
|
||||
import { WarpOverlay, SandOverlay } from './VisualEffects';
|
||||
|
||||
export type TransitionType =
|
||||
| 'warp'
|
||||
| 'wipe-right'
|
||||
| 'shutters'
|
||||
| 'paint-ripple'
|
||||
| 'sand'
|
||||
| 'slide'
|
||||
| 'gradient';
|
||||
|
||||
interface Props {
|
||||
children: React.ReactNode;
|
||||
id: string;
|
||||
className?: string;
|
||||
transparent?: boolean;
|
||||
transitionEffect?: TransitionType;
|
||||
fitScreen?: boolean;
|
||||
}
|
||||
|
||||
const SectionWrapper: React.FC<Props> = ({
|
||||
children,
|
||||
id,
|
||||
className = "",
|
||||
transparent = false,
|
||||
transitionEffect = 'slide',
|
||||
fitScreen = true
|
||||
}) => {
|
||||
const ref = useRef<HTMLElement>(null);
|
||||
const isInView = useInView(ref, { amount: 0.2 });
|
||||
|
||||
// Parallax Logic
|
||||
const { scrollYProgress } = useScroll({
|
||||
target: ref,
|
||||
offset: ["start end", "end start"]
|
||||
});
|
||||
|
||||
const y = useTransform(scrollYProgress, [0, 1], ["5%", "-5%"]);
|
||||
const smoothEase: [number, number, number, number] = [0.645, 0.045, 0.355, 1.000];
|
||||
|
||||
// --- RENDER OVERLAYS BASED ON TYPE ---
|
||||
|
||||
const renderOverlay = () => {
|
||||
switch (transitionEffect) {
|
||||
case 'warp':
|
||||
return (
|
||||
<motion.div
|
||||
className="absolute inset-0 z-50 pointer-events-none"
|
||||
initial={{ opacity: 0 }}
|
||||
whileInView={{ opacity: 1 }}
|
||||
viewport={{ once: true, margin: "-10%" }}
|
||||
transition={{ duration: 0.5 }}
|
||||
>
|
||||
{/* Only render WarpOverlay when in view to restart the animation/canvas clock */}
|
||||
{isInView && <WarpOverlay />}
|
||||
<motion.div
|
||||
className="absolute inset-0 bg-[#0e0e10]"
|
||||
initial={{ opacity: 1 }}
|
||||
whileInView={{ opacity: 0 }}
|
||||
viewport={{ once: true }}
|
||||
transition={{ duration: 0.8 }}
|
||||
/>
|
||||
</motion.div>
|
||||
);
|
||||
|
||||
case 'wipe-right':
|
||||
return (
|
||||
<>
|
||||
<motion.div className="absolute inset-0 z-50 bg-[#ff4b4b] pointer-events-none"
|
||||
initial={{ x: "0%" }} whileInView={{ x: "100%" }} viewport={{ once: true, amount: 0.2 }} transition={{ duration: 0.7, ease: smoothEase }} />
|
||||
<motion.div className="absolute inset-0 z-40 bg-[#20e3b2] pointer-events-none"
|
||||
initial={{ x: "0%" }} whileInView={{ x: "100%" }} viewport={{ once: true, amount: 0.2 }} transition={{ duration: 0.7, ease: smoothEase, delay: 0.1 }} />
|
||||
</>
|
||||
);
|
||||
|
||||
case 'shutters':
|
||||
return (
|
||||
<div className="absolute inset-0 z-50 pointer-events-none flex flex-row">
|
||||
{[0, 1, 2, 3].map((i) => (
|
||||
<motion.div
|
||||
key={i}
|
||||
className={`relative h-full w-1/4 bg-[#1c1c1f] border-x border-[#333]/50 ${i % 2 === 0 ? 'border-b-4 border-b-[#20e3b2]' : 'border-t-4 border-t-[#ff4b4b]'}`}
|
||||
initial={{ scaleY: 1 }}
|
||||
whileInView={{ scaleY: 0 }}
|
||||
viewport={{ once: true, amount: 0.2 }}
|
||||
transition={{
|
||||
duration: 0.8,
|
||||
ease: [0.76, 0, 0.24, 1], // expoOut
|
||||
delay: i * 0.05
|
||||
}}
|
||||
style={{ originY: i % 2 === 0 ? 0 : 1 }} // Evens shrink to top, Odds shrink to bottom
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
|
||||
case 'paint-ripple':
|
||||
return (
|
||||
<>
|
||||
{/* The Drop */}
|
||||
<motion.div
|
||||
className="absolute left-1/2 top-0 z-[60] w-3 h-12 bg-white rounded-full pointer-events-none -translate-x-1/2"
|
||||
initial={{ y: "-10vh", opacity: 1 }}
|
||||
whileInView={{ y: "50vh", opacity: [1, 1, 0] }}
|
||||
viewport={{ once: true, amount: 0.4 }}
|
||||
transition={{ duration: 0.6, ease: "easeIn" }}
|
||||
/>
|
||||
{/* The Ripple */}
|
||||
<motion.div
|
||||
className="absolute inset-0 z-0 bg-[#e3e1db] pointer-events-none flex items-center justify-center overflow-hidden"
|
||||
initial={{ clipPath: "circle(0% at 50% 50%)" }}
|
||||
whileInView={{ clipPath: "circle(150% at 50% 50%)" }}
|
||||
viewport={{ once: true, amount: 0.4 }}
|
||||
transition={{ duration: 0.8, ease: "circOut", delay: 0.55 }}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
|
||||
case 'gradient':
|
||||
return (
|
||||
<motion.div
|
||||
className="absolute inset-0 z-[20] pointer-events-none"
|
||||
style={{
|
||||
background: "linear-gradient(to bottom, #e3e1db 0%, transparent 100%)"
|
||||
}}
|
||||
initial={{ opacity: 1 }}
|
||||
whileInView={{ opacity: 0 }}
|
||||
viewport={{ once: true, amount: 0.1 }} // Trigger almost immediately on mobile to prevent blocking text
|
||||
transition={{ duration: 1.5 }}
|
||||
/>
|
||||
);
|
||||
|
||||
case 'sand':
|
||||
return (
|
||||
<motion.div
|
||||
className="absolute inset-0 z-0 overflow-hidden"
|
||||
initial={{ opacity: 0 }}
|
||||
whileInView={{ opacity: 1 }}
|
||||
viewport={{ once: true }}
|
||||
transition={{ duration: 1 }}
|
||||
>
|
||||
<SandOverlay />
|
||||
</motion.div>
|
||||
)
|
||||
|
||||
case 'slide':
|
||||
return (
|
||||
<motion.div
|
||||
className="absolute inset-0 z-50 bg-[#ebe9e4] pointer-events-none"
|
||||
initial={{ y: "0%" }}
|
||||
whileInView={{ y: "100%" }}
|
||||
viewport={{ once: true, amount: 0.01 }} // Trigger almost immediately
|
||||
transition={{ duration: 1.0, ease: [0.76, 0, 0.24, 1] }}
|
||||
/>
|
||||
);
|
||||
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
// --- CONTENT VARIANTS ---
|
||||
const getContentVariants = (): Variants | undefined => {
|
||||
if (transitionEffect === 'sand') {
|
||||
return {
|
||||
hidden: { opacity: 0, scale: 0.95, filter: "blur(10px)" },
|
||||
visible: {
|
||||
opacity: 1,
|
||||
scale: 1,
|
||||
filter: "blur(0px)",
|
||||
transition: { duration: 1.2, ease: "easeOut" }
|
||||
}
|
||||
};
|
||||
}
|
||||
return undefined;
|
||||
};
|
||||
|
||||
return (
|
||||
<section
|
||||
ref={ref}
|
||||
id={id}
|
||||
className={`w-full relative overflow-hidden snap-start shrink-0 flex flex-col
|
||||
min-h-[100dvh]
|
||||
${transparent ? 'bg-transparent' : 'bg-theme-main'}
|
||||
${transitionEffect === 'gradient' ? 'bg-[#0e0e10]' : ''}
|
||||
border-b border-theme/20 ${className}`}
|
||||
>
|
||||
{renderOverlay()}
|
||||
|
||||
<motion.div
|
||||
style={transitionEffect !== 'slide' ? { y } : undefined}
|
||||
initial={transitionEffect === 'sand' ? "hidden" : undefined}
|
||||
whileInView={transitionEffect === 'sand' ? "visible" : undefined}
|
||||
viewport={{ once: true, amount: 0.2 }}
|
||||
variants={getContentVariants()}
|
||||
className="w-full flex-grow flex flex-col items-center justify-center px-4 md:px-12 py-20 md:py-28 relative z-10"
|
||||
>
|
||||
{children}
|
||||
</motion.div>
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
||||
export default SectionWrapper;
|
||||
Reference in New Issue
Block a user