First commit #1
@@ -0,0 +1,28 @@
|
||||
import js from '@eslint/js'
|
||||
import globals from 'globals'
|
||||
import reactHooks from 'eslint-plugin-react-hooks'
|
||||
import reactRefresh from 'eslint-plugin-react-refresh'
|
||||
import tseslint from 'typescript-eslint'
|
||||
|
||||
export default tseslint.config(
|
||||
{ ignores: ['dist'] },
|
||||
{
|
||||
extends: [js.configs.recommended, ...tseslint.configs.recommended],
|
||||
files: ['**/*.{ts,tsx}'],
|
||||
languageOptions: {
|
||||
ecmaVersion: 2020,
|
||||
globals: globals.browser,
|
||||
},
|
||||
plugins: {
|
||||
'react-hooks': reactHooks,
|
||||
'react-refresh': reactRefresh,
|
||||
},
|
||||
rules: {
|
||||
...reactHooks.configs.recommended.rules,
|
||||
'react-refresh/only-export-components': [
|
||||
'warn',
|
||||
{ allowConstantExport: true },
|
||||
],
|
||||
},
|
||||
},
|
||||
)
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 1.5 MiB |
+24
@@ -0,0 +1,24 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<!-- Font Awesome -->
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
|
||||
<!-- DevIcon -->
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/devicons/devicon@v2.15.1/devicon.min.css">
|
||||
<!-- Google Fonts -->
|
||||
<link href="https://fonts.googleapis.com/css2?family=Cinzel:wght@400;700&family=Fira+Code:wght@400;600&display=swap" rel="stylesheet">
|
||||
<!-- Bootstrap CSS -->
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha1/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||
<title>SecCodeSmith - Code Forged in Digital Fire</title>
|
||||
<meta name="description" content="SecCodeSmith - Master craftsman of code specializing in embedded systems, machine learning, and web development. Forging digital solutions with security and precision.">
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<script type="module" src="/src/main.tsx"></script>
|
||||
<!-- Bootstrap JS -->
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha1/dist/js/bootstrap.bundle.min.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
Generated
+4532
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,33 @@
|
||||
{
|
||||
"name": "seccodesmith-frontend",
|
||||
"private": true,
|
||||
"version": "0.0.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "tsc -b && vite build",
|
||||
"lint": "eslint .",
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"dependencies": {
|
||||
"@testing-library/react": "^16.3.0",
|
||||
"react": "^19.1.0",
|
||||
"react-dom": "^19.1.0",
|
||||
"react-router-dom": "^7.6.0",
|
||||
"vitest": "^3.1.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/js": "^9.25.0",
|
||||
"@testing-library/jest-dom": "^6.6.3",
|
||||
"@types/react": "^19.1.2",
|
||||
"@types/react-dom": "^19.1.2",
|
||||
"@vitejs/plugin-react": "^4.4.1",
|
||||
"eslint": "^9.25.0",
|
||||
"eslint-plugin-react-hooks": "^5.2.0",
|
||||
"eslint-plugin-react-refresh": "^0.4.19",
|
||||
"globals": "^16.0.0",
|
||||
"typescript": "~5.8.3",
|
||||
"typescript-eslint": "^8.30.1",
|
||||
"vite": "^6.3.5"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>
|
||||
|
After Width: | Height: | Size: 1.5 KiB |
+42
@@ -0,0 +1,42 @@
|
||||
#root {
|
||||
max-width: 1280px;
|
||||
margin: 0 auto;
|
||||
padding: 2rem;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.logo {
|
||||
height: 6em;
|
||||
padding: 1.5em;
|
||||
will-change: filter;
|
||||
transition: filter 300ms;
|
||||
}
|
||||
.logo:hover {
|
||||
filter: drop-shadow(0 0 2em #646cffaa);
|
||||
}
|
||||
.logo.react:hover {
|
||||
filter: drop-shadow(0 0 2em #61dafbaa);
|
||||
}
|
||||
|
||||
@keyframes logo-spin {
|
||||
from {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: no-preference) {
|
||||
a:nth-of-type(2) .logo {
|
||||
animation: logo-spin infinite 20s linear;
|
||||
}
|
||||
}
|
||||
|
||||
.card {
|
||||
padding: 2em;
|
||||
}
|
||||
|
||||
.read-the-docs {
|
||||
color: #888;
|
||||
}
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
import { BrowserRouter as Router, Routes, Route } from 'react-router-dom'
|
||||
import { Layout } from './components/Layout'
|
||||
import { Home } from './pages/Home'
|
||||
import { About } from './pages/About'
|
||||
import { Blog } from './pages/Blog'
|
||||
import { BlogPost } from './pages/BlogPost'
|
||||
import { Projects } from './pages/Projects'
|
||||
import { Contact } from './pages/Contact'
|
||||
import { NotFound } from './pages/NotFound'
|
||||
import './components/styles.css'
|
||||
|
||||
function App() {
|
||||
return (
|
||||
<Router>
|
||||
<Routes>
|
||||
<Route path="/" element={<Layout />}>
|
||||
<Route index element={<Home />} />
|
||||
<Route path="/about" element={<About />} />
|
||||
<Route path="/blog" element={<Blog />} />
|
||||
<Route path="/blog/:slug" element={<BlogPost />} />
|
||||
<Route path="/projects" element={<Projects />} />
|
||||
<Route path="/contact" element={<Contact />} />
|
||||
<Route path="*" element={<NotFound />} />
|
||||
</Route>
|
||||
</Routes>
|
||||
</Router>
|
||||
)
|
||||
}
|
||||
|
||||
export default App
|
||||
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="35.93" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 228"><path fill="#00D8FF" d="M210.483 73.824a171.49 171.49 0 0 0-8.24-2.597c.465-1.9.893-3.777 1.273-5.621c6.238-30.281 2.16-54.676-11.769-62.708c-13.355-7.7-35.196.329-57.254 19.526a171.23 171.23 0 0 0-6.375 5.848a155.866 155.866 0 0 0-4.241-3.917C100.759 3.829 77.587-4.822 63.673 3.233C50.33 10.957 46.379 33.89 51.995 62.588a170.974 170.974 0 0 0 1.892 8.48c-3.28.932-6.445 1.924-9.474 2.98C17.309 83.498 0 98.307 0 113.668c0 15.865 18.582 31.778 46.812 41.427a145.52 145.52 0 0 0 6.921 2.165a167.467 167.467 0 0 0-2.01 9.138c-5.354 28.2-1.173 50.591 12.134 58.266c13.744 7.926 36.812-.22 59.273-19.855a145.567 145.567 0 0 0 5.342-4.923a168.064 168.064 0 0 0 6.92 6.314c21.758 18.722 43.246 26.282 56.54 18.586c13.731-7.949 18.194-32.003 12.4-61.268a145.016 145.016 0 0 0-1.535-6.842c1.62-.48 3.21-.974 4.76-1.488c29.348-9.723 48.443-25.443 48.443-41.52c0-15.417-17.868-30.326-45.517-39.844Zm-6.365 70.984c-1.4.463-2.836.91-4.3 1.345c-3.24-10.257-7.612-21.163-12.963-32.432c5.106-11 9.31-21.767 12.459-31.957c2.619.758 5.16 1.557 7.61 2.4c23.69 8.156 38.14 20.213 38.14 29.504c0 9.896-15.606 22.743-40.946 31.14Zm-10.514 20.834c2.562 12.94 2.927 24.64 1.23 33.787c-1.524 8.219-4.59 13.698-8.382 15.893c-8.067 4.67-25.32-1.4-43.927-17.412a156.726 156.726 0 0 1-6.437-5.87c7.214-7.889 14.423-17.06 21.459-27.246c12.376-1.098 24.068-2.894 34.671-5.345a134.17 134.17 0 0 1 1.386 6.193ZM87.276 214.515c-7.882 2.783-14.16 2.863-17.955.675c-8.075-4.657-11.432-22.636-6.853-46.752a156.923 156.923 0 0 1 1.869-8.499c10.486 2.32 22.093 3.988 34.498 4.994c7.084 9.967 14.501 19.128 21.976 27.15a134.668 134.668 0 0 1-4.877 4.492c-9.933 8.682-19.886 14.842-28.658 17.94ZM50.35 144.747c-12.483-4.267-22.792-9.812-29.858-15.863c-6.35-5.437-9.555-10.836-9.555-15.216c0-9.322 13.897-21.212 37.076-29.293c2.813-.98 5.757-1.905 8.812-2.773c3.204 10.42 7.406 21.315 12.477 32.332c-5.137 11.18-9.399 22.249-12.634 32.792a134.718 134.718 0 0 1-6.318-1.979Zm12.378-84.26c-4.811-24.587-1.616-43.134 6.425-47.789c8.564-4.958 27.502 2.111 47.463 19.835a144.318 144.318 0 0 1 3.841 3.545c-7.438 7.987-14.787 17.08-21.808 26.988c-12.04 1.116-23.565 2.908-34.161 5.309a160.342 160.342 0 0 1-1.76-7.887Zm110.427 27.268a347.8 347.8 0 0 0-7.785-12.803c8.168 1.033 15.994 2.404 23.343 4.08c-2.206 7.072-4.956 14.465-8.193 22.045a381.151 381.151 0 0 0-7.365-13.322Zm-45.032-43.861c5.044 5.465 10.096 11.566 15.065 18.186a322.04 322.04 0 0 0-30.257-.006c4.974-6.559 10.069-12.652 15.192-18.18ZM82.802 87.83a323.167 323.167 0 0 0-7.227 13.238c-3.184-7.553-5.909-14.98-8.134-22.152c7.304-1.634 15.093-2.97 23.209-3.984a321.524 321.524 0 0 0-7.848 12.897Zm8.081 65.352c-8.385-.936-16.291-2.203-23.593-3.793c2.26-7.3 5.045-14.885 8.298-22.6a321.187 321.187 0 0 0 7.257 13.246c2.594 4.48 5.28 8.868 8.038 13.147Zm37.542 31.03c-5.184-5.592-10.354-11.779-15.403-18.433c4.902.192 9.899.29 14.978.29c5.218 0 10.376-.117 15.453-.343c-4.985 6.774-10.018 12.97-15.028 18.486Zm52.198-57.817c3.422 7.8 6.306 15.345 8.596 22.52c-7.422 1.694-15.436 3.058-23.88 4.071a382.417 382.417 0 0 0 7.859-13.026a347.403 347.403 0 0 0 7.425-13.565Zm-16.898 8.101a358.557 358.557 0 0 1-12.281 19.815a329.4 329.4 0 0 1-23.444.823c-7.967 0-15.716-.248-23.178-.732a310.202 310.202 0 0 1-12.513-19.846h.001a307.41 307.41 0 0 1-10.923-20.627a310.278 310.278 0 0 1 10.89-20.637l-.001.001a307.318 307.318 0 0 1 12.413-19.761c7.613-.576 15.42-.876 23.31-.876H128c7.926 0 15.743.303 23.354.883a329.357 329.357 0 0 1 12.335 19.695a358.489 358.489 0 0 1 11.036 20.54a329.472 329.472 0 0 1-11 20.722Zm22.56-122.124c8.572 4.944 11.906 24.881 6.52 51.026c-.344 1.668-.73 3.367-1.15 5.09c-10.622-2.452-22.155-4.275-34.23-5.408c-7.034-10.017-14.323-19.124-21.64-27.008a160.789 160.789 0 0 1 5.888-5.4c18.9-16.447 36.564-22.941 44.612-18.3ZM128 90.808c12.625 0 22.86 10.235 22.86 22.86s-10.235 22.86-22.86 22.86s-22.86-10.235-22.86-22.86s10.235-22.86 22.86-22.86Z"></path></svg>
|
||||
|
After Width: | Height: | Size: 4.0 KiB |
@@ -0,0 +1,67 @@
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
interface BlogCardProps {
|
||||
post: BlogPostProps;
|
||||
}
|
||||
|
||||
export const BlogCard: React.FC<BlogCardProps> = ({ post }) => {
|
||||
return (
|
||||
<div className={`card ${post.featured ? 'featured-card' : ''}`}>
|
||||
<div className="position-relative">
|
||||
<img src={post.image} className={`card-img-top ${post.featured ? 'featured-img' : ''}`} alt={post.title} />
|
||||
<div className="category-badge">{post.category}</div>
|
||||
|
||||
{post.featured && (
|
||||
<div className="featured-badge">
|
||||
<i className="fas fa-star"></i>
|
||||
<span>Featured</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<div className="card-body">
|
||||
<h2 className="card-title">
|
||||
<Link to={`/blog/${post.slug}`}>{post.title}</Link>
|
||||
</h2>
|
||||
<div className="d-flex meta-items mb-3">
|
||||
<div className="meta-item">
|
||||
<i className="fas fa-calendar-alt meta-icon"></i>
|
||||
<span>{post.date}</span>
|
||||
</div>
|
||||
<div className="meta-item">
|
||||
<i className="fas fa-user meta-icon"></i>
|
||||
<span>{post.author}</span>
|
||||
</div>
|
||||
<div className="meta-item">
|
||||
<i className="fas fa-comments meta-icon"></i>
|
||||
<span>{post.commentCount} Comments</span>
|
||||
</div>
|
||||
</div>
|
||||
<p className="card-text">
|
||||
{post.excerpt}
|
||||
</p>
|
||||
<Link to={`/blog/${post.slug}`} className="read-more">Continue Reading</Link>
|
||||
<div className="mt-3">
|
||||
{post.tags.map((tag, index) => (
|
||||
<span key={index} className="post-tag">{tag}</span>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,199 @@
|
||||
import { useState, type FormEvent } from 'react';
|
||||
|
||||
export const ContactForm = () => {
|
||||
const [formData, setFormData] = useState({
|
||||
name: '',
|
||||
email: '',
|
||||
subject: '',
|
||||
projectType: '',
|
||||
message: '',
|
||||
budget: ''
|
||||
});
|
||||
|
||||
const [isSubmitting, setIsSubmitting] = useState(false);
|
||||
const [showSuccess, setShowSuccess] = useState(false);
|
||||
const [showError, setShowError] = useState(false);
|
||||
|
||||
const handleChange = (e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement>) => {
|
||||
const { name, value } = e.target;
|
||||
setFormData(prev => ({
|
||||
...prev,
|
||||
[name]: value
|
||||
}));
|
||||
};
|
||||
|
||||
const handleSubmit = (e: FormEvent) => {
|
||||
e.preventDefault();
|
||||
setIsSubmitting(true);
|
||||
|
||||
// Simulate form submission
|
||||
setTimeout(() => {
|
||||
setIsSubmitting(false);
|
||||
setShowSuccess(true);
|
||||
setFormData({
|
||||
name: '',
|
||||
email: '',
|
||||
subject: '',
|
||||
projectType: '',
|
||||
message: '',
|
||||
budget: ''
|
||||
});
|
||||
|
||||
// Hide success message after 5 seconds
|
||||
setTimeout(() => {
|
||||
setShowSuccess(false);
|
||||
}, 5000);
|
||||
}, 1500);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="card h-100">
|
||||
<div className="card-header bg-transparent">
|
||||
<h2 className="mb-0 py-2">Send a Message to the Forge</h2>
|
||||
</div>
|
||||
<div className="card-body p-4">
|
||||
{/* Success message */}
|
||||
{showSuccess && (
|
||||
<div className="alert alert-success" role="alert">
|
||||
<i className="fas fa-check-circle me-2"></i> Your message has been successfully sent. I'll get back to you soon!
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Error message */}
|
||||
{showError && (
|
||||
<div className="alert alert-danger" role="alert">
|
||||
<i className="fas fa-exclamation-circle me-2"></i> There was an error sending your message. Please try again later.
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Contact Form */}
|
||||
<form id="contact-form" className="needs-validation" noValidate onSubmit={handleSubmit}>
|
||||
<div className="row mb-3">
|
||||
<div className="col-md-6 mb-3 mb-md-0">
|
||||
<label htmlFor="name" className="form-label">Your Name</label>
|
||||
<input
|
||||
type="text"
|
||||
className="form-control"
|
||||
id="name"
|
||||
name="name"
|
||||
placeholder="Enter your name"
|
||||
required
|
||||
value={formData.name}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
<div className="invalid-feedback">
|
||||
Please provide your name.
|
||||
</div>
|
||||
</div>
|
||||
<div className="col-md-6">
|
||||
<label htmlFor="email" className="form-label">Your Email</label>
|
||||
<input
|
||||
type="email"
|
||||
className="form-control"
|
||||
id="email"
|
||||
name="email"
|
||||
placeholder="Enter your email"
|
||||
required
|
||||
value={formData.email}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
<div className="invalid-feedback">
|
||||
Please provide a valid email address.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="mb-3">
|
||||
<label htmlFor="subject" className="form-label">Subject</label>
|
||||
<input
|
||||
type="text"
|
||||
className="form-control"
|
||||
id="subject"
|
||||
name="subject"
|
||||
placeholder="What's this regarding?"
|
||||
required
|
||||
value={formData.subject}
|
||||
onChange={handleChange}
|
||||
/>
|
||||
<div className="invalid-feedback">
|
||||
Please provide a subject for your message.
|
||||
</div>
|
||||
</div>
|
||||
<div className="mb-3">
|
||||
<label htmlFor="projectType" className="form-label">Project Type</label>
|
||||
<select
|
||||
className="form-select"
|
||||
id="projectType"
|
||||
name="projectType"
|
||||
required
|
||||
value={formData.projectType}
|
||||
onChange={handleChange}
|
||||
>
|
||||
<option value="" selected disabled>Select a project type</option>
|
||||
<option value="embedded">Embedded Systems</option>
|
||||
<option value="web">Web Development</option>
|
||||
<option value="ml">Machine Learning</option>
|
||||
<option value="iot">IoT Solution</option>
|
||||
<option value="consulting">Technical Consulting</option>
|
||||
<option value="other">Other</option>
|
||||
</select>
|
||||
<div className="invalid-feedback">
|
||||
Please select a project type.
|
||||
</div>
|
||||
</div>
|
||||
<div className="mb-3">
|
||||
<label htmlFor="message" className="form-label">Your Message</label>
|
||||
<textarea
|
||||
className="form-control"
|
||||
id="message"
|
||||
name="message"
|
||||
rows={5}
|
||||
placeholder="Tell me about your project, requirements, or questions..."
|
||||
required
|
||||
value={formData.message}
|
||||
onChange={handleChange}
|
||||
></textarea>
|
||||
<div className="invalid-feedback">
|
||||
Please provide a message.
|
||||
</div>
|
||||
</div>
|
||||
<div className="mb-4">
|
||||
<label htmlFor="budget" className="form-label">Budget Range (Optional)</label>
|
||||
<select
|
||||
className="form-select"
|
||||
id="budget"
|
||||
name="budget"
|
||||
value={formData.budget}
|
||||
onChange={handleChange}
|
||||
>
|
||||
<option value="" selected disabled>Select your budget range</option>
|
||||
<option value="small">$1,000 - $5,000</option>
|
||||
<option value="medium">$5,000 - $15,000</option>
|
||||
<option value="large">$15,000 - $30,000</option>
|
||||
<option value="enterprise">$30,000+</option>
|
||||
<option value="undecided">Not sure yet</option>
|
||||
</select>
|
||||
</div>
|
||||
<div className="text-center">
|
||||
<button
|
||||
type="submit"
|
||||
className="btn btn-primary position-relative"
|
||||
id="submit-button"
|
||||
disabled={isSubmitting}
|
||||
>
|
||||
{isSubmitting ? (
|
||||
<>
|
||||
<span className="spinner-border spinner-border-sm me-2" role="status" aria-hidden="true"></span>
|
||||
Sending...
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<i className="fas fa-paper-plane me-2"></i> Send Message
|
||||
</>
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
export const Footer = () => {
|
||||
const currentYear = new Date().getFullYear()
|
||||
|
||||
return (
|
||||
<footer className="footer">
|
||||
<div className="container">
|
||||
<div className="social-links">
|
||||
<a href="#" className="social-icon"><i className="fab fa-github"></i></a>
|
||||
<a href="#" className="social-icon"><i className="fab fa-linkedin"></i></a>
|
||||
<a href="#" className="social-icon"><i className="fab fa-twitter"></i></a>
|
||||
<a href="#" className="social-icon"><i className="fab fa-discord"></i></a>
|
||||
<a href="#" className="social-icon"><i className="fas fa-envelope"></i></a>
|
||||
<a href="#" className="social-icon"><i className="fab fa-hackerrank"></i></a>
|
||||
</div>
|
||||
<p className="copyright">© {currentYear} SecCodeSmith. All rights forged in digital fire.</p>
|
||||
</div>
|
||||
</footer>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
import { useState, useEffect } from 'react'
|
||||
import { NavLink, useLocation } from 'react-router-dom'
|
||||
|
||||
export const Header = () => {
|
||||
const location = useLocation()
|
||||
const [isNavCollapsed, setIsNavCollapsed] = useState(true)
|
||||
|
||||
// Close the mobile menu when changing routes
|
||||
useEffect(() => {
|
||||
setIsNavCollapsed(true)
|
||||
}, [location])
|
||||
|
||||
return (
|
||||
<nav className="navbar navbar-expand-lg fixed-top">
|
||||
<div className="container">
|
||||
<NavLink className="navbar-brand" to="/">
|
||||
<div className="logo-icon">
|
||||
<i className="fas fa-hammer"></i>
|
||||
<i
|
||||
className="fas fa-dragon"
|
||||
style={{
|
||||
fontSize: '1.2rem',
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
right: '-12px',
|
||||
color: 'var(--accent-dragon)'
|
||||
}}
|
||||
></i>
|
||||
</div>
|
||||
<div className="logo-text">SecCodeSmith</div>
|
||||
</NavLink>
|
||||
|
||||
<button
|
||||
className="navbar-toggler"
|
||||
type="button"
|
||||
onClick={() => setIsNavCollapsed(!isNavCollapsed)}
|
||||
aria-controls="navbarNav"
|
||||
aria-expanded={!isNavCollapsed}
|
||||
aria-label="Toggle navigation"
|
||||
>
|
||||
<span className="navbar-toggler-icon"></span>
|
||||
</button>
|
||||
|
||||
<div className={`${isNavCollapsed ? 'collapse' : ''} navbar-collapse justify-content-end`} id="navbarNav">
|
||||
<ul className="navbar-nav">
|
||||
<li className="nav-item">
|
||||
<NavLink
|
||||
className={({ isActive }) => `nav-link ${isActive ? 'active' : ''}`}
|
||||
to="/"
|
||||
>
|
||||
Home
|
||||
</NavLink>
|
||||
</li>
|
||||
<li className="nav-item">
|
||||
<NavLink
|
||||
className={({ isActive }) => `nav-link ${isActive ? 'active' : ''}`}
|
||||
to="/blog"
|
||||
>
|
||||
Blog
|
||||
</NavLink>
|
||||
</li>
|
||||
<li className="nav-item">
|
||||
<NavLink
|
||||
className={({ isActive }) => `nav-link ${isActive ? 'active' : ''}`}
|
||||
to="/about"
|
||||
>
|
||||
About
|
||||
</NavLink>
|
||||
</li>
|
||||
<li className="nav-item">
|
||||
<NavLink
|
||||
className={({ isActive }) => `nav-link ${isActive ? 'active' : ''}`}
|
||||
to="/projects"
|
||||
>
|
||||
Projects
|
||||
</NavLink>
|
||||
</li>
|
||||
<li className="nav-item">
|
||||
<NavLink
|
||||
className={({ isActive }) => `nav-link ${isActive ? 'active' : ''}`}
|
||||
to="/contact"
|
||||
>
|
||||
Contact
|
||||
</NavLink>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
import { Outlet } from 'react-router-dom'
|
||||
import { Header } from './Header'
|
||||
import { Footer } from './Footer'
|
||||
|
||||
export const Layout = () => {
|
||||
return (
|
||||
<>
|
||||
<Header />
|
||||
<main>
|
||||
<Outlet />
|
||||
</main>
|
||||
<Footer />
|
||||
</>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
interface PageHeaderProps {
|
||||
title: string;
|
||||
subtitle?: string;
|
||||
}
|
||||
|
||||
export const PageHeader: React.FC<PageHeaderProps> = ({ title, subtitle }) => {
|
||||
return (
|
||||
<section className="page-header text-center">
|
||||
<div className="container position-relative">
|
||||
<h1 className="page-title">{title}</h1>
|
||||
{subtitle && <p className="page-subtitle mt-4">{subtitle}</p>}
|
||||
|
||||
{/* Binary background effect */}
|
||||
<div className="binary-bg">
|
||||
<div className="binary-line" style={{ top: '20%', left: '10%' }}>01001100 01101111 01110010 01100101 01101101</div>
|
||||
<div className="binary-line" style={{ bottom: '30%', right: '15%' }}>01001001 01110000 01110011 01110101 01101101</div>
|
||||
<div className="binary-line" style={{ top: '50%', left: '80%' }}>01000100 01101111 01101100 01101111 01110010</div>
|
||||
<div className="binary-line" style={{ bottom: '10%', left: '40%' }}>01010011 01101001 01110100 00100000 01000001 01101101 01100101 01110100</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
import { 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;
|
||||
};
|
||||
}
|
||||
|
||||
interface ProjectCardProps {
|
||||
project: ProjectProps;
|
||||
onOpenDetails: (id: string) => void;
|
||||
}
|
||||
|
||||
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>
|
||||
))}
|
||||
</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>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
import type { ProjectProps } from './ProjectCard';
|
||||
|
||||
interface ProjectModalProps {
|
||||
project: ProjectProps | null;
|
||||
onClose: () => void;
|
||||
}
|
||||
|
||||
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} />
|
||||
</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>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,773 @@
|
||||
/* About Page Styles */
|
||||
.profile-image {
|
||||
width: 100%;
|
||||
aspect-ratio: 1/1;
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
border: 3px solid rgba(255, 100, 0, 0.3);
|
||||
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.4);
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.profile-image::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: linear-gradient(45deg, rgba(255, 100, 0, 0.2), transparent);
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.profile-image img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
filter: contrast(1.1) brightness(0.9);
|
||||
}
|
||||
|
||||
.intro-text {
|
||||
font-size: 1.1rem;
|
||||
line-height: 1.8;
|
||||
color: var(--text-ash);
|
||||
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
||||
}
|
||||
|
||||
.intro-text p {
|
||||
margin-bottom: 1.25rem;
|
||||
}
|
||||
|
||||
.intro-text strong {
|
||||
color: var(--text-light);
|
||||
}
|
||||
|
||||
.intro-text .accent {
|
||||
color: var(--accent-fire);
|
||||
}
|
||||
|
||||
.connect-link {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 8px 20px;
|
||||
background-color: rgba(255, 255, 255, 0.05);
|
||||
border-radius: 4px;
|
||||
text-decoration: none;
|
||||
color: var(--text-light);
|
||||
transition: all 0.3s ease;
|
||||
border: 1px solid var(--card-border);
|
||||
margin-right: 0.5rem;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.connect-link:hover {
|
||||
background-color: rgba(255, 100, 0, 0.1);
|
||||
transform: translateY(-3px);
|
||||
border-color: var(--accent-fire);
|
||||
color: var(--text-light);
|
||||
}
|
||||
|
||||
.connect-icon {
|
||||
color: var(--accent-fire);
|
||||
}
|
||||
|
||||
/* Value cards */
|
||||
.value-card {
|
||||
background-color: var(--card-bg);
|
||||
border-radius: 8px;
|
||||
padding: 1.875rem;
|
||||
border: 1px solid var(--card-border);
|
||||
transition: all 0.3s ease;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
text-align: center;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.value-card:hover {
|
||||
transform: translateY(-5px);
|
||||
border-color: rgba(255, 100, 0, 0.3);
|
||||
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.4);
|
||||
}
|
||||
|
||||
.value-icon {
|
||||
font-size: 2.5rem;
|
||||
color: var(--accent-fire);
|
||||
margin-bottom: 1.25rem;
|
||||
}
|
||||
|
||||
.value-title {
|
||||
font-size: 1.3rem;
|
||||
margin-bottom: 1rem;
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
color: var(--text-light);
|
||||
}
|
||||
|
||||
.value-title::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
width: 40px;
|
||||
height: 2px;
|
||||
background: var(--accent-fire);
|
||||
bottom: -5px;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
}
|
||||
|
||||
.value-text {
|
||||
color: var(--text-ash);
|
||||
line-height: 1.6;
|
||||
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
||||
}
|
||||
|
||||
/* Skills card */
|
||||
.skill-card {
|
||||
background-color: var(--card-bg);
|
||||
border-radius: 8px;
|
||||
padding: 1.875rem;
|
||||
border: 1px solid var(--card-border);
|
||||
transition: all 0.3s ease;
|
||||
position: relative;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.skill-card:hover {
|
||||
transform: translateY(-10px);
|
||||
border-color: rgba(255, 100, 0, 0.3);
|
||||
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.4);
|
||||
}
|
||||
|
||||
.skill-card::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 5px;
|
||||
top: 0;
|
||||
left: 0;
|
||||
background: linear-gradient(to right, var(--accent-dragon), var(--accent-fire));
|
||||
border-radius: 8px 8px 0 0;
|
||||
}
|
||||
|
||||
.skill-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 15px;
|
||||
margin-bottom: 1.25rem;
|
||||
padding-bottom: 0.625rem;
|
||||
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
.skill-icon {
|
||||
font-size: 2rem;
|
||||
color: var(--accent-fire);
|
||||
}
|
||||
|
||||
.skill-title {
|
||||
font-size: 1.3rem;
|
||||
margin-bottom: 0;
|
||||
color: var(--text-light);
|
||||
}
|
||||
|
||||
.skill-check {
|
||||
color: var(--accent-fire);
|
||||
margin-right: 0.625rem;
|
||||
}
|
||||
|
||||
/* Timeline */
|
||||
.timeline {
|
||||
position: relative;
|
||||
padding: 0;
|
||||
margin-top: 3rem;
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
.timeline:before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
width: 3px;
|
||||
height: 100%;
|
||||
background: linear-gradient(to bottom, var(--accent-fire), var(--accent-dragon), rgba(255, 100, 0, 0.2));
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.timeline-item {
|
||||
position: relative;
|
||||
margin-bottom: 3rem;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.timeline-item:nth-child(odd) .timeline-content {
|
||||
margin-left: auto;
|
||||
padding-left: 3rem;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.timeline-item:nth-child(even) .timeline-content {
|
||||
margin-right: auto;
|
||||
padding-right: 3rem;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.timeline-dot {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
background-color: var(--accent-fire);
|
||||
border-radius: 50%;
|
||||
position: absolute;
|
||||
top: 20px;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
box-shadow: var(--glow-effect);
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.timeline-date {
|
||||
display: inline-block;
|
||||
font-size: 0.9rem;
|
||||
background-color: rgba(255, 100, 0, 0.1);
|
||||
color: var(--accent-fire);
|
||||
padding: 5px 10px;
|
||||
border-radius: 4px;
|
||||
margin-bottom: 1rem;
|
||||
font-weight: 600;
|
||||
letter-spacing: 1px;
|
||||
}
|
||||
|
||||
.timeline-title {
|
||||
font-size: 1.3rem;
|
||||
margin-bottom: 0.625rem;
|
||||
color: var(--text-light);
|
||||
}
|
||||
|
||||
.timeline-text {
|
||||
color: var(--text-ash);
|
||||
line-height: 1.6;
|
||||
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
||||
}
|
||||
|
||||
.timeline-content {
|
||||
background-color: var(--card-bg);
|
||||
border-radius: 8px;
|
||||
padding: 1.5rem;
|
||||
border: 1px solid var(--card-border);
|
||||
position: relative;
|
||||
transition: all 0.3s ease;
|
||||
width: 45%;
|
||||
}
|
||||
|
||||
.timeline-content:hover {
|
||||
transform: translateY(-5px);
|
||||
border-color: rgba(255, 100, 0, 0.3);
|
||||
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.4);
|
||||
}
|
||||
|
||||
/* Testimonials */
|
||||
.testimonial {
|
||||
background-color: var(--card-bg);
|
||||
border-radius: 8px;
|
||||
padding: 1.875rem;
|
||||
margin-bottom: 1.875rem;
|
||||
border: 1px solid var(--card-border);
|
||||
transition: all 0.3s ease;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.testimonial:hover {
|
||||
transform: translateY(-5px);
|
||||
border-color: rgba(255, 100, 0, 0.3);
|
||||
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.4);
|
||||
}
|
||||
|
||||
.testimonial-content {
|
||||
padding-left: 1.25rem;
|
||||
border-left: 3px solid var(--accent-fire);
|
||||
font-style: italic;
|
||||
color: var(--text-ash);
|
||||
margin-bottom: 1.25rem;
|
||||
line-height: 1.7;
|
||||
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
||||
}
|
||||
|
||||
.author-avatar {
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
border-radius: 50%;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.author-avatar img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.author-name {
|
||||
font-weight: 600;
|
||||
font-size: 1.1rem;
|
||||
color: var(--text-light);
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.author-title {
|
||||
color: var(--text-ash);
|
||||
font-size: 0.9rem;
|
||||
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
||||
}
|
||||
|
||||
/* Blog post specific styles */
|
||||
.post-container {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
padding: 120px 20px 60px;
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 300px;
|
||||
gap: 40px;
|
||||
}
|
||||
|
||||
.post-main {
|
||||
background-color: var(--card-bg);
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
border: 1px solid var(--card-border);
|
||||
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
.post-header {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.post-featured-image {
|
||||
height: 400px;
|
||||
background-position: center;
|
||||
background-size: cover;
|
||||
background-repeat: no-repeat;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.post-featured-image::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100px;
|
||||
background: linear-gradient(to top, var(--card-bg), transparent);
|
||||
}
|
||||
|
||||
.post-info {
|
||||
padding: 30px 40px;
|
||||
}
|
||||
|
||||
.post-content {
|
||||
padding: 0 40px 40px;
|
||||
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
||||
line-height: 1.8;
|
||||
color: #e0e0e0;
|
||||
}
|
||||
|
||||
.share-section {
|
||||
margin-top: 40px;
|
||||
padding-top: 20px;
|
||||
border-top: 1px solid var(--card-border);
|
||||
}
|
||||
|
||||
.share-title {
|
||||
font-size: 1.2rem;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.share-buttons {
|
||||
display: flex;
|
||||
gap: 15px;
|
||||
}
|
||||
|
||||
.share-button {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border-radius: 50%;
|
||||
background-color: rgba(255, 255, 255, 0.05);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: var(--text-ash);
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.share-button:hover {
|
||||
background-color: var(--accent-fire);
|
||||
color: var(--text-light);
|
||||
transform: translateY(-3px);
|
||||
}
|
||||
|
||||
.author-section {
|
||||
margin-top: 40px;
|
||||
padding: 30px;
|
||||
background-color: rgba(255, 255, 255, 0.02);
|
||||
border-radius: 8px;
|
||||
display: flex;
|
||||
gap: 20px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.toc-list {
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
.toc-item {
|
||||
margin-bottom: 12px;
|
||||
padding-left: 15px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.toc-item::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 10px;
|
||||
width: 5px;
|
||||
height: 5px;
|
||||
border-radius: 50%;
|
||||
background-color: var(--accent-fire);
|
||||
}
|
||||
|
||||
.toc-link {
|
||||
color: var(--text-ash);
|
||||
text-decoration: none;
|
||||
transition: all 0.3s ease;
|
||||
font-size: 0.9rem;
|
||||
line-height: 1.5;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.toc-link:hover {
|
||||
color: var(--accent-fire);
|
||||
transform: translateX(5px);
|
||||
}
|
||||
|
||||
.toc-subitem {
|
||||
margin-top: 8px;
|
||||
padding-left: 15px;
|
||||
}
|
||||
|
||||
.related-posts-list {
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
.related-post-item {
|
||||
margin-bottom: 15px;
|
||||
padding-bottom: 15px;
|
||||
border-bottom: 1px solid rgba(255, 255, 255, 0.05);
|
||||
}
|
||||
|
||||
.related-post-item:last-child {
|
||||
border-bottom: none;
|
||||
margin-bottom: 0;
|
||||
padding-bottom: 0;
|
||||
}
|
||||
|
||||
.related-post-link {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
text-decoration: none;
|
||||
color: var(--text-ash);
|
||||
transition: color 0.3s ease;
|
||||
}
|
||||
|
||||
.related-post-link:hover {
|
||||
color: var(--accent-fire);
|
||||
}
|
||||
|
||||
.related-post-thumb {
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
border-radius: 4px;
|
||||
background-size: cover;
|
||||
background-position: center;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.related-post-info {
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.related-post-title {
|
||||
font-size: 0.9rem;
|
||||
line-height: 1.4;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.related-post-date {
|
||||
font-size: 0.8rem;
|
||||
color: rgba(255, 255, 255, 0.5);
|
||||
}
|
||||
|
||||
/* Social grid for contact page */
|
||||
.social-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(100px, 1fr));
|
||||
gap: 15px;
|
||||
margin-top: 1.5rem;
|
||||
}
|
||||
|
||||
.social-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
text-align: center;
|
||||
gap: 10px;
|
||||
padding: 15px;
|
||||
border-radius: 8px;
|
||||
background-color: rgba(255, 255, 255, 0.05);
|
||||
transition: all 0.3s ease;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.social-item:hover {
|
||||
background-color: rgba(255, 100, 0, 0.1);
|
||||
transform: translateY(-5px);
|
||||
}
|
||||
|
||||
.social-icon-lg {
|
||||
font-size: 1.8rem;
|
||||
color: var(--accent-fire);
|
||||
}
|
||||
|
||||
.social-name {
|
||||
color: var(--text-ash);
|
||||
font-size: 0.9rem;
|
||||
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
||||
}
|
||||
|
||||
/* 404 page styles */
|
||||
.error-container {
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
padding-top: 150px;
|
||||
padding-bottom: 100px;
|
||||
}
|
||||
|
||||
.error-code {
|
||||
font-size: 8rem;
|
||||
font-weight: 700;
|
||||
background: linear-gradient(to right, var(--accent-fire), var(--text-light));
|
||||
-webkit-background-clip: text;
|
||||
background-clip: text;
|
||||
color: transparent;
|
||||
margin-bottom: 1rem;
|
||||
text-shadow: var(--glow-effect);
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.error-title {
|
||||
font-size: 2rem;
|
||||
margin-bottom: 1.5rem;
|
||||
color: var(--text-light);
|
||||
}
|
||||
|
||||
.error-text {
|
||||
font-size: 1.1rem;
|
||||
color: var(--text-ash);
|
||||
margin-bottom: 2rem;
|
||||
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
||||
max-width: 600px;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
|
||||
.broken-sword {
|
||||
font-size: 5rem;
|
||||
color: var(--accent-fire);
|
||||
margin-bottom: 2rem;
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.broken-sword::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
width: 40px;
|
||||
height: 4px;
|
||||
background: var(--accent-fire);
|
||||
transform: rotate(-45deg);
|
||||
top: 60%;
|
||||
left: 45%;
|
||||
box-shadow: var(--glow-effect);
|
||||
}
|
||||
|
||||
.home-button {
|
||||
background: linear-gradient(45deg, var(--accent-dragon), var(--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;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
color: var(--text-light);
|
||||
text-decoration: none;
|
||||
border-radius: 4px;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.home-button:hover {
|
||||
transform: translateY(-3px);
|
||||
box-shadow: 0 7px 25px rgba(139, 0, 0, 0.5);
|
||||
color: var(--text-light);
|
||||
}
|
||||
|
||||
.home-button::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: -100%;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.2), transparent);
|
||||
transition: 0.6s;
|
||||
}
|
||||
|
||||
.home-button:hover::before {
|
||||
left: 100%;
|
||||
}
|
||||
|
||||
.error-code-container {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.error-code-container::before,
|
||||
.error-code-container::after {
|
||||
content: "<";
|
||||
position: absolute;
|
||||
color: var(--accent-fire);
|
||||
opacity: 0.6;
|
||||
font-size: 6rem;
|
||||
font-family: 'Fira Code', monospace;
|
||||
}
|
||||
|
||||
.error-code-container::before {
|
||||
left: -4rem;
|
||||
}
|
||||
|
||||
.error-code-container::after {
|
||||
content: "/>";
|
||||
right: -6rem;
|
||||
}
|
||||
|
||||
/* Media Queries */
|
||||
@media (max-width: 992px) {
|
||||
.timeline:before {
|
||||
left: 2rem;
|
||||
}
|
||||
|
||||
.timeline-item:nth-child(odd) .timeline-content,
|
||||
.timeline-item:nth-child(even) .timeline-content {
|
||||
width: calc(100% - 3rem);
|
||||
margin-left: 3rem;
|
||||
padding-left: 1.5rem;
|
||||
padding-right: 1.5rem;
|
||||
}
|
||||
|
||||
.timeline-dot {
|
||||
left: 2rem;
|
||||
}
|
||||
|
||||
.post-container {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.post-sidebar {
|
||||
position: static;
|
||||
order: -1;
|
||||
}
|
||||
|
||||
.post-featured-image {
|
||||
height: 300px;
|
||||
}
|
||||
|
||||
.post-title {
|
||||
font-size: 2rem;
|
||||
}
|
||||
|
||||
.post-info, .post-content {
|
||||
padding: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.intro-text {
|
||||
margin-top: 2rem;
|
||||
}
|
||||
|
||||
.testimonial-author {
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.author-info {
|
||||
margin-top: 1rem;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.error-code {
|
||||
font-size: 6rem;
|
||||
}
|
||||
|
||||
.error-title {
|
||||
font-size: 1.8rem;
|
||||
}
|
||||
|
||||
.broken-sword {
|
||||
font-size: 4rem;
|
||||
}
|
||||
|
||||
.error-code-container::before,
|
||||
.error-code-container::after {
|
||||
font-size: 4rem;
|
||||
}
|
||||
|
||||
.error-code-container::before {
|
||||
left: -3rem;
|
||||
}
|
||||
|
||||
.error-code-container::after {
|
||||
right: -4.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 576px) {
|
||||
.error-code {
|
||||
font-size: 4rem;
|
||||
}
|
||||
|
||||
.error-title {
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
.broken-sword {
|
||||
font-size: 3rem;
|
||||
}
|
||||
|
||||
.error-code-container::before,
|
||||
.error-code-container::after {
|
||||
font-size: 3rem;
|
||||
}
|
||||
|
||||
.error-code-container::before {
|
||||
left: -2rem;
|
||||
}
|
||||
|
||||
.error-code-container::after {
|
||||
right: -3rem;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,133 @@
|
||||
import type { BlogPostProps } from "../components/BlogCard";
|
||||
|
||||
export const blogPostsData: BlogPostProps[] = [
|
||||
{
|
||||
id: "1",
|
||||
slug: "summoning-digital-entities-stm32-techniques",
|
||||
title: "Summoning Digital Entities: Advanced STM32 Techniques for IoT Communication",
|
||||
excerpt: "Delve into the arcane arts of STM32 microcontroller programming as we explore advanced techniques for establishing robust communication between IoT devices. This comprehensive guide reveals how to optimize I2C, SPI, and UART protocols for maximum efficiency in your embedded systems projects.",
|
||||
image: "/images/blog/stm32-communication.jpg",
|
||||
category: "Embedded Systems",
|
||||
date: "May 5, 2025",
|
||||
author: "SecCodeSmith",
|
||||
commentCount: 12,
|
||||
readTime: "15 min read",
|
||||
featured: true,
|
||||
tags: ["STM32", "Embedded", "IoT", "Communication Protocols"],
|
||||
content: `
|
||||
<p>In the digital forge where hardware meets software, the STM32 microcontroller family stands as a powerful conduit for summoning digital entities into our physical realm. Today, we shall delve into advanced techniques for establishing robust communication between these entities through the mystical arts of embedded systems programming.</p>
|
||||
|
||||
<p>The realm of IoT demands reliable, efficient communication protocols that can withstand the chaotic nature of real-world environments. Through careful implementation and optimization of the STM32's communication peripherals, we can craft resilient connections that bridge the gap between digital and physical domains.</p>
|
||||
|
||||
<h2>The Three Elemental Protocols: I2C, SPI, and UART</h2>
|
||||
|
||||
<p>Before we forge ahead into advanced territory, let us briefly recall the elemental forces at our disposal. Each protocol serves as a channel through which our digital entities communicate, each with its unique strengths and limitations:</p>
|
||||
|
||||
<ul>
|
||||
<li><strong>I2C (Inter-Integrated Circuit)</strong> - A two-wire interface requiring minimal physical connections, ideal for communication with multiple peripheral devices over short distances.</li>
|
||||
<li><strong>SPI (Serial Peripheral Interface)</strong> - A faster, full-duplex protocol with dedicated select lines for each peripheral, allowing simultaneous transmission and reception of data.</li>
|
||||
<li><strong>UART (Universal Asynchronous Receiver-Transmitter)</strong> - A simple two-wire asynchronous interface, perfect for direct device-to-device communication without a shared clock.</li>
|
||||
</ul>
|
||||
`
|
||||
},
|
||||
{
|
||||
id: "2",
|
||||
slug: "forging-neural-networks-pytorch-blacksmith-approach",
|
||||
title: "Forging Neural Networks with PyTorch: A Blacksmith's Approach",
|
||||
excerpt: "Learn how to craft powerful neural networks using PyTorch, approaching the process with the precision and artistry of a master blacksmith. This tutorial combines theory with practical implementation.",
|
||||
image: "/images/blog/neural-networks-pytorch.jpg",
|
||||
category: "Machine Learning",
|
||||
date: "April 28, 2025",
|
||||
author: "SecCodeSmith",
|
||||
commentCount: 8,
|
||||
readTime: "12 min read",
|
||||
tags: ["PyTorch", "Machine Learning", "Neural Networks", "Python"],
|
||||
content: `
|
||||
<p>The art of forging neural networks requires both scientific precision and creative intuition—much like the work of a master blacksmith. In this guide, we'll explore how to craft robust neural network architectures using PyTorch, one of the most flexible deep learning frameworks available.</p>
|
||||
|
||||
<h2>Preparing the Forge: Setting Up Your PyTorch Environment</h2>
|
||||
|
||||
<p>Before we begin crafting our neural networks, we need to prepare our digital forge with the proper tools and materials...</p>
|
||||
`
|
||||
},
|
||||
{
|
||||
id: "3",
|
||||
slug: "dark-arts-memory-management-cpp",
|
||||
title: "The Dark Arts of Memory Management in C++",
|
||||
excerpt: "Explore the shadowy realm of manual memory management in C++. From smart pointers to custom allocators, master techniques that will prevent memory leaks and boost performance.",
|
||||
image: "/images/blog/cpp-memory.jpg",
|
||||
category: "C++",
|
||||
date: "April 22, 2025",
|
||||
author: "SecCodeSmith",
|
||||
commentCount: 15,
|
||||
readTime: "18 min read",
|
||||
tags: ["C++", "Memory Management", "Performance", "Smart Pointers"],
|
||||
content: `
|
||||
<p>Memory management in C++ is often viewed as a mysterious and perilous art, filled with potential hazards for the unwary programmer. Yet, mastering this art grants you unparalleled control over your program's resources and performance.</p>
|
||||
|
||||
<h2>The Ancient Runes: Raw Pointers and Manual Allocation</h2>
|
||||
|
||||
<p>At the foundation of C++ memory management lie raw pointers and manual allocation through the new and delete operators...</p>
|
||||
`
|
||||
},
|
||||
{
|
||||
id: "4",
|
||||
slug: "docker-containment-spells-isolating-digital-entities",
|
||||
title: "Docker Containment Spells: Isolating Digital Entities",
|
||||
excerpt: "Master the containment of digital entities with Docker. This guide covers advanced containerization techniques for creating isolated, reproducible environments for your applications.",
|
||||
image: "/images/blog/docker-containers.jpg",
|
||||
category: "DevOps",
|
||||
date: "April 15, 2025",
|
||||
author: "SecCodeSmith",
|
||||
commentCount: 6,
|
||||
readTime: "14 min read",
|
||||
tags: ["Docker", "Containerization", "DevOps", "Microservices"],
|
||||
content: `
|
||||
<p>In the realm of modern software development, Docker has emerged as a powerful tool for containing and isolating digital entities. Like a master sorcerer binding spirits, Docker allows us to encapsulate applications and their dependencies into sealed containers, ensuring consistent behavior across different environments.</p>
|
||||
|
||||
<h2>The Fundamentals of Container Magic</h2>
|
||||
|
||||
<p>At its core, Docker utilizes Linux kernel features like namespaces and control groups to create isolated environments that share the host's OS kernel but are otherwise separated from one another...</p>
|
||||
`
|
||||
},
|
||||
{
|
||||
id: "5",
|
||||
slug: "blazor-incantations-building-reactive-web-uis-dotnet",
|
||||
title: "Blazor Incantations: Building Reactive Web UIs with .NET",
|
||||
excerpt: "Harness the power of Blazor to create dynamic, responsive web interfaces using C# and .NET. Learn component-based architecture and state management for modern web applications.",
|
||||
image: "/images/blog/blazor-web.jpg",
|
||||
category: "Web Dev",
|
||||
date: "April 8, 2025",
|
||||
author: "SecCodeSmith",
|
||||
commentCount: 9,
|
||||
readTime: "16 min read",
|
||||
tags: ["Blazor", ".NET", "Web Development", "C#"],
|
||||
content: `
|
||||
<p>Blazor represents a paradigm shift in web development, allowing .NET developers to craft rich, interactive web applications using C# instead of JavaScript. This modern framework brings the power and elegance of .NET to the browser, opening new possibilities for web development.</p>
|
||||
|
||||
<h2>Understanding the Blazor Magic</h2>
|
||||
|
||||
<p>Blazor comes in two flavors: Blazor WebAssembly, which runs directly in the browser via WebAssembly, and Blazor Server, which executes on the server and communicates with the client via SignalR...</p>
|
||||
`
|
||||
},
|
||||
{
|
||||
id: "6",
|
||||
slug: "esp32-ethereal-connections-wireless-sensor-network",
|
||||
title: "ESP32 Ethereal Connections: Building a Wireless Sensor Network",
|
||||
excerpt: "Create a mesh of interconnected ESP32 devices that communicate wirelessly. This tutorial covers WiFi, Bluetooth, and custom RF protocols for building resilient IoT systems.",
|
||||
image: "/images/blog/esp32-network.jpg",
|
||||
category: "IoT",
|
||||
date: "March 30, 2025",
|
||||
author: "SecCodeSmith",
|
||||
commentCount: 11,
|
||||
readTime: "20 min read",
|
||||
tags: ["ESP32", "IoT", "Wireless", "Sensor Networks"],
|
||||
content: `
|
||||
<p>The ESP32 microcontroller has revolutionized the world of IoT with its powerful wireless capabilities, making it the perfect foundation for building ethereal connections between sensors and systems. In this guide, we'll explore how to craft a resilient wireless sensor network using these versatile devices.</p>
|
||||
|
||||
<h2>The Mystical Properties of ESP32</h2>
|
||||
|
||||
<p>Before diving into network architecture, let's examine what makes the ESP32 uniquely suited for wireless sensor networks. With dual-core processors, integrated WiFi and Bluetooth, and numerous GPIO pins...</p>
|
||||
`
|
||||
}
|
||||
];
|
||||
@@ -0,0 +1,105 @@
|
||||
import type { ProjectProps } from "../components/ProjectCard";
|
||||
|
||||
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.",
|
||||
image: "/images/projects/shadowguardian.jpg",
|
||||
category: ["iot", "embedded"],
|
||||
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" }
|
||||
],
|
||||
links: {
|
||||
github: "https://github.com/SecCodeSmith/shadowguardian",
|
||||
demo: "https://shadowguardian.demo.com"
|
||||
}
|
||||
},
|
||||
{
|
||||
id: "pyrosight",
|
||||
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"],
|
||||
technologies: [
|
||||
{ name: "Python", icon: "devicon-python-plain tech-icon" },
|
||||
{ name: "PyTorch", icon: "devicon-pytorch-original tech-icon" },
|
||||
{ name: "OpenCV", icon: "fas fa-camera tech-icon" },
|
||||
{ name: "Raspberry Pi", icon: "fas fa-microchip tech-icon" }
|
||||
],
|
||||
links: {
|
||||
github: "https://github.com/SecCodeSmith/pyrosight"
|
||||
}
|
||||
},
|
||||
{
|
||||
id: "nexusflow",
|
||||
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"],
|
||||
technologies: [
|
||||
{ name: "C#", icon: "devicon-csharp-plain tech-icon" },
|
||||
{ name: "Blazor", icon: "devicon-dotnetcore-plain tech-icon" },
|
||||
{ name: "Entity Framework", icon: "fas fa-database tech-icon" },
|
||||
{ name: "Docker", icon: "devicon-docker-plain tech-icon" }
|
||||
],
|
||||
links: {
|
||||
github: "https://github.com/SecCodeSmith/nexusflow",
|
||||
demo: "https://nexusflow.demo.com"
|
||||
}
|
||||
},
|
||||
{
|
||||
id: "etherwhisper",
|
||||
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"],
|
||||
technologies: [
|
||||
{ name: "C", icon: "devicon-c-plain tech-icon" },
|
||||
{ name: "STM32", icon: "fas fa-memory tech-icon" },
|
||||
{ name: "LoRa", icon: "fas fa-broadcast-tower tech-icon" },
|
||||
{ name: "AES-256", icon: "fas fa-shield-alt tech-icon" }
|
||||
],
|
||||
links: {
|
||||
github: "https://github.com/SecCodeSmith/etherwhisper"
|
||||
}
|
||||
},
|
||||
{
|
||||
id: "dataforge",
|
||||
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"],
|
||||
technologies: [
|
||||
{ name: "Python", icon: "devicon-python-plain tech-icon" },
|
||||
{ name: "Django", icon: "devicon-django-plain tech-icon" },
|
||||
{ name: "React", icon: "devicon-react-original tech-icon" },
|
||||
{ name: "Pandas", icon: "devicon-pandas-original tech-icon" }
|
||||
],
|
||||
links: {
|
||||
github: "https://github.com/SecCodeSmith/dataforge",
|
||||
demo: "https://dataforge.demo.com"
|
||||
}
|
||||
},
|
||||
{
|
||||
id: "visionkeeper",
|
||||
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"],
|
||||
technologies: [
|
||||
{ name: "Python", icon: "devicon-python-plain tech-icon" },
|
||||
{ name: "PyTorch", icon: "devicon-pytorch-original tech-icon" },
|
||||
{ name: "OpenCV", icon: "fas fa-camera tech-icon" },
|
||||
{ name: "NVIDIA Jetson", icon: "fas fa-microchip tech-icon" }
|
||||
],
|
||||
links: {
|
||||
github: "https://github.com/SecCodeSmith/visionkeeper"
|
||||
}
|
||||
}
|
||||
];
|
||||
+439
@@ -0,0 +1,439 @@
|
||||
:root {
|
||||
--primary-dark: #0a0a0a;
|
||||
--secondary-dark: #1a1a1a;
|
||||
--accent-fire: #ff6400;
|
||||
--accent-ember: #ff4500;
|
||||
--accent-dragon: #8B0000;
|
||||
--text-light: #f5f5f5;
|
||||
--text-ash: #aaaaaa;
|
||||
--glow-effect: 0 0 10px var(--accent-fire), 0 0 20px rgba(255, 100, 0, 0.4);
|
||||
--card-bg: #141414;
|
||||
--card-border: #2a2a2a;
|
||||
}
|
||||
|
||||
body {
|
||||
background-color: var(--primary-dark);
|
||||
color: var(--text-light);
|
||||
background-image: url('../images/background.png');
|
||||
background-repeat: round;
|
||||
background-size: 300px 300px;
|
||||
background-position: 0 0;
|
||||
position: relative;
|
||||
overflow-x: hidden;
|
||||
font-family: 'Cinzel', serif;
|
||||
}
|
||||
|
||||
body::before {
|
||||
content: "";
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: radial-gradient(circle at 50% 50%, rgba(139, 0, 0, 0.08), rgba(10, 10, 10, 0.4));
|
||||
pointer-events: none;
|
||||
z-index: -1;
|
||||
}
|
||||
|
||||
/* Bootstrap Overrides */
|
||||
.navbar {
|
||||
background-color: rgba(10, 10, 10, 0.95) !important;
|
||||
border-bottom: 1px solid rgba(255, 100, 0, 0.3);
|
||||
}
|
||||
|
||||
.navbar-brand {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.navbar-toggler {
|
||||
border: 1px solid rgba(255, 100, 0, 0.5);
|
||||
color: var(--accent-fire);
|
||||
}
|
||||
|
||||
.navbar-toggler-icon {
|
||||
background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 30 30'%3e%3cpath stroke='rgba%28255, 100, 0, 0.8%29' stroke-linecap='round' stroke-miterlimit='10' stroke-width='2' d='M4 7h22M4 15h22M4 23h22'/%3e%3c/svg%3e");
|
||||
}
|
||||
|
||||
.nav-link {
|
||||
color: var(--text-light) !important;
|
||||
position: relative;
|
||||
padding: 0.5rem 1rem;
|
||||
transition: color 0.3s;
|
||||
}
|
||||
|
||||
.nav-link:hover, .nav-link.active {
|
||||
color: var(--accent-fire) !important;
|
||||
}
|
||||
|
||||
.nav-link::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
width: 0;
|
||||
height: 2px;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
background: linear-gradient(to right, var(--accent-fire), transparent);
|
||||
transition: width 0.3s ease;
|
||||
}
|
||||
|
||||
.nav-link:hover::after, .nav-link.active::after {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
/* Logo styling */
|
||||
.logo-icon {
|
||||
font-size: 2rem;
|
||||
color: var(--accent-fire);
|
||||
text-shadow: var(--glow-effect);
|
||||
position: relative;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.logo-text {
|
||||
font-weight: 700;
|
||||
font-size: 1.5rem;
|
||||
background: linear-gradient(to right, var(--accent-fire), var(--text-light));
|
||||
-webkit-background-clip: text;
|
||||
background-clip: text;
|
||||
color: transparent;
|
||||
letter-spacing: 1px;
|
||||
}
|
||||
|
||||
/* Hero Section */
|
||||
.hero {
|
||||
height: 100vh;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding-top: 76px; /* Account for navbar height */
|
||||
}
|
||||
|
||||
.hero::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: url('/api/placeholder/1200/800') center/cover no-repeat;
|
||||
opacity: 0.15;
|
||||
filter: brightness(0.5) contrast(1.2);
|
||||
z-index: -1;
|
||||
}
|
||||
|
||||
.title-main {
|
||||
font-size: 4rem;
|
||||
font-weight: 700;
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.title-main::before {
|
||||
content: "< ";
|
||||
color: var(--accent-fire);
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.title-main::after {
|
||||
content: " />";
|
||||
color: var(--accent-fire);
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
font-size: 1.6rem;
|
||||
color: var(--text-ash);
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
/* Binary Background */
|
||||
.binary-bg {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
z-index: -1;
|
||||
opacity: 0.07;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.binary-line {
|
||||
position: absolute;
|
||||
font-family: 'Fira Code', monospace;
|
||||
color: var(--accent-fire);
|
||||
font-size: 1rem;
|
||||
animation: fall linear infinite;
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
@keyframes fall {
|
||||
0% {
|
||||
transform: translateY(-100%);
|
||||
}
|
||||
100% {
|
||||
transform: translateY(100vh);
|
||||
}
|
||||
}
|
||||
|
||||
/* Button Styling */
|
||||
.btn-primary {
|
||||
background: linear-gradient(45deg, var(--accent-dragon), var(--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;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.btn-primary:hover {
|
||||
transform: translateY(-3px);
|
||||
box-shadow: 0 7px 25px rgba(139, 0, 0, 0.5);
|
||||
background-color: var(--accent-fire);
|
||||
background-image: none;
|
||||
}
|
||||
|
||||
.btn-primary::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: -100%;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.2), transparent);
|
||||
transition: all 0.6s ease;
|
||||
}
|
||||
|
||||
.btn-primary:hover::before {
|
||||
left: 100%;
|
||||
}
|
||||
|
||||
/* Skills Section */
|
||||
.skills-section {
|
||||
padding: 6rem 0;
|
||||
background-color: var(--secondary-dark);
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.section-title {
|
||||
text-align: center;
|
||||
font-size: 2.5rem;
|
||||
margin-bottom: 3rem;
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.section-title::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
width: 80px;
|
||||
height: 3px;
|
||||
background: var(--accent-fire);
|
||||
bottom: -10px;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
box-shadow: var(--glow-effect);
|
||||
}
|
||||
|
||||
.card {
|
||||
background-color: var(--card-bg);
|
||||
border: 1px solid var(--card-border);
|
||||
border-radius: 8px;
|
||||
transition: all 0.3s ease;
|
||||
height: 100%;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.card:hover {
|
||||
transform: translateY(-10px);
|
||||
border-color: rgba(255, 100, 0, 0.3);
|
||||
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
|
||||
.card::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 5px;
|
||||
top: 0;
|
||||
left: 0;
|
||||
background: linear-gradient(to right, var(--accent-dragon), var(--accent-fire));
|
||||
}
|
||||
|
||||
.card-header {
|
||||
background-color: transparent;
|
||||
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.category-icon {
|
||||
color: var(--accent-fire);
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
.category-title {
|
||||
font-size: 1.4rem;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.skill-item {
|
||||
margin-bottom: 12px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.skill-icon {
|
||||
color: var(--accent-fire);
|
||||
font-size: 1.5rem;
|
||||
min-width: 24px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.skill-icon i {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.skill-name {
|
||||
color: var(--text-ash);
|
||||
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
||||
}
|
||||
|
||||
/* Blinking cursor effect */
|
||||
.blinking-cursor {
|
||||
display: inline-block;
|
||||
width: 10px;
|
||||
height: 24px;
|
||||
background-color: var(--accent-fire);
|
||||
margin-left: 5px;
|
||||
animation: blink 1s step-end infinite;
|
||||
box-shadow: var(--glow-effect);
|
||||
}
|
||||
|
||||
@keyframes blink {
|
||||
0%, 100% { opacity: 1; }
|
||||
50% { opacity: 0; }
|
||||
}
|
||||
|
||||
/* Particles effect */
|
||||
.particles {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
pointer-events: none;
|
||||
z-index: 0;
|
||||
}
|
||||
|
||||
.particle {
|
||||
position: absolute;
|
||||
width: 3px;
|
||||
height: 3px;
|
||||
background-color: var(--accent-fire);
|
||||
border-radius: 50%;
|
||||
opacity: 0;
|
||||
animation: rise linear forwards;
|
||||
}
|
||||
|
||||
@keyframes rise {
|
||||
0% {
|
||||
transform: translateY(0) translateX(0);
|
||||
opacity: 0;
|
||||
}
|
||||
10% {
|
||||
opacity: 0.5;
|
||||
}
|
||||
90% {
|
||||
opacity: 0.5;
|
||||
}
|
||||
100% {
|
||||
transform: translateY(-100px) translateX(var(--x));
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* Footer */
|
||||
.footer {
|
||||
background-color: var(--primary-dark);
|
||||
padding: 3rem 0;
|
||||
border-top: 1px solid rgba(255, 100, 0, 0.2);
|
||||
margin-top: 5rem;
|
||||
}
|
||||
|
||||
.social-links {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
gap: 20px;
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
.social-icon {
|
||||
font-size: 1.5rem;
|
||||
color: var(--text-ash);
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.social-icon:hover {
|
||||
color: var(--accent-fire);
|
||||
transform: translateY(-5px);
|
||||
}
|
||||
|
||||
.copyright {
|
||||
color: var(--text-ash);
|
||||
font-size: 0.9rem;
|
||||
margin-top: 1rem;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
/* Responsive adjustments */
|
||||
@media (max-width: 992px) {
|
||||
.title-main {
|
||||
font-size: 3rem;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.title-main {
|
||||
font-size: 2.5rem;
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
|
||||
.binary-line {
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 576px) {
|
||||
.title-main {
|
||||
font-size: 2rem;
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
padding: 0.6rem 1.5rem;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
import React from 'react'
|
||||
import ReactDOM from 'react-dom/client'
|
||||
import App from './App.tsx'
|
||||
import './index.css'
|
||||
|
||||
ReactDOM.createRoot(document.getElementById('root')!).render(
|
||||
<React.StrictMode>
|
||||
<App />
|
||||
</React.StrictMode>,
|
||||
)
|
||||
@@ -0,0 +1,380 @@
|
||||
import { PageHeader } from '../components/PageHeader';
|
||||
|
||||
export const About = () => {
|
||||
return (
|
||||
<>
|
||||
<PageHeader
|
||||
title="The Forgemaster"
|
||||
subtitle="Crafting digital solutions from the raw materials of innovation"
|
||||
/>
|
||||
|
||||
{/* Introduction Section */}
|
||||
<section className="py-5">
|
||||
<div className="container">
|
||||
<div className="row">
|
||||
<div className="col-lg-4">
|
||||
<h2 className="section-title">The Master Behind the Mask</h2>
|
||||
<div className="profile-image">
|
||||
<img src="/images/profile.jpg" alt="SecCodeSmith" />
|
||||
</div>
|
||||
</div>
|
||||
<div className="col-lg-8">
|
||||
<div className="intro-text">
|
||||
<p>Greetings, seeker of digital arcana. I am <strong>SecCodeSmith</strong>, a master craftsman of code and conductor of silicon. For over a decade, I have been forging robust solutions across the realms of embedded systems, machine learning, and web development.</p>
|
||||
|
||||
<p>My journey began in the depths of <span className="accent">low-level programming</span>, where I learned to bend hardware to my will through carefully crafted instructions. From the arcane energies of microcontrollers to the vast landscapes of cloud architecture, I have honed my skills to create solutions that stand the test of time.</p>
|
||||
|
||||
<p>What distinguishes my work is the <strong>meticulous attention to security</strong> and performance. Like a blacksmith who understands that a sword's true value lies not just in its edge but in the integrity of its steel, I craft code that is not merely functional but resilient against the chaotic forces of the digital wilderness.</p>
|
||||
|
||||
<p>Beyond the technical realms, I am a devoted practitioner of open-source sorcery, contributing to projects that empower others to create and innovate. When not at the forge, I can be found exploring the ancient texts of computer science, delving into new programming languages, or mentoring apprentices on their own journey.</p>
|
||||
|
||||
<div className="mt-4">
|
||||
<a href="#" className="connect-link">
|
||||
<i className="fab fa-github connect-icon"></i>
|
||||
<span>GitHub</span>
|
||||
</a>
|
||||
<a href="#" className="connect-link">
|
||||
<i className="fab fa-linkedin connect-icon"></i>
|
||||
<span>LinkedIn</span>
|
||||
</a>
|
||||
<a href="#" className="connect-link">
|
||||
<i className="fab fa-twitter connect-icon"></i>
|
||||
<span>Twitter</span>
|
||||
</a>
|
||||
<a href="#" className="connect-link">
|
||||
<i className="fas fa-envelope connect-icon"></i>
|
||||
<span>Email</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Core Values Section */}
|
||||
<section className="py-5 bg-dark">
|
||||
<div className="container">
|
||||
<div className="row mb-4">
|
||||
<div className="col-12">
|
||||
<h2 className="section-title">Forging Principles</h2>
|
||||
</div>
|
||||
</div>
|
||||
<div className="row g-4">
|
||||
<div className="col-md-6 col-lg-3">
|
||||
<div className="value-card h-100">
|
||||
<div className="value-icon">
|
||||
<i className="fas fa-shield-alt"></i>
|
||||
</div>
|
||||
<h3 className="value-title">Security by Design</h3>
|
||||
<p className="value-text">Security is not an afterthought but the very foundation upon which robust systems are built. Every line of code is crafted with defensive principles in mind.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="col-md-6 col-lg-3">
|
||||
<div className="value-card h-100">
|
||||
<div className="value-icon">
|
||||
<i className="fas fa-tachometer-alt"></i>
|
||||
</div>
|
||||
<h3 className="value-title">Performance Optimization</h3>
|
||||
<p className="value-text">Like a finely-honed blade, code should be both effective and efficient. I meticulously optimize every component for maximum speed and minimal resource consumption.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="col-md-6 col-lg-3">
|
||||
<div className="value-card h-100">
|
||||
<div className="value-icon">
|
||||
<i className="fas fa-gem"></i>
|
||||
</div>
|
||||
<h3 className="value-title">Elegant Simplicity</h3>
|
||||
<p className="value-text">True mastery is revealed not in complexity but in elegant simplicity. I strive to create solutions that are accessible, maintainable, and beautifully crafted.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="col-md-6 col-lg-3">
|
||||
<div className="value-card h-100">
|
||||
<div className="value-icon">
|
||||
<i className="fas fa-sync-alt"></i>
|
||||
</div>
|
||||
<h3 className="value-title">Continuous Evolution</h3>
|
||||
<p className="value-text">The digital landscape changes with the seasons. I embrace continuous learning and adaptation, integrating new techniques and technologies to forge ever-better solutions.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Technical Expertise Section */}
|
||||
<section className="py-5">
|
||||
<div className="container">
|
||||
<div className="row mb-4">
|
||||
<div className="col-12">
|
||||
<h2 className="section-title">Arsenal of Expertise</h2>
|
||||
</div>
|
||||
</div>
|
||||
<div className="row g-4">
|
||||
<div className="col-md-6 col-lg-3">
|
||||
<div className="skill-card h-100">
|
||||
<div className="skill-header">
|
||||
<div className="skill-icon">
|
||||
<i className="fas fa-microchip"></i>
|
||||
</div>
|
||||
<h3 className="skill-title">Embedded Systems</h3>
|
||||
</div>
|
||||
<ul className="list-unstyled">
|
||||
<li className="mb-2">
|
||||
<i className="fas fa-check skill-check"></i>
|
||||
<span>STM32 microcontroller programming (I2C, SPI, UART)</span>
|
||||
</li>
|
||||
<li className="mb-2">
|
||||
<i className="fas fa-check skill-check"></i>
|
||||
<span>ESP32 wireless solutions</span>
|
||||
</li>
|
||||
<li className="mb-2">
|
||||
<i className="fas fa-check skill-check"></i>
|
||||
<span>Firmware development with C/C++</span>
|
||||
</li>
|
||||
<li className="mb-2">
|
||||
<i className="fas fa-check skill-check"></i>
|
||||
<span>Raspberry Pi and Pico-SDK integration</span>
|
||||
</li>
|
||||
<li className="mb-2">
|
||||
<i className="fas fa-check skill-check"></i>
|
||||
<span>RTOS implementation</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="col-md-6 col-lg-3">
|
||||
<div className="skill-card h-100">
|
||||
<div className="skill-header">
|
||||
<div className="skill-icon">
|
||||
<i className="fas fa-brain"></i>
|
||||
</div>
|
||||
<h3 className="skill-title">AI & Data Analysis</h3>
|
||||
</div>
|
||||
<ul className="list-unstyled">
|
||||
<li className="mb-2">
|
||||
<i className="fas fa-check skill-check"></i>
|
||||
<span>Machine learning with PyTorch</span>
|
||||
</li>
|
||||
<li className="mb-2">
|
||||
<i className="fas fa-check skill-check"></i>
|
||||
<span>Data manipulation with Pandas</span>
|
||||
</li>
|
||||
<li className="mb-2">
|
||||
<i className="fas fa-check skill-check"></i>
|
||||
<span>Predictive modeling via Scikit-learn</span>
|
||||
</li>
|
||||
<li className="mb-2">
|
||||
<i className="fas fa-check skill-check"></i>
|
||||
<span>Computer vision with Scikit-image</span>
|
||||
</li>
|
||||
<li className="mb-2">
|
||||
<i className="fas fa-check skill-check"></i>
|
||||
<span>Edge AI deployment</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="col-md-6 col-lg-3">
|
||||
<div className="skill-card h-100">
|
||||
<div className="skill-header">
|
||||
<div className="skill-icon">
|
||||
<i className="fas fa-window-maximize"></i>
|
||||
</div>
|
||||
<h3 className="skill-title">Web Development</h3>
|
||||
</div>
|
||||
<ul className="list-unstyled">
|
||||
<li className="mb-2">
|
||||
<i className="fas fa-check skill-check"></i>
|
||||
<span>Backend development with ASP.NET</span>
|
||||
</li>
|
||||
<li className="mb-2">
|
||||
<i className="fas fa-check skill-check"></i>
|
||||
<span>Database management with Entity Framework</span>
|
||||
</li>
|
||||
<li className="mb-2">
|
||||
<i className="fas fa-check skill-check"></i>
|
||||
<span>Frontend frameworks: Blazor, React</span>
|
||||
</li>
|
||||
<li className="mb-2">
|
||||
<i className="fas fa-check skill-check"></i>
|
||||
<span>Responsive design with Bootstrap</span>
|
||||
</li>
|
||||
<li className="mb-2">
|
||||
<i className="fas fa-check skill-check"></i>
|
||||
<span>Python-based web solutions with Django</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="col-md-6 col-lg-3">
|
||||
<div className="skill-card h-100">
|
||||
<div className="skill-header">
|
||||
<div className="skill-icon">
|
||||
<i className="fas fa-server"></i>
|
||||
</div>
|
||||
<h3 className="skill-title">DevOps & Infrastructure</h3>
|
||||
</div>
|
||||
<ul className="list-unstyled">
|
||||
<li className="mb-2">
|
||||
<i className="fas fa-check skill-check"></i>
|
||||
<span>Containerization with Docker</span>
|
||||
</li>
|
||||
<li className="mb-2">
|
||||
<i className="fas fa-check skill-check"></i>
|
||||
<span>Multi-container orchestration via Docker-compose</span>
|
||||
</li>
|
||||
<li className="mb-2">
|
||||
<i className="fas fa-check skill-check"></i>
|
||||
<span>Linux server administration</span>
|
||||
</li>
|
||||
<li className="mb-2">
|
||||
<i className="fas fa-check skill-check"></i>
|
||||
<span>CI/CD pipeline implementation</span>
|
||||
</li>
|
||||
<li className="mb-2">
|
||||
<i className="fas fa-check skill-check"></i>
|
||||
<span>Build system configuration with CMake</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Professional Journey Section */}
|
||||
<section className="py-5 bg-dark">
|
||||
<div className="container">
|
||||
<div className="row mb-5">
|
||||
<div className="col-12 text-center">
|
||||
<h2 className="section-title">The Smith's Journey</h2>
|
||||
</div>
|
||||
</div>
|
||||
<div className="row">
|
||||
<div className="col-12">
|
||||
<ul className="timeline">
|
||||
{/* Timeline Item 1 */}
|
||||
<li className="timeline-item clearfix">
|
||||
<div className="timeline-dot"></div>
|
||||
<div className="timeline-content">
|
||||
<div className="timeline-date">2021 - Present</div>
|
||||
<h3 className="timeline-title">Lead Systems Engineer</h3>
|
||||
<p className="timeline-text">Architecting secure IoT solutions for critical infrastructure, combining embedded firmware expertise with cloud integration. Leading a team of 8 engineers across hardware and software disciplines.</p>
|
||||
</div>
|
||||
</li>
|
||||
|
||||
{/* Timeline Item 2 */}
|
||||
<li className="timeline-item clearfix">
|
||||
<div className="timeline-dot"></div>
|
||||
<div className="timeline-content">
|
||||
<div className="timeline-date">2018 - 2021</div>
|
||||
<h3 className="timeline-title">Senior Embedded Developer</h3>
|
||||
<p className="timeline-text">Developed firmware for next-generation industrial control systems. Implemented machine learning capabilities on resource-constrained devices, reducing predictive maintenance false positives by 87%.</p>
|
||||
</div>
|
||||
</li>
|
||||
|
||||
{/* Timeline Item 3 */}
|
||||
<li className="timeline-item clearfix">
|
||||
<div className="timeline-dot"></div>
|
||||
<div className="timeline-content">
|
||||
<div className="timeline-date">2015 - 2018</div>
|
||||
<h3 className="timeline-title">Full-Stack Developer</h3>
|
||||
<p className="timeline-text">Created web applications and APIs for data visualization and analysis. Specialized in integrating hardware systems with web interfaces, enabling real-time monitoring of distributed sensor networks.</p>
|
||||
</div>
|
||||
</li>
|
||||
|
||||
{/* Timeline Item 4 */}
|
||||
<li className="timeline-item clearfix">
|
||||
<div className="timeline-dot"></div>
|
||||
<div className="timeline-content">
|
||||
<div className="timeline-date">2012 - 2015</div>
|
||||
<h3 className="timeline-title">Firmware Engineer</h3>
|
||||
<p className="timeline-text">Designed and implemented firmware for medical devices, focusing on reliability and safety-critical operations. Reduced power consumption by 35% while improving processing performance.</p>
|
||||
</div>
|
||||
</li>
|
||||
|
||||
{/* Timeline Item 5 */}
|
||||
<li className="timeline-item clearfix">
|
||||
<div className="timeline-dot"></div>
|
||||
<div className="timeline-content">
|
||||
<div className="timeline-date">2010 - 2012</div>
|
||||
<h3 className="timeline-title">Junior Software Developer</h3>
|
||||
<p className="timeline-text">Began the journey into the digital forge, working on embedded C programming for consumer electronics. Learned the fundamentals of hardware-software integration and real-time systems.</p>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Testimonials Section */}
|
||||
<section className="py-5">
|
||||
<div className="container">
|
||||
<div className="row mb-4">
|
||||
<div className="col-12">
|
||||
<h2 className="section-title">Tales from the Guild</h2>
|
||||
</div>
|
||||
</div>
|
||||
<div className="row">
|
||||
<div className="col-lg-8 mx-auto">
|
||||
{/* Testimonial 1 */}
|
||||
<div className="testimonial">
|
||||
<div className="testimonial-content">
|
||||
"Working with SecCodeSmith transformed our approach to IoT security. Their expertise in both embedded systems and network security allowed us to create a solution that our clients trust implicitly. The attention to detail and foresight regarding potential vulnerabilities saved us from what could have been catastrophic security incidents."
|
||||
</div>
|
||||
<div className="d-flex align-items-center gap-3 testimonial-author">
|
||||
<div className="author-avatar">
|
||||
<img src="/images/testimonials/alexandra.jpg" alt="Alexandra Foster" />
|
||||
</div>
|
||||
<div className="author-info">
|
||||
<h4 className="author-name">Alexandra Foster</h4>
|
||||
<div className="author-title">CTO, ConnectedWorld Technologies</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Testimonial 2 */}
|
||||
<div className="testimonial">
|
||||
<div className="testimonial-content">
|
||||
"The custom firmware developed by SecCodeSmith for our industrial controllers exceeded all expectations. Not only was the code incredibly efficient, but the documentation was meticulous—something rare in our industry. Three years later, we're still building upon that solid foundation with minimal issues."
|
||||
</div>
|
||||
<div className="d-flex align-items-center gap-3 testimonial-author">
|
||||
<div className="author-avatar">
|
||||
<img src="/images/testimonials/marcus.jpg" alt="Marcus Chen" />
|
||||
</div>
|
||||
<div className="author-info">
|
||||
<h4 className="author-name">Marcus Chen</h4>
|
||||
<div className="author-title">Lead Engineer, Precision Automation Inc.</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Testimonial 3 */}
|
||||
<div className="testimonial">
|
||||
<div className="testimonial-content">
|
||||
"I've collaborated with numerous developers over my 20-year career, but SecCodeSmith stands apart for their ability to bridge the gap between theoretical concepts and practical implementation. Their machine learning models for anomaly detection operate efficiently even on our limited hardware, which competitors claimed was impossible."
|
||||
</div>
|
||||
<div className="d-flex align-items-center gap-3 testimonial-author">
|
||||
<div className="author-avatar">
|
||||
<img src="/images/testimonials/sophia.jpg" alt="Sophia Rodriguez" />
|
||||
</div>
|
||||
<div className="author-info">
|
||||
<h4 className="author-name">Sophia Rodriguez</h4>
|
||||
<div className="author-title">Research Director, Advanced Sensing Lab</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,180 @@
|
||||
import { useState } from 'react';
|
||||
import { PageHeader } from '../components/PageHeader';
|
||||
import { BlogCard } from '../components/BlogCard';
|
||||
import { blogPostsData } from '../data/blogPostsData';
|
||||
|
||||
export const Blog = () => {
|
||||
const [searchQuery, setSearchQuery] = useState('');
|
||||
|
||||
// Filter blog posts based on search query
|
||||
const filteredPosts = searchQuery
|
||||
? blogPostsData.filter(post =>
|
||||
post.title.toLowerCase().includes(searchQuery.toLowerCase()) ||
|
||||
post.excerpt.toLowerCase().includes(searchQuery.toLowerCase()) ||
|
||||
post.tags.some(tag => tag.toLowerCase().includes(searchQuery.toLowerCase()))
|
||||
)
|
||||
: blogPostsData;
|
||||
|
||||
// Get the featured post
|
||||
const featuredPost = blogPostsData.find(post => post.featured);
|
||||
|
||||
// Get regular posts (non-featured)
|
||||
const regularPosts = filteredPosts.filter(post => !post.featured);
|
||||
|
||||
// Handle search input change
|
||||
const handleSearchChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setSearchQuery(e.target.value);
|
||||
};
|
||||
|
||||
// Generate a list of unique categories
|
||||
const categories = Array.from(new Set(blogPostsData.map(post => post.category)));
|
||||
|
||||
// Generate a list of unique tags
|
||||
const allTags = blogPostsData.flatMap(post => post.tags);
|
||||
const uniqueTags = Array.from(new Set(allTags));
|
||||
|
||||
return (
|
||||
<>
|
||||
<PageHeader
|
||||
title="The Arcane Chronicles"
|
||||
subtitle="Insights, discoveries, and technical runes from the digital forge"
|
||||
/>
|
||||
|
||||
<div className="container mt-5">
|
||||
<div className="row">
|
||||
{/* Main Content */}
|
||||
<div className="col-lg-8">
|
||||
{/* Featured Post */}
|
||||
{featuredPost && (!searchQuery ||
|
||||
featuredPost.title.toLowerCase().includes(searchQuery.toLowerCase()) ||
|
||||
featuredPost.excerpt.toLowerCase().includes(searchQuery.toLowerCase()) ||
|
||||
featuredPost.tags.some(tag => tag.toLowerCase().includes(searchQuery.toLowerCase()))
|
||||
) && (
|
||||
<div className="card featured-card mb-5">
|
||||
<BlogCard post={featuredPost} />
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Regular Posts */}
|
||||
<div className="row">
|
||||
{regularPosts.map(post => (
|
||||
<div className="col-md-6" key={post.id}>
|
||||
<BlogCard post={post} />
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* No Posts Message */}
|
||||
{filteredPosts.length === 0 && (
|
||||
<div className="text-center mt-5">
|
||||
<h3>No posts found matching your search.</h3>
|
||||
<button
|
||||
className="btn btn-primary mt-3"
|
||||
onClick={() => setSearchQuery('')}
|
||||
>
|
||||
Clear Search
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Pagination */}
|
||||
{filteredPosts.length > 0 && (
|
||||
<nav className="mt-5" aria-label="Blog pagination">
|
||||
<ul className="pagination justify-content-center">
|
||||
<li className="page-item">
|
||||
<a className="page-link" href="#" aria-label="Previous">
|
||||
<i className="fas fa-angle-left"></i>
|
||||
</a>
|
||||
</li>
|
||||
<li className="page-item active"><a className="page-link" href="#">1</a></li>
|
||||
<li className="page-item"><a className="page-link" href="#">2</a></li>
|
||||
<li className="page-item"><a className="page-link" href="#">3</a></li>
|
||||
<li className="page-item">
|
||||
<a className="page-link" href="#" aria-label="Next">
|
||||
<i className="fas fa-angle-right"></i>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Sidebar */}
|
||||
<div className="col-lg-4 sidebar">
|
||||
{/* Search Widget */}
|
||||
<div className="sidebar-widget">
|
||||
<h3 className="widget-title">Search</h3>
|
||||
<form className="search-form">
|
||||
<div className="input-group">
|
||||
<input
|
||||
type="text"
|
||||
className="form-control"
|
||||
placeholder="Search articles..."
|
||||
value={searchQuery}
|
||||
onChange={handleSearchChange}
|
||||
/>
|
||||
<button className="btn btn-primary" type="submit">
|
||||
<i className="fas fa-search"></i>
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
{/* Categories Widget */}
|
||||
<div className="sidebar-widget">
|
||||
<h3 className="widget-title">Categories</h3>
|
||||
<div className="list-group">
|
||||
{categories.map((category, index) => {
|
||||
const count = blogPostsData.filter(post => post.category === category).length;
|
||||
return (
|
||||
<div className="list-group-item" key={index}>
|
||||
<a href="#" onClick={() => setSearchQuery(category)}>
|
||||
<span>{category}</span>
|
||||
<span className="category-count">{count}</span>
|
||||
</a>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Recent Posts Widget */}
|
||||
<div className="sidebar-widget">
|
||||
<h3 className="widget-title">Recent Posts</h3>
|
||||
<div className="recent-posts">
|
||||
{blogPostsData.slice(0, 3).map(post => (
|
||||
<div className="recent-post-item" key={post.id}>
|
||||
<img src={post.image} className="recent-post-thumb" alt={post.title} />
|
||||
<div className="recent-post-info">
|
||||
<h4 className="recent-post-title">
|
||||
<a href={`/blog/${post.slug}`}>{post.title}</a>
|
||||
</h4>
|
||||
<span className="recent-post-date">{post.date}</span>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Tags Widget */}
|
||||
<div className="sidebar-widget">
|
||||
<h3 className="widget-title">Popular Tags</h3>
|
||||
<div>
|
||||
{uniqueTags.map((tag, index) => (
|
||||
<span
|
||||
key={index}
|
||||
className="post-tag"
|
||||
onClick={() => setSearchQuery(tag)}
|
||||
style={{ cursor: 'pointer' }}
|
||||
>
|
||||
{tag}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,207 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useParams, Link } from 'react-router-dom';
|
||||
import { blogPostsData } from '../data/blogPostsData';
|
||||
import { NotFound } from './NotFound';
|
||||
|
||||
export const BlogPost = () => {
|
||||
const { slug } = useParams<{ slug: string }>();
|
||||
const [post, setPost] = useState(blogPostsData.find(post => post.slug === slug));
|
||||
const [relatedPosts, setRelatedPosts] = useState(blogPostsData.slice(0, 3));
|
||||
|
||||
useEffect(() => {
|
||||
// Find the post by slug
|
||||
const currentPost = blogPostsData.find(post => post.slug === slug);
|
||||
|
||||
if (currentPost) {
|
||||
// Set the post
|
||||
setPost(currentPost);
|
||||
|
||||
// Find related posts (same category or shared tags)
|
||||
const related = blogPostsData
|
||||
.filter(p => p.id !== currentPost.id)
|
||||
.filter(p =>
|
||||
p.category === currentPost.category ||
|
||||
p.tags.some(tag => currentPost.tags.includes(tag))
|
||||
)
|
||||
.slice(0, 3);
|
||||
|
||||
setRelatedPosts(related);
|
||||
|
||||
// Scroll to top when new post is loaded
|
||||
window.scrollTo(0, 0);
|
||||
}
|
||||
}, [slug]);
|
||||
|
||||
if (!post) {
|
||||
return <NotFound />;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="post-container">
|
||||
<main className="post-main">
|
||||
<article>
|
||||
{/* Post Header */}
|
||||
<div className="post-header">
|
||||
<div className="post-featured-image" style={{ backgroundImage: `url(${post.image})` }}></div>
|
||||
<div className="post-category">{post.category}</div>
|
||||
</div>
|
||||
|
||||
{/* Post Info */}
|
||||
<div className="post-info">
|
||||
<h1 className="post-title">{post.title}</h1>
|
||||
<div className="post-meta">
|
||||
<div className="meta-item">
|
||||
<i className="fas fa-calendar-alt meta-icon"></i>
|
||||
<span>{post.date}</span>
|
||||
</div>
|
||||
<div className="meta-item">
|
||||
<i className="fas fa-user meta-icon"></i>
|
||||
<span>{post.author}</span>
|
||||
</div>
|
||||
<div className="meta-item">
|
||||
<i className="fas fa-clock meta-icon"></i>
|
||||
<span>{post.readTime}</span>
|
||||
</div>
|
||||
<div className="meta-item">
|
||||
<i className="fas fa-comments meta-icon"></i>
|
||||
<span>{post.commentCount} Comments</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Post Content */}
|
||||
<div className="post-content" dangerouslySetInnerHTML={{ __html: post.content }}></div>
|
||||
|
||||
{/* Post Tags */}
|
||||
<div className="post-tags">
|
||||
{post.tags.map((tag, index) => (
|
||||
<span key={index} className="post-tag">{tag}</span>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Share Section */}
|
||||
<div className="share-section">
|
||||
<h3 className="share-title">Share this arcane knowledge</h3>
|
||||
<div className="share-buttons">
|
||||
<a href="#" className="share-button"><i className="fab fa-twitter"></i></a>
|
||||
<a href="#" className="share-button"><i className="fab fa-facebook-f"></i></a>
|
||||
<a href="#" className="share-button"><i className="fab fa-linkedin-in"></i></a>
|
||||
<a href="#" className="share-button"><i className="fab fa-reddit-alien"></i></a>
|
||||
<a href="#" className="share-button"><i className="far fa-envelope"></i></a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Author Info */}
|
||||
<div className="author-section">
|
||||
<div className="author-avatar">
|
||||
<img src="/images/author.jpg" alt="SecCodeSmith" />
|
||||
</div>
|
||||
<div className="author-info">
|
||||
<h3 className="author-name">SecCodeSmith</h3>
|
||||
<p className="author-bio">Digital blacksmith forging embedded systems and IoT solutions in the fires of innovation. Specializes in low-level firmware, communication protocols, and hardware-software integration for resource-constrained devices.</p>
|
||||
<div className="author-social">
|
||||
<a href="#"><i className="fab fa-github"></i></a>
|
||||
<a href="#"><i className="fab fa-twitter"></i></a>
|
||||
<a href="#"><i className="fab fa-linkedin-in"></i></a>
|
||||
<a href="#"><i className="fab fa-stack-overflow"></i></a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Comments Section */}
|
||||
<div className="comments-section">
|
||||
<h3 className="comments-title">Discussions ({post.commentCount})</h3>
|
||||
|
||||
{/* Sample Comments */}
|
||||
<div className="comment">
|
||||
<div className="comment-avatar">
|
||||
<img src="/images/user-1.jpg" alt="User" />
|
||||
</div>
|
||||
<div className="comment-content">
|
||||
<h4 className="comment-author">Michael Chen</h4>
|
||||
<div className="comment-date">May 5, 2025 at 15:42</div>
|
||||
<p className="comment-text">Excellent article! I've been struggling with I2C bus lockups in my weather station project. The recovery function you provided solved the issue. Have you considered adding a timeout mechanism to the SPI transfers for error detection?</p>
|
||||
<a href="#" className="comment-reply">Reply</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="comment">
|
||||
<div className="comment-avatar">
|
||||
<img src="/images/author.jpg" alt="User" />
|
||||
</div>
|
||||
<div className="comment-content">
|
||||
<h4 className="comment-author">SecCodeSmith</h4>
|
||||
<div className="comment-date">May 5, 2025 at 16:10</div>
|
||||
<p className="comment-text">@Michael - Good point about the timeout! For SPI transfers, you can implement a timeout using either the HAL Timeout parameter or a hardware timer. For maximum reliability, I prefer using the hardware timer approach as it continues to function even if the main code gets stuck. I'll cover this in a future article on error handling.</p>
|
||||
<a href="#" className="comment-reply">Reply</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Comment Form */}
|
||||
<div className="comment-form">
|
||||
<h3 className="form-title">Leave your mark</h3>
|
||||
<form>
|
||||
<div className="form-row">
|
||||
<div className="form-group">
|
||||
<input type="text" className="form-control" placeholder="Your Name" />
|
||||
</div>
|
||||
<div className="form-group">
|
||||
<input type="email" className="form-control" placeholder="Your Email" />
|
||||
</div>
|
||||
</div>
|
||||
<div className="form-group">
|
||||
<textarea className="form-control" rows={5} placeholder="Your Comment"></textarea>
|
||||
</div>
|
||||
<button type="submit" className="submit-button">Post Comment</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
</main>
|
||||
|
||||
<aside className="post-sidebar">
|
||||
{/* Table of Contents */}
|
||||
<div className="sidebar-widget">
|
||||
<h3 className="widget-title">Table of Contents</h3>
|
||||
<ul className="toc-list">
|
||||
<li className="toc-item">
|
||||
<a href="#" className="toc-link">Introduction</a>
|
||||
</li>
|
||||
<li className="toc-item">
|
||||
<a href="#" className="toc-link">The Three Elemental Protocols</a>
|
||||
</li>
|
||||
<li className="toc-item">
|
||||
<a href="#" className="toc-link">Optimizing I2C for Reliable Field Operations</a>
|
||||
<ul className="toc-list">
|
||||
<li className="toc-subitem">
|
||||
<a href="#" className="toc-link">Hardware Incantations</a>
|
||||
</li>
|
||||
<li className="toc-subitem">
|
||||
<a href="#" className="toc-link">Software Rituals</a>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
{/* Related Posts */}
|
||||
<div className="sidebar-widget">
|
||||
<h3 className="widget-title">Related Scrolls</h3>
|
||||
<ul className="related-posts-list">
|
||||
{relatedPosts.map(relatedPost => (
|
||||
<li className="related-post-item" key={relatedPost.id}>
|
||||
<Link to={`/blog/${relatedPost.slug}`} className="related-post-link">
|
||||
<div className="related-post-thumb" style={{ backgroundImage: `url(${relatedPost.image})` }}></div>
|
||||
<div className="related-post-info">
|
||||
<h4 className="related-post-title">{relatedPost.title}</h4>
|
||||
<span className="related-post-date">{relatedPost.date}</span>
|
||||
</div>
|
||||
</Link>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
</aside>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,214 @@
|
||||
import { PageHeader } from '../components/PageHeader';
|
||||
import { ContactForm } from '../components/ContactForm';
|
||||
|
||||
export const Contact = () => {
|
||||
return (
|
||||
<>
|
||||
<PageHeader
|
||||
title="Forge Connection"
|
||||
subtitle="Have a project in mind? Send a message to the forge and let's craft something extraordinary together"
|
||||
/>
|
||||
|
||||
<section className="py-5">
|
||||
<div className="container">
|
||||
<div className="row g-4">
|
||||
{/* Contact Form Column */}
|
||||
<div className="col-lg-7">
|
||||
<ContactForm />
|
||||
</div>
|
||||
|
||||
{/* Contact Info Column */}
|
||||
<div className="col-lg-5">
|
||||
{/* Email Info */}
|
||||
<div className="card mb-4">
|
||||
<div className="card-body p-4">
|
||||
<div className="contact-card">
|
||||
<div className="contact-icon">
|
||||
<i className="fas fa-envelope"></i>
|
||||
</div>
|
||||
<h3 className="contact-title">Email</h3>
|
||||
<div className="contact-content">
|
||||
<p>For project inquiries or questions:</p>
|
||||
<p>
|
||||
<a href="mailto:forge@seccodesmtih.com" className="contact-link">
|
||||
forge@seccodesmtih.com
|
||||
</a>
|
||||
</p>
|
||||
<p>For business collaborations:</p>
|
||||
<p>
|
||||
<a href="mailto:business@seccodesmtih.com" className="contact-link">
|
||||
business@seccodesmtih.com
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Connect Through Socials */}
|
||||
<div className="card">
|
||||
<div className="card-body p-4">
|
||||
<div className="contact-card">
|
||||
<div className="contact-icon">
|
||||
<i className="fas fa-link"></i>
|
||||
</div>
|
||||
<h3 className="contact-title">Connect Through the Ether</h3>
|
||||
<div className="contact-content">
|
||||
<p>You can also reach me through these digital pathways:</p>
|
||||
<div className="social-grid">
|
||||
<a href="#" className="social-item">
|
||||
<i className="fab fa-github social-icon-lg"></i>
|
||||
<span className="social-name">GitHub</span>
|
||||
</a>
|
||||
<a href="#" className="social-item">
|
||||
<i className="fab fa-linkedin social-icon-lg"></i>
|
||||
<span className="social-name">LinkedIn</span>
|
||||
</a>
|
||||
<a href="#" className="social-item">
|
||||
<i className="fab fa-twitter social-icon-lg"></i>
|
||||
<span className="social-name">Twitter</span>
|
||||
</a>
|
||||
<a href="#" className="social-item">
|
||||
<i className="fab fa-discord social-icon-lg"></i>
|
||||
<span className="social-name">Discord</span>
|
||||
</a>
|
||||
<a href="#" className="social-item">
|
||||
<i className="fab fa-telegram social-icon-lg"></i>
|
||||
<span className="social-name">Telegram</span>
|
||||
</a>
|
||||
<a href="#" className="social-item">
|
||||
<i className="fab fa-stack-overflow social-icon-lg"></i>
|
||||
<span className="social-name">Stack Overflow</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* FAQ Section */}
|
||||
<section className="py-5 bg-dark">
|
||||
<div className="container">
|
||||
<div className="row mb-4">
|
||||
<div className="col-12 text-center">
|
||||
<h2 className="mb-3">Frequently Asked Questions</h2>
|
||||
<p className="text-muted">Common inquiries about forging digital solutions</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="row">
|
||||
<div className="col-lg-8 mx-auto">
|
||||
<div className="accordion" id="faqAccordion">
|
||||
{/* FAQ Item 1 */}
|
||||
<div className="accordion-item">
|
||||
<h2 className="accordion-header" id="headingOne">
|
||||
<button
|
||||
className="accordion-button collapsed"
|
||||
type="button"
|
||||
data-bs-toggle="collapse"
|
||||
data-bs-target="#collapseOne"
|
||||
aria-expanded="false"
|
||||
aria-controls="collapseOne"
|
||||
>
|
||||
What is your typical project timeline?
|
||||
</button>
|
||||
</h2>
|
||||
<div
|
||||
id="collapseOne"
|
||||
className="accordion-collapse collapse"
|
||||
aria-labelledby="headingOne"
|
||||
data-bs-parent="#faqAccordion"
|
||||
>
|
||||
<div className="accordion-body">
|
||||
Project timelines vary based on complexity and scope. Small projects typically take 2-4 weeks, medium-sized projects 1-2 months, and larger enterprise solutions 3-6 months. I provide detailed timeline estimates during the initial consultation, and maintain transparent communication about progress throughout the development cycle.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* FAQ Item 2 */}
|
||||
<div className="accordion-item">
|
||||
<h2 className="accordion-header" id="headingTwo">
|
||||
<button
|
||||
className="accordion-button collapsed"
|
||||
type="button"
|
||||
data-bs-toggle="collapse"
|
||||
data-bs-target="#collapseTwo"
|
||||
aria-expanded="false"
|
||||
aria-controls="collapseTwo"
|
||||
>
|
||||
How does your collaboration process work?
|
||||
</button>
|
||||
</h2>
|
||||
<div
|
||||
id="collapseTwo"
|
||||
className="accordion-collapse collapse"
|
||||
aria-labelledby="headingTwo"
|
||||
data-bs-parent="#faqAccordion"
|
||||
>
|
||||
<div className="accordion-body">
|
||||
My collaboration process consists of five phases: initial consultation, requirements gathering, design and planning, implementation, and testing/deployment. I use agile methodologies with regular check-ins to ensure your vision is being realized. Communication is maintained through your preferred channels (email, Slack, etc.), and you'll have access to development milestones through shared repositories and project management tools.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* FAQ Item 3 */}
|
||||
<div className="accordion-item">
|
||||
<h2 className="accordion-header" id="headingThree">
|
||||
<button
|
||||
className="accordion-button collapsed"
|
||||
type="button"
|
||||
data-bs-toggle="collapse"
|
||||
data-bs-target="#collapseThree"
|
||||
aria-expanded="false"
|
||||
aria-controls="collapseThree"
|
||||
>
|
||||
What are your payment terms and methods?
|
||||
</button>
|
||||
</h2>
|
||||
<div
|
||||
id="collapseThree"
|
||||
className="accordion-collapse collapse"
|
||||
aria-labelledby="headingThree"
|
||||
data-bs-parent="#faqAccordion"
|
||||
>
|
||||
<div className="accordion-body">
|
||||
I typically work with a milestone-based payment structure: 30% upfront to begin work, 30% at the midpoint after key deliverables are approved, and 40% upon project completion. For longer projects, we can establish additional milestones. I accept bank transfers, PayPal, and cryptocurrency payments. All payment terms are clearly outlined in the project contract before work begins.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Map Section */}
|
||||
<section className="py-5">
|
||||
<div className="container">
|
||||
<div className="row mb-4">
|
||||
<div className="col-12 text-center">
|
||||
<h2 className="mb-3">The Forge Location</h2>
|
||||
<p className="text-muted mb-4">Where digital creations are hammered into existence</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="row">
|
||||
<div className="col-12">
|
||||
<div className="map-container">
|
||||
<iframe
|
||||
className="map-frame"
|
||||
src="https://www.google.com/maps/embed?pb=!1m18!1m12!1m3!1d82077.2903513271!2d21.9433946!3d50.0411861!2m3!1f0!2f0!3f0!3m2!1i1024!2i768!4f13.1!3m3!1m2!1s0x473cfae3cc14d449%3A0xd2240d31b33eb2ed!2zUnplc3rDs3c!5e0!3m2!1sen!2spl!4v1651813309336!5m2!1sen!2spl"
|
||||
allowFullScreen
|
||||
loading="lazy"
|
||||
referrerPolicy="no-referrer-when-downgrade"
|
||||
></iframe>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,211 @@
|
||||
import { useEffect } from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
export const Home = () => {
|
||||
// Create binary background and particles effects
|
||||
useEffect(() => {
|
||||
// Create binary background
|
||||
const createBinaryBg = () => {
|
||||
const binaryBg = document.getElementById('binary-bg');
|
||||
if (!binaryBg) return;
|
||||
|
||||
const chars = '01';
|
||||
const linesCount = 20;
|
||||
|
||||
// Clear existing lines
|
||||
binaryBg.innerHTML = '';
|
||||
|
||||
for (let i = 0; i < linesCount; i++) {
|
||||
const line = document.createElement('div');
|
||||
line.className = 'binary-line';
|
||||
line.style.left = `${Math.random() * 100}%`;
|
||||
line.style.animationDuration = `${Math.random() * 10 + 15}s`;
|
||||
|
||||
let binaryString = '';
|
||||
for (let j = 0; j < 100; j++) {
|
||||
binaryString += chars.charAt(Math.floor(Math.random() * chars.length));
|
||||
}
|
||||
|
||||
line.textContent = binaryString;
|
||||
binaryBg.appendChild(line);
|
||||
}
|
||||
};
|
||||
|
||||
// Create particles effect (ember/spark effect)
|
||||
const createParticles = () => {
|
||||
const particlesContainer = document.getElementById('particles');
|
||||
if (!particlesContainer) return;
|
||||
|
||||
// Clear existing particles
|
||||
particlesContainer.innerHTML = '';
|
||||
|
||||
const particlesCount = 50;
|
||||
|
||||
for (let i = 0; i < particlesCount; i++) {
|
||||
setTimeout(() => {
|
||||
const particle = document.createElement('div');
|
||||
particle.className = 'particle';
|
||||
|
||||
// Random position at the bottom of the hero section
|
||||
const posX = Math.random() * window.innerWidth;
|
||||
const posY = window.innerHeight - 100 + (Math.random() * 50);
|
||||
|
||||
particle.style.left = `${posX}px`;
|
||||
particle.style.top = `${posY}px`;
|
||||
|
||||
// Random size
|
||||
const size = Math.random() * 3 + 1;
|
||||
particle.style.width = `${size}px`;
|
||||
particle.style.height = `${size}px`;
|
||||
|
||||
// Random drift
|
||||
const xDrift = (Math.random() - 0.5) * 100;
|
||||
particle.style.setProperty('--x', `${xDrift}px`);
|
||||
|
||||
// Random animation duration
|
||||
const duration = Math.random() * 3 + 2;
|
||||
particle.style.animationDuration = `${duration}s`;
|
||||
|
||||
particlesContainer.appendChild(particle);
|
||||
|
||||
// Remove particle after animation completes
|
||||
setTimeout(() => {
|
||||
particle.remove();
|
||||
}, duration * 1000);
|
||||
}, i * 100);
|
||||
}
|
||||
};
|
||||
|
||||
createBinaryBg();
|
||||
createParticles();
|
||||
|
||||
// Create new particles periodically
|
||||
const particleInterval = setInterval(() => {
|
||||
const particlesContainer = document.getElementById('particles');
|
||||
if (particlesContainer && particlesContainer.childElementCount < 30) {
|
||||
createParticles();
|
||||
}
|
||||
}, 5000);
|
||||
|
||||
// Cleanup
|
||||
return () => {
|
||||
clearInterval(particleInterval);
|
||||
};
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<>
|
||||
{/* Hero Section */}
|
||||
<section className="hero" id="home">
|
||||
<div id="binary-bg" className="binary-bg"></div>
|
||||
<div className="container text-center">
|
||||
<div className="row justify-content-center">
|
||||
<div className="col-lg-10">
|
||||
<h1 className="title-main">SecCodeSmith</h1>
|
||||
<div className="blinking-cursor"></div>
|
||||
<p className="subtitle mt-3">Forging Digital Solutions in the Fires of Innovation</p>
|
||||
<Link to="/projects" className="btn btn-primary mt-4">
|
||||
<i className="fas fa-fire-alt me-2"></i> View My Forge
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="particles" className="particles"></div>
|
||||
</section>
|
||||
|
||||
{/* Skills Section */}
|
||||
<section id="skills" className="skills-section">
|
||||
<div className="container">
|
||||
<div className="row justify-content-center mb-5">
|
||||
<div className="col-lg-6 text-center">
|
||||
<h2 className="section-title">Technical Arsenal</h2>
|
||||
</div>
|
||||
</div>
|
||||
<div className="row g-4">
|
||||
{/* Languages Card */}
|
||||
<div className="col-md-6 col-lg-4">
|
||||
<div className="card h-100">
|
||||
<div className="card-header">
|
||||
<i className="fas fa-code category-icon"></i>
|
||||
<h3 className="category-title">Languages</h3>
|
||||
</div>
|
||||
<div className="card-body">
|
||||
<ul className="list-unstyled">
|
||||
<li className="skill-item">
|
||||
<span className="skill-icon"><i className="devicon-cplusplus-plain colored"></i></span>
|
||||
<span className="skill-name">C/C++</span>
|
||||
</li>
|
||||
<li className="skill-item">
|
||||
<span className="skill-icon"><i className="devicon-python-plain colored"></i></span>
|
||||
<span className="skill-name">Python</span>
|
||||
</li>
|
||||
<li className="skill-item">
|
||||
<span className="skill-icon"><i className="devicon-csharp-plain colored"></i></span>
|
||||
<span className="skill-name">.NET</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Embedded Systems Card */}
|
||||
<div className="col-md-6 col-lg-4">
|
||||
<div className="card h-100">
|
||||
<div className="card-header">
|
||||
<i className="fas fa-microchip category-icon"></i>
|
||||
<h3 className="category-title">Embedded Systems</h3>
|
||||
</div>
|
||||
<div className="card-body">
|
||||
<ul className="list-unstyled">
|
||||
<li className="skill-item">
|
||||
<span className="skill-icon"><i className="fas fa-microchip"></i></span>
|
||||
<span className="skill-name">STM32 (I2C, SPI, UART)</span>
|
||||
</li>
|
||||
<li className="skill-item">
|
||||
<span className="skill-icon"><i className="fas fa-wifi"></i></span>
|
||||
<span className="skill-name">ESP32</span>
|
||||
</li>
|
||||
<li className="skill-item">
|
||||
<span className="skill-icon"><i className="fas fa-memory"></i></span>
|
||||
<span className="skill-name">Pico-SDK</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Data & ML Card */}
|
||||
<div className="col-md-6 col-lg-4">
|
||||
<div className="card h-100">
|
||||
<div className="card-header">
|
||||
<i className="fas fa-brain category-icon"></i>
|
||||
<h3 className="category-title">Data & ML</h3>
|
||||
</div>
|
||||
<div className="card-body">
|
||||
<ul className="list-unstyled">
|
||||
<li className="skill-item">
|
||||
<span className="skill-icon"><i className="devicon-pandas-original colored"></i></span>
|
||||
<span className="skill-name">Pandas</span>
|
||||
</li>
|
||||
<li className="skill-item">
|
||||
<span className="skill-icon"><i className="fas fa-cogs"></i></span>
|
||||
<span className="skill-name">Scikit-learn</span>
|
||||
</li>
|
||||
<li className="skill-item">
|
||||
<span className="skill-icon"><i className="fas fa-image"></i></span>
|
||||
<span className="skill-name">Scikit-image</span>
|
||||
</li>
|
||||
<li className="skill-item">
|
||||
<span className="skill-icon"><i className="devicon-pytorch-original colored"></i></span>
|
||||
<span className="skill-name">PyTorch</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,92 @@
|
||||
import { useEffect } from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
export const NotFound = () => {
|
||||
useEffect(() => {
|
||||
// Create ember particles effect
|
||||
const createEmberParticles = () => {
|
||||
const particlesContainer = document.getElementById('ember-particles');
|
||||
if (!particlesContainer) return;
|
||||
|
||||
// Clear existing particles
|
||||
particlesContainer.innerHTML = '';
|
||||
|
||||
const emberCount = 50;
|
||||
|
||||
for (let i = 0; i < emberCount; i++) {
|
||||
const ember = document.createElement('div');
|
||||
ember.className = 'ember-particle';
|
||||
|
||||
// Random position
|
||||
const startPosX = Math.random() * window.innerWidth;
|
||||
|
||||
// Random x-axis drift
|
||||
const xDrift = (Math.random() - 0.5) * 200;
|
||||
ember.style.setProperty('--x', `${xDrift}px`);
|
||||
|
||||
ember.style.left = `${startPosX}px`;
|
||||
ember.style.bottom = '0';
|
||||
|
||||
// Random size
|
||||
const size = Math.random() * 3 + 1;
|
||||
ember.style.width = `${size}px`;
|
||||
ember.style.height = `${size}px`;
|
||||
|
||||
// Random animation duration
|
||||
const duration = Math.random() * 7 + 3;
|
||||
ember.style.animationDuration = `${duration}s`;
|
||||
|
||||
// Random animation delay
|
||||
const delay = Math.random() * 5;
|
||||
ember.style.animationDelay = `${delay}s`;
|
||||
|
||||
particlesContainer.appendChild(ember);
|
||||
|
||||
// Remove and recreate ember after animation
|
||||
setTimeout(() => {
|
||||
ember.remove();
|
||||
createEmberParticles();
|
||||
}, (duration + delay) * 1000);
|
||||
}
|
||||
};
|
||||
|
||||
createEmberParticles();
|
||||
|
||||
// Cleanup
|
||||
return () => {
|
||||
const particlesContainer = document.getElementById('ember-particles');
|
||||
if (particlesContainer) {
|
||||
particlesContainer.innerHTML = '';
|
||||
}
|
||||
};
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className="binary-bg">
|
||||
<div className="binary-line" style={{ top: '20%', left: '10%' }}>01000101 01110010 01110010 01101111 01110010</div>
|
||||
<div className="binary-line" style={{ bottom: '30%', right: '15%' }}>01001110 01101111 01110100 01000110 01101111 01110101 01101110 01100100</div>
|
||||
<div className="binary-line" style={{ top: '50%', left: '80%' }}>01000110 01100001 01101001 01101100 01100101 01100100</div>
|
||||
<div className="binary-line" style={{ bottom: '10%', left: '40%' }}>01000011 01101111 01100100 01100101 01000100 01100001 01101101 01100001 01100111 01100101 01100100</div>
|
||||
|
||||
<div className="ember-particles" id="ember-particles"></div>
|
||||
|
||||
<div className="container text-center error-container">
|
||||
<div className="broken-sword">
|
||||
<i className="fas fa-khanda"></i>
|
||||
</div>
|
||||
|
||||
<div className="error-code-container">
|
||||
<h1 className="error-code">404</h1>
|
||||
</div>
|
||||
|
||||
<h2 className="error-title">The Artifact Was Not Found</h2>
|
||||
|
||||
<p className="error-text">The digital creation you seek has either been consumed by the void, moved to a different realm, or was never forged in the first place. Check your path and try again, or return to the main forge.</p>
|
||||
|
||||
<Link to="/" className="home-button">
|
||||
<i className="fas fa-home"></i> Return to the Forge
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,120 @@
|
||||
import { useState } from 'react';
|
||||
import { PageHeader } from '../components/PageHeader';
|
||||
import { ProjectCard, type ProjectProps } from '../components/ProjectCard';
|
||||
import { ProjectModal } from '../components/ProjectModal';
|
||||
import { projectsData } from '../data/projectsData';
|
||||
|
||||
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 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"
|
||||
/>
|
||||
|
||||
<div className="container mt-5">
|
||||
{/* Projects Filter */}
|
||||
<div className="filter-container text-center">
|
||||
<button
|
||||
className={`filter-button ${activeFilter === 'all' ? '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>
|
||||
</div>
|
||||
|
||||
{/* Projects Grid */}
|
||||
<div className="row">
|
||||
{/* Featured Project */}
|
||||
{featuredProject && (activeFilter === 'all' || featuredProject.category.includes(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>
|
||||
))}
|
||||
</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">
|
||||
<i className="fab fa-github me-2"></i> View All Projects on GitHub
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Project Modal */}
|
||||
{selectedProject && (
|
||||
<ProjectModal project={selectedProject} onClose={handleCloseDetails} />
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
Vendored
+1
@@ -0,0 +1 @@
|
||||
/// <reference types="vite/client" />
|
||||
@@ -0,0 +1,66 @@
|
||||
import { describe, it, expect, beforeEach } from 'vitest';
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import { BrowserRouter } from 'react-router-dom';
|
||||
import { Header } from '../src/components/Header';
|
||||
import { Footer } from '../src/components/Footer';
|
||||
import { PageHeader } from '../src/components/PageHeader';
|
||||
|
||||
// Setup wrapper for components that use router
|
||||
const renderWithRouter = (ui: React.ReactElement) => {
|
||||
return render(ui, { wrapper: BrowserRouter });
|
||||
};
|
||||
|
||||
describe('Header Component', () => {
|
||||
beforeEach(() => {
|
||||
renderWithRouter(<Header />);
|
||||
});
|
||||
|
||||
it('renders the logo text', () => {
|
||||
expect(screen.getByText('SecCodeSmith')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders navigation links', () => {
|
||||
expect(screen.getByText('Home')).toBeInTheDocument();
|
||||
expect(screen.getByText('Blog')).toBeInTheDocument();
|
||||
expect(screen.getByText('About')).toBeInTheDocument();
|
||||
expect(screen.getByText('Projects')).toBeInTheDocument();
|
||||
expect(screen.getByText('Contact')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Footer Component', () => {
|
||||
beforeEach(() => {
|
||||
render(<Footer />);
|
||||
});
|
||||
|
||||
it('renders social links', () => {
|
||||
const socialLinks = document.querySelectorAll('.social-icon');
|
||||
expect(socialLinks.length).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
it('renders copyright text with current year', () => {
|
||||
const currentYear = new Date().getFullYear();
|
||||
expect(screen.getByText(`© ${currentYear} SecCodeSmith. All rights forged in digital fire.`)).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
describe('PageHeader Component', () => {
|
||||
it('renders with title and subtitle', () => {
|
||||
render(
|
||||
<PageHeader
|
||||
title="Test Title"
|
||||
subtitle="Test Subtitle"
|
||||
/>
|
||||
);
|
||||
|
||||
expect(screen.getByText('Test Title')).toBeInTheDocument();
|
||||
expect(screen.getByText('Test Subtitle')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders with only title when no subtitle is provided', () => {
|
||||
render(<PageHeader title="Only Title" />);
|
||||
|
||||
expect(screen.getByText('Only Title')).toBeInTheDocument();
|
||||
expect(screen.queryByText('Test Subtitle')).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1 @@
|
||||
import '@testing-library/jest-dom';
|
||||
@@ -0,0 +1,27 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
|
||||
"target": "ES2020",
|
||||
"useDefineForClassFields": true,
|
||||
"lib": ["ES2020", "DOM", "DOM.Iterable"],
|
||||
"module": "ESNext",
|
||||
"skipLibCheck": true,
|
||||
|
||||
/* Bundler mode */
|
||||
"moduleResolution": "bundler",
|
||||
"allowImportingTsExtensions": true,
|
||||
"verbatimModuleSyntax": true,
|
||||
"moduleDetection": "force",
|
||||
"noEmit": true,
|
||||
"jsx": "react-jsx",
|
||||
|
||||
/* Linting */
|
||||
"strict": true,
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"erasableSyntaxOnly": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"noUncheckedSideEffectImports": true
|
||||
},
|
||||
"include": ["src"]
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"files": [],
|
||||
"references": [
|
||||
{ "path": "./tsconfig.app.json" },
|
||||
{ "path": "./tsconfig.node.json" }
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
|
||||
"target": "ES2022",
|
||||
"lib": ["ES2023"],
|
||||
"module": "ESNext",
|
||||
"skipLibCheck": true,
|
||||
|
||||
/* Bundler mode */
|
||||
"moduleResolution": "bundler",
|
||||
"allowImportingTsExtensions": true,
|
||||
"verbatimModuleSyntax": true,
|
||||
"moduleDetection": "force",
|
||||
"noEmit": true,
|
||||
|
||||
/* Linting */
|
||||
"strict": true,
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"erasableSyntaxOnly": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"noUncheckedSideEffectImports": true
|
||||
},
|
||||
"include": ["vite.config.ts"]
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
import { defineConfig } from 'vite'
|
||||
import react from '@vitejs/plugin-react'
|
||||
|
||||
// https://vite.dev/config/
|
||||
export default defineConfig({
|
||||
plugins: [react()],
|
||||
})
|
||||
Reference in New Issue
Block a user