diff --git a/src/assets/styles/Home.module.scss b/src/assets/styles/Home.module.scss index 14129a5..be74f6f 100644 --- a/src/assets/styles/Home.module.scss +++ b/src/assets/styles/Home.module.scss @@ -73,7 +73,7 @@ color: $accent-fire; font-size: 1rem; animation: fall linear infinite; - opacity: 0.8; + opacity: 0.9; } @keyframes fall { diff --git a/src/assets/styles/Project.module.scss b/src/assets/styles/Project.module.scss new file mode 100644 index 0000000..d66485a --- /dev/null +++ b/src/assets/styles/Project.module.scss @@ -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; + } +} \ No newline at end of file diff --git a/src/components/BlogCard.tsx b/src/components/BlogCard.tsx index d9f34bd..198d36e 100644 --- a/src/components/BlogCard.tsx +++ b/src/components/BlogCard.tsx @@ -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 = ({ post }) => {
{post.title} -
{post.category}
+
{post.category}
{post.featured && (
diff --git a/src/components/ProjectCard.tsx b/src/components/ProjectCard.tsx index 6a4f293..25b582c 100644 --- a/src/components/ProjectCard.tsx +++ b/src/components/ProjectCard.tsx @@ -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 }) => ( +
+

// TECHNOLOGIES_USED

+
+ {technology.map((tech, index) => ( + + + {tech.name} + + ))} +
+
+) + +const CategoryBadge: React.FC<{ category: Category[] }> = ({ category }) => ( + < div className={style.categoryBadgeContainer} > + { + category.map((cat, index) => ( + + {cat.shortName} + + ))} +
+) + +const ProjectLinks: React.FC<{ links: { github?: string; demo?: string }, id: string, onOpenDetails: (id: string) => void }> = ({ links, id, onOpenDetails }) => ( +
+ + + {links.github && ( + + + View Code + + )} + + {links.demo && ( + + + Live Demo + + )} +
+) + export const ProjectCard: React.FC = ({ project, onOpenDetails }) => { const [, setHovered] = useState(false); - - return ( -
setHovered(true)} - onMouseLeave={() => setHovered(false)} - > -
- {project.title} -
{project.category[0]}
- - {project.featured && ( -
- - Featured -
- )} -
-
-

{project.title}

-

- {project.description} -

-
-

// TECHNOLOGIES_USED

-
- {project.technologies.map((tech, index) => ( - - - {tech.name} - - ))} + + 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 ( +
+
setHovered(true)} + onMouseLeave={() => setHovered(false)} + > +
+
+ {project.title} +
+ + Featured +
+ +
+
+
+

{project.title}

+

+ {project.description} +

+ + +
+
-
- - - {project.links.github && ( - - - View Code - - )} - - {project.links.demo && ( - - - Live Demo - - )} +
+ ) + } + + + return ( +
+
setHovered(true)} + onMouseLeave={() => setHovered(false)}> +
+ {project.title} + +
+
+

{project.title}

+

+ {project.description} +

+ +
) -} \ No newline at end of file +}; diff --git a/src/components/ProjectModal.tsx b/src/components/ProjectModal.tsx index 78da803..79f3483 100644 --- a/src/components/ProjectModal.tsx +++ b/src/components/ProjectModal.tsx @@ -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 = ({ project, onClose }) => { - if (!project) return null; - - return ( -
-
-
-
-
{project.title}
- -
-
-
- {project.title} +export const ProjectModal: React.FC = ({ project }) => { + if (!project) return null; + + return ( + -
- {project.links.github && ( - - Source Code - - )} - - {project.links.demo && ( - - Live Demo - - )} - - -
-
-
-
- ); -} \ No newline at end of file + ); +}; \ No newline at end of file diff --git a/src/data/blogPostsData.ts b/src/data/blogPostsData.ts index ee437ef..415fa77 100644 --- a/src/data/blogPostsData.ts +++ b/src/data/blogPostsData.ts @@ -1,4 +1,4 @@ -import type { BlogPostProps } from "../components/BlogCard"; +import type {BlogPostProps} from '../untils/BlogPostProps' export const blogPostsData: BlogPostProps[] = [ { diff --git a/src/data/projectsData.ts b/src/data/projectsData.ts index 4132324..1574efd 100644 --- a/src/data/projectsData.ts +++ b/src/data/projectsData.ts @@ -1,23 +1,74 @@ -import type { ProjectProps } from "../components/ProjectCard"; +import type { ProjectProps, Category } from "../untils/ProjectProps"; + +type CategoryMap = Record +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" }, diff --git a/src/pages/Blog.tsx b/src/pages/Blog.tsx index 3e6d1be..63e50af 100644 --- a/src/pages/Blog.tsx +++ b/src/pages/Blog.tsx @@ -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 => diff --git a/src/pages/Projects.tsx b/src/pages/Projects.tsx index d2e1f0e..d56ce27 100644 --- a/src/pages/Projects.tsx +++ b/src/pages/Projects.tsx @@ -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(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 ( <> - - +
{/* Projects Filter */} -
- - - - - + {Object.values(categories).map((category) => ( + + ))}
- {/* Projects Grid */}
- {/* Featured Project */} - {featuredProject && (activeFilter === 'all' || featuredProject.category.includes(activeFilter)) && ( + {featuredProject && (activeFilter === 'all' || featuredProject.category.some(cat => cat.shortName.toLowerCase() === activeFilter)) && (
)} - {/* Regular Projects */} {regularProjects.map(project => ( -
- -
+ + ))}
- {/* No Projects Message */} {filteredProjects.length === 0 && (

No projects found for this category.

)} - {/* View All Button */} - - - {/* Project Modal */} + {selectedProject && ( - + )} ); diff --git a/src/untils/BlogPostProps.ts b/src/untils/BlogPostProps.ts new file mode 100644 index 0000000..ee36a9d --- /dev/null +++ b/src/untils/BlogPostProps.ts @@ -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; +} + diff --git a/src/untils/ProjectProps.ts b/src/untils/ProjectProps.ts new file mode 100644 index 0000000..1586526 --- /dev/null +++ b/src/untils/ProjectProps.ts @@ -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; +}