feat: Implement Skills page, ProjectCard and BioSection components, and update navigation and About page content.
This commit is contained in:
BIN
public/beszel.png
Normal file
BIN
public/beszel.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 116 KiB |
BIN
public/fampic.jpg
Normal file
BIN
public/fampic.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 78 KiB |
BIN
public/gangpic.jpg
Normal file
BIN
public/gangpic.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 405 KiB |
BIN
public/janepic.jpg
Normal file
BIN
public/janepic.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 6.4 MiB |
23
src/App.css
23
src/App.css
@@ -357,26 +357,31 @@ p {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.project-card {
|
.project-card {
|
||||||
background: rgba(255, 255, 255, 0.07);
|
background: rgba(255, 255, 255, 0.08);
|
||||||
backdrop-filter: blur(12px);
|
/* Slightly lighter base */
|
||||||
border: 1px solid rgba(255, 255, 255, 0.15);
|
backdrop-filter: blur(16px);
|
||||||
border-radius: 16px;
|
/* Increased blur */
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.2);
|
||||||
|
/* Softer border */
|
||||||
|
border-radius: 24px;
|
||||||
|
/* More rounded */
|
||||||
padding: 25px;
|
padding: 25px;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
transition: all 0.4s cubic-bezier(0.175, 0.885, 0.32, 1.275);
|
transition: all 0.4s cubic-bezier(0.175, 0.885, 0.32, 1.275);
|
||||||
/* Bouncy feel */
|
|
||||||
height: 100%;
|
height: 100%;
|
||||||
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
|
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.1);
|
||||||
|
/* Softer shadow */
|
||||||
position: relative;
|
position: relative;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
.project-card:hover {
|
.project-card:hover {
|
||||||
background: rgba(255, 255, 255, 0.12);
|
background: rgba(255, 255, 255, 0.15);
|
||||||
transform: translateY(-8px) scale(1.02);
|
transform: translateY(-8px) scale(1.02);
|
||||||
box-shadow: 0 15px 30px rgba(0, 0, 0, 0.3);
|
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.2);
|
||||||
border-color: rgba(255, 255, 255, 0.3);
|
/* Deeper but soft shadow */
|
||||||
|
border-color: rgba(255, 255, 255, 0.4);
|
||||||
}
|
}
|
||||||
|
|
||||||
.project-card::before {
|
.project-card::before {
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import Home from './pages/Home';
|
|||||||
import About from './pages/About';
|
import About from './pages/About';
|
||||||
import WorkExperience from './pages/WorkExperience';
|
import WorkExperience from './pages/WorkExperience';
|
||||||
import Projects from './pages/Projects';
|
import Projects from './pages/Projects';
|
||||||
|
import Skills from './pages/Skills';
|
||||||
import Contact from './pages/Contact';
|
import Contact from './pages/Contact';
|
||||||
|
|
||||||
function ScrollToTop() {
|
function ScrollToTop() {
|
||||||
@@ -39,6 +40,7 @@ function App() {
|
|||||||
<Route path="/about" element={<About />} />
|
<Route path="/about" element={<About />} />
|
||||||
<Route path="/work-experience" element={<WorkExperience />} />
|
<Route path="/work-experience" element={<WorkExperience />} />
|
||||||
<Route path="/projects" element={<Projects />} />
|
<Route path="/projects" element={<Projects />} />
|
||||||
|
<Route path="/skills" element={<Skills />} />
|
||||||
<Route path="/contact" element={<Contact />} />
|
<Route path="/contact" element={<Contact />} />
|
||||||
</Routes>
|
</Routes>
|
||||||
<div>
|
<div>
|
||||||
|
|||||||
100
src/components/BioSection.tsx
Normal file
100
src/components/BioSection.tsx
Normal file
@@ -0,0 +1,100 @@
|
|||||||
|
import { motion, AnimatePresence } from "framer-motion";
|
||||||
|
import { useState, useEffect } from "react";
|
||||||
|
|
||||||
|
interface BioSectionProps {
|
||||||
|
imageSrc: string;
|
||||||
|
imageAlt: string;
|
||||||
|
text: string;
|
||||||
|
reversed?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function BioSection({ imageSrc, imageAlt, text, reversed = false }: BioSectionProps) {
|
||||||
|
const images = imageSrc.split(',').map(src => src.trim()).filter(src => src.length > 0);
|
||||||
|
const [currentIndex, setCurrentIndex] = useState(0);
|
||||||
|
const [prevIndex, setPrevIndex] = useState(0);
|
||||||
|
const [direction, setDirection] = useState(1); // 1 for right, -1 for left (though we always slide right here)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (images.length <= 1) return;
|
||||||
|
|
||||||
|
const interval = setInterval(() => {
|
||||||
|
setPrevIndex(currentIndex);
|
||||||
|
setCurrentIndex((prev) => (prev + 1) % images.length);
|
||||||
|
}, 6000);
|
||||||
|
|
||||||
|
return () => clearInterval(interval);
|
||||||
|
}, [images.length, currentIndex]);
|
||||||
|
|
||||||
|
const slideVariants = {
|
||||||
|
enter: {
|
||||||
|
x: "-100%",
|
||||||
|
opacity: 1
|
||||||
|
},
|
||||||
|
center: {
|
||||||
|
x: 0,
|
||||||
|
opacity: 1
|
||||||
|
},
|
||||||
|
exit: {
|
||||||
|
x: "100%",
|
||||||
|
opacity: 1
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className="about-content"
|
||||||
|
style={{
|
||||||
|
flexDirection: reversed ? "row-reverse" : "row",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<motion.div
|
||||||
|
className="about-image-container"
|
||||||
|
initial={{ x: reversed ? 30 : -30, opacity: 0 }}
|
||||||
|
animate={{ x: 0, opacity: 1 }}
|
||||||
|
transition={{ delay: 0.4, duration: 0.6 }}
|
||||||
|
style={{
|
||||||
|
position: "relative",
|
||||||
|
width: "250px",
|
||||||
|
height: "250px",
|
||||||
|
overflow: "hidden",
|
||||||
|
borderRadius: "20px",
|
||||||
|
border: "3px solid rgba(255, 255, 255, 0.8)",
|
||||||
|
boxShadow: "0 8px 32px rgba(0, 0, 0, 0.2)",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<AnimatePresence initial={false}>
|
||||||
|
<motion.img
|
||||||
|
key={currentIndex}
|
||||||
|
src={images[currentIndex]}
|
||||||
|
alt={imageAlt}
|
||||||
|
variants={slideVariants}
|
||||||
|
initial="enter"
|
||||||
|
animate="center"
|
||||||
|
exit="exit"
|
||||||
|
transition={{ duration: 0.8, ease: "easeInOut" }}
|
||||||
|
style={{
|
||||||
|
position: "absolute",
|
||||||
|
top: 0,
|
||||||
|
left: 0,
|
||||||
|
width: "100%",
|
||||||
|
height: "100%",
|
||||||
|
objectFit: "cover",
|
||||||
|
}}
|
||||||
|
whileHover={{ scale: 1.05 }}
|
||||||
|
/>
|
||||||
|
</AnimatePresence>
|
||||||
|
</motion.div>
|
||||||
|
|
||||||
|
<motion.div
|
||||||
|
className="about-text-container"
|
||||||
|
initial={{ x: reversed ? -30 : 30, opacity: 0 }}
|
||||||
|
animate={{ x: 0, opacity: 1 }}
|
||||||
|
transition={{ delay: 0.6, duration: 0.6 }}
|
||||||
|
>
|
||||||
|
<p className="about-text">
|
||||||
|
{text}
|
||||||
|
</p>
|
||||||
|
</motion.div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -35,31 +35,6 @@ const ParticlesBackground: React.FC = () => {
|
|||||||
this.x += this.speedX;
|
this.x += this.speedX;
|
||||||
this.y += this.speedY;
|
this.y += this.speedY;
|
||||||
|
|
||||||
// update speedX
|
|
||||||
let speedXRng = Math.random();
|
|
||||||
if (speedXRng > 0.75) {
|
|
||||||
this.speedX += Math.random() * 0.1;
|
|
||||||
} else if (speedXRng < 0.25) {
|
|
||||||
this.speedX -= Math.random() * 0.1;
|
|
||||||
}
|
|
||||||
if (this.speedX > 1) {
|
|
||||||
this.speedX = 1;
|
|
||||||
} else if (this.speedX < -1) {
|
|
||||||
this.speedX = -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// update speedY
|
|
||||||
let speedYRng = Math.random();
|
|
||||||
if (speedYRng > 0.75) {
|
|
||||||
this.speedY += Math.random() * 0.1;
|
|
||||||
} else if (speedYRng < 0.25) {
|
|
||||||
this.speedY -= Math.random() * 0.1;
|
|
||||||
}
|
|
||||||
if (this.speedY > 1) {
|
|
||||||
this.speedY = 1;
|
|
||||||
} else if (this.speedY < -1) {
|
|
||||||
this.speedY = -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
//size
|
//size
|
||||||
let sizeRng = Math.random();
|
let sizeRng = Math.random();
|
||||||
|
|||||||
62
src/components/ProjectCard.tsx
Normal file
62
src/components/ProjectCard.tsx
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
import { motion, Variants } from "framer-motion";
|
||||||
|
|
||||||
|
export interface Project {
|
||||||
|
id: number;
|
||||||
|
title: string;
|
||||||
|
description: string;
|
||||||
|
techStack: string[];
|
||||||
|
image: string;
|
||||||
|
links: {
|
||||||
|
demo?: string;
|
||||||
|
repo?: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ProjectCardProps {
|
||||||
|
project: Project;
|
||||||
|
variants: Variants;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function ProjectCard({ project, variants }: ProjectCardProps) {
|
||||||
|
return (
|
||||||
|
<motion.div
|
||||||
|
className="project-card"
|
||||||
|
variants={variants}
|
||||||
|
whileHover={{ y: -10, transition: { duration: 0.2 } }}
|
||||||
|
>
|
||||||
|
<div className="project-image-container">
|
||||||
|
<img
|
||||||
|
src={project.image}
|
||||||
|
alt={project.title}
|
||||||
|
className="project-image"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="project-header">
|
||||||
|
<h2 className="project-name">{project.title}</h2>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="project-tech-stack">
|
||||||
|
{project.techStack.map(tech => (
|
||||||
|
<span key={tech} className="tech-chip">{tech}</span>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p className="project-description">
|
||||||
|
{project.description}
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div className="project-links">
|
||||||
|
{project.links.demo && (
|
||||||
|
<a href={project.links.demo} className="project-link" target="_blank" rel="noopener noreferrer">
|
||||||
|
Live Demo <span>→</span>
|
||||||
|
</a>
|
||||||
|
)}
|
||||||
|
{project.links.repo && (
|
||||||
|
<a href={project.links.repo} className="project-link" target="_blank" rel="noopener noreferrer">
|
||||||
|
GitHub <span>↗</span>
|
||||||
|
</a>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</motion.div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -18,6 +18,12 @@ export default function FloatingHeader() {
|
|||||||
>
|
>
|
||||||
Work Experience
|
Work Experience
|
||||||
</Link>
|
</Link>
|
||||||
|
<Link
|
||||||
|
to="/skills"
|
||||||
|
className={`nav-link ${location.pathname === "/skills" ? "active" : ""}`}
|
||||||
|
>
|
||||||
|
Skills
|
||||||
|
</Link>
|
||||||
<Link
|
<Link
|
||||||
to="/about"
|
to="/about"
|
||||||
className={`nav-link ${location.pathname === "/about" ? "active" : ""}`}
|
className={`nav-link ${location.pathname === "/about" ? "active" : ""}`}
|
||||||
@@ -28,7 +34,7 @@ export default function FloatingHeader() {
|
|||||||
to="/projects"
|
to="/projects"
|
||||||
className={`nav-link ${location.pathname === "/projects" ? "active" : ""}`}
|
className={`nav-link ${location.pathname === "/projects" ? "active" : ""}`}
|
||||||
>
|
>
|
||||||
Projects
|
Projects and Sidequests
|
||||||
</Link>
|
</Link>
|
||||||
<Link
|
<Link
|
||||||
to="/contact"
|
to="/contact"
|
||||||
|
|||||||
@@ -1,8 +1,14 @@
|
|||||||
import { motion } from "framer-motion";
|
import { motion } from "framer-motion";
|
||||||
import VisitedMap from "../components/VisitedMap";
|
import VisitedMap from "../components/VisitedMap";
|
||||||
|
import BioSection from "../components/BioSection";
|
||||||
|
|
||||||
const ABOUT_TEXT = "Hi! I'm Sasha Bayda, a passionate developer focused on creating beautiful and functional web experiences. With a background in computer science and a keen eye for design, I strive to bridge the gap between technology and user-centric solutions. When I'm not coding, you can find me exploring the outdoors, experimenting with new recipes, or indulging in photography. Feel free to explore my projects and get in touch if you'd like to collaborate or learn more about my work!" + "\n\n" + "Thank you for visiting my digital resume site. I look forward to connecting with you!";
|
const ABOUT_TEXT = "Hi! I'm Sasha Bayda, a passionate developer focused on creating beautiful and functional web experiences. With a background in computer science and a keen eye for design, I strive to bridge the gap between technology and user-centric solutions. When I'm not coding, you can find me exploring the outdoors, experimenting with new recipes, or indulging in photography. Feel free to explore my projects and get in touch if you'd like to collaborate or learn more about my work!" + "\n\n" + "Thank you for visiting my digital resume site. I look forward to connecting with you!";
|
||||||
|
|
||||||
|
const JANE_TEXT = "Outside of work, I enjoy spending time with my girlfriend, family and friends. Doing things such as cooking, watching shows or movies, gaming, snowboarding, or just relaxing with a glass of something warm.";
|
||||||
|
|
||||||
|
const HOMELAB_TEXT = "I love hosting my own applications and learning new technologies. Right now I host my own gitea server to host my own git repositories and host my own 'runners', immich to host my own photo gallery, and a beszel to monitor all of my applications with a dashboards, graphs and webhook alerts."
|
||||||
|
|
||||||
|
|
||||||
const VISITED_CITIES = [
|
const VISITED_CITIES = [
|
||||||
"Melfort",
|
"Melfort",
|
||||||
"Star City",
|
"Star City",
|
||||||
@@ -41,31 +47,12 @@ export default function About() {
|
|||||||
About Me
|
About Me
|
||||||
</motion.h1>
|
</motion.h1>
|
||||||
|
|
||||||
<div className="about-content">
|
<BioSection
|
||||||
<motion.div
|
imageSrc="/dapperSasha.jpg"
|
||||||
className="about-image-container"
|
imageAlt="profile"
|
||||||
initial={{ x: -30, opacity: 0 }}
|
text={ABOUT_TEXT}
|
||||||
animate={{ x: 0, opacity: 1 }}
|
|
||||||
transition={{ delay: 0.4, duration: 0.6 }}
|
|
||||||
>
|
|
||||||
<img
|
|
||||||
src="/dapperSasha.jpg"
|
|
||||||
alt="profile"
|
|
||||||
className="about-image"
|
|
||||||
/>
|
/>
|
||||||
</motion.div>
|
|
||||||
|
|
||||||
<motion.div
|
|
||||||
className="about-text-container"
|
|
||||||
initial={{ x: 30, opacity: 0 }}
|
|
||||||
animate={{ x: 0, opacity: 1 }}
|
|
||||||
transition={{ delay: 0.6, duration: 0.6 }}
|
|
||||||
>
|
|
||||||
<p className="about-text">
|
|
||||||
{ABOUT_TEXT}
|
|
||||||
</p>
|
|
||||||
</motion.div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<motion.div
|
<motion.div
|
||||||
style={{ marginTop: "40px", width: "100%" }}
|
style={{ marginTop: "40px", width: "100%" }}
|
||||||
@@ -73,12 +60,28 @@ export default function About() {
|
|||||||
animate={{ y: 0, opacity: 1 }}
|
animate={{ y: 0, opacity: 1 }}
|
||||||
transition={{ delay: 0.8, duration: 0.6 }}
|
transition={{ delay: 0.8, duration: 0.6 }}
|
||||||
>
|
>
|
||||||
<h2 style={{ textAlign: "center", fontSize: "1.5rem", marginBottom: "10px", color: "rgba(255,255,255,0.9)" }}>My Hot Chocolate Journey</h2>
|
<h2 style={{ textAlign: "center", fontSize: "1.5rem", marginBottom: "10px", color: "rgba(255,255,255,0.9)" }}>Places I've Visited</h2>
|
||||||
<p style={{ textAlign: "center", marginBottom: "20px", color: "rgba(255,255,255,0.7)", fontSize: "0.9rem" }}>
|
<p style={{ textAlign: "center", marginBottom: "20px", color: "rgba(255,255,255,0.7)", fontSize: "0.9rem" }}>
|
||||||
Places across the world where I've enjoyed a hot chocolate ☕
|
Some of the More Interesting Places I've visited, I hope to one day fill this map with more cool spots!
|
||||||
</p>
|
</p>
|
||||||
<VisitedMap places={VISITED_CITIES} />
|
<VisitedMap places={VISITED_CITIES} />
|
||||||
</motion.div>
|
</motion.div>
|
||||||
|
<div style={{ marginTop: "40px" }}>
|
||||||
|
<BioSection
|
||||||
|
imageSrc="/janepic.jpg, fampic.jpg, gangpic.jpg"
|
||||||
|
imageAlt="profile"
|
||||||
|
text={JANE_TEXT}
|
||||||
|
reversed={true}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div style={{ marginTop: "40px" }}>
|
||||||
|
<BioSection
|
||||||
|
imageSrc="/beszel.png"
|
||||||
|
imageAlt="profile"
|
||||||
|
text={HOMELAB_TEXT}
|
||||||
|
reversed={false}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -20,7 +20,11 @@ const ANIMATION_TIMINGS = {
|
|||||||
testItemStaggerDelay: 0.2, // Delay between each test item (in seconds)
|
testItemStaggerDelay: 0.2, // Delay between each test item (in seconds)
|
||||||
testItemAnimationDuration: 0.5, // Duration of each item's slide-in animation
|
testItemAnimationDuration: 0.5, // Duration of each item's slide-in animation
|
||||||
};
|
};
|
||||||
const welcomeText = "Hello! My name is Sasha Bayda and welcome to my digital resume site! Here you will find some of my projects, skills, contact information and any information I couldn't fit into my resume. Feel free to explore and learn more about me and if something isn't answered, don't hesitate to reach out via the contact page!";
|
const welcomeText = `Hello! My name is Sasha Bayda and welcome to my digital resume site!
|
||||||
|
|
||||||
|
Here you will find some of my projects, skills, contact information and any information I couldn't fit into my resume.
|
||||||
|
|
||||||
|
Feel free to explore and learn more about me and if something isn't answered, don't hesitate to reach out via the contact page!`;
|
||||||
|
|
||||||
const CONTACT_LINKS = [
|
const CONTACT_LINKS = [
|
||||||
{ label: "Email", url: "mailto:sasha.bayda@outlook.com" },
|
{ label: "Email", url: "mailto:sasha.bayda@outlook.com" },
|
||||||
|
|||||||
@@ -1,19 +1,8 @@
|
|||||||
import { motion, Variants } from "framer-motion";
|
import { motion, Variants } from "framer-motion";
|
||||||
import FullPageImage from "../components/fullPageImage";
|
import FullPageImage from "../components/fullPageImage";
|
||||||
|
import ProjectCard, { Project } from "../components/ProjectCard";
|
||||||
|
|
||||||
interface Project {
|
const FEATURED_PROJECTS: Project[] = [
|
||||||
id: number;
|
|
||||||
title: string;
|
|
||||||
description: string;
|
|
||||||
techStack: string[];
|
|
||||||
image: string; // Ensure these images exist in public/ or use placeholders
|
|
||||||
links: {
|
|
||||||
demo?: string;
|
|
||||||
repo?: string;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const PROJECTS: Project[] = [
|
|
||||||
{
|
{
|
||||||
id: 1,
|
id: 1,
|
||||||
title: "Digital Resume",
|
title: "Digital Resume",
|
||||||
@@ -27,6 +16,29 @@ const PROJECTS: Project[] = [
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
const SIDEQUESTS: Project[] = [
|
||||||
|
{
|
||||||
|
id: 101, // Different ID range for sidequests
|
||||||
|
title: "Experimental Shader",
|
||||||
|
description: "A WebGL shader experiment creating procedural textures and animations. Exploring noise functions and light interactions.",
|
||||||
|
techStack: ["WebGL", "GLSL", "React Three Fiber"],
|
||||||
|
image: "https://placehold.co/600x400/1a1a1a/cccccc?text=Shader+Experiment", // Placeholder image
|
||||||
|
links: {
|
||||||
|
repo: "https://github.com/Bayda77/sidequests", // Placeholder link
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 102,
|
||||||
|
title: "CLI Tool",
|
||||||
|
description: "A command-line utility for automating daily workflows and file management tasks.",
|
||||||
|
techStack: ["Rust", "Clap"],
|
||||||
|
image: "https://placehold.co/600x400/2a2a2a/dddddd?text=CLI+Tool", // Placeholder
|
||||||
|
links: {
|
||||||
|
repo: "https://github.com/Bayda77/cli-tools"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
const containerVariants: Variants = {
|
const containerVariants: Variants = {
|
||||||
hidden: { opacity: 0 },
|
hidden: { opacity: 0 },
|
||||||
visible: {
|
visible: {
|
||||||
@@ -55,6 +67,12 @@ export default function Projects() {
|
|||||||
<div style={{ position: "relative", width: "100%", minHeight: "100vh" }}>
|
<div style={{ position: "relative", width: "100%", minHeight: "100vh" }}>
|
||||||
<div className="mainContentBlock" style={{ width: "100%", minWidth: "100vw", maxWidth: "100vw", paddingTop: "20px", paddingBottom: "10px", boxSizing: "border-box", display: "flex", justifyContent: "center", position: "relative", zIndex: 1 }}>
|
<div className="mainContentBlock" style={{ width: "100%", minWidth: "100vw", maxWidth: "100vw", paddingTop: "20px", paddingBottom: "10px", boxSizing: "border-box", display: "flex", justifyContent: "center", position: "relative", zIndex: 1 }}>
|
||||||
<div className="projects-container">
|
<div className="projects-container">
|
||||||
|
{/* Featured Projects Section */}
|
||||||
|
<motion.div
|
||||||
|
initial="hidden"
|
||||||
|
animate="visible"
|
||||||
|
variants={containerVariants}
|
||||||
|
>
|
||||||
<motion.h1
|
<motion.h1
|
||||||
className="projects-title"
|
className="projects-title"
|
||||||
initial={{ y: -30, opacity: 0 }}
|
initial={{ y: -30, opacity: 0 }}
|
||||||
@@ -64,54 +82,35 @@ export default function Projects() {
|
|||||||
Featured Projects
|
Featured Projects
|
||||||
</motion.h1>
|
</motion.h1>
|
||||||
|
|
||||||
<motion.div
|
<div className="projects-grid">
|
||||||
className="projects-grid"
|
{FEATURED_PROJECTS.map((project) => (
|
||||||
variants={containerVariants}
|
<ProjectCard key={project.id} project={project} variants={cardVariants} />
|
||||||
initial="hidden"
|
|
||||||
animate="visible"
|
|
||||||
>
|
|
||||||
{PROJECTS.map((project) => (
|
|
||||||
<motion.div
|
|
||||||
key={project.id}
|
|
||||||
className="project-card"
|
|
||||||
variants={cardVariants}
|
|
||||||
whileHover={{ y: -10, transition: { duration: 0.2 } }}
|
|
||||||
>
|
|
||||||
<div className="project-image-container">
|
|
||||||
<img
|
|
||||||
src={project.image}
|
|
||||||
alt={project.title}
|
|
||||||
className="project-image"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="project-header">
|
|
||||||
<h2 className="project-name">{project.title}</h2>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="project-tech-stack">
|
|
||||||
{project.techStack.map(tech => (
|
|
||||||
<span key={tech} className="tech-chip">{tech}</span>
|
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<p className="project-description">
|
|
||||||
{project.description}
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<div className="project-links">
|
|
||||||
{project.links.demo && (
|
|
||||||
<a href={project.links.demo} className="project-link" target="_blank" rel="noopener noreferrer">
|
|
||||||
Live Demo <span>→</span>
|
|
||||||
</a>
|
|
||||||
)}
|
|
||||||
{project.links.repo && (
|
|
||||||
<a href={project.links.repo} className="project-link" target="_blank" rel="noopener noreferrer">
|
|
||||||
GitHub <span>↗</span>
|
|
||||||
</a>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</motion.div>
|
</motion.div>
|
||||||
|
|
||||||
|
|
||||||
|
{/* Sidequests Section */}
|
||||||
|
<motion.div
|
||||||
|
initial="hidden"
|
||||||
|
whileInView="visible"
|
||||||
|
viewport={{ once: true, amount: 0.1 }}
|
||||||
|
variants={containerVariants}
|
||||||
|
style={{ marginTop: "80px" }}
|
||||||
|
>
|
||||||
|
<motion.h2
|
||||||
|
className="projects-title" // Reusing title style for consistency, maybe smaller?
|
||||||
|
style={{ fontSize: "clamp(24px, 4vw, 40px)", marginBottom: "30px" }}
|
||||||
|
variants={cardVariants}
|
||||||
|
>
|
||||||
|
Sidequests
|
||||||
|
</motion.h2>
|
||||||
|
|
||||||
|
<div className="projects-grid">
|
||||||
|
{SIDEQUESTS.map((project) => (
|
||||||
|
<ProjectCard key={project.id} project={project} variants={cardVariants} />
|
||||||
))}
|
))}
|
||||||
|
</div>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
197
src/pages/Skills.tsx
Normal file
197
src/pages/Skills.tsx
Normal file
@@ -0,0 +1,197 @@
|
|||||||
|
import { motion, Variants } from "framer-motion";
|
||||||
|
|
||||||
|
interface SkillCategory {
|
||||||
|
category: string;
|
||||||
|
skills: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ProfessionalSkill {
|
||||||
|
skill: string;
|
||||||
|
description: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const TECHNICAL_SKILLS: SkillCategory[] = [
|
||||||
|
{
|
||||||
|
category: "Languages",
|
||||||
|
skills: ["JavaScript (ES6+)", "TypeScript", "Python", "Java", "C", "C++", "Lua", "HTML", "CSS", "SQL"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
category: "Frameworks/Libraries",
|
||||||
|
skills: ["React", "Express.js", "Django REST Framework", "AWS Lambda"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
category: "Databases",
|
||||||
|
skills: ["PostgreSQL (SQL Based)", "CouchDB (No-SQL Based)"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
category: "DevOps & Tools",
|
||||||
|
skills: ["Docker", "CircleCI", "Git (CLI)", "Postman", "AWS CDK", "Linux CLI"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
category: "AWS Technologies",
|
||||||
|
skills: ["S3", "Cloudfront", "Route 53", "API Gateway", "Lambda", "RDS", "VPC", "ECS", "SQS"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
category: "Concepts",
|
||||||
|
skills: ["RESTful APIs", "Agile/Scrum", "MVC Architecture", "Cloud Deployment", "Responsive Design"]
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
const PROFESSIONAL_SKILLS: ProfessionalSkill[] = [
|
||||||
|
{
|
||||||
|
skill: "Customer Service & Sales",
|
||||||
|
description: "Clear communication and coordination with customers and clients"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
skill: "Technical Support",
|
||||||
|
description: "Diagnosing and resolving hardware/software issues across all major devices."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
skill: "Web Development",
|
||||||
|
description: "WordPress plugin development, front-end design, and site optimization."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
skill: "Scripting & Programming",
|
||||||
|
description: "JavaScript, Python, HTML/CSS, Git, and NPM package management."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
skill: "Collaboration & Problem-Solving",
|
||||||
|
description: "Working effectively with diverse teams to meet deadlines and ensure quality results."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
skill: "Adaptability",
|
||||||
|
description: "Quickly learning new tools, technologies, and processes in dynamic work environments."
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
const containerVariants: Variants = {
|
||||||
|
hidden: { opacity: 0 },
|
||||||
|
visible: {
|
||||||
|
opacity: 1,
|
||||||
|
transition: {
|
||||||
|
staggerChildren: 0.1,
|
||||||
|
delayChildren: 0.2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const itemVariants: Variants = {
|
||||||
|
hidden: { y: 20, opacity: 0 },
|
||||||
|
visible: {
|
||||||
|
y: 0,
|
||||||
|
opacity: 1,
|
||||||
|
transition: { type: "spring", stiffness: 100 }
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function Skills() {
|
||||||
|
return (
|
||||||
|
<div className="mainContentBlock" style={{ minWidth: "66vw", alignItems: "center" }}>
|
||||||
|
<div style={{ height: "30px" }}></div>
|
||||||
|
<motion.div
|
||||||
|
className="skills-container"
|
||||||
|
variants={containerVariants}
|
||||||
|
initial="hidden"
|
||||||
|
animate="visible"
|
||||||
|
style={{ width: "100%", maxWidth: "1000px", padding: "0 20px" }}
|
||||||
|
>
|
||||||
|
<div style={{ textAlign: "center", marginBottom: "40px" }}>
|
||||||
|
<motion.h1
|
||||||
|
style={{
|
||||||
|
fontSize: "clamp(32px, 5vw, 60px)",
|
||||||
|
fontFamily: "Roboto, sans-serif",
|
||||||
|
color: "white",
|
||||||
|
marginBottom: "20px",
|
||||||
|
fontWeight: 700,
|
||||||
|
textShadow: "0 4px 10px rgba(0, 0, 0, 0.3)"
|
||||||
|
}}
|
||||||
|
variants={itemVariants}
|
||||||
|
>
|
||||||
|
Skills
|
||||||
|
</motion.h1>
|
||||||
|
<motion.p
|
||||||
|
style={{
|
||||||
|
color: "rgba(255, 255, 255, 0.8)",
|
||||||
|
fontSize: "1.1rem",
|
||||||
|
maxWidth: "600px",
|
||||||
|
margin: "0 auto"
|
||||||
|
}}
|
||||||
|
variants={itemVariants}
|
||||||
|
>
|
||||||
|
My technical toolkit and professional competencies.
|
||||||
|
</motion.p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<motion.div variants={itemVariants} style={{ marginBottom: "50px" }}>
|
||||||
|
<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(300px, 1fr))", gap: "25px" }}>
|
||||||
|
{TECHNICAL_SKILLS.map((category) => (
|
||||||
|
<motion.div
|
||||||
|
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: "20px",
|
||||||
|
}}
|
||||||
|
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: "8px" }}>
|
||||||
|
{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>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</motion.div>
|
||||||
|
|
||||||
|
<motion.div variants={itemVariants} style={{ marginBottom: "50px" }}>
|
||||||
|
<h2 style={{ color: "white", borderBottom: "1px solid rgba(255,255,255,0.2)", paddingBottom: "10px", marginBottom: "25px" }}>
|
||||||
|
Professional Skills
|
||||||
|
</h2>
|
||||||
|
<div style={{ display: "grid", gridTemplateColumns: "repeat(auto-fit, minmax(300px, 1fr))", gap: "20px" }}>
|
||||||
|
{PROFESSIONAL_SKILLS.map((skill) => (
|
||||||
|
<motion.div
|
||||||
|
key={skill.skill}
|
||||||
|
style={{
|
||||||
|
background: "rgba(255, 255, 255, 0.05)",
|
||||||
|
backdropFilter: "blur(5px)",
|
||||||
|
border: "1px solid rgba(255, 255, 255, 0.1)",
|
||||||
|
borderRadius: "12px",
|
||||||
|
padding: "20px",
|
||||||
|
display: "flex",
|
||||||
|
flexDirection: "column",
|
||||||
|
gap: "8px"
|
||||||
|
}}
|
||||||
|
whileHover={{ x: 5, backgroundColor: "rgba(255, 255, 255, 0.08)" }}
|
||||||
|
>
|
||||||
|
<h3 style={{ color: "#fff", margin: 0, fontSize: "1.1rem" }}>{skill.skill}</h3>
|
||||||
|
<p style={{ color: "rgba(255, 255, 255, 0.7)", margin: 0, fontSize: "0.95rem", lineHeight: "1.5" }}>
|
||||||
|
{skill.description}
|
||||||
|
</p>
|
||||||
|
</motion.div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</motion.div>
|
||||||
|
</motion.div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user