fix: Reduce component flashing on page load and scroll
Some checks failed
Production Deployment (Zero Downtime) / deploy-production (push) Has been cancelled
Some checks failed
Production Deployment (Zero Downtime) / deploy-production (push) Has been cancelled
- Remove mounted state checks that return null (Hero, About, Projects) - Reduce animation delays and durations for faster initial render - Change viewport margins from -100px to -50px for earlier trigger - Reduce initial animation distances (y: 40 -> 20, y: 30 -> 20) - Use requestAnimationFrame for Header mount to prevent flash - Always render components instead of returning null to prevent layout shift - Optimize Framer Motion transitions for smoother scrolling
This commit is contained in:
@@ -1,6 +1,5 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { useState, useEffect } from "react";
|
|
||||||
import { motion, Variants } from "framer-motion";
|
import { motion, Variants } from "framer-motion";
|
||||||
import { Globe, Server, Wrench, Shield, Gamepad2, Code, Activity, Lightbulb } from "lucide-react";
|
import { Globe, Server, Wrench, Shield, Gamepad2, Code, Activity, Lightbulb } from "lucide-react";
|
||||||
|
|
||||||
@@ -16,24 +15,18 @@ const staggerContainer: Variants = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const fadeInUp: Variants = {
|
const fadeInUp: Variants = {
|
||||||
hidden: { opacity: 0, y: 30 },
|
hidden: { opacity: 0, y: 20 },
|
||||||
visible: {
|
visible: {
|
||||||
opacity: 1,
|
opacity: 1,
|
||||||
y: 0,
|
y: 0,
|
||||||
transition: {
|
transition: {
|
||||||
duration: 1,
|
duration: 0.5,
|
||||||
ease: [0.25, 0.1, 0.25, 1],
|
ease: [0.25, 0.1, 0.25, 1],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const About = () => {
|
const About = () => {
|
||||||
const [mounted, setMounted] = useState(false);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
setMounted(true);
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const techStack = [
|
const techStack = [
|
||||||
{
|
{
|
||||||
category: "Frontend & Mobile",
|
category: "Frontend & Mobile",
|
||||||
@@ -64,8 +57,6 @@ const About = () => {
|
|||||||
{ icon: Activity, text: "Jogging to clear my mind and stay active" },
|
{ icon: Activity, text: "Jogging to clear my mind and stay active" },
|
||||||
];
|
];
|
||||||
|
|
||||||
if (!mounted) return null;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<section
|
<section
|
||||||
id="about"
|
id="about"
|
||||||
|
|||||||
@@ -155,10 +155,10 @@ const Contact = () => {
|
|||||||
<div className="max-w-7xl mx-auto">
|
<div className="max-w-7xl mx-auto">
|
||||||
{/* Section Header */}
|
{/* Section Header */}
|
||||||
<motion.div
|
<motion.div
|
||||||
initial={{ opacity: 0, y: 30 }}
|
initial={{ opacity: 0, y: 20 }}
|
||||||
whileInView={{ opacity: 1, y: 0 }}
|
whileInView={{ opacity: 1, y: 0 }}
|
||||||
viewport={{ once: true, margin: "-100px" }}
|
viewport={{ once: true, margin: "-50px" }}
|
||||||
transition={{ duration: 1, ease: [0.25, 0.1, 0.25, 1] }}
|
transition={{ duration: 0.5, ease: [0.25, 0.1, 0.25, 1] }}
|
||||||
className="text-center mb-16"
|
className="text-center mb-16"
|
||||||
>
|
>
|
||||||
<h2 className="text-4xl md:text-5xl font-bold mb-6 text-stone-900">
|
<h2 className="text-4xl md:text-5xl font-bold mb-6 text-stone-900">
|
||||||
@@ -173,10 +173,10 @@ const Contact = () => {
|
|||||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-12">
|
<div className="grid grid-cols-1 lg:grid-cols-2 gap-12">
|
||||||
{/* Contact Information */}
|
{/* Contact Information */}
|
||||||
<motion.div
|
<motion.div
|
||||||
initial={{ opacity: 0, x: -30 }}
|
initial={{ opacity: 0, x: -20 }}
|
||||||
whileInView={{ opacity: 1, x: 0 }}
|
whileInView={{ opacity: 1, x: 0 }}
|
||||||
viewport={{ once: true, margin: "-100px" }}
|
viewport={{ once: true, margin: "-50px" }}
|
||||||
transition={{ duration: 1, ease: [0.25, 0.1, 0.25, 1] }}
|
transition={{ duration: 0.5, ease: [0.25, 0.1, 0.25, 1] }}
|
||||||
className="space-y-8"
|
className="space-y-8"
|
||||||
>
|
>
|
||||||
<div>
|
<div>
|
||||||
@@ -196,12 +196,12 @@ const Contact = () => {
|
|||||||
<motion.a
|
<motion.a
|
||||||
key={info.title}
|
key={info.title}
|
||||||
href={info.href}
|
href={info.href}
|
||||||
initial={{ opacity: 0, x: -20 }}
|
initial={{ opacity: 0, x: -10 }}
|
||||||
whileInView={{ opacity: 1, x: 0 }}
|
whileInView={{ opacity: 1, x: 0 }}
|
||||||
viewport={{ once: true }}
|
viewport={{ once: true, margin: "-50px" }}
|
||||||
transition={{
|
transition={{
|
||||||
duration: 0.8,
|
duration: 0.5,
|
||||||
delay: index * 0.15,
|
delay: index * 0.1,
|
||||||
ease: [0.25, 0.1, 0.25, 1],
|
ease: [0.25, 0.1, 0.25, 1],
|
||||||
}}
|
}}
|
||||||
whileHover={{
|
whileHover={{
|
||||||
@@ -226,10 +226,10 @@ const Contact = () => {
|
|||||||
|
|
||||||
{/* Contact Form */}
|
{/* Contact Form */}
|
||||||
<motion.div
|
<motion.div
|
||||||
initial={{ opacity: 0, x: 30 }}
|
initial={{ opacity: 0, x: 20 }}
|
||||||
whileInView={{ opacity: 1, x: 0 }}
|
whileInView={{ opacity: 1, x: 0 }}
|
||||||
viewport={{ once: true, margin: "-100px" }}
|
viewport={{ once: true, margin: "-50px" }}
|
||||||
transition={{ duration: 1, ease: [0.25, 0.1, 0.25, 1] }}
|
transition={{ duration: 0.5, ease: [0.25, 0.1, 0.25, 1] }}
|
||||||
className="glass-card p-8 rounded-3xl bg-white/50 border border-white/70"
|
className="glass-card p-8 rounded-3xl bg-white/50 border border-white/70"
|
||||||
>
|
>
|
||||||
<h3 className="text-2xl font-bold text-gray-800 mb-6">
|
<h3 className="text-2xl font-bold text-gray-800 mb-6">
|
||||||
|
|||||||
@@ -30,10 +30,10 @@ const Footer = () => {
|
|||||||
<div className="flex flex-col md:flex-row justify-between items-center space-y-6 md:space-y-0">
|
<div className="flex flex-col md:flex-row justify-between items-center space-y-6 md:space-y-0">
|
||||||
{/* Brand */}
|
{/* Brand */}
|
||||||
<motion.div
|
<motion.div
|
||||||
initial={{ opacity: 0, y: 20 }}
|
initial={{ opacity: 0, y: 10 }}
|
||||||
whileInView={{ opacity: 1, y: 0 }}
|
whileInView={{ opacity: 1, y: 0 }}
|
||||||
viewport={{ once: true }}
|
viewport={{ once: true, margin: "-50px" }}
|
||||||
transition={{ duration: 0.6 }}
|
transition={{ duration: 0.4 }}
|
||||||
className="flex items-center space-x-3"
|
className="flex items-center space-x-3"
|
||||||
>
|
>
|
||||||
<motion.div
|
<motion.div
|
||||||
@@ -53,10 +53,10 @@ const Footer = () => {
|
|||||||
|
|
||||||
{/* Social Links */}
|
{/* Social Links */}
|
||||||
<motion.div
|
<motion.div
|
||||||
initial={{ opacity: 0, y: 20 }}
|
initial={{ opacity: 0, y: 10 }}
|
||||||
whileInView={{ opacity: 1, y: 0 }}
|
whileInView={{ opacity: 1, y: 0 }}
|
||||||
viewport={{ once: true }}
|
viewport={{ once: true, margin: "-50px" }}
|
||||||
transition={{ duration: 0.6, delay: 0.1 }}
|
transition={{ duration: 0.4, delay: 0.05 }}
|
||||||
className="flex space-x-3"
|
className="flex space-x-3"
|
||||||
>
|
>
|
||||||
{socialLinks.map((social) => (
|
{socialLinks.map((social) => (
|
||||||
@@ -77,10 +77,10 @@ const Footer = () => {
|
|||||||
|
|
||||||
{/* Copyright */}
|
{/* Copyright */}
|
||||||
<motion.div
|
<motion.div
|
||||||
initial={{ opacity: 0, y: 20 }}
|
initial={{ opacity: 0, y: 10 }}
|
||||||
whileInView={{ opacity: 1, y: 0 }}
|
whileInView={{ opacity: 1, y: 0 }}
|
||||||
viewport={{ once: true }}
|
viewport={{ once: true, margin: "-50px" }}
|
||||||
transition={{ duration: 0.6, delay: 0.2 }}
|
transition={{ duration: 0.4, delay: 0.1 }}
|
||||||
className="flex items-center space-x-2 text-stone-400 text-sm"
|
className="flex items-center space-x-2 text-stone-400 text-sm"
|
||||||
>
|
>
|
||||||
<span>© {currentYear}</span>
|
<span>© {currentYear}</span>
|
||||||
@@ -96,10 +96,10 @@ const Footer = () => {
|
|||||||
|
|
||||||
{/* Legal Links */}
|
{/* Legal Links */}
|
||||||
<motion.div
|
<motion.div
|
||||||
initial={{ opacity: 0, y: 20 }}
|
initial={{ opacity: 0, y: 10 }}
|
||||||
whileInView={{ opacity: 1, y: 0 }}
|
whileInView={{ opacity: 1, y: 0 }}
|
||||||
viewport={{ once: true }}
|
viewport={{ once: true, margin: "-50px" }}
|
||||||
transition={{ duration: 0.6, delay: 0.3 }}
|
transition={{ duration: 0.4, delay: 0.15 }}
|
||||||
className="mt-8 pt-6 border-t border-stone-100 flex flex-col md:flex-row justify-between items-center space-y-4 md:space-y-0"
|
className="mt-8 pt-6 border-t border-stone-100 flex flex-col md:flex-row justify-between items-center space-y-4 md:space-y-0"
|
||||||
>
|
>
|
||||||
<div className="flex space-x-6 text-sm">
|
<div className="flex space-x-6 text-sm">
|
||||||
|
|||||||
@@ -12,7 +12,10 @@ const Header = () => {
|
|||||||
const [mounted, setMounted] = useState(false);
|
const [mounted, setMounted] = useState(false);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setMounted(true);
|
// Use requestAnimationFrame to ensure smooth transition
|
||||||
|
requestAnimationFrame(() => {
|
||||||
|
setMounted(true);
|
||||||
|
});
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -41,17 +44,16 @@ const Header = () => {
|
|||||||
{ icon: Mail, href: "mailto:contact@dk0.dev", label: "Email" },
|
{ icon: Mail, href: "mailto:contact@dk0.dev", label: "Email" },
|
||||||
];
|
];
|
||||||
|
|
||||||
if (!mounted) {
|
// Always render to prevent flash, but use opacity transition
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<motion.header
|
<motion.header
|
||||||
initial={{ y: -100, opacity: 0 }}
|
initial={false}
|
||||||
animate={{ y: 0, opacity: 1 }}
|
animate={{ y: 0, opacity: mounted ? 1 : 0 }}
|
||||||
transition={{ duration: 0.8, ease: "easeOut" }}
|
transition={{ duration: 0.3, ease: "easeOut" }}
|
||||||
className="fixed top-6 left-0 right-0 z-50 flex justify-center px-4 pointer-events-none"
|
className="fixed top-6 left-0 right-0 z-50 flex justify-center px-4 pointer-events-none"
|
||||||
|
style={{ opacity: mounted ? 1 : 0 }}
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
className={`pointer-events-auto transition-all duration-500 ease-out ${
|
className={`pointer-events-auto transition-all duration-500 ease-out ${
|
||||||
@@ -59,9 +61,9 @@ const Header = () => {
|
|||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
<motion.div
|
<motion.div
|
||||||
initial={{ opacity: 0, y: -20 }}
|
initial={false}
|
||||||
animate={{ opacity: 1, y: 0 }}
|
animate={{ opacity: mounted ? 1 : 0, y: 0 }}
|
||||||
transition={{ duration: 0.6, delay: 0.2, ease: "easeOut" }}
|
transition={{ duration: 0.3, ease: "easeOut" }}
|
||||||
className={`
|
className={`
|
||||||
backdrop-blur-xl transition-all duration-500
|
backdrop-blur-xl transition-all duration-500
|
||||||
${
|
${
|
||||||
|
|||||||
@@ -1,27 +1,16 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { useState, useEffect } from "react";
|
|
||||||
import { motion } from "framer-motion";
|
import { motion } from "framer-motion";
|
||||||
import { ArrowDown, Code, Zap, Rocket } from "lucide-react";
|
import { ArrowDown, Code, Zap, Rocket } from "lucide-react";
|
||||||
import Image from "next/image";
|
import Image from "next/image";
|
||||||
|
|
||||||
const Hero = () => {
|
const Hero = () => {
|
||||||
const [mounted, setMounted] = useState(false);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
setMounted(true);
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const features = [
|
const features = [
|
||||||
{ icon: Code, text: "Next.js & Flutter" },
|
{ icon: Code, text: "Next.js & Flutter" },
|
||||||
{ icon: Zap, text: "Docker Swarm & CI/CD" },
|
{ icon: Zap, text: "Docker Swarm & CI/CD" },
|
||||||
{ icon: Rocket, text: "Self-Hosted Infrastructure" },
|
{ icon: Rocket, text: "Self-Hosted Infrastructure" },
|
||||||
];
|
];
|
||||||
|
|
||||||
if (!mounted) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<section className="relative min-h-screen flex items-center justify-center overflow-hidden pt-32 pb-16 bg-gradient-to-br from-liquid-mint/10 via-liquid-lavender/10 to-liquid-rose/10">
|
<section className="relative min-h-screen flex items-center justify-center overflow-hidden pt-32 pb-16 bg-gradient-to-br from-liquid-mint/10 via-liquid-lavender/10 to-liquid-rose/10">
|
||||||
<div className="relative z-10 text-center px-4 max-w-5xl mx-auto">
|
<div className="relative z-10 text-center px-4 max-w-5xl mx-auto">
|
||||||
@@ -29,7 +18,7 @@ const Hero = () => {
|
|||||||
<motion.div
|
<motion.div
|
||||||
initial={{ opacity: 0, scale: 0.9 }}
|
initial={{ opacity: 0, scale: 0.9 }}
|
||||||
animate={{ opacity: 1, scale: 1 }}
|
animate={{ opacity: 1, scale: 1 }}
|
||||||
transition={{ duration: 1.2, delay: 0.3, ease: [0.25, 0.1, 0.25, 1] }}
|
transition={{ duration: 0.6, delay: 0.1, ease: [0.25, 0.1, 0.25, 1] }}
|
||||||
className="mb-12 flex justify-center relative z-20"
|
className="mb-12 flex justify-center relative z-20"
|
||||||
>
|
>
|
||||||
<div className="relative w-64 h-64 md:w-80 md:h-80 flex items-center justify-center">
|
<div className="relative w-64 h-64 md:w-80 md:h-80 flex items-center justify-center">
|
||||||
@@ -111,7 +100,7 @@ const Hero = () => {
|
|||||||
<motion.div
|
<motion.div
|
||||||
initial={{ opacity: 0, y: 20 }}
|
initial={{ opacity: 0, y: 20 }}
|
||||||
animate={{ opacity: 1, y: 0 }}
|
animate={{ opacity: 1, y: 0 }}
|
||||||
transition={{ duration: 1, delay: 0.8, ease: "easeOut" }}
|
transition={{ duration: 0.6, delay: 0.3, ease: "easeOut" }}
|
||||||
className="absolute -bottom-8 left-1/2 -translate-x-1/2 z-30"
|
className="absolute -bottom-8 left-1/2 -translate-x-1/2 z-30"
|
||||||
>
|
>
|
||||||
<div className="px-6 py-2.5 rounded-full glass-panel text-stone-700 font-mono text-sm tracking-wider shadow-lg backdrop-blur-xl border border-white/50">
|
<div className="px-6 py-2.5 rounded-full glass-panel text-stone-700 font-mono text-sm tracking-wider shadow-lg backdrop-blur-xl border border-white/50">
|
||||||
@@ -123,7 +112,7 @@ const Hero = () => {
|
|||||||
<motion.div
|
<motion.div
|
||||||
initial={{ scale: 0, opacity: 0 }}
|
initial={{ scale: 0, opacity: 0 }}
|
||||||
animate={{ scale: 1, opacity: 1 }}
|
animate={{ scale: 1, opacity: 1 }}
|
||||||
transition={{ delay: 1.2, duration: 0.8, ease: "easeOut" }}
|
transition={{ delay: 0.4, duration: 0.5, ease: "easeOut" }}
|
||||||
whileHover={{ scale: 1.1, rotate: 5 }}
|
whileHover={{ scale: 1.1, rotate: 5 }}
|
||||||
className="absolute -top-4 right-0 md:-right-4 p-3 bg-white/95 backdrop-blur-md shadow-lg rounded-full text-stone-700 z-30"
|
className="absolute -top-4 right-0 md:-right-4 p-3 bg-white/95 backdrop-blur-md shadow-lg rounded-full text-stone-700 z-30"
|
||||||
>
|
>
|
||||||
@@ -132,7 +121,7 @@ const Hero = () => {
|
|||||||
<motion.div
|
<motion.div
|
||||||
initial={{ scale: 0, opacity: 0 }}
|
initial={{ scale: 0, opacity: 0 }}
|
||||||
animate={{ scale: 1, opacity: 1 }}
|
animate={{ scale: 1, opacity: 1 }}
|
||||||
transition={{ delay: 1.4, duration: 0.8, ease: "easeOut" }}
|
transition={{ delay: 0.5, duration: 0.5, ease: "easeOut" }}
|
||||||
whileHover={{ scale: 1.1, rotate: -5 }}
|
whileHover={{ scale: 1.1, rotate: -5 }}
|
||||||
className="absolute bottom-4 -left-4 md:-left-8 p-3 bg-white/95 backdrop-blur-md shadow-lg rounded-full text-stone-700 z-30"
|
className="absolute bottom-4 -left-4 md:-left-8 p-3 bg-white/95 backdrop-blur-md shadow-lg rounded-full text-stone-700 z-30"
|
||||||
>
|
>
|
||||||
@@ -145,7 +134,7 @@ const Hero = () => {
|
|||||||
<motion.div
|
<motion.div
|
||||||
initial={{ opacity: 0, y: 20 }}
|
initial={{ opacity: 0, y: 20 }}
|
||||||
animate={{ opacity: 1, y: 0 }}
|
animate={{ opacity: 1, y: 0 }}
|
||||||
transition={{ duration: 1, delay: 0.6, ease: [0.25, 0.1, 0.25, 1] }}
|
transition={{ duration: 0.6, delay: 0.2, ease: [0.25, 0.1, 0.25, 1] }}
|
||||||
className="mb-8 flex flex-col items-center justify-center relative"
|
className="mb-8 flex flex-col items-center justify-center relative"
|
||||||
>
|
>
|
||||||
<h1 className="text-5xl md:text-8xl font-bold tracking-tighter text-stone-900 mb-2">
|
<h1 className="text-5xl md:text-8xl font-bold tracking-tighter text-stone-900 mb-2">
|
||||||
@@ -160,7 +149,7 @@ const Hero = () => {
|
|||||||
<motion.p
|
<motion.p
|
||||||
initial={{ opacity: 0, y: 20 }}
|
initial={{ opacity: 0, y: 20 }}
|
||||||
animate={{ opacity: 1, y: 0 }}
|
animate={{ opacity: 1, y: 0 }}
|
||||||
transition={{ duration: 1, delay: 0.9, ease: [0.25, 0.1, 0.25, 1] }}
|
transition={{ duration: 0.6, delay: 0.3, ease: [0.25, 0.1, 0.25, 1] }}
|
||||||
className="text-lg md:text-xl text-stone-700 mb-12 max-w-2xl mx-auto leading-relaxed"
|
className="text-lg md:text-xl text-stone-700 mb-12 max-w-2xl mx-auto leading-relaxed"
|
||||||
>
|
>
|
||||||
Student and passionate{" "}
|
Student and passionate{" "}
|
||||||
@@ -182,7 +171,7 @@ const Hero = () => {
|
|||||||
<motion.div
|
<motion.div
|
||||||
initial={{ opacity: 0, y: 20 }}
|
initial={{ opacity: 0, y: 20 }}
|
||||||
animate={{ opacity: 1, y: 0 }}
|
animate={{ opacity: 1, y: 0 }}
|
||||||
transition={{ duration: 1, delay: 1.1, ease: [0.25, 0.1, 0.25, 1] }}
|
transition={{ duration: 0.6, delay: 0.4, ease: [0.25, 0.1, 0.25, 1] }}
|
||||||
className="flex flex-wrap justify-center gap-4 mb-12"
|
className="flex flex-wrap justify-center gap-4 mb-12"
|
||||||
>
|
>
|
||||||
{features.map((feature, index) => (
|
{features.map((feature, index) => (
|
||||||
@@ -191,8 +180,8 @@ const Hero = () => {
|
|||||||
initial={{ opacity: 0, scale: 0.9 }}
|
initial={{ opacity: 0, scale: 0.9 }}
|
||||||
animate={{ opacity: 1, scale: 1 }}
|
animate={{ opacity: 1, scale: 1 }}
|
||||||
transition={{
|
transition={{
|
||||||
duration: 0.8,
|
duration: 0.5,
|
||||||
delay: 1.3 + index * 0.15,
|
delay: 0.5 + index * 0.1,
|
||||||
ease: [0.25, 0.1, 0.25, 1],
|
ease: [0.25, 0.1, 0.25, 1],
|
||||||
}}
|
}}
|
||||||
whileHover={{ scale: 1.03, y: -3 }}
|
whileHover={{ scale: 1.03, y: -3 }}
|
||||||
@@ -210,7 +199,7 @@ const Hero = () => {
|
|||||||
<motion.div
|
<motion.div
|
||||||
initial={{ opacity: 0, y: 20 }}
|
initial={{ opacity: 0, y: 20 }}
|
||||||
animate={{ opacity: 1, y: 0 }}
|
animate={{ opacity: 1, y: 0 }}
|
||||||
transition={{ duration: 1, delay: 1.6, ease: [0.25, 0.1, 0.25, 1] }}
|
transition={{ duration: 0.6, delay: 0.6, ease: [0.25, 0.1, 0.25, 1] }}
|
||||||
className="flex flex-col sm:flex-row gap-5 justify-center items-center"
|
className="flex flex-col sm:flex-row gap-5 justify-center items-center"
|
||||||
>
|
>
|
||||||
<motion.a
|
<motion.a
|
||||||
|
|||||||
@@ -7,12 +7,12 @@ import Link from "next/link";
|
|||||||
import Image from "next/image";
|
import Image from "next/image";
|
||||||
|
|
||||||
const fadeInUp: Variants = {
|
const fadeInUp: Variants = {
|
||||||
hidden: { opacity: 0, y: 40 },
|
hidden: { opacity: 0, y: 20 },
|
||||||
visible: {
|
visible: {
|
||||||
opacity: 1,
|
opacity: 1,
|
||||||
y: 0,
|
y: 0,
|
||||||
transition: {
|
transition: {
|
||||||
duration: 0.8,
|
duration: 0.5,
|
||||||
ease: [0.25, 0.1, 0.25, 1],
|
ease: [0.25, 0.1, 0.25, 1],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -44,11 +44,9 @@ interface Project {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const Projects = () => {
|
const Projects = () => {
|
||||||
const [mounted, setMounted] = useState(false);
|
|
||||||
const [projects, setProjects] = useState<Project[]>([]);
|
const [projects, setProjects] = useState<Project[]>([]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setMounted(true);
|
|
||||||
const loadProjects = async () => {
|
const loadProjects = async () => {
|
||||||
try {
|
try {
|
||||||
const response = await fetch(
|
const response = await fetch(
|
||||||
@@ -58,7 +56,7 @@ const Projects = () => {
|
|||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
setProjects(data.projects || []);
|
setProjects(data.projects || []);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (process.env.NODE_ENV === "development") {
|
if (process.env.NODE_ENV === "development") {
|
||||||
console.error("Error loading projects:", error);
|
console.error("Error loading projects:", error);
|
||||||
}
|
}
|
||||||
@@ -67,8 +65,6 @@ const Projects = () => {
|
|||||||
loadProjects();
|
loadProjects();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
if (!mounted) return null;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<section
|
<section
|
||||||
id="projects"
|
id="projects"
|
||||||
@@ -78,7 +74,7 @@ const Projects = () => {
|
|||||||
<motion.div
|
<motion.div
|
||||||
initial="hidden"
|
initial="hidden"
|
||||||
whileInView="visible"
|
whileInView="visible"
|
||||||
viewport={{ once: true, margin: "-100px" }}
|
viewport={{ once: true, margin: "-50px" }}
|
||||||
variants={fadeInUp}
|
variants={fadeInUp}
|
||||||
className="text-center mb-20"
|
className="text-center mb-20"
|
||||||
>
|
>
|
||||||
@@ -94,7 +90,7 @@ const Projects = () => {
|
|||||||
<motion.div
|
<motion.div
|
||||||
initial="hidden"
|
initial="hidden"
|
||||||
whileInView="visible"
|
whileInView="visible"
|
||||||
viewport={{ once: true, margin: "-100px" }}
|
viewport={{ once: true, margin: "-50px" }}
|
||||||
variants={staggerContainer}
|
variants={staggerContainer}
|
||||||
className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8"
|
className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8"
|
||||||
>
|
>
|
||||||
|
|||||||
Reference in New Issue
Block a user