First commit
This commit is contained in:
@@ -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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user