updated route pathing, skills.tsx for skill card abstraction and header for mobile

This commit is contained in:
2026-01-12 19:16:48 -06:00
parent 54831798d0
commit 127aa9acc7
4 changed files with 185 additions and 50 deletions

View File

@@ -0,0 +1,82 @@
import { motion } from "framer-motion";
export interface SkillDetail {
name: string;
description: string;
}
export interface SkillCategory {
category: string;
skills: SkillDetail[];
}
interface SkillCardProps {
category: SkillCategory;
isExpanded: boolean;
onToggle: () => void;
}
export default function SkillCard({ category, isExpanded, onToggle }: SkillCardProps) {
return (
<motion.div
layout
onClick={onToggle}
style={{
background: isExpanded ? "rgba(255, 255, 255, 0.12)" : "rgba(255, 255, 255, 0.07)",
backdropFilter: "blur(10px)",
border: isExpanded ? "1px solid rgba(255, 255, 255, 0.3)" : "1px solid rgba(255, 255, 255, 0.15)",
borderRadius: "12px",
padding: "24px",
cursor: "pointer",
gridColumn: isExpanded ? "1 / -1" : "auto",
zIndex: isExpanded ? 10 : 1
}}
whileHover={!isExpanded ? { scale: 1.02, backgroundColor: "rgba(255, 255, 255, 0.12)" } : {}}
transition={{ duration: 0.3, type: "spring" }}
>
<motion.h3 layout="position" style={{ color: "white", marginTop: 0, marginBottom: "15px", fontSize: "1.2rem" }}>
{category.category} {isExpanded ? <span style={{ fontSize: "0.8em", opacity: 0.7 }}>(Click to collapse)</span> : null}
</motion.h3>
<motion.div layout="position" style={{ display: "flex", flexWrap: "wrap", gap: "10px", marginBottom: isExpanded ? "20px" : "0" }}>
{category.skills.map((skill) => (
<span
key={skill.name}
style={{
background: "rgba(255, 255, 255, 0.15)",
color: "rgba(255, 255, 255, 0.9)",
padding: "6px 12px",
borderRadius: "20px",
fontSize: "0.85rem",
fontWeight: 500,
border: "1px solid rgba(255, 255, 255, 0.1)"
}}
>
{skill.name}
</span>
))}
</motion.div>
{isExpanded && (
<motion.div
initial={{ opacity: 0, y: 10 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: 10 }}
transition={{ duration: 0.3 }}
style={{ borderTop: "1px solid rgba(255,255,255,0.2)", paddingTop: "20px" }}
>
<div style={{ display: "grid", gridTemplateColumns: "repeat(auto-fit, minmax(280px, 1fr))", gap: "15px" }}>
{category.skills.map((skill) => (
<div key={skill.name} style={{ display: "flex", flexDirection: "column", gap: "5px" }}>
<strong style={{ color: "#fff", fontSize: "1rem" }}>{skill.name}</strong>
<p style={{ margin: 0, color: "rgba(255,255,255,0.7)", fontSize: "0.9rem" }}>
{skill.description}
</p>
</div>
))}
</div>
</motion.div>
)}
</motion.div>
);
}

View File

@@ -16,9 +16,23 @@ export default function FloatingHeader() {
setIsMenuOpen(false);
}, [location]);
const ROUTE_TITLES: { [key: string]: string } = {
"/": "Sasha Bayda",
"/skills": "Skills",
"/work-experience": "Work Experience",
"/projects": "Projects",
"/about": "About Me"
};
const currentTitle = ROUTE_TITLES[location.pathname] || "Sasha Bayda";
return (
<header className="floating-header">
<div className="header-content">
<div className="mobile-page-title">
{currentTitle}
</div>
<button
className={`mobile-toggle ${isMenuOpen ? "open" : ""}`}
onClick={toggleMenu}

View File

@@ -1,9 +1,6 @@
import { motion, Variants } from "framer-motion";
interface SkillCategory {
category: string;
skills: string[];
}
import { motion, Variants, AnimatePresence } from "framer-motion";
import { useState } from "react";
import SkillCard, { SkillCategory } from "../components/SkillCard";
interface ProfessionalSkill {
skill: string;
@@ -13,27 +10,69 @@ interface ProfessionalSkill {
const TECHNICAL_SKILLS: SkillCategory[] = [
{
category: "Languages",
skills: ["JavaScript (ES6+)", "TypeScript", "Python", "Java", "C", "C++", "Lua", "HTML", "CSS", "SQL"]
skills: [
{ name: "JavaScript (ES6+)", description: "Core language for web interactivity, closures, and async programming." },
{ name: "TypeScript", description: "Strict syntactical superset of JavaScript adding static typing." },
{ name: "Python", description: "Versatile language used for backend scripting and data processing." },
{ name: "Java", description: "Object-oriented language for robust enterprise backends." },
{ name: "C", description: "Low-level system programming and memory management." },
{ name: "C++", description: "High-performance application development with OOP features." },
{ name: "Lua", description: "Lightweight scripting often used in embedded systems and game dev." },
{ name: "HTML", description: "Standard markup language for document structure." },
{ name: "CSS", description: "Style sheet language for presentation and layout." },
{ name: "SQL", description: "Standard language for relational database management." }
]
},
{
category: "Frameworks/Libraries",
skills: ["React", "Express.js", "Django REST Framework", "AWS Lambda"]
skills: [
{ name: "React", description: "Library for building component-based user interfaces." },
{ name: "Express.js", description: "Minimalist web framework for Node.js servers." },
{ name: "Django REST Framework", description: "Toolkit for building Web APIs with Python/Django." },
{ name: "AWS Lambda", description: "Serverless compute service for running code without provisioning." }
]
},
{
category: "Databases",
skills: ["PostgreSQL (SQL Based)", "CouchDB (No-SQL Based)"]
skills: [
{ name: "PostgreSQL", description: "Advanced open-source relational database (SQL)." },
{ name: "CouchDB", description: "Seamless multi-master sync capability database (No-SQL)." }
]
},
{
category: "DevOps & Tools",
skills: ["Docker", "CircleCI", "Git (CLI)", "Postman", "AWS CDK", "Linux CLI"]
skills: [
{ name: "Docker", description: "Platform for developing, shipping, and running applications in containers." },
{ name: "CircleCI", description: "Continuous integration and delivery platform." },
{ name: "Git (CLI)", description: "Distributed version control system." },
{ name: "Postman", description: "API platform for building and using APIs." },
{ name: "AWS CDK", description: "Software development framework for defining cloud infrastructure in code." },
{ name: "Linux CLI", description: "Command line interface for system interaction and scripting." }
]
},
{
category: "AWS Technologies",
skills: ["S3", "Cloudfront", "Route 53", "API Gateway", "Lambda", "RDS", "VPC", "ECS", "SQS"]
skills: [
{ name: "S3", description: "Scalable object storage service." },
{ name: "Cloudfront", description: "Content Delivery Network (CDN) service." },
{ name: "Route 53", description: "Scalable Domain Name System (DNS) web service." },
{ name: "API Gateway", description: "Service to create, publish, maintain, monitor, and secure APIs." },
{ name: "Lambda", description: "Serverless compute service." },
{ name: "RDS", description: "Managed relational database service." },
{ name: "VPC", description: "Logically isolated section of the AWS Cloud." },
{ name: "ECS", description: "Fully managed container orchestration service." },
{ name: "SQS", description: "Message queuing service for decoupling microservices." }
]
},
{
category: "Concepts",
skills: ["RESTful APIs", "Agile/Scrum", "MVC Architecture", "Cloud Deployment", "Responsive Design"]
skills: [
{ name: "RESTful APIs", description: "Architectural style for network-based software." },
{ name: "Agile/Scrum", description: "Iterative approach to project software delivery." },
{ name: "MVC Architecture", description: "Design pattern separating Model, View, and Controller." },
{ name: "Cloud Deployment", description: "Process of deploying applications to cloud infrastructure." },
{ name: "Responsive Design", description: "Design approach for rendering well on variety of devices." }
]
}
];
@@ -85,6 +124,8 @@ const itemVariants: Variants = {
};
export default function Skills() {
const [expandedCategory, setExpandedCategory] = useState<string | null>(null);
return (
<div className="mainContentBlock" style={{ minWidth: "66vw", display: "flex", justifyContent: "center" }}>
<div style={{ height: "30px" }}></div>
@@ -99,40 +140,22 @@ export default function Skills() {
<h2 style={{ color: "white", borderBottom: "1px solid rgba(255,255,255,0.2)", paddingBottom: "10px", marginBottom: "25px" }}>
Technical Skills
</h2>
<div style={{ display: "grid", gridTemplateColumns: "repeat(auto-fit, minmax(320px, 1fr))", gap: "30px" }}>
<div className="tech-skills-grid" style={{
display: "grid",
gridTemplateColumns: "repeat(auto-fit, minmax(320px, 1fr))",
gap: "30px",
position: "relative" // For layout animations
}}>
<AnimatePresence>
{TECHNICAL_SKILLS.map((category) => (
<motion.div
<SkillCard
key={category.category}
style={{
background: "rgba(255, 255, 255, 0.07)",
backdropFilter: "blur(10px)",
border: "1px solid rgba(255, 255, 255, 0.15)",
borderRadius: "12px",
padding: "24px",
}}
whileHover={{ scale: 1.02, backgroundColor: "rgba(255, 255, 255, 0.12)" }}
>
<h3 style={{ color: "white", marginTop: 0, marginBottom: "15px", fontSize: "1.2rem" }}>{category.category}</h3>
<div style={{ display: "flex", flexWrap: "wrap", gap: "10px" }}>
{category.skills.map((skill) => (
<span
key={skill}
style={{
background: "rgba(255, 255, 255, 0.15)",
color: "rgba(255, 255, 255, 0.9)",
padding: "6px 12px",
borderRadius: "20px",
fontSize: "0.85rem",
fontWeight: 500,
border: "1px solid rgba(255, 255, 255, 0.1)"
}}
>
{skill}
</span>
))}
</div>
</motion.div>
category={category}
isExpanded={expandedCategory === category.category}
onToggle={() => setExpandedCategory(expandedCategory === category.category ? null : category.category)}
/>
))}
</AnimatePresence>
</div>
</motion.div>
@@ -164,7 +187,7 @@ export default function Skills() {
))}
</div>
</motion.div>
</motion.div>
</div>
</motion.div >
</div >
);
}

View File

@@ -73,6 +73,11 @@
margin: 0 auto;
}
.mobile-page-title {
display: none;
/* Hidden on desktop */
}
.mobile-toggle {
display: none;
flex-direction: column;
@@ -100,9 +105,20 @@
text-align: left;
}
.mobile-page-title {
display: block;
color: white;
font-size: 1.2rem;
font-weight: 700;
letter-spacing: 0.5px;
z-index: 1001;
/* Ensure visible above nav overlay if needed, though nav usually covers content */
position: relative;
}
.header-content {
justify-content: flex-end;
/* Align hamburger to right */
justify-content: space-between;
/* Push title left, hamburger right */
}
.mobile-toggle {