feat: project pages
This commit is contained in:
		
							parent
							
								
									c806a772e3
								
							
						
					
					
						commit
						a60f3cf0b5
					
				| @ -17,7 +17,7 @@ import UsersIcon from '@mui/icons-material/People' | ||||
| import ThemeRegistry from '@/components/ThemeRegistry/ThemeRegistry' | ||||
| 
 | ||||
| export const metadata = { | ||||
|   title: 'Next.js MUI Starter Template', | ||||
|   title: 'd4m13n.dev', | ||||
|   description: 'Next.js App Router + Material UI v5 Starter Template' | ||||
| } | ||||
| 
 | ||||
|  | ||||
							
								
								
									
										129
									
								
								src/app/page.tsx
									
									
									
									
									
								
							
							
						
						
									
										129
									
								
								src/app/page.tsx
									
									
									
									
									
								
							| @ -5,16 +5,26 @@ import { useState } from 'react'; | ||||
| import Box from '@mui/material/Box'; | ||||
| import Typography from '@mui/material/Typography'; | ||||
| import Fade from '@mui/material/Fade'; | ||||
| import Button from '@mui/material/Button'; | ||||
| import Tabs from '@mui/material/Tabs'; | ||||
| import Tab from '@mui/material/Tab'; | ||||
| import TypingAnimation from '@/components/TypingAnimation'; | ||||
| import ProjectMasonry from '@/components/ProjectMasonry'; | ||||
| import projects from '@/data/projects'; | ||||
| 
 | ||||
| export default function HomePage() { | ||||
|   const [showContent, setShowContent] = useState(false); | ||||
|   const [activeTab, setActiveTab] = useState(0); | ||||
| 
 | ||||
|   const handleTabChange = (event: React.SyntheticEvent, newValue: number) => { | ||||
|     setActiveTab(newValue); | ||||
|   }; | ||||
| 
 | ||||
|   return ( | ||||
|     <> | ||||
|       {!showContent && ( | ||||
|         <TypingAnimation | ||||
|           titleText="My name is Damien." | ||||
|           titleText="My name is D4m13n." | ||||
|           subtitleText="Welcome to my website!" | ||||
|           typingSpeed={80} | ||||
|           delayBeforeRemoval={3000} | ||||
| @ -27,18 +37,117 @@ export default function HomePage() { | ||||
|           display: 'flex', | ||||
|           flexDirection: 'column', | ||||
|           alignItems: 'center', | ||||
|           justifyContent: 'center', | ||||
|           minHeight: '100vh', | ||||
|           p: 4, | ||||
|           width: '100%', | ||||
|           p: { xs: 2, sm: 3, md: 4 }, | ||||
|           visibility: showContent ? 'visible' : 'hidden' | ||||
|         }}> | ||||
|           <Typography variant="h3" component="h1" gutterBottom> | ||||
|             Damien's Website | ||||
|           <Typography | ||||
|             variant="h3" | ||||
|             component="h1" | ||||
|             gutterBottom | ||||
|             sx={{ | ||||
|               textAlign: 'center', | ||||
|               fontWeight: 'bold', | ||||
|               mb: 4 | ||||
|             }} | ||||
|           > | ||||
|             <Box | ||||
|               component="span" | ||||
|               sx={{ | ||||
|                 color: '#4fd1ff', | ||||
|                 textShadow: '0 0 10px rgba(79, 209, 255, 0.15), 0 0 20px rgba(79, 209, 255, 0.1)', | ||||
|                 filter: 'drop-shadow(0 2px 4px rgba(0, 0, 0, 0.25))' | ||||
|               }} | ||||
|             > | ||||
|               d4m13n | ||||
|             </Box> | ||||
|             <Box component="span">.dev</Box> | ||||
|           </Typography> | ||||
|           <Typography variant="body1" paragraph> | ||||
|           This is a test sentence. | ||||
|           </Typography> | ||||
|           {/* Add more content here */} | ||||
|            | ||||
|           <Box sx={{ width: '100%', mb: 4 }}> | ||||
|             <Tabs | ||||
|               value={activeTab} | ||||
|               onChange={handleTabChange} | ||||
|               centered | ||||
|               sx={{ | ||||
|                 '& .MuiTabs-indicator': { | ||||
|                   backgroundColor: '#4fd1ff', | ||||
|                   height: 3, | ||||
|                   boxShadow: '0px 0px 15px rgba(79, 209, 255, 0.3)', | ||||
|                 }, | ||||
|                 '& .MuiTab-root': { | ||||
|                   transition: 'all 0.3s ease-in-out', | ||||
|                   '&:hover': { | ||||
|                     color: '#4fd1ff', | ||||
|                     textShadow: '0px 0px 15px rgba(79, 209, 255, 0.3)', | ||||
|                   }, | ||||
|                 }, | ||||
|                 '& .Mui-selected': { | ||||
|                   color: '#4fd1ff !important', | ||||
|                   textShadow: '0px 0px 15px rgba(79, 209, 255, 0.3)', | ||||
|                 } | ||||
|               }} | ||||
|             > | ||||
|               <Tab label="Software Dev Projects" /> | ||||
|               <Tab label="Game Dev Projects" /> | ||||
|               <Tab label="About" /> | ||||
|               <Tab label="Contact" /> | ||||
|             </Tabs> | ||||
|           </Box> | ||||
|            | ||||
|           {activeTab === 0 && ( | ||||
|             <Box sx={{ width: '100%', maxWidth: 1200, mx: 'auto' }}> | ||||
|               <ProjectMasonry projects={projects} /> | ||||
|             </Box> | ||||
|           )} | ||||
|            | ||||
|           {activeTab === 1 && ( | ||||
|             <Box sx={{ width: '100%', maxWidth: 1200, mx: 'auto', p: 2 }}> | ||||
|               <ProjectMasonry projects={projects} /> | ||||
|             </Box> | ||||
|           )} | ||||
|           {activeTab === 2 && ( | ||||
|             <Box sx={{ width: '100%', maxWidth: 800, mx: 'auto', p: 2 }}> | ||||
|               <Typography variant="h5" component="h2" gutterBottom sx={{ mb: 2 }}> | ||||
|                 About Me | ||||
|               </Typography> | ||||
|               <Typography variant="body1" paragraph> | ||||
|                 I'm a passionate developer with expertise in modern web technologies. | ||||
|                 I love creating responsive, user-friendly applications that solve real-world problems. | ||||
|               </Typography> | ||||
|               <Typography variant="body1"> | ||||
|                 When I'm not coding, you can find me exploring new technologies, contributing to open-source projects, | ||||
|                 or enjoying outdoor activities. | ||||
|               </Typography> | ||||
|             </Box> | ||||
|           )} | ||||
|            | ||||
|           {activeTab === 3 && ( | ||||
|             <Box sx={{ width: '100%', maxWidth: 800, mx: 'auto', p: 2 }}> | ||||
|               <Typography variant="h5" component="h2" gutterBottom sx={{ mb: 2 }}> | ||||
|                 Contact | ||||
|               </Typography> | ||||
|               <Typography variant="body1" paragraph> | ||||
|                 Feel free to reach out to me for collaboration opportunities or just to say hello! | ||||
|               </Typography> | ||||
|               <Button | ||||
|                 variant="contained" | ||||
|                 href="mailto:contact@example.com" | ||||
|                 sx={{ | ||||
|                   backgroundColor: '#1a365d', | ||||
|                   color: '#ffffff', | ||||
|                   boxShadow: '0px 4px 8px rgba(0,0,0,0.3)', | ||||
|                   '&:hover': { | ||||
|                     backgroundColor: '#2a466d', | ||||
|                     boxShadow: '0px 0px 15px rgba(79, 209, 255, 0.3)', | ||||
|                   }, | ||||
|                   transition: 'all 0.3s ease-in-out', | ||||
|                 }} | ||||
|               > | ||||
|                 Email Me | ||||
|               </Button> | ||||
|             </Box> | ||||
|           )} | ||||
|         </Box> | ||||
|       </Fade> | ||||
|     </> | ||||
|  | ||||
							
								
								
									
										313
									
								
								src/components/ProjectCard.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										313
									
								
								src/components/ProjectCard.tsx
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,313 @@ | ||||
| import React, { useState } from 'react'; | ||||
| import {  | ||||
|   Box,  | ||||
|   Card,  | ||||
|   CardContent,  | ||||
|   CardMedia,  | ||||
|   Typography,  | ||||
|   Button,  | ||||
|   IconButton, | ||||
|   Chip, | ||||
|   Stack, | ||||
|   useTheme, | ||||
|   useMediaQuery | ||||
| } from '@mui/material'; | ||||
| import GitHubIcon from '@mui/icons-material/GitHub'; | ||||
| import LaunchIcon from '@mui/icons-material/Launch'; | ||||
| import ArrowBackIosNewIcon from '@mui/icons-material/ArrowBackIosNew'; | ||||
| import ArrowForwardIosIcon from '@mui/icons-material/ArrowForwardIos'; | ||||
| 
 | ||||
| // Define the project image interface
 | ||||
| interface ProjectImage { | ||||
|   src: string; | ||||
|   alt: string; | ||||
| } | ||||
| 
 | ||||
| // Define the repository interface
 | ||||
| interface Repository { | ||||
|   name: string; | ||||
|   url: string; | ||||
| } | ||||
| 
 | ||||
| // Define the project interface
 | ||||
| export interface Project { | ||||
|   id: string; | ||||
|   title: string; | ||||
|   description: string; | ||||
|   technologies: string[]; | ||||
|   images: ProjectImage[]; | ||||
|   repositories?: Repository[]; // Multiple repositories with names
 | ||||
|   demoUrl?: string; | ||||
| } | ||||
| 
 | ||||
| interface ProjectCardProps { | ||||
|   project: Project; | ||||
| } | ||||
| 
 | ||||
| const ProjectCard: React.FC<ProjectCardProps> = ({ project }) => { | ||||
|   const [currentImageIndex, setCurrentImageIndex] = useState(0); | ||||
|   const [isHovered, setIsHovered] = useState(false); | ||||
|   const theme = useTheme(); | ||||
|   const isMobile = useMediaQuery(theme.breakpoints.down('sm')); | ||||
|    | ||||
|   // Colors and effects based on specifications
 | ||||
|   const cardBgColor = theme.palette.grey[900]; // Dark grey from theme
 | ||||
|   const textColor = '#ffffff'; // Crisp white
 | ||||
|   const dropShadow = '0px 4px 8px rgba(0,0,0,0.3)'; | ||||
|   const ambientGlow = '0px 0px 15px rgba(255, 255, 255, 0.15)'; // White glow
 | ||||
|   const enhancedGlow = '0px 0px 20px rgba(255, 255, 255, 0.3)'; // Enhanced white glow
 | ||||
|   const titleColor = '#4fd1ff'; // Same blue as active tab
 | ||||
|   const titleGlow = '0px 0px 10px rgba(255, 255, 255, 0.15), 0px 0px 20px rgba(255, 255, 255, 0.1)'; // White glow for title
 | ||||
|    | ||||
|   // Handle image navigation
 | ||||
|   const handlePrevImage = (e: React.MouseEvent) => { | ||||
|     e.stopPropagation(); | ||||
|     setCurrentImageIndex(prev =>  | ||||
|       prev === 0 ? project.images.length - 1 : prev - 1 | ||||
|     ); | ||||
|   }; | ||||
|    | ||||
|   const handleNextImage = (e: React.MouseEvent) => { | ||||
|     e.stopPropagation(); | ||||
|     setCurrentImageIndex(prev =>  | ||||
|       prev === project.images.length - 1 ? 0 : prev + 1 | ||||
|     ); | ||||
|   }; | ||||
| 
 | ||||
|   return ( | ||||
|     <Card | ||||
|       onMouseEnter={() => setIsHovered(true)} | ||||
|       onMouseLeave={() => setIsHovered(false)} | ||||
|       sx={{ | ||||
|         backgroundColor: cardBgColor, // Use the deep blue background color
 | ||||
|         color: textColor, | ||||
|         boxShadow: isHovered ? enhancedGlow : dropShadow, | ||||
|         transition: 'all 0.3s ease-in-out', | ||||
|         height: '100%', // Ensure card takes full height
 | ||||
|         display: 'flex', | ||||
|         flexDirection: 'column', | ||||
|         position: 'relative', | ||||
|         overflow: 'hidden', | ||||
|         '&:hover': { | ||||
|           transform: 'translateY(-5px)', | ||||
|           boxShadow: enhancedGlow, | ||||
|         }, | ||||
|       }} | ||||
|     > | ||||
|       {project.images.length > 0 && ( | ||||
|         <Box sx={{ position: 'relative' }}> | ||||
|           <CardMedia | ||||
|             component="img" | ||||
|             image={project.images[currentImageIndex].src} | ||||
|             alt={project.images[currentImageIndex].alt} | ||||
|             sx={{ | ||||
|               objectFit: 'cover', | ||||
|               height: { xs: '180px', sm: '220px', md: '240px' }, // Responsive height
 | ||||
|               loading: 'lazy', // Enable lazy loading for performance
 | ||||
|               transition: 'all 0.3s ease-in-out' | ||||
|             }} | ||||
|           /> | ||||
|            | ||||
|           {/* Image navigation controls - only show if there are multiple images */} | ||||
|           {project.images.length > 1 && ( | ||||
|             <> | ||||
|               <IconButton | ||||
|                 size="small" | ||||
|                 onClick={handlePrevImage} | ||||
|                 sx={{ | ||||
|                   position: 'absolute', | ||||
|                   left: 8, | ||||
|                   top: '50%', | ||||
|                   transform: 'translateY(-50%)', | ||||
|                   backgroundColor: 'rgba(0,0,0,0.5)', | ||||
|                   color: textColor, | ||||
|                   '&:hover': { | ||||
|                     backgroundColor: 'rgba(0,0,0,0.7)', | ||||
|                     boxShadow: ambientGlow, | ||||
|                   }, | ||||
|                   transition: 'all 0.3s ease-in-out', | ||||
|                   opacity: isHovered || isMobile ? 1 : 0, | ||||
|                 }} | ||||
|               > | ||||
|                 <ArrowBackIosNewIcon fontSize="small" /> | ||||
|               </IconButton> | ||||
|                | ||||
|               <IconButton | ||||
|                 size="small" | ||||
|                 onClick={handleNextImage} | ||||
|                 sx={{ | ||||
|                   position: 'absolute', | ||||
|                   right: 8, | ||||
|                   top: '50%', | ||||
|                   transform: 'translateY(-50%)', | ||||
|                   backgroundColor: 'rgba(0,0,0,0.5)', | ||||
|                   color: textColor, | ||||
|                   '&:hover': { | ||||
|                     backgroundColor: 'rgba(0,0,0,0.7)', | ||||
|                     boxShadow: ambientGlow, | ||||
|                   }, | ||||
|                   transition: 'all 0.3s ease-in-out', | ||||
|                   opacity: isHovered || isMobile ? 1 : 0, | ||||
|                 }} | ||||
|               > | ||||
|                 <ArrowForwardIosIcon fontSize="small" /> | ||||
|               </IconButton> | ||||
|                | ||||
|               {/* Image counter indicator */} | ||||
|               <Box | ||||
|                 sx={{ | ||||
|                   position: 'absolute', | ||||
|                   bottom: 8, | ||||
|                   right: 8, | ||||
|                   backgroundColor: 'rgba(0,0,0,0.5)', | ||||
|                   color: textColor, | ||||
|                   padding: '2px 8px', | ||||
|                   borderRadius: '10px', | ||||
|                   fontSize: '0.75rem', | ||||
|                   opacity: isHovered || isMobile ? 1 : 0, | ||||
|                   transition: 'opacity 0.3s ease-in-out', | ||||
|                 }} | ||||
|               > | ||||
|                 {currentImageIndex + 1} / {project.images.length} | ||||
|               </Box> | ||||
|             </> | ||||
|           )} | ||||
|         </Box> | ||||
|       )} | ||||
|        | ||||
|       <CardContent sx={{ | ||||
|         flexGrow: 1, | ||||
|         display: 'flex', | ||||
|         flexDirection: 'column', | ||||
|         p: { xs: 2, sm: 3 }, // Responsive padding
 | ||||
|         '&:last-child': { pb: { xs: 2, sm: 3 } } // Override MUI's default padding-bottom
 | ||||
|       }}> | ||||
|         <Typography | ||||
|           variant="h5" | ||||
|           component="div" | ||||
|           sx={{ | ||||
|             fontWeight: 'bold', | ||||
|             color: titleColor, | ||||
|             textShadow: titleGlow, | ||||
|             mb: 1.5, | ||||
|             lineHeight: 1.2 | ||||
|           }} | ||||
|         > | ||||
|           {project.title} | ||||
|         </Typography> | ||||
|          | ||||
|         <Typography | ||||
|           variant="body2" | ||||
|           sx={{ | ||||
|             mb: 2.5, | ||||
|             flexGrow: 1, | ||||
|             opacity: 0.9, | ||||
|             lineHeight: 1.6, | ||||
|             letterSpacing: '0.015em' | ||||
|           }} | ||||
|         > | ||||
|           {project.description} | ||||
|         </Typography> | ||||
|          | ||||
|         <Box | ||||
|           sx={{ | ||||
|             mb: 3, | ||||
|             display: 'flex', | ||||
|             flexWrap: 'wrap', | ||||
|             gap: 0.75 | ||||
|           }} | ||||
|         > | ||||
|           {project.technologies.map((tech, index) => ( | ||||
|             <Chip | ||||
|               key={index} | ||||
|               label={tech} | ||||
|               size="small" | ||||
|               sx={{ | ||||
|                 backgroundColor: 'rgba(255,255,255,0.1)', | ||||
|                 color: textColor, | ||||
|                 height: 24, | ||||
|                 fontSize: '0.75rem', | ||||
|                 '&:hover': { | ||||
|                   backgroundColor: 'rgba(255,255,255,0.2)', | ||||
|                   boxShadow: ambientGlow, | ||||
|                 }, | ||||
|                 transition: 'all 0.3s ease-in-out', | ||||
|               }} | ||||
|             /> | ||||
|           ))} | ||||
|         </Box> | ||||
|          | ||||
|         <Box | ||||
|           sx={{ | ||||
|             display: 'flex', | ||||
|             flexWrap: 'wrap', | ||||
|             gap: 1.5, | ||||
|             justifyContent: 'flex-end', | ||||
|             mt: 'auto', // Push buttons to bottom of card
 | ||||
|             pt: 1 // Add some padding at the top
 | ||||
|           }} | ||||
|         > | ||||
|           {/* Repository buttons */} | ||||
|           {project.repositories && project.repositories.length > 0 && ( | ||||
|             <> | ||||
|               {project.repositories.map((repo, index) => ( | ||||
|                 <Button | ||||
|                   key={index} | ||||
|                   variant="outlined" | ||||
|                   size="small" | ||||
|                   startIcon={<GitHubIcon />} | ||||
|                   href={repo.url} | ||||
|                   target="_blank" | ||||
|                   rel="noopener noreferrer" | ||||
|                   sx={{ | ||||
|                     color: textColor, | ||||
|                     borderColor: 'rgba(255,255,255,0.3)', | ||||
|                     borderRadius: '6px', | ||||
|                     padding: '4px 12px', | ||||
|                     minWidth: '80px', | ||||
|                     '&:hover': { | ||||
|                       borderColor: textColor, | ||||
|                       backgroundColor: 'rgba(255,255,255,0.05)', | ||||
|                       boxShadow: ambientGlow, | ||||
|                     }, | ||||
|                     transition: 'all 0.3s ease-in-out', | ||||
|                   }} | ||||
|                 > | ||||
|                   {repo.name} | ||||
|                 </Button> | ||||
|               ))} | ||||
|             </> | ||||
|           )} | ||||
|            | ||||
|           {/* Demo button */} | ||||
|           {project.demoUrl && ( | ||||
|             <Button | ||||
|               variant="contained" | ||||
|               size="small" | ||||
|               startIcon={<LaunchIcon />} | ||||
|               href={project.demoUrl} | ||||
|               target="_blank" | ||||
|               rel="noopener noreferrer" | ||||
|               sx={{ | ||||
|                 backgroundColor: 'rgba(79, 209, 255, 0.4)', | ||||
|                 color: textColor, | ||||
|                 borderRadius: '6px', | ||||
|                 padding: '4px 12px', | ||||
|                 minWidth: '80px', | ||||
|                 '&:hover': { | ||||
|                   backgroundColor: 'rgba(79, 209, 255, 0.4)', | ||||
|                   boxShadow: enhancedGlow, | ||||
|                 }, | ||||
|                 transition: 'all 0.3s ease-in-out', | ||||
|               }} | ||||
|             > | ||||
|               Demo | ||||
|             </Button> | ||||
|           )} | ||||
|         </Box> | ||||
|       </CardContent> | ||||
|     </Card> | ||||
|   ); | ||||
| }; | ||||
| 
 | ||||
| export default ProjectCard; | ||||
							
								
								
									
										90
									
								
								src/components/ProjectMasonry.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										90
									
								
								src/components/ProjectMasonry.tsx
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,90 @@ | ||||
| import React, { useEffect, useState } from 'react'; | ||||
| import { Box, Grid, useMediaQuery, useTheme } from '@mui/material'; | ||||
| import ProjectCard, { Project } from './ProjectCard'; | ||||
| 
 | ||||
| interface ProjectMasonryProps { | ||||
|   projects: Project[]; | ||||
| } | ||||
| 
 | ||||
| const ProjectMasonry: React.FC<ProjectMasonryProps> = ({ projects }) => { | ||||
|   const theme = useTheme(); | ||||
|   const isXs = useMediaQuery(theme.breakpoints.only('xs')); | ||||
|   const isSm = useMediaQuery(theme.breakpoints.only('sm')); | ||||
|   const isMd = useMediaQuery(theme.breakpoints.only('md')); | ||||
|    | ||||
|   // Assign varying widths to projects
 | ||||
|   const getProjectWidths = () => { | ||||
|     return projects.map((project, index) => { | ||||
|       // Create a pattern of varying widths
 | ||||
|       // This creates a more interesting masonry layout
 | ||||
|       if (isXs) return 12; // On mobile, all cards are full width
 | ||||
|        | ||||
|       // Create a pattern for varying widths
 | ||||
|       const patterns = [ | ||||
|         // Pattern 1: [6, 6, 12, 6, 6]
 | ||||
|         [6, 6, 12, 6, 6], | ||||
|         // Pattern 2: [8, 4, 4, 8, 12]
 | ||||
|         [8, 4, 4, 8, 12], | ||||
|         // Pattern 3: [4, 8, 4, 8, 4, 8]
 | ||||
|         [4, 8, 4, 8, 4, 8] | ||||
|       ]; | ||||
|        | ||||
|       // Select a pattern based on the index
 | ||||
|       const patternIndex = Math.floor(index / 5) % patterns.length; | ||||
|       const pattern = patterns[patternIndex]; | ||||
|       const positionInPattern = index % pattern.length; | ||||
|        | ||||
|       return pattern[positionInPattern]; | ||||
|     }); | ||||
|   }; | ||||
|    | ||||
|   const projectWidths = getProjectWidths(); | ||||
|    | ||||
|   return ( | ||||
|     <Box | ||||
|       sx={{ | ||||
|         width: '100%', | ||||
|         position: 'relative', | ||||
|         px: { xs: 1, sm: 2 }, // Add horizontal padding that scales with screen size
 | ||||
|       }} | ||||
|     > | ||||
|       <Grid container spacing={3} alignItems="flex-start"> | ||||
|         {projects.map((project, index) => ( | ||||
|           <Grid | ||||
|             item | ||||
|             xs={12} | ||||
|             sm={projectWidths[index]} | ||||
|             key={project.id} | ||||
|             sx={{ | ||||
|               mb: 3, | ||||
|               opacity: 1, | ||||
|               transform: 'translateY(0)', | ||||
|               transition: 'all 0.4s ease-in-out', | ||||
|               '&:hover': { | ||||
|                 zIndex: 1, | ||||
|               }, | ||||
|               // Add animation for when items are added to the DOM
 | ||||
|               '@keyframes fadeIn': { | ||||
|                 from: { | ||||
|                   opacity: 0, | ||||
|                   transform: 'translateY(20px)', | ||||
|                 }, | ||||
|                 to: { | ||||
|                   opacity: 1, | ||||
|                   transform: 'translateY(0)', | ||||
|                 }, | ||||
|               }, | ||||
|               animation: 'fadeIn 0.5s ease-in-out', | ||||
|               // Stagger the animation based on index
 | ||||
|               animationDelay: `${index * 0.05}s`, | ||||
|             }} | ||||
|           > | ||||
|             <ProjectCard project={project} /> | ||||
|           </Grid> | ||||
|         ))} | ||||
|       </Grid> | ||||
|     </Box> | ||||
|   ); | ||||
| }; | ||||
| 
 | ||||
| export default ProjectMasonry; | ||||
| @ -116,7 +116,7 @@ const TypingAnimation: React.FC<TypingAnimationProps> = ({ | ||||
| 
 | ||||
|   // Blue glow for "Damien"
 | ||||
|   const blueGlowEffects = { | ||||
|     textShadow: '0 0 10px rgba(25, 118, 210, 0.35), 0 0 20px rgba(25, 118, 210, 0.25)', | ||||
|     textShadow: '0 0 10px rgba(79, 209, 255, 0.15), 0 0 20px rgba(79, 209, 255, 0.1)', | ||||
|     filter: 'drop-shadow(0 2px 4px rgba(0, 0, 0, 0.25))' | ||||
|   }; | ||||
| 
 | ||||
| @ -132,7 +132,7 @@ const TypingAnimation: React.FC<TypingAnimationProps> = ({ | ||||
|     const fullTitleText = titleText; | ||||
|      | ||||
|     // Find where "Damien" starts in the full text
 | ||||
|     const damienStartIndex = fullTitleText.toLowerCase().indexOf('damien'); | ||||
|     const damienStartIndex = fullTitleText.toLowerCase().indexOf('d4m13n'); | ||||
|      | ||||
|     // If "Damien" isn't in the text (shouldn't happen, but just in case)
 | ||||
|     if (damienStartIndex === -1) { | ||||
| @ -159,7 +159,7 @@ const TypingAnimation: React.FC<TypingAnimationProps> = ({ | ||||
|             key={i} | ||||
|             component="span" | ||||
|             sx={{ | ||||
|               color: '#1976d2', /* Material UI primary blue */ | ||||
|               color: '#4fd1ff', /* Same blue as active tab */ | ||||
|               ...blueGlowEffects | ||||
|             }} | ||||
|           > | ||||
|  | ||||
							
								
								
									
										181
									
								
								src/data/projects.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										181
									
								
								src/data/projects.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,181 @@ | ||||
| import { Project } from '@/components/ProjectCard'; | ||||
| 
 | ||||
| // Sample project data
 | ||||
| const projects: Project[] = [ | ||||
|   { | ||||
|     id: '1', | ||||
|     title: 'E-Commerce Platform', | ||||
|     description: 'A full-featured e-commerce platform with product management, shopping cart, and payment processing capabilities.', | ||||
|     technologies: ['React', 'Node.js', 'MongoDB', 'Stripe'], | ||||
|     images: [ | ||||
|       { | ||||
|         src: 'https://source.unsplash.com/random/800x600?ecommerce', | ||||
|         alt: 'E-commerce dashboard' | ||||
|       }, | ||||
|       { | ||||
|         src: 'https://source.unsplash.com/random/800x600?shopping', | ||||
|         alt: 'Shopping cart interface' | ||||
|       }, | ||||
|       { | ||||
|         src: 'https://source.unsplash.com/random/800x600?payment', | ||||
|         alt: 'Payment processing screen' | ||||
|       } | ||||
|     ], | ||||
|     repositories: [ | ||||
|       { name: 'Frontend', url: 'https://github.com/username/ecommerce-frontend' }, | ||||
|       { name: 'Backend', url: 'https://github.com/username/ecommerce-api' } | ||||
|     ], | ||||
|     demoUrl: 'https://ecommerce-demo.example.com' | ||||
|   }, | ||||
|   { | ||||
|     id: '2', | ||||
|     title: 'Weather App', | ||||
|     description: 'A responsive weather application that provides real-time weather data and forecasts for locations worldwide.', | ||||
|     technologies: ['JavaScript', 'React', 'OpenWeather API', 'CSS'], | ||||
|     images: [ | ||||
|       { | ||||
|         src: 'https://source.unsplash.com/random/800x600?weather', | ||||
|         alt: 'Weather app interface' | ||||
|       }, | ||||
|       { | ||||
|         src: 'https://source.unsplash.com/random/800x600?forecast', | ||||
|         alt: 'Forecast view' | ||||
|       } | ||||
|     ], | ||||
|     repositories: [ | ||||
|       { name: 'Repo', url: 'https://github.com/username/weather-app' } | ||||
|     ], | ||||
|     demoUrl: 'https://weather-app-demo.example.com' | ||||
|   }, | ||||
|   { | ||||
|     id: '3', | ||||
|     title: 'Task Management System', | ||||
|     description: 'A comprehensive task management system with features like task assignment, progress tracking, and deadline notifications.', | ||||
|     technologies: ['TypeScript', 'Angular', 'Firebase', 'Material UI'], | ||||
|     images: [ | ||||
|       { | ||||
|         src: 'https://source.unsplash.com/random/800x600?tasks', | ||||
|         alt: 'Task management dashboard' | ||||
|       } | ||||
|     ], | ||||
|     repositories: [ | ||||
|       { name: 'Repo', url: 'https://github.com/username/task-management' } | ||||
|     ], | ||||
|     demoUrl: 'https://task-app-demo.example.com' | ||||
|   }, | ||||
|   { | ||||
|     id: '4', | ||||
|     title: 'Portfolio Website', | ||||
|     description: 'A personal portfolio website showcasing projects, skills, and professional experience with a modern, responsive design.', | ||||
|     technologies: ['HTML', 'CSS', 'JavaScript', 'GSAP'], | ||||
|     images: [ | ||||
|       { | ||||
|         src: 'https://source.unsplash.com/random/800x600?portfolio', | ||||
|         alt: 'Portfolio homepage' | ||||
|       }, | ||||
|       { | ||||
|         src: 'https://source.unsplash.com/random/800x600?website', | ||||
|         alt: 'Projects section' | ||||
|       }, | ||||
|       { | ||||
|         src: 'https://source.unsplash.com/random/800x600?design', | ||||
|         alt: 'Contact form' | ||||
|       } | ||||
|     ], | ||||
|     repositories: [ | ||||
|       { name: 'Repo', url: 'https://github.com/username/portfolio' } | ||||
|     ], | ||||
|     demoUrl: 'https://portfolio-demo.example.com' | ||||
|   }, | ||||
|   { | ||||
|     id: '5', | ||||
|     title: 'Recipe Finder', | ||||
|     description: 'An application that allows users to search for recipes based on ingredients, dietary restrictions, and cuisine preferences.', | ||||
|     technologies: ['React', 'Redux', 'Spoonacular API', 'Styled Components'], | ||||
|     images: [ | ||||
|       { | ||||
|         src: 'https://source.unsplash.com/random/800x600?recipe', | ||||
|         alt: 'Recipe search interface' | ||||
|       }, | ||||
|       { | ||||
|         src: 'https://source.unsplash.com/random/800x600?food', | ||||
|         alt: 'Recipe details' | ||||
|       } | ||||
|     ], | ||||
|     repositories: [ | ||||
|       { name: 'Repo', url: 'https://github.com/username/recipe-finder' } | ||||
|     ], | ||||
|     demoUrl: 'https://recipe-finder-demo.example.com' | ||||
|   }, | ||||
|   { | ||||
|     id: '6', | ||||
|     title: 'Fitness Tracker', | ||||
|     description: 'A fitness tracking application that helps users monitor workouts, set goals, and track progress over time.', | ||||
|     technologies: ['React Native', 'Firebase', 'Redux', 'Chart.js'], | ||||
|     images: [ | ||||
|       { | ||||
|         src: 'https://source.unsplash.com/random/800x600?fitness', | ||||
|         alt: 'Fitness tracker dashboard' | ||||
|       }, | ||||
|       { | ||||
|         src: 'https://source.unsplash.com/random/800x600?workout', | ||||
|         alt: 'Workout tracking screen' | ||||
|       }, | ||||
|       { | ||||
|         src: 'https://source.unsplash.com/random/800x600?exercise', | ||||
|         alt: 'Progress charts' | ||||
|       } | ||||
|     ], | ||||
|     repositories: [ | ||||
|       { name: 'Repo', url: 'https://github.com/username/fitness-tracker' } | ||||
|     ], | ||||
|     demoUrl: 'https://fitness-app-demo.example.com' | ||||
|   }, | ||||
|   { | ||||
|     id: '7', | ||||
|     title: 'Chat Application', | ||||
|     description: 'A real-time chat application with features like private messaging, group chats, and file sharing capabilities.', | ||||
|     technologies: ['Socket.io', 'Express', 'MongoDB', 'React'], | ||||
|     images: [ | ||||
|       { | ||||
|         src: 'https://source.unsplash.com/random/800x600?chat', | ||||
|         alt: 'Chat interface' | ||||
|       }, | ||||
|       { | ||||
|         src: 'https://source.unsplash.com/random/800x600?messaging', | ||||
|         alt: 'Messaging screen' | ||||
|       } | ||||
|     ], | ||||
|     repositories: [ | ||||
|       { name: 'Repo', url: 'https://github.com/username/chat-app' } | ||||
|     ], | ||||
|     demoUrl: 'https://chat-app-demo.example.com' | ||||
|   }, | ||||
|   // {
 | ||||
|   //   id: '8',
 | ||||
|   //   title: 'Budget Tracker',
 | ||||
|   //   description: 'A financial management application that helps users track income, expenses, and savings goals with visual reports.',
 | ||||
|   //   technologies: ['Vue.js', 'Node.js', 'PostgreSQL', 'D3.js'],
 | ||||
|   //   images: [
 | ||||
|   //     {
 | ||||
|   //       src: 'https://source.unsplash.com/random/800x600?budget',
 | ||||
|   //       alt: 'Budget dashboard'
 | ||||
|   //     },
 | ||||
|   //     {
 | ||||
|   //       src: 'https://source.unsplash.com/random/800x600?finance',
 | ||||
|   //       alt: 'Expense tracking'
 | ||||
|   //     },
 | ||||
|   //     {
 | ||||
|   //       src: 'https://source.unsplash.com/random/800x600?money',
 | ||||
|   //       alt: 'Financial reports'
 | ||||
|   //     }
 | ||||
|   //   ],
 | ||||
|   //   repositories: [
 | ||||
|   //     { name: 'Frontend', url: 'https://github.com/username/budget-tracker-ui' },
 | ||||
|   //     { name: 'API', url: 'https://github.com/username/budget-tracker-api' }
 | ||||
|   //   ],
 | ||||
|   //   demoUrl: 'https://budget-app-demo.example.com'
 | ||||
|   // }
 | ||||
| ]; | ||||
| 
 | ||||
| export default projects; | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user