24 Commits

Author SHA1 Message Date
ba87c60360 restored the typing text on the home page 2026-01-07 16:25:30 -06:00
4fea41af28 refined the home page, not sure if I like it yet 2026-01-07 16:21:48 -06:00
eed16efb1c removed title for the skills page, looks cleaner without and update deploy to link to nginx on deploy 2026-01-06 18:18:57 -06:00
2e6fb7fa49 feat: Implement Skills page, ProjectCard and BioSection components, and update navigation and About page content. 2026-01-05 22:23:30 -06:00
bd671155a3 test: port mapping
All checks were successful
Deploy to Production / deploy (push) Successful in 4s
2026-01-04 04:41:48 -06:00
108b2a9c8a test: execute
All checks were successful
Deploy to Production / deploy (push) Successful in 4s
2026-01-04 04:39:22 -06:00
6992fd44b9 test: verbose
All checks were successful
Deploy to Production / deploy (push) Successful in 2s
2026-01-04 04:37:30 -06:00
bc8cae3ea0 test: dep
All checks were successful
Deploy to Production / deploy (push) Successful in 2s
2026-01-04 04:35:07 -06:00
55e280de72 test: one dep
All checks were successful
Deploy to Production / deploy (push) Successful in 2s
2026-01-04 04:32:48 -06:00
169a6c213e test commands: split
Some checks failed
Deploy to Production / deploy (push) Failing after 5s
2026-01-04 04:24:46 -06:00
418014fb04 test: no comments
Some checks failed
Deploy to Production / deploy (push) Failing after 2s
2026-01-04 04:20:24 -06:00
295908b8cf test: powershell ssh deploy
Some checks failed
Deploy to Production / deploy (push) Failing after 3s
2026-01-04 04:19:08 -06:00
32852be42b test: deploy build
All checks were successful
Deploy to Production / deploy (push) Successful in 3s
2026-01-04 04:12:48 -06:00
f79ff6c434 test
All checks were successful
Deploy to Production / deploy (push) Successful in 10s
2026-01-04 04:09:05 -06:00
6c0d137ba9 update workflow
Some checks failed
Deploy to Production / deploy (push) Failing after 2s
2026-01-04 04:07:58 -06:00
4f98210b3c test: deploy config 3
Some checks failed
Deploy to Production / deploy (push) Failing after 2s
2026-01-04 03:19:18 -06:00
5f8300612d test: deploy 2
Some checks failed
Deploy to Production / deploy (push) Failing after 2s
2026-01-04 03:12:48 -06:00
67791ba6a3 test: main deploy
Some checks failed
Deploy to Production / build-and-push (push) Failing after 2m2s
Deploy to Production / deploy (push) Has been skipped
2026-01-04 02:20:22 -06:00
563787c8cb test: deploy
Some checks failed
Deploy to Production / build-and-push (push) Failing after 2m5s
Deploy to Production / deploy (push) Has been skipped
2026-01-04 01:54:41 -06:00
877005ed79 Merge pull request 'feat: Add Dockerfile, Nginx configuration, and GitHub Actions workflow for continuous deployment.' (#1) from test-deploy into main
Some checks failed
Deploy to Production / build-and-push (push) Has been cancelled
Deploy to Production / deploy (push) Has been cancelled
Reviewed-on: #1
2026-01-03 23:06:05 +00:00
9cbb405ba0 feat: Add Dockerfile, Nginx configuration, and GitHub Actions workflow for continuous deployment. 2026-01-03 17:04:43 -06:00
aaa95be433 test upload limit: upload images 2025-12-30 17:13:36 -06:00
cd79edaf89 Merge branch 'main' of https://gitea.sashabayda.ca/Bayda77/digital-resume-FE 2025-12-30 16:58:57 -06:00
0d7eb30ffd Commit Major Implementation now that gitea is setup 2025-12-30 16:58:18 -06:00
61 changed files with 656 additions and 188 deletions

19
.github/workflows/deploy.yml vendored Normal file
View File

@@ -0,0 +1,19 @@
name: Deploy to Production
on:
push:
branches: [ "main" ]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- name: Deploy Application
uses: appleboy/ssh-action@v1.0.3
with:
host: ${{ secrets.REMOTE_HOST }}
username: ${{ secrets.REMOTE_USER }}
key: ${{ secrets.SSH_PRIVATE_KEY }}
passphrase: ${{ secrets.SSH_PASSPHRASE }}
script: |
powershell -ExecutionPolicy Bypass -Command "Write-Host '=== Starting deployment ==='; if (Test-Path 'C:\projects\digital-resume-FE') { Set-Location 'C:\projects\digital-resume-FE'; git pull origin main } else { New-Item -ItemType Directory -Path 'C:\projects' -Force; Set-Location 'C:\projects'; git clone https://gitea.sashabayda.ca/Bayda77/digital-resume-FE.git }; Write-Host '=== Stopping container ==='; docker stop resume-frontend; docker rm resume-frontend; Write-Host '=== Building image ==='; Set-Location 'C:\projects\digital-resume-FE'; docker build -t resume-frontend:latest .; Write-Host '=== Running container ==='; docker run -d --name resume-frontend --network nginx_web --restart unless-stopped -p 3001:80 resume-frontend:latest; Write-Host '=== Verifying ==='; docker ps -a --filter name=resume-frontend"

2
.gitignore vendored
View File

@@ -24,4 +24,4 @@ yarn-error.log*
.env
#public facing Images
/public
# /public

20
Dockerfile Normal file
View File

@@ -0,0 +1,20 @@
# Build stage
FROM node:18-alpine as build
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
# Production stage
FROM nginx:alpine
COPY --from=build /app/build /usr/share/nginx/html
COPY nginx.conf /etc/nginx/conf.d/default.conf
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]

14
nginx.conf Normal file
View File

@@ -0,0 +1,14 @@
server {
listen 80;
location / {
root /usr/share/nginx/html;
index index.html index.htm;
try_files $uri $uri/ /index.html;
}
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root /usr/share/nginx/html;
}
}

BIN
public/1647091917916.jpeg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

BIN
public/1647091917916.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 185 KiB

BIN
public/20251111_224823.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 MiB

BIN
public/20251111_224833.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 MiB

BIN
public/20251111_224848.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 MiB

BIN
public/20251111_224859.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 MiB

BIN
public/20251111_224959.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 MiB

BIN
public/20251111_225017.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 MiB

BIN
public/20251111_225025.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 MiB

BIN
public/20251112_223733.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 MiB

BIN
public/20251112_223743.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 MiB

BIN
public/20251112_223748.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 MiB

BIN
public/20251112_223756.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 MiB

BIN
public/20251112_223813.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 MiB

BIN
public/20251112_223820.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 MiB

BIN
public/20251112_223834.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 MiB

BIN
public/20251112_223852.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 MiB

BIN
public/20251112_224119.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 MiB

BIN
public/20251112_224125.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 MiB

BIN
public/20251112_224134.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 MiB

BIN
public/20251112_224150.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 MiB

BIN
public/20251112_224156.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 MiB

BIN
public/20251112_224236.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.2 MiB

BIN
public/20251112_224246.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.3 MiB

BIN
public/20251112_224411.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.8 MiB

BIN
public/20251112_224416.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.5 MiB

BIN
public/20251112_224420.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.6 MiB

BIN
public/20251112_224435.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 893 KiB

BIN
public/20251112_224437.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 783 KiB

BIN
public/20251123_233533.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 752 KiB

BIN
public/20251123_233541.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 718 KiB

BIN
public/20251123_233608.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 MiB

BIN
public/20251123_233616.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 MiB

BIN
public/20251123_233625.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 MiB

BIN
public/20251123_233643.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

BIN
public/beszel.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 116 KiB

BIN
public/dapperSasha.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 124 KiB

BIN
public/digitCode.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 130 KiB

BIN
public/fampic.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 KiB

BIN
public/gangpic.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 405 KiB

BIN
public/homedepot.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 71 KiB

BIN
public/janepic.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.4 MiB

BIN
public/metaImage.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

BIN
public/nutrien.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 86 KiB

BIN
public/towerglass.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

View File

@@ -55,7 +55,99 @@ p {
}
/* Home page specific styles */
.home-container {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
min-height: 80vh;
/* Takes up most of the viewport */
text-align: center;
gap: 30px;
padding: 20px;
}
.hero-profile-container {
padding: 10px;
background: rgba(255, 255, 255, 0.05);
/* Subtle backing */
border-radius: 50%;
backdrop-filter: blur(10px);
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1);
border: 1px solid rgba(255, 255, 255, 0.1);
}
.hero-profile-img {
width: 200px;
height: 200px;
object-fit: cover;
border-radius: 50%;
/* Circle */
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2);
display: block;
}
.hero-content {
display: flex;
flex-direction: column;
align-items: center;
gap: 15px;
color: white;
max-width: 700px;
}
.hero-name {
font-size: clamp(40px, 6vw, 80px);
font-family: 'Roboto', sans-serif;
font-weight: 700;
color: white;
margin: 0;
letter-spacing: -0.5px;
text-shadow: 0 4px 10px rgba(0, 0, 0, 0.1);
}
.hero-bio {
font-size: clamp(16px, 1.5vw, 20px);
/* font-family: 'Roboto', sans-serif; - inherent*/
font-weight: 300;
line-height: 1.6;
color: rgba(255, 255, 255, 0.9);
max-width: 600px;
}
.hero-socials {
display: flex;
gap: 20px;
margin-top: 20px;
flex-wrap: wrap;
justify-content: center;
}
.hero-social-link {
text-decoration: none;
color: rgba(255, 255, 255, 0.85);
font-size: 16px;
font-weight: 500;
padding: 10px 24px;
border: 1px solid rgba(255, 255, 255, 0.2);
border-radius: 50px;
/* Pill shape */
background: rgba(255, 255, 255, 0.05);
transition: all 0.3s ease;
backdrop-filter: blur(5px);
}
.hero-social-link:hover {
background: rgba(255, 255, 255, 0.2);
color: white;
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
border-color: rgba(255, 255, 255, 0.5);
}
/* Legacy Home styles (keep if referenced elsewhere, otherwise minimal) */
.hero-card {
/* Kept for backward compat if needed, but redesigned home won't use it */
border: 1px solid rgba(255, 255, 255, 0.2);
border-radius: 10px;
background: rgba(255, 255, 255, 0.1);
@@ -318,6 +410,10 @@ p {
width: 100%;
padding: 20px;
}
.home-container {
padding-top: 50px;
}
}
@media (max-width: 768px) {
@@ -329,6 +425,11 @@ p {
.about-title {
margin-bottom: 20px;
}
.home-container {
min-height: 70vh;
gap: 20px;
}
}
/* Projects Page Styles */
@@ -357,26 +458,31 @@ p {
}
.project-card {
background: rgba(255, 255, 255, 0.07);
backdrop-filter: blur(12px);
border: 1px solid rgba(255, 255, 255, 0.15);
border-radius: 16px;
background: rgba(255, 255, 255, 0.08);
/* Slightly lighter base */
backdrop-filter: blur(16px);
/* Increased blur */
border: 1px solid rgba(255, 255, 255, 0.2);
/* Softer border */
border-radius: 24px;
/* More rounded */
padding: 25px;
display: flex;
flex-direction: column;
transition: all 0.4s cubic-bezier(0.175, 0.885, 0.32, 1.275);
/* Bouncy feel */
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;
overflow: hidden;
}
.project-card:hover {
background: rgba(255, 255, 255, 0.12);
background: rgba(255, 255, 255, 0.15);
transform: translateY(-8px) scale(1.02);
box-shadow: 0 15px 30px rgba(0, 0, 0, 0.3);
border-color: rgba(255, 255, 255, 0.3);
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.2);
/* Deeper but soft shadow */
border-color: rgba(255, 255, 255, 0.4);
}
.project-card::before {

View File

@@ -7,6 +7,7 @@ import Home from './pages/Home';
import About from './pages/About';
import WorkExperience from './pages/WorkExperience';
import Projects from './pages/Projects';
import Skills from './pages/Skills';
import Contact from './pages/Contact';
function ScrollToTop() {
@@ -39,6 +40,7 @@ function App() {
<Route path="/about" element={<About />} />
<Route path="/work-experience" element={<WorkExperience />} />
<Route path="/projects" element={<Projects />} />
<Route path="/skills" element={<Skills />} />
<Route path="/contact" element={<Contact />} />
</Routes>
<div>

View 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>
);
}

View File

@@ -35,31 +35,6 @@ const ParticlesBackground: React.FC = () => {
this.x += this.speedX;
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
let sizeRng = Math.random();

View 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>
);
}

View File

@@ -18,6 +18,12 @@ export default function FloatingHeader() {
>
Work Experience
</Link>
<Link
to="/skills"
className={`nav-link ${location.pathname === "/skills" ? "active" : ""}`}
>
Skills
</Link>
<Link
to="/about"
className={`nav-link ${location.pathname === "/about" ? "active" : ""}`}
@@ -28,7 +34,7 @@ export default function FloatingHeader() {
to="/projects"
className={`nav-link ${location.pathname === "/projects" ? "active" : ""}`}
>
Projects
Projects and Sidequests
</Link>
<Link
to="/contact"

View File

@@ -1,8 +1,14 @@
import { motion } from "framer-motion";
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 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 = [
"Melfort",
"Star City",
@@ -41,31 +47,12 @@ export default function About() {
About Me
</motion.h1>
<div className="about-content">
<motion.div
className="about-image-container"
initial={{ x: -30, opacity: 0 }}
animate={{ x: 0, opacity: 1 }}
transition={{ delay: 0.4, duration: 0.6 }}
>
<img
src="/dapperSasha.jpg"
alt="profile"
className="about-image"
<BioSection
imageSrc="/dapperSasha.jpg"
imageAlt="profile"
text={ABOUT_TEXT}
/>
</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
style={{ marginTop: "40px", width: "100%" }}
@@ -73,12 +60,28 @@ export default function About() {
animate={{ y: 0, opacity: 1 }}
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" }}>
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>
<VisitedMap places={VISITED_CITIES} />
</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>
</motion.div>
</div>

View File

@@ -28,7 +28,7 @@ export default function Contact() {
>
<div className="contact-intro">
<p>
I'd love to hear from you! Feel free to reach out through any of these channels:
I would love to hear from you! Feel free to reach out through any of these channels:
</p>
</div>

View File

@@ -1,26 +1,11 @@
import { motion } from "framer-motion";
import TypingText from "../components/animatedTyping";
import FullPageImage from "../components/fullPageImage";
import { delay, motion } from "framer-motion";
// Animation and typing timing configuration
const ANIMATION_TIMINGS = {
// Animation delays (in seconds)
elementIn: 2.0, // When elements fade in
const welcomeText = `Hello! My name is Sasha Bayda and welcome to my digital resume site!
// Typing speeds (in milliseconds per character)
welcomeTextSpeed: 45,
nameSpeed: 120,
Here you will find some of my projects, skills, contact information and any information I couldn't fit into my resume.
// Typing delays (in milliseconds) - time before text starts typing
welcomeTextDelay: 2000,
nameDelay: 2000,
// Test items animation (slides in from bottom)
testItemsStartDelay: 3.5, // When to start the first test item animation (in seconds)
testItemStaggerDelay: 0.2, // Delay between each test item (in seconds)
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!";
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 = [
{ label: "Email", url: "mailto:sasha.bayda@outlook.com" },
@@ -33,48 +18,55 @@ const CONTACT_LINKS = [
export default function Home() {
return (
<div className="mainContentBlock">
<div style={{ height: "30px" }}></div>
<div className="hero-card">
<div className="horizontalContentItem" style={{ maxHeight: "100vh", justifyContent: "center" }}>
<div className="home-container">
<motion.div
className="flexContainer"
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ duration: ANIMATION_TIMINGS.elementIn, ease: "easeOut" }}
className="hero-profile-container"
initial={{ opacity: 0, scale: 0.8 }}
animate={{ opacity: 1, scale: 1 }}
transition={{ duration: 0.8, ease: "easeOut" }}
>
<img
src="/1647091917916.png"
alt="portfolio"
className="portrait-img"
alt="Sasha Bayda"
className="hero-profile-img"
/>
</motion.div>
<div
className="welcome-text flexContainer"
>
<TypingText text={welcomeText} msPerChar={ANIMATION_TIMINGS.welcomeTextSpeed} delayMs={ANIMATION_TIMINGS.welcomeTextDelay} />
</div>
</div>
<div
className="verticalContentItem"
>
<div className="name-text flexContainer">
<TypingText text="Sasha Bayda" msPerChar={ANIMATION_TIMINGS.nameSpeed} delayMs={ANIMATION_TIMINGS.nameDelay} textAlign="center" />
<div className="hero-content">
<div className="hero-name">
<TypingText
text="Sasha Bayda"
msPerChar={120}
delayMs={100}
textAlign="center"
/>
</div>
<div className="horizontalContentItem skills-list">
<motion.p
className="hero-bio"
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ duration: 1, delay: 2.0 }}
>
{welcomeText}
</motion.p>
<div className="hero-socials">
{CONTACT_LINKS.map((link, index) => (
<motion.a
key={index}
href={link.url}
className="contact-link"
initial={{ y: 50, opacity: 0 }}
animate={{ y: 0, opacity: 1 }}
className="hero-social-link"
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{
duration: ANIMATION_TIMINGS.testItemAnimationDuration,
ease: "easeOut",
delay: ANIMATION_TIMINGS.testItemsStartDelay + (index * ANIMATION_TIMINGS.testItemStaggerDelay)
duration: 0.5,
delay: 2.5 + (index * 0.1),
ease: "easeOut"
}}
>{link.label}</motion.a>
>
{link.label}
</motion.a>
))}
</div>
</div>

View File

@@ -1,19 +1,8 @@
import { motion, Variants } from "framer-motion";
import FullPageImage from "../components/fullPageImage";
import ProjectCard, { Project } from "../components/ProjectCard";
interface 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[] = [
const FEATURED_PROJECTS: Project[] = [
{
id: 1,
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 = {
hidden: { opacity: 0 },
visible: {
@@ -55,6 +67,12 @@ export default function Projects() {
<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="projects-container">
{/* Featured Projects Section */}
<motion.div
initial="hidden"
animate="visible"
variants={containerVariants}
>
<motion.h1
className="projects-title"
initial={{ y: -30, opacity: 0 }}
@@ -64,54 +82,35 @@ export default function Projects() {
Featured Projects
</motion.h1>
<motion.div
className="projects-grid"
variants={containerVariants}
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 className="projects-grid">
{FEATURED_PROJECTS.map((project) => (
<ProjectCard key={project.id} project={project} variants={cardVariants} />
))}
</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>
{/* 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>
</div>
</div>

170
src/pages/Skills.tsx Normal file
View File

@@ -0,0 +1,170 @@
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" }}
>
<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>
);
}