Updates dependencies and implements portfolio

Updates eslint and typescript-eslint dependencies.

Migrates to a new portfolio structure with components, styling, route management, and internationalization.

Adds project display with multiple project types using custom components.

Adds image assets to be displayed in experience sections
This commit is contained in:
Louis
2025-10-29 00:13:50 +01:00
parent 3e3a6dd125
commit e91f55b80d
19 changed files with 3929 additions and 199 deletions

View File

@@ -0,0 +1,76 @@
import React, { useState, useEffect } from 'react';
import { motion, AnimatePresence } from 'framer-motion';
interface OverlayPictureProps {
src: string;
alt: string;
className?: string;
}
const OverlayPicture: React.FC<OverlayPictureProps> = ({ src, alt, className = '' }) => {
const [isOpen, setIsOpen] = useState(false);
useEffect(() => {
const handleEscape = (event: KeyboardEvent) => {
if (event.key === 'Escape' && isOpen) {
// We don't want the escape key event to propagate to the overlay parent
// If the picture overlay is used inside another overlay, this prevents both overlays from closing
event.stopPropagation();
setIsOpen(false);
}
};
if (isOpen) {
// Use capture phase
document.addEventListener('keydown', handleEscape, true);
document.body.style.overflow = 'hidden';
}
return () => {
document.removeEventListener('keydown', handleEscape, true);
document.body.style.overflow = 'unset';
};
}, [isOpen]);
return (
<>
<motion.div className={`${className} flex items-center justify-center`}>
<motion.img
src={src}
alt={alt}
className={`cursor-zoom-in flex self-center max-w-xl max-h-96 rounded-lg shadow-md object-contain`}
onClick={() => setIsOpen(true)}
transition={{ duration: 0.2 }}
layoutId='overlay-image'
/>
</motion.div>
<AnimatePresence>
{isOpen && (
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
transition={{ duration: 0.2 }}
className="fixed inset-0 z-50 flex items-center justify-center bg-black bg-opacity-75 backdrop-blur-sm"
onClick={(e) => {
if (e.target === e.currentTarget) {
setIsOpen(false);
}
}}
>
<motion.img
src={src}
alt={alt}
className="max-w-[90%] max-h-[80%] object-contain rounded-md"
// We don't want the click on the image to close the overlay
layoutId='overlay-image'
/>
</motion.div>
)}
</AnimatePresence>
</>
);
};
export default OverlayPicture;