Merge pull request 'Fix-projects' (#8) from Fix-projects into main

Reviewed-on: #8
This commit was merged in pull request #8.
This commit is contained in:
2025-05-26 20:40:50 +00:00
11 changed files with 834 additions and 226 deletions
+1 -1
View File
@@ -73,7 +73,7 @@
color: $accent-fire;
font-size: 1rem;
animation: fall linear infinite;
opacity: 0.8;
opacity: 0.9;
}
@keyframes fall {
+378
View File
@@ -0,0 +1,378 @@
@use './variables' as *;
.filterContainer {
margin-bottom: 2rem;
}
.filterButton {
background-color: rgba(255, 255, 255, 0.05);
border: 1px solid $card-border;
color: $text-ash;
padding: 0.5rem 1rem;
margin: 0 0.25rem 0.5rem;
border-radius: 4px;
transition: all 0.3s ease;
}
.filterButton:hover,
.filterButton.active {
background-color: rgba(255, 100, 0, 0.1);
border-color: rgba(255, 100, 0, 0.3);
color: $accent-fire;
}
.filterButton.active {
border-color: $accent-fire;
}
/* Project Cards */
.card {
background-color: $card-bg;
border: 1px solid $card-border;
border-radius: 8px;
transition: all 0.3s ease;
margin-bottom: 1.5rem;
overflow: hidden;
height: 100%;
}
.card:hover {
transform: translateY(-10px);
border-color: rgba(255, 100, 0, 0.3);
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.4);
}
.cardImgTop {
height: 200px;
object-fit: cover;
}
.cardTitle {
font-size: 1.5rem;
margin-bottom: 0.75rem;
}
.cardText {
color: $text-ash;
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
line-height: 1.6;
margin-bottom: 1.5rem;
}
.featuredCard {
position: relative;
}
.featuredCard .cardImgTop {
height: 300px;
}
/* Category badges */
.categoryBadgeContainer {
position: absolute;
top: 1rem;
right: 1rem;
display: flex;
flex-direction: row-reverse;
gap: 0.5rem;
z-index: 1;
}
.categoryBadge {
background-color: rgba(139, 0, 0, 0.9);
color: $text-light;
padding: 0.25rem 0.75rem;
border-radius: 4px;
font-size: 0.8rem;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 1px;
}
.featuredBadge {
position: absolute;
top: 1rem;
left: 1rem;
background: linear-gradient(45deg, $accent-dragon, $accent-fire);
color: $text-light;
padding: 0.25rem 0.75rem;
border-radius: 4px;
font-size: 0.8rem;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 1px;
display: flex;
align-items: center;
gap: 5px;
z-index: 1;
}
/* Tech stack */
.techTitle {
font-size: 0.9rem;
margin-bottom: 0.625rem;
color: $text-ash;
font-family: 'Fira Code', monospace;
}
.techStack {
display: flex;
flex-wrap: wrap;
gap: 8px;
margin-bottom: 1.5rem;
}
.techItem {
display: inline-flex;
align-items: center;
gap: 6px;
background-color: rgba(255, 255, 255, 0.05);
padding: 5px 10px;
border-radius: 4px;
font-size: 0.8rem;
font-family: 'Fira Code', monospace;
color: $text-ash;
transition: all 0.3s ease;
}
.techItem:hover {
background-color: rgba(255, 100, 0, 0.1);
color: $accent-fire;
transform: translateY(-2px);
}
.techIcon {
font-size: 1rem;
color: $accent-fire;
}
/* Project links */
.btnOutlineSecondary {
background-color: rgba(255, 255, 255, 0.05);
border-color: $card-border;
color: $text-light;
}
.btnOutlineSecondary:hover {
background-color: rgba(255, 100, 0, 0.1);
border-color: $accent-fire;
color: $text-light;
}
.btnIcon {
margin-right: 0.5rem;
color: $accent-fire;
}
/* View All Button */
.btnPrimary {
background: linear-gradient(45deg, $accent-dragon, $accent-fire);
border: none;
padding: 0.75rem 2rem;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 1px;
box-shadow: 0 4px 15px rgba(139, 0, 0, 0.3);
transition: all 0.3s ease;
}
.btnPrimary:hover {
transform: translateY(-3px);
box-shadow: 0 7px 25px rgba(139, 0, 0, 0.5);
background-color: $accent-fire;
background-image: none;
}
/* Modal Styling */
.modalContent {
background-color: $card-bg;
border: 1px solid $card-border;
color: $text-light;
}
.modalHeader {
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
}
.modalTitle {
color: $text-light;
}
.modalFooter {
border-top: 1px solid rgba(255, 255, 255, 0.1);
}
.btnClose {
filter: invert(1) grayscale(100%) brightness(200%);
}
.featuresList {
padding-left: 1.5rem;
color: $text-ash;
}
.featuresList li {
margin-bottom: 0.5rem;
position: relative;
}
.featuresList li::before {
content: "";
color: $accent-fire;
position: absolute;
left: -1rem;
top: 0;
font-size: 1.2rem;
}
.techChip {
display: inline-flex;
align-items: center;
gap: 6px;
background-color: rgba(255, 255, 255, 0.05);
padding: 8px 15px;
border-radius: 20px;
margin-right: 0.5rem;
margin-bottom: 0.5rem;
transition: all 0.3s ease;
}
.techChip:hover {
background-color: rgba(255, 100, 0, 0.1);
transform: translateY(-3px);
}
.techChip-icon {
color: $accent-fire;
font-size: 1.1rem;
}
.techChip-name {
color: $text-ash;
font-size: 0.9rem;
font-family: 'Fira Code', monospace;
}
.galleryItem {
height: 150px;
border-radius: 8px;
overflow: hidden;
transition: all 0.3s ease;
cursor: pointer;
margin-bottom: 15px;
}
.galleryItem:hover {
transform: scale(1.05);
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.3);
}
.galleryImage {
width: 100%;
height: 100%;
object-fit: cover;
}
/* Binary background effect */
.binaryLine {
position: absolute;
font-family: 'Fira Code', monospace;
color: $accent-fire;
font-size: 0.8rem;
opacity: 0.15;
pointer-events: none;
}
/* Ember particle effect */
.emberParticle {
position: absolute;
width: 3px;
height: 3px;
background-color: $accent-fire;
border-radius: 50%;
opacity: 0.6;
pointer-events: none;
z-index: 2;
box-shadow: 0 0 5px $accent-fire;
animation: float 3s linear infinite;
}
@keyframes float {
0% {
transform: translateY(0) rotate(0deg);
opacity: 0.6;
}
100% {
transform: translateY(-100px) rotate(360deg);
opacity: 0;
}
}
/* Footer */
.footer {
background-color: $primary-dark;
padding: 3rem 0;
border-top: 1px solid rgba(255, 100, 0, 0.2);
margin-top: 3rem;
}
.socialLinks {
display: flex;
justify-content: center;
gap: 20px;
margin-bottom: 1.5rem;
}
.socialIcon {
font-size: 1.5rem;
color: $text-ash;
transition: all 0.3s ease;
}
.socialIcon:hover {
color: $accent-fire;
transform: translateY(-5px);
}
.copyright {
color: $text-ash;
font-size: 0.9rem;
text-align: center;
}
/* Responsive adjustments */
@media (max-width: 992px) {
.pageTitle {
font-size: 2.5rem;
}
.featuredCard .row {
flex-direction: column;
}
}
@media (max-width: 768px) {
.pageTitle {
font-size: 2rem;
}
.techStack {
justify-content: center;
}
}
@media (max-width: 576px) {
.pageTitle {
font-size: 1.8rem;
}
.projectLinks {
flex-direction: column;
}
.projectLinks .btn {
margin-bottom: 0.5rem;
}
}
+2 -16
View File
@@ -1,21 +1,7 @@
import { Link } from 'react-router-dom';
import styles from '@styles/BlogCard.module.scss';
export interface BlogPostProps {
id: string;
slug: string;
title: string;
excerpt: string;
image: string;
category: string;
date: string;
author: string;
commentCount: number;
readTime: string;
featured?: boolean;
tags: string[];
content: string;
}
import type {BlogPostProps} from '../untils/BlogPostProps';
interface BlogCardProps {
post: BlogPostProps;
@@ -26,7 +12,7 @@ export const BlogCard: React.FC<BlogCardProps> = ({ post }) => {
<div className={`card ${styles.card} ${post.featured ? styles.featuredCard : ''}`}>
<div className="position-relative">
<img src={post.image} className={`card-img-top ${post.featured ? styles.featuredImg : ''}`} alt={post.title} />
<div className="category-badge">{post.category}</div>
<div className={styles.categoryBadge}>{post.category}</div>
{post.featured && (
<div className={`featured-badge ${styles.featuredBadge}`}>
+126 -75
View File
@@ -1,90 +1,141 @@
import { useState } from 'react'
import React, { useEffect, useState } from 'react'
export interface ProjectTech {
name: string;
icon: string;
}
export interface ProjectProps {
id: string;
title: string;
description: string;
image: string;
category: string[];
featured?: boolean;
technologies: ProjectTech[];
links: {
github?: string;
demo?: string;
};
}
import style from '@styles/Project.module.scss'
import type {ProjectTech, Category, ProjectProps } from '../untils/ProjectProps'
interface ProjectCardProps {
project: ProjectProps;
onOpenDetails: (id: string) => void;
}
const ProjectCardTech: React.FC<{ technology: ProjectTech[] }> = ({ technology }) => (
<div>
<h4 className={style.techTitle}>// TECHNOLOGIES_USED</h4>
<div className={style.techStack}>
{technology.map((tech, index) => (
<span className={`${style.techItem}`} key={index}>
<i className={`${tech.icon} ${style.techIcon}`}></i>
<span>{tech.name}</span>
</span>
))}
</div>
</div>
)
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
className={`btn btn-outline-secondary ${style.btnOutlineSecondary}`}
onClick={() => onOpenDetails(id)}
data-bs-toggle="modal" data-bs-target="#shadowguardianModal">
<i className={`fas fa-info-circle ${style.btnIcon}`} ></i>
Project Details
</button>
{links.github && (
<a href={links.github} className={`btn btn-outline-secondary ${style.btnOutlineSecondary} `}>
<i className={`fab fa-github ${style.btnIcon}`} ></i>
View Code
</a>
)}
{links.demo && (
<a href={links.demo} className={`btn btn-outline-secondary ${style.btnOutlineSecondary}`}>
<i className={`fas fa-external-link-alt ${style.btnIcon}`}></i>
Live Demo
</a>
)}
</div>
)
export const ProjectCard: React.FC<ProjectCardProps> = ({ project, onOpenDetails }) => {
const [, setHovered] = useState(false);
return (
<div
className={`card h-100 ${project.featured ? 'featured-card' : ''}`}
data-category={project.category.join(',')}
onMouseEnter={() => setHovered(true)}
onMouseLeave={() => setHovered(false)}
>
<div className="position-relative">
<img src={project.image} className="card-img-top" alt={project.title} />
<div className="category-badge">{project.category[0]}</div>
{project.featured && (
<div className="featured-badge">
<i className="fas fa-star"></i>
<span>Featured</span>
</div>
)}
</div>
<div className="card-body d-flex flex-column">
<h3 className="card-title">{project.title}</h3>
<p className="card-text">
{project.description}
</p>
<div>
<h4 className="tech-title">// TECHNOLOGIES_USED</h4>
<div className="tech-stack">
{project.technologies.map((tech, index) => (
<span className="tech-item" key={index}>
<i className={tech.icon}></i>
<span>{tech.name}</span>
</span>
))}
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 className="mt-auto d-flex gap-2 flex-wrap project-links">
<button
className="btn btn-outline-secondary"
onClick={() => onOpenDetails(project.id)}
>
<i className="fas fa-info-circle me-2"></i>
Project Details
</button>
{project.links.github && (
<a href={project.links.github} className="btn btn-outline-secondary">
<i className="fab fa-github me-2"></i>
View Code
</a>
)}
{project.links.demo && (
<a href={project.links.demo} className="btn btn-outline-secondary">
<i className="fas fa-external-link-alt me-2"></i>
Live Demo
</a>
)}
</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>
)
}
};
+152 -50
View File
@@ -1,56 +1,158 @@
import type { ProjectProps } from './ProjectCard';
import type { ProjectProps } from '../untils/ProjectProps';
import style from '@styles/Project.module.scss'
interface ProjectModalProps {
project: ProjectProps | null;
onClose: () => void;
project: ProjectProps | null;
}
export const ProjectModal: React.FC<ProjectModalProps> = ({ project, onClose }) => {
if (!project) return null;
return (
<div className="modal fade show" style={{ display: 'block' }} tabIndex={-1}>
<div className="modal-dialog modal-xl modal-dialog-scrollable">
<div className="modal-content">
<div className="modal-header">
<h5 className="modal-title">{project.title}</h5>
<button type="button" className="btn-close" onClick={onClose}></button>
</div>
<div className="modal-body">
<div className="mb-4">
<img src={project.image} className="img-fluid rounded mb-3" alt={project.title} />
export const ProjectModal: React.FC<ProjectModalProps> = ({ project }) => {
if (!project) return null;
return (
<div className="modal fade" id="shadowguardianModal" tabIndex={-1} aria-labelledby="shadowguardianModalLabel" aria-hidden="true">
<div className="modal-dialog modal-xl modal-dialog-scrollable">
<div className={`modal-content ${style.modalContent}`}>
<div className={`modal-header ${style.modalHeader}`}>
<h5 className={style.modalHeader} id="shadowguardianModalLabel">
{project.title}
</h5>
<button type="button" className={`btn-close ${style.btnClose}`} data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div className={`modal-body ${style.modalBody}`}>
<div className="mb-4">
<img src={project.image} className="img-fluid rounded mb-3" alt={project.title} />
<div className={`row ${style.row}`}>
<div className="col-md-8">
{
project.projectDetails ? project.projectDetails.descriptions?.map((desc, index) => (
<p key={index} className="mb-3">{desc}</p>
)) :
<p>{project.description}</p>
}
</div>
<div className="col-md-4">
<div className={`card h-100 ${style.card}`}>
<div className={`card-header ${style.cardHeader}`}>
<h5 className="mb-0">Project Overview</h5>
</div>
<div className="card-body">
{project.projectDetails && project.projectDetails.startDate && project.projectDetails.endDate ? (
<p><strong>Duration:</strong> {new Date(project.projectDetails.startDate).toLocaleDateString()} - {new Date(project.projectDetails.endDate).toLocaleDateString()}</p>
) : (
<p><strong>Duration:</strong> Ongoing</p>
)
}
{
project.projectDetails && project.projectDetails.role ? (
<p><strong>Role:</strong> {project.projectDetails.role}</p>
) : (
<p><strong>Role:</strong> Developer</p>
)
}
{
project.projectDetails && project.projectDetails.client ? (
<p><strong>Client:</strong> {project.projectDetails.client}</p>
) : (
<p><strong>Client:</strong> Internal Project</p>
)
}
</div>
</div>
</div>
</div>
{
project.projectDetails?.keyFeatures && project.projectDetails.keyFeatures.length > 0 && (
<>
<h4 className="mt-4 mb-3">Key Features</h4>
{
project.projectDetails?.keyFeatures.map((feature, index) => (
<p key={index} className="mb-2">
<i className="fas fa-check-circle text-success me-2"></i>
{feature}
</p>
))
}
</>
)
}
<h4 className="mt-4 mb-3">Technologies Used</h4>
<div className="mb-4"></div>
{
project.projectDetails?.fullTechStack && project.projectDetails?.fullTechStack?.length > 0 ? (
<>
{project.projectDetails.fullTechStack.map((tech, index) => (
<span key={index} className={`tech-chip ${style.techChip}`}>
<i className={`${tech.icons} tech-chip-icon`}></i>
<span className="tech-chip-name">{tech.name}</span>
</span>
))}
</>
) : (
<>
{project.technologies && project.technologies.length > 0 ? (
project.technologies.map((tech, index) => (
<span key={index} className={`tech-chip ${style.techChip}`}>
<i className={`${tech.icon} tech-chip-icon`}></i>
<span className="tech-chip-name">{tech.name}</span>
</span>
))
) : (
<p className="text-muted">No technologies listed.</p>
)
}
</>
)
}
</div>
{
project.projectDetails?.gallery && project.projectDetails?.gallery.length > 0 && (
<>
<h4 className="mt-4 mb-3">Gallery</h4>
<div className="row">
{
project.projectDetails.gallery.map((image, index) => (
<div className="col-md-4 col-6">
<div key={index} className={`gallery-item ${style.galleryItem}`}>
<img src={image} alt={`Gallery Image ${index + 1}`} className={`gallery-image ${style.galleryImage}`}></img>
</div>
</div>
))
}
</div>
</>
)
}
</div>
<div className={`modal-footer ${style.modalFooter}`}>
{project.links.github && (
<a href={project.links.github} className={`btn btn-outline-secondary ${style.btnOutlineSecondary}`} target="_blank" rel="noopener noreferrer">
<i className="fab fa-github btn-icon"></i>Source Code
</a>
)}
{project.links.demo && (
<a href={project.links.demo} className={`btn btn-outline-secondary ${style.btnOutlineSecondary}`} target="_blank" rel="noopener noreferrer">
<i className="fas fa-external-link-alt btn-icon"></i>Live Demo
</a>
)}
{project.links.documentation && (
<a href={project.links.documentation} className={`btn btn-outline-secondary ${style.btnOutlineSecondary}`} target="_blank" rel="noopener noreferrer">
<i className="fas fa-book btn-icon"></i>Documentation
</a>
)}
<button type="button" className={`btn btn-secondary`} data-bs-dismiss="modal">Close</button>
</div>
</div>
</div>
<p>{project.description}</p>
<h4 className="mt-4 mb-3">Technologies Used</h4>
<div className="mb-4">
{project.technologies.map((tech, index) => (
<span className="tech-chip" key={index}>
<i className={`${tech.icon} tech-chip-icon`}></i>
<span className="tech-chip-name">{tech.name}</span>
</span>
))}
</div>
</div>
<div className="modal-footer">
{project.links.github && (
<a href={project.links.github} className="btn btn-outline-secondary">
<i className="fab fa-github me-2"></i>Source Code
</a>
)}
{project.links.demo && (
<a href={project.links.demo} className="btn btn-outline-secondary">
<i className="fas fa-external-link-alt me-2"></i>Live Demo
</a>
)}
<button type="button" className="btn btn-secondary" onClick={onClose}>Close</button>
</div>
</div>
</div>
<div className="modal-backdrop fade show"></div>
</div>
);
}
);
};
+1 -1
View File
@@ -1,4 +1,4 @@
import type { BlogPostProps } from "../components/BlogCard";
import type {BlogPostProps} from '../untils/BlogPostProps'
export const blogPostsData: BlogPostProps[] = [
{
+67 -16
View File
@@ -1,23 +1,74 @@
import type { ProjectProps } from "../components/ProjectCard";
import type { ProjectProps, Category } from "../untils/ProjectProps";
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[] = [
{
id: "shadowguardian",
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.",
id: "SecCodeSmithFrontend",
title: "SecCodeSmith Portfolio Page - Frontend",
description: "A modern, responsive portfolio page showcasing my projects, skills, and blog posts. Built with React and styled-components for a sleek user interface.",
image: "/images/projects/shadowguardian.jpg",
category: ["iot", "embedded"],
category: [Categories["Web"]],
featured: true,
technologies: [
{ name: "C++", icon: "devicon-cplusplus-plain tech-icon" },
{ name: "STM32", icon: "fas fa-microchip tech-icon" },
{ name: "ESP32", icon: "fas fa-wifi tech-icon" },
{ name: "Encryption", icon: "fas fa-shield-alt tech-icon" },
{ name: "React Native", icon: "devicon-react-original tech-icon" }
{ name: "React", icon: "devicon-react-original tech-icon" },
{ name: "TypeScript", icon: "devicon-typescript-plain tech-icon" },
{ name: "SCSS", icon: "devicon-sass-original tech-icon" },
{ name: "Vite", icon: "devicon-vitejs-plain tech-icon" }
],
links: {
github: "https://github.com/SecCodeSmith/shadowguardian",
demo: "https://shadowguardian.demo.com"
demo: "https://seccodesmith.pl"
},
projectDetails: {
descriptions: [
"This portfolio page is designed to highlight my skills and projects in a visually appealing way. It features a clean layout, smooth animations, and responsive design for optimal viewing on all devices.",
"The project utilizes React for building the user interface, TypeScript for type safety, and SCSS for styling. The use of Vite ensures fast development and build times."
],
startDate: "12-05-2025",
endDate: "",
dateFormatted: "DD MMMM YYYY",
role: "Frontend Developer",
status: "In Progress",
keyFeatures: [
"Responsive design with mobile-first approach",
"Dynamic project showcase with filtering options",
"Interactive blog section with latest posts"
],
gallery: ["/images/projects/shadowguardian1.jpg", "/images/projects/shadowguardian2.jpg"],
fullTechStack: [
{ name: "React", icons: "devicon-react-original" },
{ name: "TypeScript", icons: "devicon-typescript-plain" },
{ name: "SCSS", icons: "devicon-sass-original" },
{ name: "Vite", icons: "devicon-vitejs-plain" }
]
}
},
{
@@ -25,7 +76,7 @@ export const projectsData: ProjectProps[] = [
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.",
image: "/images/projects/pyrosight.jpg",
category: ["ml", "iot"],
category: [Categories["ML"], Categories["IoT"]],
technologies: [
{ name: "Python", icon: "devicon-python-plain tech-icon" },
{ name: "PyTorch", icon: "devicon-pytorch-original tech-icon" },
@@ -41,7 +92,7 @@ export const projectsData: ProjectProps[] = [
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.",
image: "/images/projects/nexusflow.jpg",
category: ["web"],
category: [Categories["Web"]],
technologies: [
{ name: "C#", icon: "devicon-csharp-plain tech-icon" },
{ name: "Blazor", icon: "devicon-dotnetcore-plain tech-icon" },
@@ -58,7 +109,7 @@ export const projectsData: ProjectProps[] = [
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.",
image: "/images/projects/etherwhisper.jpg",
category: ["embedded", "iot"],
category: [Categories["Embedded"], Categories["IoT"]],
technologies: [
{ name: "C", icon: "devicon-c-plain tech-icon" },
{ name: "STM32", icon: "fas fa-memory tech-icon" },
@@ -74,7 +125,7 @@ export const projectsData: ProjectProps[] = [
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.",
image: "/images/projects/dataforge.jpg",
category: ["web"],
category: [Categories["Web"]],
technologies: [
{ name: "Python", icon: "devicon-python-plain tech-icon" },
{ name: "Django", icon: "devicon-django-plain tech-icon" },
@@ -91,7 +142,7 @@ export const projectsData: ProjectProps[] = [
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.",
image: "/images/projects/visionkeeper.jpg",
category: ["ml"],
category: [Categories["ML"]],
technologies: [
{ name: "Python", icon: "devicon-python-plain tech-icon" },
{ name: "PyTorch", icon: "devicon-pytorch-original tech-icon" },
+1 -1
View File
@@ -7,7 +7,7 @@ import style from '@styles/Blog.module.scss';
export const Blog = () => {
const [searchQuery, setSearchQuery] = useState('');
const [currentPage, setCurrentPage] = useState(1);
const [currentPage, /*setCurrentPage*/] = useState(1);
const filteredPosts = searchQuery
? blogPostsData.filter(post =>
+44 -66
View File
@@ -1,119 +1,97 @@
import { useState } from 'react';
import { PageHeader } from '../components/PageHeader';
import { ProjectCard, type ProjectProps } from '../components/ProjectCard';
import { ProjectCard } from '../components/ProjectCard';
import { ProjectModal } from '../components/ProjectModal';
import { projectsData } from '../data/projectsData';
import { projectsData, Categories } from '../data/projectsData';
import type {ProjectProps} from '../untils/ProjectProps';
import style from '@styles/Project.module.scss'
export const Projects = () => {
const [activeFilter, setActiveFilter] = useState('all');
const [selectedProject, setSelectedProject] = useState<ProjectProps | null>(null);
// Filter projects based on selected category
const filteredProjects = activeFilter === 'all'
? projectsData
: projectsData.filter(project => project.category.includes(activeFilter));
// Get the featured project
const filteredProjects = activeFilter === 'all'
? projectsData
: projectsData.filter(project => project.category.some(cat => cat.shortName.toLowerCase() === activeFilter));
if (selectedProject == null && filteredProjects.length > 0)
setSelectedProject(filteredProjects[0]);
const categories = Categories;
const featuredProject = projectsData.find(project => project.featured);
// Get regular projects (non-featured)
const regularProjects = filteredProjects.filter(project => !project.featured);
// Handle project filter click
const handleFilterClick = (filter: string) => {
setActiveFilter(filter);
};
// Handle opening project details modal
const handleOpenDetails = (id: string) => {
const project = projectsData.find(p => p.id === id);
if (project) {
setSelectedProject(project);
}
};
// Handle closing project details modal
const handleCloseDetails = () => {
setSelectedProject(null);
};
return (
<>
<PageHeader
title="The Digital Forge"
subtitle="Artifacts crafted with code, hammered with precision, and tempered in the fires of innovation"
<PageHeader
title="The Digital Forge"
subtitle="Artifacts crafted with code, hammered with precision, and tempered in the fires of innovation"
/>
<div className="container mt-5">
{/* Projects Filter */}
<div className="filter-container text-center">
<button
className={`filter-button ${activeFilter === 'all' ? 'active' : ''}`}
<div className={`filter-container ${style.filterContainer} text-center`}>
<button
className={`filter-button ${style.filterButton} ${activeFilter === 'all' ? style.active : ''}`}
onClick={() => handleFilterClick('all')}
>
All Artifacts
</button>
<button
className={`filter-button ${activeFilter === 'embedded' ? 'active' : ''}`}
onClick={() => handleFilterClick('embedded')}
>
Embedded Systems
</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>
{Object.values(categories).map((category) => (
<button
key={category.shortName}
className={`filter-button ${style.filterButton} ${activeFilter === category.shortName.toLowerCase() ? style.active : ''}`}
onClick={() => handleFilterClick(category.shortName.toLowerCase())}
>
{category.fullName}
</button>
))}
</div>
{/* Projects Grid */}
<div className="row">
{/* Featured Project */}
{featuredProject && (activeFilter === 'all' || featuredProject.category.includes(activeFilter)) && (
{featuredProject && (activeFilter === 'all' || featuredProject.category.some(cat => cat.shortName.toLowerCase() === activeFilter)) && (
<div className="col-12 mb-4">
<ProjectCard project={featuredProject} onOpenDetails={handleOpenDetails} />
</div>
)}
{/* Regular Projects */}
{regularProjects.map(project => (
<div className="col-md-6 col-lg-4 mb-4" key={project.id}>
<ProjectCard project={project} onOpenDetails={handleOpenDetails} />
</div>
<ProjectCard project={project} onOpenDetails={handleOpenDetails} />
))}
</div>
{/* No Projects Message */}
{filteredProjects.length === 0 && (
<div className="text-center mt-5">
<h3>No projects found for this category.</h3>
</div>
)}
{/* View All Button */}
<div className="text-center mt-4">
<a href="https://github.com/SecCodeSmith" className="btn btn-primary">
<div className="text-center mt-4 mb-5">
<a href="https://github.com/SecCodeSmith" className={`btn btn-primary ${style.btnPrimary}`}>
<i className="fab fa-github me-2"></i> View All Projects on GitHub
</a>
</div>
</div>
{/* Project Modal */}
{selectedProject && (
<ProjectModal project={selectedProject} onClose={handleCloseDetails} />
<ProjectModal project={selectedProject} />
)}
</>
);
+16
View File
@@ -0,0 +1,16 @@
export interface BlogPostProps {
id: string;
slug: string;
title: string;
excerpt: string;
image: string;
category: string;
date: string;
author: string;
commentCount: number;
readTime: string;
featured?: boolean;
tags: string[];
content: string;
}
+46
View File
@@ -0,0 +1,46 @@
export interface ProjectProps {
id: string;
title: string;
description: string;
projectDetails?: ProjectDetails;
image: string;
category: Category[];
featured?: boolean;
technologies: ProjectTech[];
links: {
github?: string;
demo?: string;
documentation?: string;
};
}
export interface ProjectDetails {
descriptions?: string[];
startDate?: string;
endDate?: string;
dateFormatted?: string;
role?: string;
status?: string;
client?: string;
keyFeatures?: string[];
gallery?: string[];
fullTechStack?: FullTechStack[];
}
export interface Category {
[key: string]: string;
fullName: string;
shortName: string;
icon: string;
}
export interface ProjectTech {
name: string;
icon: string;
}
export interface FullTechStack {
name: string;
icons: string;
}