diff --git a/frontend/src/App.css b/frontend/src/App.css index 95b3ee8..3a04a8d 100644 --- a/frontend/src/App.css +++ b/frontend/src/App.css @@ -80,3 +80,7 @@ color: var(--shiki-dark); background-color: var(--shiki-dark-bg); } + +.macchiato .icon-invert { + filter: invert(1); +} diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 57c7108..a959e47 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -5,6 +5,7 @@ import Layout from "./components/Layout"; import Home from "./pages/Home"; import Stream from "./pages/Stream"; import Contact from "./pages/Contact"; +import Portfolio from "./pages/Portfolio"; export default function App(): ReactElement { const path = window.location.pathname.toLowerCase(); @@ -18,5 +19,9 @@ export default function App(): ReactElement { content = ; } + if (path === "/portfolio") { + content = ; + } + return {content}; } diff --git a/frontend/src/pages/Portfolio.tsx b/frontend/src/pages/Portfolio.tsx new file mode 100644 index 0000000..270b15c --- /dev/null +++ b/frontend/src/pages/Portfolio.tsx @@ -0,0 +1,244 @@ +import type { ReactElement } from "react"; + +import PortfolioSection from "../components/portfolio/PortfolioSection"; +import TimelineEntry from "../components/portfolio/TimelineEntry"; +import Skills from "../components/portfolio/Skills"; +import portfolio from "../content/portfolio.json"; + +import forgejo from "../assets/images/forgejo.svg"; +import gitea from "../assets/images/gitea.svg"; + +const LOGOS: Record = { forgejo, gitea }; + +interface Profile { + name: string; + headline: string; + region: string; + location: string; + links: { label: string; svg: string; url: string }[]; + summary: string; +} + +interface TimelineItem { + role?: string; + company?: string; + companyUrl?: string; + degree?: string; + school?: string; + schoolUrl?: string; + start: string; + end: string; + current: boolean; + highlights: string[]; +} + +interface Experience extends TimelineItem { + role: string; + company: string; + companyUrl?: string; +} + +interface Education extends TimelineItem { + degree: string; + school: string; + schoolUrl?: string; +} + +interface SkillGroup { + category: string; + items: string[]; +} + +interface Language { + name: string; + proficiency: string; +} + +interface Project { + name: string; + url: string; + description: string; + tags: string[]; +} + +interface PortfolioData { + profile: Profile; + experience: Experience[]; + education: Education[]; + skills: SkillGroup[]; + languages: Language[]; + projects: Project[]; +} + +const data = portfolio as PortfolioData; + +const MONTHS = [ + "Jan", + "Feb", + "Mar", + "Apr", + "May", + "Jun", + "Jul", + "Aug", + "Sep", + "Oct", + "Nov", + "Dec", +]; + +const formatDate = (value: string): string => { + if (value.toLowerCase() === "present") return "Present"; + const [year, month] = value.split("-"); + return month ? `${MONTHS[Number(month) - 1]} ${year}` : year; +}; + +const formatRange = (start: string, end: string): string => + `${formatDate(start)} – ${formatDate(end)}`; + +export default function Portfolio(): ReactElement { + const { profile, experience, education, skills, languages, projects } = + data; + + return ( + + + + Portfolio + + + + + + {projects.map((proj) => ( + + + {proj.name} + + + {proj.description} + + + {proj.tags.map((tag) => ( + + ))} + + + ))} + + + {skills.map((group) => ( + + + {group.category} + + + {group.items.map((skill) => ( + + ))} + + + ))} + + + {education.map((edu) => ( + + ))} + + + {experience.map((exp) => ( + + ))} + + + + + + {profile.name[0]} + + + {profile.name} + + + {profile.headline} + + + {profile.location} + + + {profile.region} + + + {profile.links.map((link) => ( + + + + ))} + + + + + + {profile.summary} + + + + + + {languages.map((lang) => ( + + {lang.name} + + {lang.proficiency} + + + ))} + + + + + + + ); +}
+ {proj.description} +
+ {profile.headline} +
+ {profile.location} +
+ {profile.region} +
+ {profile.summary} +