import PropTypes from 'prop-types'; import { motion, AnimatePresence } from "framer-motion"; import { useTranslation } from "react-i18next"; import { useMemo, useState, useRef, useEffect } from 'react'; import { FiExternalLink, FiGithub, FiX, FiArrowRight, FiZap, FiCpu, FiLayers, FiSmartphone, FiCode, FiShoppingCart, FiBarChart2, FiGlobe, FiShield, FiFolder } from 'react-icons/fi'; import OptimizedImage from './OptimizedImage'; import GlassBackground from "./GlassBackground"; // Importaciones de imágenes import image1 from "/1.webp"; import image2 from "/2.webp"; import image3 from "/3.webp"; import image4 from "/4.webp"; import image5 from "/5.webp"; import image7 from "/7.webp"; import image8 from "/8.webp"; import image9 from "/9.webp"; import image10 from "/10.webp"; import image11 from "/11.webp"; import image12 from "/12.webp"; // Categorías con iconos y colores const categoryConfig = { 'AI/ML': { Icon: FiCpu, textClass: 'text-violet-300 dark:text-violet-300 light:text-violet-700' }, 'IA/ML': { Icon: FiCpu, textClass: 'text-violet-300 dark:text-violet-300 light:text-violet-700' }, 'AI/Expert Systems': { Icon: FiLayers, textClass: 'text-indigo-300 dark:text-indigo-300 light:text-indigo-700' }, 'IA/Sistemas Expertos': { Icon: FiLayers, textClass: 'text-indigo-300 dark:text-indigo-300 light:text-indigo-700' }, 'Mobile/Backend': { Icon: FiSmartphone, textClass: 'text-emerald-300 dark:text-emerald-300 light:text-emerald-700' }, 'Móvil/Backend': { Icon: FiSmartphone, textClass: 'text-emerald-300 dark:text-emerald-300 light:text-emerald-700' }, 'Full Stack': { Icon: FiCode, textClass: 'text-sky-300 dark:text-sky-300 light:text-sky-700' }, 'E-commerce': { Icon: FiShoppingCart, textClass: 'text-amber-300 dark:text-amber-300 light:text-amber-700' }, 'Data Science': { Icon: FiBarChart2, textClass: 'text-pink-300 dark:text-pink-300 light:text-pink-700' }, 'Ciencia Datos': { Icon: FiBarChart2, textClass: 'text-pink-300 dark:text-pink-300 light:text-pink-700' }, 'Web App': { Icon: FiGlobe, textClass: 'text-cyan-300 dark:text-cyan-300 light:text-cyan-700' }, 'App Web': { Icon: FiGlobe, textClass: 'text-cyan-300 dark:text-cyan-300 light:text-cyan-700' }, 'Backend/Security': { Icon: FiShield, textClass: 'text-slate-300 dark:text-slate-300 light:text-slate-700' }, 'Backend/Seguridad': { Icon: FiShield, textClass: 'text-slate-300 dark:text-slate-300 light:text-slate-700' } }; // Modal de proyecto expandido const ProjectModal = ({ project, projectData, onClose, technologies }) => { const { t } = useTranslation(); const hasResults = projectData.results && Array.isArray(projectData.results); const config = categoryConfig[projectData.category] || { Icon: FiFolder, textClass: 'text-adaptive' }; const CategoryIcon = config.Icon; const closeButtonRef = useRef(null); const previouslyFocusedRef = useRef(null); useEffect(() => { const handleKeyDown = (e) => { if (e.key === "Escape") onClose(); }; window.addEventListener("keydown", handleKeyDown); return () => window.removeEventListener("keydown", handleKeyDown); }, [onClose]); useEffect(() => { previouslyFocusedRef.current = document.activeElement; closeButtonRef.current?.focus?.(); return () => { const previous = previouslyFocusedRef.current; if (previous && typeof previous.focus === "function") previous.focus(); }; }, []); return ( {/* Backdrop con blur */} {/* Contenido del modal */} e.stopPropagation()} role="dialog" aria-modal="true" aria-label={projectData.title} className="relative w-full max-w-4xl max-h-[90vh] overflow-hidden glass border-0 rounded-3xl bg-black/75 dark:bg-black/75 light:bg-white/92 backdrop-blur-xl shadow-2xl shadow-black/40 dark:shadow-black/40 light:shadow-black/10 theme-transition" style={{ border: "none" }} > {/* Botón cerrar */} {/* Imagen hero */} {/* Título sobre la imagen */} {projectData.category && ( )} {project.year} {projectData.title} {/* Contenido */} {/* Descripción */} {projectData.description} {/* Resultados - Verde en dark mode */} {hasResults && ( {t('projects.live.results') || 'Resultados Clave'} {projectData.results.map((result, idx) => ( {result} ))} )} {/* Tecnologías - Cyan/Azul en dark mode */} {t('tech.title') || 'Tecnologías'} {technologies.map((tech, idx) => ( {tech} ))} {/* Botones de acción */} {project.demo && ( {t('projects.live.demo') || 'Ver Demo'} )} {project.repo && ( {t('projects.live.repo') || 'Código Fuente'} )} ); }; ProjectModal.propTypes = { project: PropTypes.object.isRequired, projectData: PropTypes.object.isRequired, onClose: PropTypes.func.isRequired, technologies: PropTypes.array.isRequired }; // Tarjeta de proyecto interactiva con efectos const ProjectCard = ({ project, displayIndex, onSelect }) => { const { t } = useTranslation(); const [isHovered, setIsHovered] = useState(false); const cardRef = useRef(null); const [mousePosition, setMousePosition] = useState({ x: 50, y: 50 }); const projectData = t(`projects.${project.i18nKey}`, { returnObjects: true }); const category = projectData.category; const config = categoryConfig[category] || { Icon: FiFolder, textClass: 'text-adaptive' }; const CategoryIcon = config.Icon; const handleMouseMove = (e) => { if (!cardRef.current) return; const rect = cardRef.current.getBoundingClientRect(); const x = ((e.clientX - rect.left) / rect.width) * 100; const y = ((e.clientY - rect.top) / rect.height) * 100; setMousePosition({ x, y }); }; return ( setIsHovered(true)} onMouseLeave={() => setIsHovered(false)} onMouseMove={handleMouseMove} onClick={() => onSelect(project)} className="group relative cursor-pointer" > {/* Efecto de brillo siguiendo el mouse */} {/* Imagen con efectos */} {/* Overlay gradiente */} {/* Número flotante */} {String(displayIndex + 1).padStart(2, '0')} {/* Icono de categoría flotante con animación */} {/* Contenido */} {/* Meta info */} {project.year} • {category} {/* Título */} {projectData.title} {/* Descripción */} {projectData.description} {/* Tags de tecnología - Cyan/Azul */} {(projectData.tech || project.technologies).slice(0, 4).map((tech, idx) => ( {tech} ))} {(projectData.tech || project.technologies).length > 4 && ( +{(projectData.tech || project.technologies).length - 4} )} {/* CTA con animación */} {t('projects.viewDetails')} {/* Barra de progreso decorativa animada */} ); }; ProjectCard.propTypes = { project: PropTypes.object.isRequired, displayIndex: PropTypes.number.isRequired, onSelect: PropTypes.func.isRequired }; // Datos de proyectos const projectsData = [ { i18nKey: "project1", year: 2024, image: image1, technologies: ["Python", "FastAPI", "YOLOv8", "React", "MongoDB", "WebSockets"], demo: "https://el-dorado-nu.vercel.app/", repo: "https://github.com/never130/ElDorado" }, { i18nKey: "project2", year: 2025, image: image11, technologies: ["Flutter", "Express.js", "Firebase", "SMS Auth"], demo: null, repo: null }, { i18nKey: "project3", year: 2024, image: image2, technologies: ["Node.js", "Express", "React", "MongoDB", "API Integration"], demo: "https://youtube.com/shorts/T8HjMw814Uk?feature=share", repo: null }, { i18nKey: "project4", year: 2024, image: image4, technologies: ["React", "Node.js", "MercadoPago", "MongoDB"], demo: "https://tiendadelfuego2025.vercel.app", repo: "https://github.com/never130/tiendadelfuego2025" }, { i18nKey: "project5", year: 2024, image: image5, technologies: ["Python", "Flask", "DecisionTree", "JSON", "Next.js", "Tailwind CSS"], demo: "https://politecnico-sistema-experto.vercel.app/", repo: "https://github.com/never130/Politecnico_Sistema_Experto" }, { i18nKey: "project6", year: 2023, image: image3, technologies: ["React", "Weather API", "Local Storage", "Responsive Design"], demo: "https://miclimapp.netlify.app/#/current-location", repo: "https://github.com/never130/Climapp" }, { i18nKey: "project7", year: 2023, image: image7, technologies: ["MongoDB", "React", "Chakra UI", "TailwindCSS", "CRUD"], demo: "https://proyecto1-tiendadelfuego.onrender.com/create", repo: "https://github.com/never130/proyecto-mern1" }, { i18nKey: "project8", year: 2023, image: image8, technologies: ["Node.js", "JWT", "bcrypt", "Express", "Security"], demo: "https://php-login-register1.onrender.com", repo: "https://github.com/never130/php-login-register1" }, { i18nKey: "project9", year: 2023, image: image9, technologies: ["React", "E-commerce", "Payment Integration", "MongoDB"], demo: "https://solomusica.netlify.app/", repo: "https://github.com/never130/proyecto-mern1" }, { i18nKey: "project10", year: 2025, image: image12, technologies: ["React", "Vite", "TypeScript", "Tailwind", "Node.js", "Express", "MongoDB", "JWT Auth", "MercadoPago", "Cloudinary"], demo: "https://ecommerce-mates-bay.vercel.app/", }, { i18nKey: "project11", year: 2025, image: image10, technologies: ["React 19", "Vite", "Tailwind CSS v4", "Framer Motion", "Three.js", "React Three Fiber", "Maath"], demo: "https://inteligencia-fueguina.vercel.app/", repo: null, }, ]; const Projects = () => { const { t } = useTranslation(); const [selectedProject, setSelectedProject] = useState(null); const scrollLockRef = useRef(null); const sortedProjects = useMemo(() => { return [...projectsData].sort((a, b) => { const yearDiff = (b.year ?? 0) - (a.year ?? 0); if (yearDiff !== 0) return yearDiff; return a.i18nKey.localeCompare(b.i18nKey); }); }, []); const handleSelect = (project) => { setSelectedProject(project); }; const handleClose = () => { setSelectedProject(null); }; useEffect(() => { if (!selectedProject) return; const body = document.body; const previous = { overflow: body.style.overflow, paddingRight: body.style.paddingRight, }; const scrollbarWidth = window.innerWidth - document.documentElement.clientWidth; body.style.overflow = "hidden"; if (scrollbarWidth > 0) body.style.paddingRight = `${scrollbarWidth}px`; scrollLockRef.current = previous; return () => { const prev = scrollLockRef.current; if (!prev) return; body.style.overflow = prev.overflow; body.style.paddingRight = prev.paddingRight; scrollLockRef.current = null; }; }, [selectedProject]); const getProjectData = (project) => { return t(`projects.${project.i18nKey}`, { returnObjects: true }); }; const getTechnologies = (project) => { const data = getProjectData(project); return data.tech || project.technologies; }; return ( <> {/* Background decorativo sutil */} {/* Header */} {/* Badge contador */} {sortedProjects.length} {t('projects.count') || 'Proyectos'} {t("projects.title")} {t("projects.subtitle")} {/* Lista de proyectos */} {sortedProjects.map((project, index) => ( ))} {/* Modal */} {selectedProject && ( )} > ); }; export default Projects;
{projectData.description}
{t("projects.subtitle")}