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