init project files

This commit is contained in:
!verity
2026-03-16 21:25:53 +01:00
commit 2045808f00
40 changed files with 3436 additions and 0 deletions

View File

@@ -0,0 +1,117 @@
'use client';
import { motion } from 'framer-motion';
import Image from 'next/image';
import { projects } from '@/content/projects';
import styles from './Projects.module.scss';
import { SiGithub } from 'react-icons/si';
import { ArrowUpRight } from 'lucide-react';
export default function Projects() {
const featured = projects.find(p => p.featured);
const rest = projects.filter(p => !p.featured);
const hasProjects = projects.length > 0;
return (
<section className={styles.section} id="projects">
<div className={styles.container}>
<motion.header
className={styles.header}
initial={{ opacity: 0, y: 20 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ duration: 0.5 }}
>
<h2 className={styles.heading}>Selected Work<span className={styles.dot}>.</span></h2>
<p className={styles.subtitle}>
{hasProjects ? 'A selection of recent projects and side work.' : 'Projects will be added here soon.'}
</p>
</motion.header>
{!hasProjects && (
<motion.div
className={styles.empty}
initial={{ opacity: 0, y: 16 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ duration: 0.5, delay: 0.1 }}
>
<p className={styles.emptyText}>Nothing here yet check back later or get in touch.</p>
</motion.div>
)}
{/* Featured Project */}
{hasProjects && featured && (
<motion.div
className={styles.featured}
initial={{ opacity: 0, y: 28 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ duration: 0.6, ease: [0.22, 0.61, 0.36, 1] }}
>
{featured.image && (
<div className={styles.featuredImage}>
<Image src={featured.image} alt={featured.title} fill style={{ objectFit: 'cover' }} />
</div>
)}
<div className={styles.featuredContent}>
<h3>{featured.title}</h3>
<p>{featured.description}</p>
<div className={styles.links}>
{featured.liveUrl && (
<a href={featured.liveUrl} target="_blank" rel="noopener noreferrer" className={styles.link}>
Live <ArrowUpRight size={14} />
</a>
)}
{featured.githubUrl && (
<a href={featured.githubUrl} target="_blank" rel="noopener noreferrer" className={styles.linkGhost}>
<SiGithub size={15} /> GitHub
</a>
)}
</div>
</div>
</motion.div>
)}
{/* Project Grid */}
{hasProjects && (
<motion.div
className={styles.grid}
initial="hidden"
whileInView="visible"
viewport={{ once: true, margin: '-20px' }}
variants={{ visible: { transition: { staggerChildren: 0.08, delayChildren: 0.05 } } }}
>
{rest.map((project, i) => (
<motion.article
key={project.id}
className={styles.card}
variants={{
hidden: { opacity: 0, y: 20 },
visible: { opacity: 1, y: 0 },
}}
transition={{ duration: 0.45, ease: [0.22, 0.61, 0.36, 1] }}
whileHover={{ y: -5, transition: { duration: 0.2 } }}
>
<h3 className={styles.cardTitle}>{project.title}</h3>
<p className={styles.cardDesc}>{project.description}</p>
<div className={styles.cardLinks}>
{project.liveUrl && (
<a href={project.liveUrl} target="_blank" rel="noopener noreferrer" className={styles.link}>
View <ArrowUpRight size={13} />
</a>
)}
{project.githubUrl && (
<a href={project.githubUrl} target="_blank" rel="noopener noreferrer" className={styles.linkGhost}>
<SiGithub size={14} />
</a>
)}
</div>
</motion.article>
))}
</motion.div>
)}
</div>
</section>
);
}