Merge pull request #1 from quaxlyqueen/resume-rewrite

Resume rewrite
This commit is contained in:
Josh Ashton
2025-01-15 11:31:59 -07:00
committed by GitHub
9 changed files with 357 additions and 199 deletions
+6 -107
View File
@@ -9,118 +9,17 @@ import "./main.css";
* Primary portfolio application. Utilizes swiper.js (via /components/VerticalSlider.jsx) to * Primary portfolio application. Utilizes swiper.js (via /components/VerticalSlider.jsx) to
* have a scrolling section to section effect. * have a scrolling section to section effect.
* *
* This is the primary place where (currently) application data is stored. This information will be read in * Managed by /main.jsx.
* from plain-text files in the future for ease of future editing. In the meantime, generated components, * Data by /main.jsx.
* specifically the Skill Card and Project Card (see /components/SkillCard.jsx and /components/ProjectCard.jsx)
* get their data from here and thus any additions are made in this file.
* *
* TODO: Read in data from a plain text file for easier editing in the future, and to separate domains. * @param projectData JSON[], data necessary to create project cards.
* @param skillData JSON, data necessary to create skill cards.
*/ */
const App = () => { const App = ({ projectData, skillData }) => {
// DATA VARIABLES
const projectData = [
{
title: "One AI",
description:
"An end-to-end self-hosted Artificial Intelligence solution, using Meta's Ollama platform to run Large Language Models. Go was used for the backend as a wrapper API and the frontend is Flutter. Here are some of the implemented features:",
featureList: [
"AES encryption of messages between client and server",
"SHA-256 hashing for message validation",
"Base64 encoding for file uploads to the server",
"Docker container for isolation and security",
],
image: "/images/one-ai-cropped.png",
imageGallery: null,
url: "https://github.com/quaxlyqueen/one-ai",
},
{
title: "Snake",
description:
"A Snake clone in the command line, written in C#. Utilized a Queue data structure to keep track of the snake's body. Implemented features include:",
featureList: [
"basic pseudo-randomized obstacle generation",
"difficulty selection by adjusting the frequency of obstacle generation",
"keyboard input to move the snake",
"ASCII art for the menu and game over state",
],
image: "/images/snake.gif",
imageGallery: null,
url: "https://github.com/quaxlyqueen/2410-Snake",
},
{
title: "Battery Monitor",
description:
"A battery monitor, written in C with a GUI written in Python. Utilizes the acpi Unix command to gather battery information. Implemented features incldue:",
featureList: [
"embed Unix commands in C to get the date, time, battery percentage, and battery status",
"basic file I/O to store historical battery data",
"daemonize to run as a background process",
"script to install as a SystemD service",
"basic Python GUI in TKinter to provide graph of data",
],
image: "/images/battery-mon.png",
imageGallery: null,
url: "https://github.com/quaxlyqueen/battery-mon",
},
{
title: "D&D Character Builder",
description:
"A Dungeons and Dragons character builder, following the 5th edition rules and creation process. A collaborative project creating a GUI in Java with Swing. Features include:",
featureList: [
"serialization of character data into custom filetype",
"global styling and custom font import",
"button hover effects",
],
image: "/images/dnd.png",
imageGallery: null,
url: "https://github.com/quaxlyqueen/dnd",
},
{
title: "Calculator",
description:
"A simple and themable calculator written in Python. This uses CustomTKinter for the GUI and the eva CLI package for the actual calculator functionality. Supports MacOS and Linux. Implemented features include:",
featureList: [
"custom theming read in from a global .theme file",
"button hover effects",
"keyboard input",
],
image: "/images/calculator.gif",
imageGallery: null,
url: "https://github.com/quaxlyqueen/calculator",
},
];
const skillsData = {
languages: [
"C & C#",
"HTML & CSS",
"Go",
"Java",
"JavaScript & Dart",
"Python",
],
tools: [
"Git & GitHub",
"Docker",
"Flutter & React",
"Linux & NeoVim",
"Cloudflare",
"Microsoft Intune",
],
other: [
"Written Communication",
"Conflict Resolution",
"Project Management",
"Event Planning",
"Electronics Repair",
"Soldering",
],
};
const slides = [ const slides = [
<Home <Home
image="/images/profile.jpg" image="/images/profile.jpg"
skills={skillsData} skills={skillData}
/>, />,
<Projects cardData={projectData} />, <Projects cardData={projectData} />,
<Contact />, <Contact />,
+4 -4
View File
@@ -5,7 +5,7 @@ import SkillCard from "./components/SkillCard";
* "Home" page/slide of my portfolio. * "Home" page/slide of my portfolio.
* *
* Managed by /App.jsx. * Managed by /App.jsx.
* Data by /App.jsx. * Data by /main.jsx.
* *
* @param image String * @param image String
* @param skills json, containing JSON values of String arrays. * @param skills json, containing JSON values of String arrays.
@@ -18,10 +18,10 @@ const Home = ({ image, skills }) => {
<> <>
<div id="primary"> <div id="primary">
<div className="header"> <div className="header">
<div className="header-image"> <div className="header-image center-row">
<img id="profile" src={image} /> <img id="profile" src={image} />
</div> </div>
<div className="header-text"> <div className="center-column">
<div className="center-column"> <div className="center-column">
<h1>Hi, I'm Josh Ashton.</h1> <h1>Hi, I'm Josh Ashton.</h1>
<h3>Junior full-stack developer.</h3> <h3>Junior full-stack developer.</h3>
@@ -32,7 +32,7 @@ const Home = ({ image, skills }) => {
</div> </div>
</div> </div>
<div id="skill-cards"> <div id="skill-cards">
<div id="card-grid"> <div className="center-row">
<SkillCard <SkillCard
header="Languages" header="Languages"
symbol="code" symbol="code"
+1 -1
View File
@@ -12,7 +12,7 @@ import "swiper/css/scrollbar";
* (see /components/ProjectCard.jsx), generated for each element of the parameter cardData. * (see /components/ProjectCard.jsx), generated for each element of the parameter cardData.
* *
* Managed by /App.jsx. * Managed by /App.jsx.
* Data by /App.jsx. * Data by /main.jsx.
* *
* @param cardData json[], each object contains the following: title, descriptionA, featureList, image, url * @param cardData json[], each object contains the following: title, descriptionA, featureList, image, url
* *
+85 -8
View File
@@ -1,19 +1,96 @@
import React from "react"; import React from "react";
import PdfViewer from "./components/PdfViewer";
/* /*
* Page to display my Resume. * Resume page component.
*
* TODO: Rework resume to make it one page
* TODO: Convert resume to a React component, with a link to save as PDF
*/ */
export default function Resume() { const Resume = ({ jobData, eduData, skillData }) => {
return ( return (
<> <>
<div id="resume-section"> <div id="resume-section">
<h1>Resume</h1> <h1>Josh Ashton</h1>
<PdfViewer file="resume.pdf" /> <h3>Junior Full-Stack Developer</h3>
<h3>Work Experience</h3>
<div className="jobs">
{jobData.map((job, index) => (
<Job key={index} company={job.company} title={job.title} start={job.start} end={job.end} location={job.location} responsibilities={job.responsibilities} />
))}
</div>
<h3>Education</h3>
<div className="education-section">
{eduData.map((edu, index) => (
<Education key={index} institution={edu.institution} degree={edu.degree} gpa={edu.gpa} start={edu.start} end={edu.end} />
))}
</div>
<h3>Skills</h3>
<Skill languages={skillData.languages} tools={skillData.tools} other={skillData.other} />
</div> </div>
</> </>
); );
} }
function Skill({ languages, tools, other }) {
return (
<>
<div className="resume-skills">
<div className="resume-skills-skill">
<h4>Languages:</h4>
<p>{languages.join(", ")}</p>
</div>
<div className="resume-skills-skill">
<h4>Tools:</h4>
<p>{tools.join(", ")}</p>
</div>
<div className="resume-skills-skill">
<h4>Other:</h4>
<p>{other.join(", ")}</p>
</div>
</div>
</>
);
}
function Education({ institution, degree, gpa, start, end }) {
return (
<>
<div className="edu">
<div className="edu-header">
<div className="edu-header-row">
<div className="edu-and-degree">
<h4>{institution}</h4>
<h6>- {degree}</h6>
</div>
<h6 className="edu-time">{start} - {end}</h6>
</div>
<h5>{gpa}</h5>
</div>
</div>
</>
);
}
function Job({ company, title, start, end, location, responsibilities }) {
return (
<>
<div className="job">
<div className="job-header">
<div className="job-header-row">
<div className="company-and-location">
<h4>{company} </h4>
<h6>- {location}</h6>
</div>
<h6 className="job-time">{start} - {end}</h6>
</div>
<h5>{title}</h5>
</div>
<ul className="job-list">
{responsibilities.map((r, index) => (
<li key={index} className="job-list-item">{r}</li>
))}
</ul>
</div>
</>
)
}
export default Resume;
-49
View File
@@ -1,49 +0,0 @@
import React, { useState } from "react";
import { Document, Page, pdfjs } from "react-pdf";
import "react-pdf/dist/Page/TextLayer.css";
import "react-pdf/dist/Page/AnnotationLayer.css";
export default function PdfViewer({ file }) {
pdfjs.GlobalWorkerOptions.workerSrc = new URL(
"pdfjs-dist/build/pdf.worker.min.mjs",
import.meta.url,
).toString();
const [numPages, setNumPages] = useState(0);
const [pageNumber, setPageNumber] = useState(1);
function prev() {
if (pageNumber >= 2) {
setPageNumber(pageNumber - 1);
}
}
function next() {
if (pageNumber < numPages) {
setPageNumber(pageNumber + 1);
}
}
return (
<>
<div className="pdf-container">
<Document
file={file}
onLoadSuccess={({ numPages: numPagesInPdf }) => {
setNumPages(numPagesInPdf);
}}
className="pdf-document"
>
<Page pageNumber={pageNumber} scale={0.8} />
</Document>
<div className="pdf-buttons shadow">
<button className="pdf-button" onClick={() => prev()}>
<span className="material-icons">arrow_back</span>
</button>
<button className="pdf-button" onClick={() => next()}>
<span className="material-icons">arrow_forward</span>
</button>
</div>
</div>
</>
);
}
+1 -1
View File
@@ -7,7 +7,7 @@ import React from "react";
* project. * project.
* *
* Managed by /Projects.jsx. * Managed by /Projects.jsx.
* Data by /App.jsx. * Data by /main.jsx > /App.jsx > /Projects.jsx.
* *
* @param title String * @param title String
* @param description String * @param description String
+4 -1
View File
@@ -5,6 +5,9 @@ import React, { useState, useEffect } from "react";
* *
* On mobile, only one card at a time may be active. The currently active card is * On mobile, only one card at a time may be active. The currently active card is
* managed by the parent component, Home.jsx, via the isActive and onToggle props. * managed by the parent component, Home.jsx, via the isActive and onToggle props.
*
* Managed by /Home.jsx.
* Data by /main.jsx > /App.jsx > /Home.jsx
* *
* @param header String, the skill card's header * @param header String, the skill card's header
* @param symbol String, a Material Icon from https://fonts.google.com/icons?icon.size=24&icon.color=%23e8eaed * @param symbol String, a Material Icon from https://fonts.google.com/icons?icon.size=24&icon.color=%23e8eaed
@@ -32,7 +35,7 @@ const SkillCard = ({ header, symbol, list, isActive, onToggle }) => {
<div className="card shadow"> <div className="card shadow">
<div className="skill-card-header" onClick={() => onToggle()}> <div className="skill-card-header" onClick={() => onToggle()}>
<span className="material-icons skill-symbol">{symbol}</span> <span className="material-icons skill-symbol">{symbol}</span>
<h3>{header}</h3> <h4>{header}</h4>
</div> </div>
{(isActive || !isMobile) && ( {(isActive || !isMobile) && (
<ul> <ul>
+110 -25
View File
@@ -41,39 +41,65 @@ body {
background-attachment: fixed; background-attachment: fixed;
} }
h1 { h1,
font-size: clamp(4.25rem, 5.5vw, 10rem); h2,
font-weight: 600; h3,
letter-spacing: 2px; h4,
width: max-content; h5,
h6,
p,
li {
color: var(--text); color: var(--text);
} }
h3, h1 {
h4 { font-size: clamp(4rem, 5vw, 10rem);
color: var(--text); font-weight: 600;
font-weight: 300; letter-spacing: 2px;
line-height: 1.5; width: max-content;
}
h2,
h3 {
font-weight: 500;
letter-spacing: 2px;
width: max-content;
} }
h2 { h2 {
font-size: clamp(2rem, 3vw, 4rem); font-size: clamp(3rem, 4vw, 7rem);
font-weight: 600;
letter-spacing: 2px;
width: max-content;
color: var(--text);
} }
h3 { h3 {
font-size: clamp(1.5rem, 2vw, 3rem); font-size: clamp(2rem, 3rem, 6rem);
} }
h4 { h4 {
font-size: clamp(1rem, 1.5vw, 1.25rem); font-weight: 400;
line-height: 1.5;
} }
p { h5,
font-size: clamp(1rem, 1.5rem, 2rem); h6 {
font-weight: 300;
}
h4 {
font-size: clamp(1rem, 1.5vw, 4rem);
}
h5 {
font-size: clamp(1rem, 1.25vw, 3.5rem);
}
h6 {
font-size: clamp(1rem, 1.125vw, 2rem);
}
p,
li {
font-weight: 300;
font-size: clamp(1rem, 1.1vw, 1.5rem);
color: var(--text); color: var(--text);
} }
@@ -139,7 +165,7 @@ p {
} }
.mySwiper { .mySwiper {
width: 100vw; width: 100%;
height: 100%; height: 100%;
} }
@@ -278,8 +304,8 @@ p {
border-radius: 18px 18px 0 0; border-radius: 18px 18px 0 0;
} }
.skill-card-header h3 { .skill-card-header h4 {
font-size: clamp(1.3rem, 2vw, 3rem); font-weight: 700;
text-align: right; text-align: right;
} }
@@ -299,7 +325,6 @@ p {
.card ul li { .card ul li {
list-style: none; list-style: none;
font-size: clamp(1rem, 1.25vw, 1.5rem);
color: var(--dark-0); color: var(--dark-0);
} }
@@ -352,7 +377,6 @@ p {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
justify-content: center; justify-content: center;
margin-right: 5vw;
} }
/* Tooltip container */ /* Tooltip container */
@@ -424,6 +448,7 @@ p {
} }
.project-text p { .project-text p {
font-weight: 400;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
justify-content: center; justify-content: center;
@@ -447,7 +472,6 @@ p {
} }
.feature-list li { .feature-list li {
font-size: clamp(1rem, 1.5rem, 2rem);
color: var(--text); color: var(--text);
} }
@@ -530,6 +554,8 @@ p {
#resume-section { #resume-section {
margin: 2vw; margin: 2vw;
display: flex; display: flex;
flex-direction: column;
align-items: center;
} }
/**PDF Viewer**/ /**PDF Viewer**/
@@ -692,6 +718,65 @@ p {
width: 50%; width: 50%;
} }
.jobs,
.education-section {
border-top: 1px solid var(--accent-0);
width: 100%;
}
.job,
.edu {
width: 100%;
padding: 0vw 1vw 1vw 1vw;
}
.job-header-row,
.edu-header-row {
display: grid;
grid-template-columns: 70% 30%;
align-items: center;
}
.job-time,
.edu-time {
justify-self: right;
}
.company-and-location,
.edu-and-degree {
display: flex;
align-items: center;
}
.company-and-location h6,
.edu-and-degree h6 {
margin-left: .25vw;
}
.job-list {
margin: 0 auto;
width: 90%;
}
.job-list-item {
font-weight: 300;
}
.resume-skills {
border-top: 1px solid var(--accent-0);
width: 100%;
}
.resume-skills-skill {
display: flex;
align-items: center;
}
.resume-skills-skill p {
margin-left: .5vw;
padding: 1vw;
}
/**Animations**/ /**Animations**/
@keyframes background-fade { @keyframes background-fade {
from { from {
+146 -3
View File
@@ -6,15 +6,158 @@ import Resume from "./Resume.jsx";
import ErrorPage from "./ErrorPage.jsx"; import ErrorPage from "./ErrorPage.jsx";
/* /*
* Root React container and renderer. Also manages the router. Any new pages should be added here. * Root React container and renderer.
*
* Contains all data and data processing, as well as routing to different pages.
* TODO: Read data in from plain text files.
*/ */
const container = document.getElementById("app"); const container = document.getElementById("app");
const root = createRoot(container); const root = createRoot(container);
const projectData = [
{
title: "One AI",
description:
"An end-to-end self-hosted Artificial Intelligence solution, using Meta's Ollama platform to run Large Language Models. Go was used for the backend as a wrapper API and the frontend is Flutter. Here are some of the implemented features:",
featureList: [
"AES encryption of messages between client and server",
"SHA-256 hashing for message validation",
"Base64 encoding for file uploads to the server",
"Docker container for isolation and security",
],
image: "/images/one-ai-cropped.png",
imageGallery: null,
url: "https://github.com/quaxlyqueen/one-ai",
},
{
title: "Snake",
description:
"A Snake clone in the command line, written in C#. Utilized a Queue data structure to keep track of the snake's body. Implemented features include:",
featureList: [
"basic pseudo-randomized obstacle generation",
"difficulty selection by adjusting the frequency of obstacle generation",
"keyboard input to move the snake",
"ASCII art for the menu and game over state",
],
image: "/images/snake.gif",
imageGallery: null,
url: "https://github.com/quaxlyqueen/2410-Snake",
},
{
title: "Battery Monitor",
description:
"A battery monitor, written in C with a GUI written in Python. Utilizes the acpi Unix command to gather battery information. Implemented features incldue:",
featureList: [
"embed Unix commands in C to get the date, time, battery percentage, and battery status",
"basic file I/O to store historical battery data",
"daemonize to run as a background process",
"script to install as a SystemD service",
"basic Python GUI in TKinter to provide graph of data",
],
image: "/images/battery-mon.png",
imageGallery: null,
url: "https://github.com/quaxlyqueen/battery-mon",
},
{
title: "D&D Character Builder",
description:
"A Dungeons and Dragons character builder, following the 5th edition rules and creation process. A collaborative project creating a GUI in Java with Swing. Features include:",
featureList: [
"serialization of character data into custom filetype",
"global styling and custom font import",
"button hover effects",
],
image: "/images/dnd.png",
imageGallery: null,
url: "https://github.com/quaxlyqueen/dnd",
},
{
title: "Calculator",
description:
"A simple and themable calculator written in Python. This uses CustomTKinter for the GUI and the eva CLI package for the actual calculator functionality. Supports MacOS and Linux. Implemented features include:",
featureList: [
"custom theming read in from a global .theme file",
"button hover effects",
"keyboard input",
],
image: "/images/calculator.gif",
imageGallery: null,
url: "https://github.com/quaxlyqueen/calculator",
},
];
const skillData = {
languages: [
"C & C#",
"HTML & CSS",
"Go",
"Java",
"JavaScript & Dart",
"Python",
],
tools: [
"Git & GitHub",
"Docker",
"Flutter & React",
"Linux & NeoVim",
"Cloudflare",
"Microsoft Intune",
],
other: [
"Written Communication",
"Conflict Resolution",
"Project Management",
"Event Planning",
"Electronics Repair",
"Soldering",
],
};
const jobData = [
{
company: "SLC Public Library",
title: "Technology & Digital Equity Assistant",
start: "Aug '23",
end: "Present",
location: "Salt Lake City, UT",
responsibilities: [
"Assist library patrons with using technology, on devices such as publicly available PCs, laptops, mobile devices, and more.",
"Teach fundamental computer usage skills.",
"Expose patrons to the importance of digital security, password management, and two-factor authentication.",
"Developing workshop curriculum for Raspberry Pi’s."
],
},
{
company: "Trust Lands Admin.",
title: "IT Intern",
start: "Aug '23",
end: "Aug '24",
location: "Salt Lake City, UT",
responsibilities: [
"Orchestrated Microsoft Intune setup and deployment to multiple platforms.",
"Streamlined IT equipment inventory process",
"Provided training to optimize the usage of hardware and software.",
"Troubleshoot and repair hardware and software on a variety of devices.",
"Developed automation scripts for use in various parts of the business.",
],
},
];
const eduData = [
{
institution: "Salt Lake Comm. College",
degree: "AS Comp Sci",
gpa: "3.48 (CS) || 2.87 (Cumulative)",
start: "Jan '21",
end: "Present",
}
];
root.render( root.render(
<Router> <Router>
<Routes> <Routes>
<Route exact path="/" element={<App />} /> <Route exact path="/" element={<App projectData={projectData} skillData={skillData} />} />
<Route exact path="/resume" element={<Resume />} /> <Route exact path="/resume" element={<Resume jobData={jobData} eduData={eduData} skillData={skillData} />} />
<Route <Route
path="*" path="*"
element={ element={