diff --git a/public/portfolio/cities/Edinburgh.png b/public/portfolio/cities/Edinburgh.png new file mode 100644 index 0000000..4dfcd59 Binary files /dev/null and b/public/portfolio/cities/Edinburgh.png differ diff --git a/public/portfolio/cities/London.png b/public/portfolio/cities/London.png new file mode 100644 index 0000000..3bbea55 Binary files /dev/null and b/public/portfolio/cities/London.png differ diff --git a/public/portfolio/cities/Manchester.png b/public/portfolio/cities/Manchester.png new file mode 100644 index 0000000..61f510d Binary files /dev/null and b/public/portfolio/cities/Manchester.png differ diff --git a/public/portfolio/cities/Warsaw.png b/public/portfolio/cities/Warsaw.png new file mode 100644 index 0000000..31f62f6 Binary files /dev/null and b/public/portfolio/cities/Warsaw.png differ diff --git a/src/portfolio/data/workExperienceData.ts b/src/portfolio/data/workExperienceData.ts index 8da154f..39747bf 100644 --- a/src/portfolio/data/workExperienceData.ts +++ b/src/portfolio/data/workExperienceData.ts @@ -7,7 +7,8 @@ const workExperienceData : WorkExperienceArgs[] = [ company: "Softwire", city: "Manchester", country: "United Kingdom", - startDate: "November 2025" + startDate: "November 2025", + isImportant: true }, { industry: "Software & AI", @@ -16,15 +17,18 @@ const workExperienceData : WorkExperienceArgs[] = [ city: "Manchester", country: "United Kingdom", startDate: "November 2024", - endDate: "November 2025" + endDate: "November 2025", + isImportant: true }, { industry: "Artificial Intelligence", title: "Programming Data Annotator", company: "DataAnnotation Tech", - city: "New York", - country: "USA", - startDate: "January 2024" + city: "Edinburgh", + country: "United Kingdom", + displayLocation: "New York, United States (Remote)", + startDate: "January 2024", + isImportant: true }, { industry: "Education", @@ -33,7 +37,8 @@ const workExperienceData : WorkExperienceArgs[] = [ city: "London", country: "United Kingdom", startDate: "September 2021", - endDate: "April 2024" + endDate: "April 2024", + isImportant: true }, { industry: "Software", @@ -42,7 +47,8 @@ const workExperienceData : WorkExperienceArgs[] = [ city: "London", country: "United Kingdom", startDate: "June 2023", - endDate: "August 2023" + endDate: "August 2023", + isImportant: true }, { industry: "Education", @@ -105,7 +111,8 @@ const workExperienceData : WorkExperienceArgs[] = [ city: "Warsaw", country: "Poland", startDate: "July 2018", - endDate: "August 2018" + endDate: "August 2018", + isImportant: true } ]; diff --git a/src/portfolio/helpers/WorkExperience.tsx b/src/portfolio/helpers/WorkExperience.tsx index 730fd09..f740976 100644 --- a/src/portfolio/helpers/WorkExperience.tsx +++ b/src/portfolio/helpers/WorkExperience.tsx @@ -8,7 +8,9 @@ export type WorkExperienceArgs = { title : string, city : string, country : string, - endDate? : string + endDate? : string, + displayLocation? : string, + isImportant? : boolean } const WorkExperience : FC = (props) => { diff --git a/src/portfolio/sections/Experience.tsx b/src/portfolio/sections/Experience.tsx index fa67e18..810d7a3 100644 --- a/src/portfolio/sections/Experience.tsx +++ b/src/portfolio/sections/Experience.tsx @@ -1,54 +1,18 @@ -import { Splide, SplideSlide } from "@splidejs/react-splide"; -import { useEffect, useState } from "react"; import styles from "../styling/experience.module.scss"; -import WorkExperience from "@/src/portfolio/helpers/WorkExperience"; -import workExperienceData, { workExperienceParagraph } from "@/src/portfolio/data/workExperienceData"; +import { getCityGroups } from "./Experience/helpers"; +import CityGroup from "./Experience/CityGroup"; const Experience = (): JSX.Element => { - const calcPagesOnWidth = (width : number): number => { - return Math.floor(width / 800 + 1); - }; - - const [pages, setPages] = useState(calcPagesOnWidth(1000)); - - useEffect(() => { - - setPages(calcPagesOnWidth(window.innerWidth)); - const handleResize = (): void => { - setPages(calcPagesOnWidth(window.innerWidth)); - }; - - window.addEventListener("resize", handleResize); - - return () => { - window.removeEventListener("resize", handleResize); - }; - }, []); + const cityGroups = getCityGroups(); return (

Work Experience

-

- {workExperienceParagraph} -

- - { - workExperienceData.map((entry, index) => - - - - ) - } - +
+ {cityGroups.map((group, index) => ( + + ))} +
); }; diff --git a/src/portfolio/sections/Experience/CityGroup.tsx b/src/portfolio/sections/Experience/CityGroup.tsx new file mode 100644 index 0000000..5ebabd6 --- /dev/null +++ b/src/portfolio/sections/Experience/CityGroup.tsx @@ -0,0 +1,57 @@ +import { FC, useState } from "react"; +import { ICityGroup } from "./helpers"; +import styles from "@/src/portfolio/styling/experience.module.scss"; +import { FiChevronDown, FiChevronUp } from "react-icons/fi"; + +interface ICityGroupProps { + group: ICityGroup; +} + +const CityGroup: FC = ({ group }) => { + const [showAll, setShowAll] = useState(false); + const displayedJobs = showAll ? group.jobs : group.visibleJobs; + + return ( +
+
+

{group.city}

+
+ {displayedJobs.map((job, index) => ( +
+ + {job.title}
+ at {job.company}
+
+ in {job.displayLocation || `${job.city}, ${job.country}`}. +
{job.industry}
+
+ {job.endDate ? `${job.startDate} - ${job.endDate}` : `Since ${job.startDate}`} +
+ ))} + {group.hasMore && ( + + )} +
+
+ {group.city} +
+
+ ); +}; + +export default CityGroup; diff --git a/src/portfolio/sections/Experience/helpers.ts b/src/portfolio/sections/Experience/helpers.ts new file mode 100644 index 0000000..66cd8eb --- /dev/null +++ b/src/portfolio/sections/Experience/helpers.ts @@ -0,0 +1,79 @@ +import workExperienceData from "@/src/portfolio/data/workExperienceData"; +import { WorkExperienceArgs } from "@/src/portfolio/helpers/WorkExperience"; + +export interface ICityGroup { + city: string; + jobs: WorkExperienceArgs[]; + visibleJobs: WorkExperienceArgs[]; + hasMore: boolean; + hiddenCount: number; + imageUrl: string; + offsetDirection: "left" | "right"; + description?: string; +} + +const CITY_IMAGES: Record = { + "Manchester": "/portfolio/cities/Manchester.png", + "Edinburgh": "/portfolio/cities/Edinburgh.png", + "London": "/portfolio/cities/London.png", + "Warsaw": "/portfolio/cities/Warsaw.png" +}; + +const createCityGroup = ( + city: string, + jobs: WorkExperienceArgs[], + offset: "left" | "right", + description?: string +): ICityGroup => { + const importantJobs = jobs.filter(job => job.isImportant); + const nonImportantJobs = jobs.filter(job => !job.isImportant); + + const needToFill = Math.max(0, 2 - importantJobs.length); + const fillerJobs = nonImportantJobs.slice(0, needToFill); + const remainingNonImportant = nonImportantJobs.slice(needToFill); + + return { + city, + jobs, + visibleJobs: [...importantJobs, ...fillerJobs], + hasMore: remainingNonImportant.length > 0, + hiddenCount: remainingNonImportant.length, + imageUrl: CITY_IMAGES[city] || "", + offsetDirection: offset, + description + }; +}; + +export const getCityGroups = (): ICityGroup[] => { + const manchesterJobs = workExperienceData.filter(job => job.city === "Manchester"); + const edinburghJobs = workExperienceData.filter(job => job.city === "Edinburgh"); + const londonJobs = workExperienceData.filter(job => job.city === "London" || job.city === "Upminster"); + const warsawJobs = workExperienceData.filter(job => job.city === "Warsaw"); + + return [ + createCityGroup( + "Manchester", + manchesterJobs, + "right", + "After finishing my master's degree, I moved to Manchester because I really liked the culture and the city." + ), + createCityGroup( + "Edinburgh", + edinburghJobs, + "left", + "I moved to Edinburgh to pursue my Master's in Artificial Intelligence at the University of Edinburgh." + ), + createCityGroup( + "London", + londonJobs, + "right", + "After Warsaw, I moved to London for my bachelor's education at Queen Mary University." + ), + createCityGroup( + "Warsaw", + warsawJobs, + "left", + "Where my journey began - my first job experience in the hospitality industry." + ) + ]; +}; diff --git a/src/portfolio/styling/experience.module.scss b/src/portfolio/styling/experience.module.scss index 93b1748..c24989a 100644 --- a/src/portfolio/styling/experience.module.scss +++ b/src/portfolio/styling/experience.module.scss @@ -20,21 +20,176 @@ } } +.cityGroupsContainer { + display: flex; + flex-direction: column; + gap: 0; + margin-top: 3rem; + width: 100%; + position: relative; +} + +.cityGroup { + position: relative; + padding: 3rem 2rem; + border-radius: 8px; + min-height: 400px; + width: 65%; + margin: 0 auto 6rem auto; + background-size: contain !important; + background-repeat: no-repeat !important; + background-position: bottom !important; + + > * { + position: relative; + z-index: 1; + } + + &::after { + content: ''; + position: absolute; + bottom: -3rem; + left: 50%; + transform: translateX(-50%); + width: 60%; + height: 2px; + background: linear-gradient(to right, transparent, $accent_colour, transparent); + z-index: 1; + } + + @media (max-width: 768px) { + width: 90%; + padding: 2rem 1rem; + margin-bottom: 4rem; + + &::after { + bottom: -2rem; + width: 80%; + } + } +} + +.overlay { + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: linear-gradient(rgba(0, 0, 0, 0.6), rgba(0, 0, 0, 0.6)); + z-index: -1; + border-radius: 8px; +} + +.cityTitle { + display: none; +} + +.cityDescription { + font-size: 1rem; + color: rgba($white, 0.9); + margin-bottom: 2rem; + font-style: italic; + text-shadow: 1px 1px 3px rgba(0, 0, 0, 0.8); + line-height: 1.5; +} + +.jobsContainer { + display: flex; + flex-direction: column; + gap: 0.75rem; + width: 100%; +} + +.offsetLeft .jobsContainer { + align-items: flex-start; +} + +.offsetRight .jobsContainer { + align-items: flex-end; +} + +.locationMarker { + position: absolute; + bottom: -3rem; + left: 50%; + transform: translate(-50%, 50%); + background: $accent_colour; + color: $white; + padding: 0.5rem 1rem; + border-radius: 20px; + font-size: 0.85rem; + font-weight: 600; + white-space: nowrap; + z-index: 10; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3); + + @media (max-width: 768px) { + bottom: -2rem; + font-size: 0.75rem; + padding: 0.4rem 0.8rem; + } +} + .job { @include lightSection; - width: 100%; - padding: 1em; - margin: auto 1em; + background-color: rgba(255, 255, 255, 0.95); + width: 45%; + padding: 0.75em; + margin: 0; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3); .largerText { - font-size: 1.3em; + font-size: 1.1em; } .redText { + color: $accent_colour; } .industry { - margin-top: 12px; + margin-top: 8px; text-align: right; + font-size: 0.9em; + } + + @media (max-width: 1024px) { + width: 60%; + } + + @media (max-width: 768px) { + width: 85%; + } + + @media (max-width: 480px) { + width: 95%; + } +} + +.showMoreBtn { + background: rgba(0, 0, 0, 0.3); + backdrop-filter: blur(8px); + color: $white; + width: 100%; + padding: 0.5rem; + margin-top: 0.75rem; + border: 1px solid rgba($white, 0.4); + border-radius: 4px; + cursor: pointer; + font-size: 0.8rem; + font-weight: 700; + display: flex; + align-items: center; + justify-content: center; + gap: 0.4rem; + transition: all 0.2s ease; + + &:hover { + background: rgba(0, 0, 0, 0.5); + color: $white; + border-color: rgba($white, 0.6); + } + + svg { + font-size: 1rem; } } \ No newline at end of file diff --git a/src/portfolio/styling/projects.module.scss b/src/portfolio/styling/projects.module.scss index f1eccde..073920b 100644 --- a/src/portfolio/styling/projects.module.scss +++ b/src/portfolio/styling/projects.module.scss @@ -61,6 +61,14 @@ flex-basis: calc(100% / 2); padding: 2em 2em; + h2 { + font-size: 1.8em; + } + + p { + font-size: 1.05em; + line-height: 1.6; + } .links { diff --git a/src/portfolio/styling/skillsLinks.module.scss b/src/portfolio/styling/skillsLinks.module.scss index 493b550..fbb508b 100644 --- a/src/portfolio/styling/skillsLinks.module.scss +++ b/src/portfolio/styling/skillsLinks.module.scss @@ -98,6 +98,17 @@ &:last-child { border-bottom: none; } + + @media (max-width: 768px) { + display: flex; + flex-direction: column; + gap: 0.25rem; + + span { + float: none !important; + text-align: left !important; + } + } }