diff --git a/bun.lockb b/bun.lockb index f2c86b6..a879dcb 100755 Binary files a/bun.lockb and b/bun.lockb differ diff --git a/package.json b/package.json index 320581c..c34bd85 100644 --- a/package.json +++ b/package.json @@ -17,6 +17,7 @@ "@types/react": "^18.3.3", "@types/react-dom": "^18.3.0", "astro": "5.15.2", + "framer-motion": "^12.23.24", "lucide-react": "^0.548.0", "mdast-util-to-string": "^4.0.0", "react": "^18.3.1", diff --git a/src/components/ui/ImageLightbox.tsx b/src/components/ui/ImageLightbox.tsx new file mode 100644 index 0000000..1c3cf76 --- /dev/null +++ b/src/components/ui/ImageLightbox.tsx @@ -0,0 +1,135 @@ +import { useState, useEffect } from 'react'; +import { motion, AnimatePresence } from 'framer-motion'; + +interface ImageLightboxProps { + images: { src: string; alt?: string }[]; + isOpen: boolean; + initialIndex: number; + onClose: () => void; +} + +export default function ImageLightbox({ + images, + isOpen, + initialIndex, + onClose, +}: ImageLightboxProps) { + const [currentIndex, setCurrentIndex] = useState(initialIndex); + + useEffect(() => { + const handleKeyDown = (e: KeyboardEvent) => { + if (e.key === 'Escape') { + onClose(); + } else if (e.key === 'ArrowRight') { + goToNext(); + } else if (e.key === 'ArrowLeft') { + goToPrev(); + } + }; + + if (isOpen) { + document.addEventListener('keydown', handleKeyDown); + document.body.style.overflow = 'hidden'; + return () => { + document.removeEventListener('keydown', handleKeyDown); + document.body.style.overflow = 'unset'; + }; + } + }, [isOpen, currentIndex]); + + if (!isOpen) return null; + + const goToNext = () => setCurrentIndex((currentIndex + 1) % images.length); + const goToPrev = () => + setCurrentIndex((currentIndex - 1 + images.length) % images.length); + + const handleClose = (e: React.MouseEvent) => { + e.stopPropagation(); + onClose(); + }; + + return ( + + {isOpen && ( + + + +
+ + e.stopPropagation()} + style={{ maxWidth: '90vw', maxHeight: '90vh' }} + /> + + + {images.length > 1 && ( + <> + + +
+ {images.map((_, idx) => ( +
+ + )} +
+
+ )} +
+ ); +} diff --git a/src/components/ui/WorkExperience.astro b/src/components/ui/WorkExperience.astro index 171c41f..99793a9 100644 --- a/src/components/ui/WorkExperience.astro +++ b/src/components/ui/WorkExperience.astro @@ -7,6 +7,13 @@ interface Props { const { entry } = Astro.props; const { Content } = await render(entry); + +const images = entry.data.images + ? entry.data.images.map((img) => ({ + src: img.src, + alt: entry.data.title, + })) + : []; ---
  • @@ -22,6 +29,66 @@ const { Content } = await render(entry);
    + + {images.length > 0 && ( +
    + {images.map((image, idx) => ( + + ))} +
    + )} -
  • \ No newline at end of file + + + \ No newline at end of file diff --git a/src/content.config.ts b/src/content.config.ts index 81f9c05..355fa2d 100644 --- a/src/content.config.ts +++ b/src/content.config.ts @@ -43,14 +43,16 @@ const linkCollection = defineCollection({ const jobCollection = defineCollection({ loader: glob({ pattern: '**/[^_]*.{md,mdx}', base: './src/content/jobs' }), - schema: z.object({ - title: z.string(), - company: z.string(), - location: z.string(), - from: z.number(), - to: z.number().or(z.enum(['Now'])), - url: z.string(), - }), + schema: ({ image }) => + z.object({ + title: z.string(), + company: z.string(), + location: z.string(), + from: z.number(), + to: z.number().or(z.enum(['Now'])), + url: z.string(), + images: z.array(image()).optional(), + }), }); const talkCollection = defineCollection({ diff --git a/src/content/jobs/reboot-studio/image-1.jpg b/src/content/jobs/reboot-studio/image-1.jpg new file mode 100644 index 0000000..ae06397 Binary files /dev/null and b/src/content/jobs/reboot-studio/image-1.jpg differ diff --git a/src/content/jobs/reboot-studio/image-2.jpg b/src/content/jobs/reboot-studio/image-2.jpg new file mode 100644 index 0000000..b455014 Binary files /dev/null and b/src/content/jobs/reboot-studio/image-2.jpg differ diff --git a/src/content/jobs/reboot-studio/image-3.jpg b/src/content/jobs/reboot-studio/image-3.jpg new file mode 100644 index 0000000..88f6e91 Binary files /dev/null and b/src/content/jobs/reboot-studio/image-3.jpg differ diff --git a/src/content/jobs/reboot-studio/image-4.jpg b/src/content/jobs/reboot-studio/image-4.jpg new file mode 100644 index 0000000..d8b59e1 Binary files /dev/null and b/src/content/jobs/reboot-studio/image-4.jpg differ diff --git a/src/content/jobs/reboot-studio.mdx b/src/content/jobs/reboot-studio/index.mdx similarity index 86% rename from src/content/jobs/reboot-studio.mdx rename to src/content/jobs/reboot-studio/index.mdx index 05d4cea..2707320 100644 --- a/src/content/jobs/reboot-studio.mdx +++ b/src/content/jobs/reboot-studio/index.mdx @@ -5,10 +5,15 @@ location: Mexico City from: 2022 to: 2022 # Use 'Now' if the job is still active url: https://reboot.studio +images: + - ./image-1.jpg + - ./image-2.jpg + - ./image-3.jpg + - ./image-4.jpg --- I developed a new feature to display related products in Freshis blog posts. I also designed and launched an operational dashboard to manage delivery routes and customer orders in real-time, improving the Freshis delivery process. Additionally, I redesigned the web mobile app used by pickers to optimize the placement process when they prepare orders and place products in the bag for delivery. I also assisted in testing and developing a new TO-DO extension for Raycast called Hypersonic, connected through Notion with OAuth. -Tools used: Node, React + Next.js, GraphQL, NestJS, Prisma, PostgreSQL, Strapi, Stitches, TypeScript. \ No newline at end of file +Tools used: Node, React + Next.js, GraphQL, NestJS, Prisma, PostgreSQL, Strapi, Stitches, TypeScript. diff --git a/src/content/jobs/sofia.mdx b/src/content/jobs/sofia/index.mdx similarity index 90% rename from src/content/jobs/sofia.mdx rename to src/content/jobs/sofia/index.mdx index e933f88..d23b8d2 100644 --- a/src/content/jobs/sofia.mdx +++ b/src/content/jobs/sofia/index.mdx @@ -13,4 +13,4 @@ Currently building a health insurance platform for Mexico. Responsibilities incl - Maintain the app and fix bugs. - Work closely with the product team to understand the needs of the business and the users. -Tools used: React, React Native, TypeScript \ No newline at end of file +Tools used: React, React Native, TypeScript