Fix-projects #8

Merged
seccodesmith merged 6 commits from Fix-projects into main 2025-05-26 20:40:51 +00:00
4 changed files with 215 additions and 114 deletions
Showing only changes of commit bce2276dfa - Show all commits
+10 -3
View File
@@ -42,7 +42,7 @@
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.4); box-shadow: 0 10px 30px rgba(0, 0, 0, 0.4);
} }
.cardImg-top { .cardImgTop {
height: 200px; height: 200px;
object-fit: cover; object-fit: cover;
} }
@@ -68,10 +68,18 @@
} }
/* Category badges */ /* Category badges */
.categoryBadge { .categoryBadgeContainer {
position: absolute; position: absolute;
top: 1rem; top: 1rem;
right: 1rem; right: 1rem;
display: flex;
flex-direction: row-reverse;
gap: 0.5rem;
z-index: 1;
}
.categoryBadge {
background-color: rgba(139, 0, 0, 0.9); background-color: rgba(139, 0, 0, 0.9);
color: $text-light; color: $text-light;
padding: 0.25rem 0.75rem; padding: 0.25rem 0.75rem;
@@ -80,7 +88,6 @@
font-weight: 600; font-weight: 600;
text-transform: uppercase; text-transform: uppercase;
letter-spacing: 1px; letter-spacing: 1px;
z-index: 1;
} }
.featuredBadge { .featuredBadge {
+116 -41
View File
@@ -1,4 +1,4 @@
import { useState } from 'react' import React, { useEffect, useState } from 'react'
import style from '@styles/Project.module.scss' import style from '@styles/Project.module.scss'
@@ -12,7 +12,7 @@ export interface ProjectProps {
title: string; title: string;
description: string; description: string;
image: string; image: string;
category: string[]; category: Category[];
featured?: boolean; featured?: boolean;
technologies: ProjectTech[]; technologies: ProjectTech[];
links: { links: {
@@ -21,71 +21,146 @@ export interface ProjectProps {
}; };
} }
export interface Category {
[key: string]: string;
fullName: string;
shortName: string;
icon: string;
}
interface ProjectCardProps { interface ProjectCardProps {
project: ProjectProps; project: ProjectProps;
onOpenDetails: (id: string) => void; onOpenDetails: (id: string) => void;
} }
export const ProjectCard: React.FC<ProjectCardProps> = ({ project, onOpenDetails }) => { const ProjectCardTech: React.FC<{ technology: ProjectTech[] }> = ({ technology }) => (
const [, setHovered] = useState(false);
return (
<div
className={`card ${style.card} h-100 ${project.featured ? style.featuredCard : ''}`}
data-category={project.category.join(',')}
onMouseEnter={() => setHovered(true)}
onMouseLeave={() => setHovered(false)}
>
<div className="position-relative">
<img src={project.image} className={project.featured ? `img-fluid rounded-start h-100 w-100 object-fit-cover` : style.cardImgTop} alt={project.title} />
<div className={style.categoryBadge}>{project.category[0]}</div>
{project.featured && (
<div className={style.featuredBadge}>
<i className="fas fa-star"></i>
<span>Featured</span>
</div>
)}
</div>
<div className={`${style.cardBody} d-flex flex-column`}>
<h3 className={style.cardTitle}>{project.title}</h3>
<p className={style.cardText}>
{project.description}
</p>
<div> <div>
<h4 className={style.techTitle}>// TECHNOLOGIES_USED</h4> <h4 className={style.techTitle}>// TECHNOLOGIES_USED</h4>
<div className={style.techStack}> <div className={style.techStack}>
{project.technologies.map((tech, index) => ( {technology.map((tech, index) => (
<span className={style.techItem} key={index}> <span className={`${style.techItem}`} key={index}>
<i className={tech.icon}></i> <i className={`${tech.icon} ${style.techIcon}`}></i>
<span>{tech.name}</span> <span>{tech.name}</span>
</span> </span>
))} ))}
</div> </div>
</div> </div>
<div className={`mt-auto d-flex gap-2 flex-wrap project-links`}> )
const CategoryBadge: React.FC<{ category: Category[] }> = ({ category }) => (
< div className={style.categoryBadgeContainer} >
{
category.map((cat, index) => (
<span key={index} className={`${style.categoryBadge}`}>
{cat.shortName}
</span>
))}
</div>
)
const ProjectLinks: React.FC<{ links: { github?: string; demo?: string }, id: string, onOpenDetails: (id: string) => void }> = ({ links, id, onOpenDetails }) => (
<div className={`mt-auto d-flex gap-2 flex-wrap project-links ${style.projectLinks}`}>
<button <button
className={`btn btn-outline-secondary`} className={`btn btn-outline-secondary ${style.btnOutlineSecondary}`}
onClick={() => onOpenDetails(project.id)} onClick={() => onOpenDetails(id)}
> >
<i className="fas fa-info-circle me-2"></i> <i className={`fas fa-info-circle ${style.btnIcon}`}></i>
Project Details Project Details
</button> </button>
{project.links.github && ( {links.github && (
<a href={project.links.github} className={`btn btn-outline-secondary`}> <a href={links.github} className={`btn btn-outline-secondary ${style.btnOutlineSecondary} `}>
<i className="fab fa-github me-2"></i> <i className={`fab fa-github ${style.btnIcon}`}></i>
View Code View Code
</a> </a>
)} )}
{project.links.demo && ( {links.demo && (
<a href={project.links.demo} className={`btn btn-outline-secondary`}> <a href={links.demo} className={`btn btn-outline-secondary ${style.btnOutlineSecondary}`}>
<i className="fas fa-external-link-alt me-2"></i> <i className={`fas fa-external-link-alt ${style.btnIcon}`}></i>
Live Demo Live Demo
</a> </a>
)} )}
</div> </div>
)
export const ProjectCard: React.FC<ProjectCardProps> = ({ project, onOpenDetails }) => {
const [, setHovered] = useState(false);
useEffect(() => {
const createEmbers = () => {
const embersContainer = document.querySelectorAll(`.${style.cardImgTop}`);
embersContainer.forEach((container) => {
for (let i = 0; i < 5; i++) {
const ember = document.createElement('div');
ember.className = style.emberParticle;
ember.style.left = `${Math.random() * 100}%`;
ember.style.bottom = '0';
ember.style.animationDuration = `${Math.random() * 2 + 3}s`;
ember.style.animationDelay = `${Math.random() * 3}s`;
container.appendChild(ember);
}
});
};
createEmbers();
}, []);
if (project.featured) {
return (
<div className={`"col-12 mb-4`}>
<div
className={`card ${style.card} ${style.featuredCard}`}
data-category={project.category.join(',')}
onMouseEnter={() => setHovered(true)}
onMouseLeave={() => setHovered(false)}
>
<div className={`row g-0`}>
<div className={`col-md-6 position-relative`}>
<img src={project.image} className={`img-fluid rounded-start h-100 w-100 object-fit-cover`} alt={project.title} />
<div className={style.featuredBadge}>
<i className="fas fa-star"></i>
<span>Featured</span>
</div>
<CategoryBadge category={project.category} />
</div>
<div className={`col-md-6`}>
<div className={`card-body d-flex flex-column h-100 ${style.cardBody}`}>
<h3 className={`${style.cardTitle} card-title`}>{project.title}</h3>
<p className={`${style.cardText} card-text`}>
{project.description}
</p>
<ProjectCardTech technology={project.technologies} />
<ProjectLinks links={project.links} id={project.id} onOpenDetails={onOpenDetails} />
</div>
</div>
</div>
</div> </div>
</div> </div>
) )
} }
return (
<div className={`col-md-6 col-lg-4 mb-4`}>
<div className={`card ${style.card} h-100`}
data-category={project.category.join(',')}
onMouseEnter={() => setHovered(true)}
onMouseLeave={() => setHovered(false)}>
<div className={`position-relative`}>
<img src={project.image} className={style.cardImgTop} alt={project.title} />
<CategoryBadge category={project.category} />
</div>
<div className={`card-body d-flex flex-column ${style.cardBody}`}>
<h3 className={`${style.cardTitle} card-title`}>{project.title}</h3>
<p className={`${style.cardText} card-text`}>
{project.description}
</p>
<ProjectCardTech technology={project.technologies} />
<ProjectLinks links={project.links} id={project.id} onOpenDetails={onOpenDetails} />
</div>
</div>
</div>
)
};
+36 -7
View File
@@ -1,4 +1,33 @@
import type { ProjectProps } from "../components/ProjectCard"; import type { ProjectProps, Category } from "../components/ProjectCard";
type CategoryMap = Record<string, Category>
export const Categories: CategoryMap = {
Embedded: {
fullName: "Embedded Systems",
shortName: "Embedded",
icon: "fas fa-microchip",
},
Web: {
fullName: "Web Development",
shortName: "Web",
icon: "fas fa-code",
},
ML: {
fullName: "Machine Learning",
shortName: "ML",
icon: "fas fa-robot",
},
IoT: {
fullName: "Internet of Things",
shortName: "IoT",
icon: "fas fa-network-wired",
},
Sec: {
fullName: "Cybersecurity",
shortName: "Security",
icon: "fas fa-shield-alt",
},
}
export const projectsData: ProjectProps[] = [ export const projectsData: ProjectProps[] = [
{ {
@@ -6,7 +35,7 @@ export const projectsData: ProjectProps[] = [
title: "ShadowGuardian: Secure IoT Monitoring System", title: "ShadowGuardian: Secure IoT Monitoring System",
description: "A comprehensive security monitoring system built on STM32 microcontrollers with end-to-end encryption and low-power operation. Integrates with home automation systems and provides real-time alerts through a mobile application.", description: "A comprehensive security monitoring system built on STM32 microcontrollers with end-to-end encryption and low-power operation. Integrates with home automation systems and provides real-time alerts through a mobile application.",
image: "/images/projects/shadowguardian.jpg", image: "/images/projects/shadowguardian.jpg",
category: ["iot", "embedded"], category: [Categories["IoT"], Categories["Embedded"]],
featured: true, featured: true,
technologies: [ technologies: [
{ name: "C++", icon: "devicon-cplusplus-plain tech-icon" }, { name: "C++", icon: "devicon-cplusplus-plain tech-icon" },
@@ -25,7 +54,7 @@ export const projectsData: ProjectProps[] = [
title: "PyroSight: Thermal Anomaly Detection", title: "PyroSight: Thermal Anomaly Detection",
description: "An intelligent system that uses computer vision and thermal imaging to detect anomalies in industrial equipment. Employs PyTorch for deep learning to predict potential failures before they occur.", description: "An intelligent system that uses computer vision and thermal imaging to detect anomalies in industrial equipment. Employs PyTorch for deep learning to predict potential failures before they occur.",
image: "/images/projects/pyrosight.jpg", image: "/images/projects/pyrosight.jpg",
category: ["ml", "iot"], category: [Categories["ML"], Categories["IoT"]],
technologies: [ technologies: [
{ name: "Python", icon: "devicon-python-plain tech-icon" }, { name: "Python", icon: "devicon-python-plain tech-icon" },
{ name: "PyTorch", icon: "devicon-pytorch-original tech-icon" }, { name: "PyTorch", icon: "devicon-pytorch-original tech-icon" },
@@ -41,7 +70,7 @@ export const projectsData: ProjectProps[] = [
title: "NexusFlow: DevOps Dashboard", title: "NexusFlow: DevOps Dashboard",
description: "A real-time monitoring dashboard for DevOps teams that visualizes CI/CD pipelines, infrastructure metrics, and deployment statuses. Built with Blazor for seamless updates without page refreshes.", description: "A real-time monitoring dashboard for DevOps teams that visualizes CI/CD pipelines, infrastructure metrics, and deployment statuses. Built with Blazor for seamless updates without page refreshes.",
image: "/images/projects/nexusflow.jpg", image: "/images/projects/nexusflow.jpg",
category: ["web"], category: [Categories["Web"]],
technologies: [ technologies: [
{ name: "C#", icon: "devicon-csharp-plain tech-icon" }, { name: "C#", icon: "devicon-csharp-plain tech-icon" },
{ name: "Blazor", icon: "devicon-dotnetcore-plain tech-icon" }, { name: "Blazor", icon: "devicon-dotnetcore-plain tech-icon" },
@@ -58,7 +87,7 @@ export const projectsData: ProjectProps[] = [
title: "EtherWhisper: Secure Communication Node", title: "EtherWhisper: Secure Communication Node",
description: "A low-power, long-range communication node for IoT networks with military-grade encryption and mesh capabilities. Perfect for remote sensing applications in harsh environments.", description: "A low-power, long-range communication node for IoT networks with military-grade encryption and mesh capabilities. Perfect for remote sensing applications in harsh environments.",
image: "/images/projects/etherwhisper.jpg", image: "/images/projects/etherwhisper.jpg",
category: ["embedded", "iot"], category: [Categories["Embedded"], Categories["IoT"]],
technologies: [ technologies: [
{ name: "C", icon: "devicon-c-plain tech-icon" }, { name: "C", icon: "devicon-c-plain tech-icon" },
{ name: "STM32", icon: "fas fa-memory tech-icon" }, { name: "STM32", icon: "fas fa-memory tech-icon" },
@@ -74,7 +103,7 @@ export const projectsData: ProjectProps[] = [
title: "DataForge: Advanced Analytics Platform", title: "DataForge: Advanced Analytics Platform",
description: "A comprehensive data analytics platform with interactive visualization tools and automated reporting features. Integrates with various data sources and provides ML-powered insights.", description: "A comprehensive data analytics platform with interactive visualization tools and automated reporting features. Integrates with various data sources and provides ML-powered insights.",
image: "/images/projects/dataforge.jpg", image: "/images/projects/dataforge.jpg",
category: ["web"], category: [Categories["Web"]],
technologies: [ technologies: [
{ name: "Python", icon: "devicon-python-plain tech-icon" }, { name: "Python", icon: "devicon-python-plain tech-icon" },
{ name: "Django", icon: "devicon-django-plain tech-icon" }, { name: "Django", icon: "devicon-django-plain tech-icon" },
@@ -91,7 +120,7 @@ export const projectsData: ProjectProps[] = [
title: "VisionKeeper: Object Recognition System", title: "VisionKeeper: Object Recognition System",
description: "A real-time object recognition system optimized for edge devices. Capable of identifying and tracking objects with high accuracy even in challenging lighting conditions.", description: "A real-time object recognition system optimized for edge devices. Capable of identifying and tracking objects with high accuracy even in challenging lighting conditions.",
image: "/images/projects/visionkeeper.jpg", image: "/images/projects/visionkeeper.jpg",
category: ["ml"], category: [Categories["ML"]],
technologies: [ technologies: [
{ name: "Python", icon: "devicon-python-plain tech-icon" }, { name: "Python", icon: "devicon-python-plain tech-icon" },
{ name: "PyTorch", icon: "devicon-pytorch-original tech-icon" }, { name: "PyTorch", icon: "devicon-pytorch-original tech-icon" },
+18 -28
View File
@@ -2,7 +2,11 @@ import { useState } from 'react';
import { PageHeader } from '../components/PageHeader'; import { PageHeader } from '../components/PageHeader';
import { ProjectCard, type ProjectProps } from '../components/ProjectCard'; import { ProjectCard, type ProjectProps } from '../components/ProjectCard';
import { ProjectModal } from '../components/ProjectModal'; import { ProjectModal } from '../components/ProjectModal';
import { projectsData } from '../data/projectsData'; import { projectsData, Categories } from '../data/projectsData';
import { styleText } from 'util';
import style from '@styles/Project.module.scss'
export const Projects = () => { export const Projects = () => {
const [activeFilter, setActiveFilter] = useState('all'); const [activeFilter, setActiveFilter] = useState('all');
@@ -10,7 +14,9 @@ export const Projects = () => {
const filteredProjects = activeFilter === 'all' const filteredProjects = activeFilter === 'all'
? projectsData ? projectsData
: projectsData.filter(project => project.category.includes(activeFilter)); : projectsData.filter(project => project.category.some(cat => cat.shortName.toLowerCase() === activeFilter));
const categories = Categories;
const featuredProject = projectsData.find(project => project.featured); const featuredProject = projectsData.find(project => project.featured);
@@ -40,50 +46,34 @@ export const Projects = () => {
<div className="container mt-5"> <div className="container mt-5">
{/* Projects Filter */} {/* Projects Filter */}
<div className="filter-container text-center"> <div className={`filter-container ${style.filterContainer} text-center`}>
<button <button
className={`filter-button ${activeFilter === 'all' ? 'active' : ''}`} className={`filter-button ${style.filterButton} ${activeFilter === 'all' ? style.active : ''}`}
onClick={() => handleFilterClick('all')} onClick={() => handleFilterClick('all')}
> >
All Artifacts All Artifacts
</button> </button>
{Object.values(categories).map((category) => (
<button <button
className={`filter-button ${activeFilter === 'embedded' ? 'active' : ''}`} key={category.shortName}
onClick={() => handleFilterClick('embedded')} className={`filter-button ${style.filterButton} ${activeFilter === category.shortName.toLowerCase() ? style.active : ''}`}
onClick={() => handleFilterClick(category.shortName.toLowerCase())}
> >
Embedded Systems {category.fullName}
</button>
<button
className={`filter-button ${activeFilter === 'web' ? 'active' : ''}`}
onClick={() => handleFilterClick('web')}
>
Web Development
</button>
<button
className={`filter-button ${activeFilter === 'ml' ? 'active' : ''}`}
onClick={() => handleFilterClick('ml')}
>
Machine Learning
</button>
<button
className={`filter-button ${activeFilter === 'iot' ? 'active' : ''}`}
onClick={() => handleFilterClick('iot')}
>
IoT
</button> </button>
))}
</div> </div>
<div className="row"> <div className="row">
{featuredProject && (activeFilter === 'all' || featuredProject.category.includes(activeFilter)) && ( {featuredProject && (activeFilter === 'all' || featuredProject.category.some(cat => cat.shortName.toLowerCase() === activeFilter)) && (
<div className="col-12 mb-4"> <div className="col-12 mb-4">
<ProjectCard project={featuredProject} onOpenDetails={handleOpenDetails} /> <ProjectCard project={featuredProject} onOpenDetails={handleOpenDetails} />
</div> </div>
)} )}
{regularProjects.map(project => ( {regularProjects.map(project => (
<div className="col-md-6 col-lg-4 mb-4" key={project.id}>
<ProjectCard project={project} onOpenDetails={handleOpenDetails} /> <ProjectCard project={project} onOpenDetails={handleOpenDetails} />
</div>
))} ))}
</div> </div>