├── .github
└── dependabot.yml
├── .gitignore
├── LICENSE
├── README.md
├── package-lock.json
├── package.json
├── public
├── _redirects
├── assets
│ ├── Resume.pdf
│ ├── favicon.ico
│ ├── portrait.png
│ ├── readme-icon.png
│ ├── screenshot_1.png
│ ├── screenshot_2.png
│ ├── screenshot_3.png
│ ├── screenshot_4.png
│ └── screenshot_5.png
├── index.html
├── manifest.json
└── robots.txt
└── src
├── App.js
├── components
├── About.js
├── Education.js
├── Error404.js
├── Footer.js
├── Intro.js
├── Linkbar.js
├── Navbar.js
├── RepoStats.js
├── Resume.js
├── SideProjects.js
└── WorkExperience.js
├── content.json
├── index.js
├── reportWebVitals.js
├── setupTests.js
└── styles
├── bsstyles.css
└── style.css
/.github/dependabot.yml:
--------------------------------------------------------------------------------
1 | # To get started with Dependabot version updates, you'll need to specify which
2 | # package ecosystems to update and where the package manifests are located.
3 | # Please see the documentation for all configuration options:
4 | # https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
5 |
6 | version: 2
7 | updates:
8 | # Maintain dependencies for npm
9 | - package-ecosystem: "npm"
10 | directory: "/"
11 | schedule:
12 | interval: "daily"
13 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 | .vscode
8 |
9 | # testing
10 | /coverage
11 |
12 | # production
13 | /build
14 |
15 | # misc
16 | .DS_Store
17 | .env.local
18 | .env.development.local
19 | .env.test.local
20 | .env.production.local
21 |
22 | npm-debug.log*
23 | yarn-debug.log*
24 | yarn-error.log*
25 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2023 Kevin Huy Trinh
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | www.kevintrinh.dev - v2
8 |
9 |
10 | A modern solution for professionally showcasing skills, projects, and experience. Built with React.js.
11 |
12 |
13 |
18 |
19 |
20 |
21 |
22 |
23 |
24 | VIEW MORE PHOTOS HERE
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 | ## 🌐 What is Reactfolio?
35 |
36 | Reactfolio is a modern, revamped, and responsive portfolio website built using ReactJS, featuring a fresh UI, and designed specifically for STEM and computer science students, as well as aspiring software engineers and students. This project combines enhanced functionality, significant UI improvements, GitHub API integration, and comprehensive bug fixes to deliver a superior user experience. Fully open-source, Reactfolio encourages community contributions and collaboration. Its intuitive design ensures easy customization, making it convenient for users to personalize their portfolios seamlessly. With Reactfolio, you can showcase your skills, projects, and academic experiences in a streamlined, visually appealing manner.
37 |
38 | ## 📌 Important Information
39 |
40 | This project recycles components from my old [Portfolio-V1](https://github.com/KevinTrinh1227/Trinh), while also adopting a new UI and theme inspired by this [DEMO](https://steam-portfolio-demo.vercel.app/). Project native version: Node v16.20.2, built on Linux OS (Ubuntu 22.04 LTS).
41 |
42 | This project is a revamped version of my [Portfolio-V1](https://kevintrinh-v1.netlify.app). It comes with numerous updates including functionality, API integration, Significant UI changes, bug fixes, and much more! This project is fully open source and I encourage anyone interested, to contribute to the project as well. This iteration continues the simplistic approach to the same "one page one scroll" design philosophy as V1.
43 |
44 | For easy use, I linked every element that makes up the app to `content.json` to eliminate having to open each component file to change content data, so you can edit everything seamlessly from one file. Refer to the corresponding JS file for more information on how to manipulate a specific section within the JSON.
45 |
46 | ## 🛠 Installation & setup - [Click here for tutorial video](https://youtu.be/CxXi6HXS5Os?si=ctYsLMjpYPKekVmu)
47 |
48 | 1. Clone repository OR download the [latest release](https://github.com/KevinTrinh1227/Reactfolio/releases)
49 |
50 | ```sh
51 | git clone https://github.com/KevinTrinh1227/Reactfolio
52 | ```
53 |
54 | ```sh
55 | cd Reactfolio
56 | ```
57 |
58 | 2. OPTIONAL: Use the correct Node version using [NVM](https://github.com/nvm-sh/nvm) (Node v16.20.2)
59 |
60 | ```sh
61 | nvm install 16
62 | ```
63 |
64 | ```sh
65 | nvm alias default 16
66 | ```
67 |
68 | 3. Install the dependencies using npm or yarn
69 |
70 | ```sh
71 | npm install
72 | ```
73 |
74 | ```sh
75 | yarn install
76 | ```
77 |
78 | 4. Start the development server using npm or yarn
79 |
80 | ```sh
81 | npm start
82 | ```
83 |
84 | ```sh
85 | yarn run start
86 | ```
87 |
88 | ## 🚀 Build and run for production
89 |
90 | 1. Generate a complete static production build
91 |
92 | ```sh
93 | npm run build
94 | ```
95 |
96 | ## 🎨 CSS hex color palette
97 |
98 | | Color | Hex Code | Usage Info | CSS Usage |
99 | | ------------- | -------------------------------------------------------------------- | --------------------------------------- | ---------------------- |
100 | | Dark Navy | ![[#0b182c]](https://singlecolorimage.com/get/0b182c/15x15) `#0b182c` | Main background color | `var(--dark-navy)` |
101 | | Navy | ![[#12223d]](https://singlecolorimage.com/get/12223d/15x15) `#12223d` | Secondary background color | `var(--navy)` |
102 | | Light Navy | ![[#233450]](https://singlecolorimage.com/get/233450/15x15) `#233450` | Data tool tip color & text highlighting | `var(--light-navy)` |
103 | | Light Lime | ![[#64ff93]](https://singlecolorimage.com/get/64ff93/15x15) `#64ff93` | Main accent color | `var(--light-lime)` |
104 | | White | ![[#e9f1fc]](https://singlecolorimage.com/get/e9f1fc/15x15) `#e9f1fc` | Main text color | `var(--white)` |
105 | | Bone White | ![[#d4ddf8]](https://singlecolorimage.com/get/d4ddf8/15x15) `#d4ddf8` | Secondary text color | `var(--bone-white)` |
106 | | Smoke | ![[#8992ac]](https://singlecolorimage.com/get/8992ac/15x15) `#8992ac` | Tertiary text color | `var(--smoke)` |
107 | | Light Smoke | ![[#acb5cf]](https://singlecolorimage.com/get/acb5cf/15x15) `#acb5cf` | Section subtitles text | `var(--light-smoke)` |
108 | | Lighter Smoke | ![[#d0d8f3]](https://singlecolorimage.com/get/d0d8f3/15x15) `#d0d8f3` | Section Title text | `var(--lighter-smoke)` |
109 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "KevinTrinh1227",
3 | "version": "0.1.0",
4 | "private": true,
5 | "dependencies": {
6 | "@emotion/react": "^11.14.0",
7 | "@fortawesome/fontawesome-svg-core": "^6.7.2",
8 | "@fortawesome/free-solid-svg-icons": "^6.7.2",
9 | "@fortawesome/react-fontawesome": "^0.2.2",
10 | "@react-pdf/renderer": "^4.2.4",
11 | "@testing-library/jest-dom": "^6.6.3",
12 | "@testing-library/react": "^16.3.0",
13 | "@testing-library/user-event": "^14.6.1",
14 | "react": "^18.2.0",
15 | "react-awesome-reveal": "^4.3.1",
16 | "react-dom": "^18.2.0",
17 | "react-icons": "^5.5.0",
18 | "react-markdown": "^9.0.3",
19 | "react-router-dom": "^7.4.1",
20 | "react-scripts": "^5.0.1"
21 | },
22 | "scripts": {
23 | "start": "react-scripts start",
24 | "build": "react-scripts build",
25 | "test": "react-scripts test",
26 | "eject": "react-scripts eject"
27 | },
28 | "eslintConfig": {
29 | "extends": [
30 | "react-app",
31 | "react-app/jest"
32 | ]
33 | },
34 | "browserslist": {
35 | "production": [
36 | ">0.2%",
37 | "not dead",
38 | "not op_mini all"
39 | ],
40 | "development": [
41 | "last 1 chrome version",
42 | "last 1 firefox version",
43 | "last 1 safari version"
44 | ]
45 | },
46 | "devDependencies": {
47 | "@babel/plugin-proposal-private-property-in-object": "^7.21.11",
48 | "web-vitals": "^4.2.4"
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/public/_redirects:
--------------------------------------------------------------------------------
1 | /* /index.html 200
2 |
--------------------------------------------------------------------------------
/public/assets/Resume.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KevinTrinh1227/Reactfolio/c1cd5e9dbfbafec3f61b78d463b7423250eb23df/public/assets/Resume.pdf
--------------------------------------------------------------------------------
/public/assets/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KevinTrinh1227/Reactfolio/c1cd5e9dbfbafec3f61b78d463b7423250eb23df/public/assets/favicon.ico
--------------------------------------------------------------------------------
/public/assets/portrait.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KevinTrinh1227/Reactfolio/c1cd5e9dbfbafec3f61b78d463b7423250eb23df/public/assets/portrait.png
--------------------------------------------------------------------------------
/public/assets/readme-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KevinTrinh1227/Reactfolio/c1cd5e9dbfbafec3f61b78d463b7423250eb23df/public/assets/readme-icon.png
--------------------------------------------------------------------------------
/public/assets/screenshot_1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KevinTrinh1227/Reactfolio/c1cd5e9dbfbafec3f61b78d463b7423250eb23df/public/assets/screenshot_1.png
--------------------------------------------------------------------------------
/public/assets/screenshot_2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KevinTrinh1227/Reactfolio/c1cd5e9dbfbafec3f61b78d463b7423250eb23df/public/assets/screenshot_2.png
--------------------------------------------------------------------------------
/public/assets/screenshot_3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KevinTrinh1227/Reactfolio/c1cd5e9dbfbafec3f61b78d463b7423250eb23df/public/assets/screenshot_3.png
--------------------------------------------------------------------------------
/public/assets/screenshot_4.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KevinTrinh1227/Reactfolio/c1cd5e9dbfbafec3f61b78d463b7423250eb23df/public/assets/screenshot_4.png
--------------------------------------------------------------------------------
/public/assets/screenshot_5.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/KevinTrinh1227/Reactfolio/c1cd5e9dbfbafec3f61b78d463b7423250eb23df/public/assets/screenshot_5.png
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | kevintrinh.dev
5 |
6 |
10 |
14 |
15 |
16 |
17 |
21 |
25 |
26 |
27 |
28 |
29 |
33 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
47 |
48 |
49 | React App
50 |
51 |
52 | You need to enable JavaScript to run this app.
53 |
54 |
55 |
56 |
57 |
--------------------------------------------------------------------------------
/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "React App",
3 | "name": "Create React App Sample",
4 | "icons": [
5 | {
6 | "src": "favicon.ico",
7 | "sizes": "64x64 32x32 24x24 16x16",
8 | "type": "image/x-icon"
9 | },
10 | {
11 | "src": "logo192.png",
12 | "type": "image/png",
13 | "sizes": "192x192"
14 | },
15 | {
16 | "src": "logo512.png",
17 | "type": "image/png",
18 | "sizes": "512x512"
19 | }
20 | ],
21 | "start_url": ".",
22 | "display": "standalone",
23 | "theme_color": "#000000",
24 | "background_color": "#ffffff"
25 | }
26 |
--------------------------------------------------------------------------------
/public/robots.txt:
--------------------------------------------------------------------------------
1 | # https://www.robotstxt.org/robotstxt.html
2 | User-agent: *
3 | Disallow:
4 |
--------------------------------------------------------------------------------
/src/App.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import RepoStats from "./components/RepoStats";
3 | import Navbar from "./components/Navbar";
4 | import Linkbar from "./components/Linkbar";
5 | import Intro from "./components/Intro";
6 | import About from "./components/About";
7 | import Education from "./components/Education";
8 | import WorkExperience from "./components/WorkExperience";
9 | import SideProjects from "./components/SideProjects";
10 | import Footer from "./components/Footer";
11 | import ResumePage from "./components/Resume";
12 | import Error404 from "./components/Error404";
13 |
14 | import { BrowserRouter as Router, Route, Routes } from "react-router-dom";
15 |
16 | import "./styles/style.css";
17 | import content from "./content.json";
18 |
19 | // Note that the section.enable_section has to equal true in
20 | // order for that specific component to load in the app.js
21 |
22 | function App() {
23 | const { intro_screen, about_me, academics, experience, projects } = content;
24 |
25 | return (
26 |
27 |
28 |
32 |
33 |
34 |
35 | {intro_screen.section.enable_section && }
36 | {about_me.section.enable_section && }
37 | {academics.section.enable_section && }
38 | {experience.section.enable_section && }
39 | {projects.section.enable_section && }
40 |
41 |
42 | }
43 | >
44 | } />
45 | } />
46 |
47 |
48 | );
49 | }
50 |
51 | export default App;
52 |
--------------------------------------------------------------------------------
/src/components/About.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import aboutMeJson from "../content.json";
3 | import { Fade } from "react-awesome-reveal";
4 | import ReactMarkdown from "react-markdown";
5 |
6 | /* ==========================================
7 | * JSON Template Example
8 | * ==========================================
9 |
10 | "about_me": {
11 | "section": {
12 | "enable_section": true,
13 | "title": "~/About",
14 | "navbar_name": "/About",
15 | "description": "Brief information about me and some of my interests."
16 | },
17 |
18 |
19 | * "enable_section": to enable/disable section
20 | * "title": "Is what is displayed on the h3 tag to distinguish the section"
21 | * "navbar_name": "Is what is displayed on the navbar"
22 | * "description": "subtitle below the title element to distinguish the section"
23 |
24 |
25 | "headShotUrl": "../assets/portrait.png",
26 | "bio": [
27 | "I am currently studying computer science at St. Mary's University with a focus in computer science with a minor in mathematics. Recently, I was an undergrad research intern at the McNair Research Program of STMU, a Post-Baccalaureate Achievement Program. Some of my current goals are to build experience and to acquire meaningful connections for personal development.",
28 | "I have a profound interest in numerous types of software development such as machine learning, operating systems, and especially in full-stack development. I'm a huge desk setup and PC enthusiast. In my free time, I enjoy playing/analyzing chess games, online window shopping, thrifting, and playing Minecraft multiplayer servers."
29 | ],
30 | "skills_caption": "Some technologies I've worked with:",
31 | "skills": [
32 | "JavaScript ES6+",
33 | "React.js",
34 | "Node.js",
35 | "HTML & CSS",
36 | "Python 3",
37 | "Ubuntu LTS & Git"
38 | ]
39 | },
40 |
41 | * "headShotUrl": "link to your portrait/headshot"
42 | * "bio": ["bio paragraph 1", "bio paragraph 2"]
43 | * "skills_caption": "Skills caption"
44 | * "skills": ["Language 1", "Language 2", "Language 3", ...]
45 |
46 | */
47 |
48 | const AboutMe = () => {
49 | const aboutMe = aboutMeJson.about_me;
50 |
51 | const firstHalf = aboutMe.skills.slice(0, aboutMe.skills.length / 2);
52 | const secondHalf = aboutMe.skills.slice(aboutMe.skills.length / 2);
53 |
54 | return (
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 | {aboutMe.section.title}
63 |
64 |
65 | {aboutMe.section.description}
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 | {aboutMe.bio.map((paragraph, index) => (
75 |
80 |
81 | {paragraph}
82 |
83 |
84 | ))}
85 |
{aboutMe.skills_caption}
86 |
87 |
88 | {firstHalf.map((skill, index) => (
89 |
94 | {skill}
95 |
96 | ))}
97 |
98 |
99 | {secondHalf.map((skill, index) => (
100 |
105 | {skill}
106 |
107 | ))}
108 |
109 |
110 |
111 |
127 |
128 |
129 |
130 |
131 |
132 |
133 | );
134 | };
135 |
136 | export default AboutMe;
137 |
--------------------------------------------------------------------------------
/src/components/Education.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import contentData from "../content.json";
3 | import { Fade } from "react-awesome-reveal";
4 |
5 | /* ==========================================
6 | * JSON Template Example
7 | * ==========================================
8 |
9 | "academics": {
10 | "section": {
11 | "enable_section": true,
12 | "title": "~/Education",
13 | "navbar_name": "/Academics",
14 | "description": "Courses that I have completed or am on track to complete. Current GPA: 3.24"
15 | },
16 |
17 | * "enable_section": to enable/disable section
18 | * "title": "Is what is displayed on the h3 tag to distinguish the section"
19 | * "navbar_name": "Is what is displayed on the navbar"
20 | * "description": "subtitle below the title element to distinguish the section"
21 |
22 | "general": {
23 | "school": "St. Mary's University",
24 | "degree": "Bachelor of Science in Computer Science",
25 | "start_year": 2021,
26 | "end_year": 2025
27 | },
28 |
29 | * "school": "The name of your school/university"
30 | * "degree": "Name of the degree your obtaining"
31 | * "start_year": "School start year"
32 | * "end_year": "school end year"
33 |
34 | "years": [
35 | {
36 | "year": 2021,
37 | "semester": "Fall",
38 | "courses": [
39 | {
40 | "abbreviation": "CS 1310",
41 | "name": "C Programming I",
42 | "credits": 3.0
43 | },
44 | {
45 | "abbreviation": "MT 1411",
46 | "name": "Pre Calculus",
47 | "credits": 4.0
48 | },
49 | {
50 | "abbreviation": "EN 1311",
51 | "name": "Rhetoric and Composition",
52 | "credits": 3.0
53 | },
54 | {
55 | "abbreviation": "PL 1301",
56 | "name": "Introduction to Philosophy",
57 | "credits": 3.0
58 | }
59 | ]
60 | },
61 |
62 | * "years": [{semester 1}, {semester 2}, ...]
63 | * "year": "Year of that semester"
64 | * "semester": "Name of the semester Fall/Spring/Summer"
65 | * "courses": [{course 1}, {course 2}, ...]
66 | * Each course has a course prefix/abreviation, full name, and credits
67 |
68 | */
69 |
70 | const Education = () => {
71 | // Extract the education data from the contentData file
72 | const education = contentData.academics;
73 |
74 | // Calculate the total credits for each year
75 | const totalCreditsPerYear = education.years.map((year) => {
76 | return parseFloat(
77 | year.courses.reduce((total, course) => {
78 | return total + course.credits;
79 | }, 0)
80 | );
81 | });
82 |
83 | // Render the education section
84 | return (
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 | {education.section.title}
93 |
94 |
{education.section.description}
95 |
96 |
97 |
98 |
99 |
100 |
101 | {education.years.map((year, index) => (
102 |
103 |
108 |
109 |
110 |
111 |
112 |
113 | {year.semester} {year.year} Courses
114 |
115 |
116 | Total Credits:{" "}
117 | {totalCreditsPerYear[index].toFixed(1)}
118 |
119 |
120 |
121 |
122 | {year.courses.map((course, index) => (
123 |
124 |
125 | {course.name}
126 |
127 | {course.credits.toFixed(1)}
128 |
129 | ))}
130 |
131 |
132 |
133 |
134 |
135 | ))}
136 |
137 |
138 |
139 |
140 |
141 |
142 | );
143 | };
144 |
145 | export default Education;
146 |
--------------------------------------------------------------------------------
/src/components/Error404.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import contentData from "../content.json";
3 |
4 | const Error404 = () => {
5 | return (
6 |
7 |
404 - Page Not Found
8 |
9 | An error has occured, to continue:
10 |
11 | Return to our homepage.
12 |
13 | Send us an e-mail about this error and try later.
14 |
15 |
Home page |
16 |
GitHub Page
17 |
18 | );
19 | };
20 |
21 | export default Error404;
22 |
--------------------------------------------------------------------------------
/src/components/Footer.js:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect } from "react";
2 | import contentData from "../content.json";
3 | import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
4 | import { faCodeFork, faStar } from "@fortawesome/free-solid-svg-icons";
5 |
6 | /* ==========================================
7 | * JSON Template Example
8 | * ==========================================
9 |
10 | "footer": {
11 | "line_one": "Built & designed by",
12 | "copyright_line": "All rights reserved."
13 | },
14 |
15 | * "line_one": "First line in footer message"
16 | * "copyright_line": "Second line in footer message"
17 |
18 | NOTE: line_one will use the first, middle and last name declared in the general section of json file. If you dont have a middle name you can leave it as a blank ""
19 |
20 | */
21 |
22 | async function fetchRepoData(repoApiLink) {
23 | const response = await fetch(repoApiLink);
24 | const data = await response.json();
25 | return data;
26 | }
27 |
28 | const Footer = () => {
29 | const general = contentData.general;
30 | const repoLink = contentData.repo_stats.repo_link;
31 | const repoApiLink = contentData.repo_stats.api_link;
32 |
33 | const iconStyle = {
34 | fontSize: "0.8rem", // Adjust the size as needed
35 | marginRight: "1.2rem", // Add space between icon and text
36 | };
37 |
38 | const [repoData, setRepoData] = useState(null);
39 |
40 | useEffect(() => {
41 | fetchRepoData(repoApiLink).then((data) => {
42 | setRepoData(data);
43 | });
44 | }, [repoApiLink]);
45 |
46 | return (
47 |
80 | );
81 | };
82 |
83 | export default Footer;
84 |
--------------------------------------------------------------------------------
/src/components/Intro.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import contentData from "../content.json";
3 | import { FaComment } from "react-icons/fa";
4 | import { Fade } from "react-awesome-reveal";
5 |
6 | /* ==========================================
7 | * JSON Template Example
8 | * ==========================================
9 |
10 | "intro_screen": {
11 | "section": {
12 | "enable_section": true
13 | },
14 |
15 |
16 | * "enable_section": to enable/disable section
17 |
18 |
19 | "main_header": "Hello, Kevin here.",
20 | "main_subtitle": "I like to build stuff occasionally.",
21 | "intro_bio": [
22 | "I'm currently a Sophomore attending St. Mary's University,",
23 | "Majoring in Computer Science with minor in Mathematics.",
24 | "I have a profound interest in machine learning, operating",
25 | "systems, full-stack development, and everything in between."
26 | ],
27 | "email_button": "Say hello to me!"
28 | },
29 |
30 | * "main_header": "Main welcome title"
31 | * "main_subtitle": "Subtitle welcome message"
32 | * "intro_bio": ["Sentence 1", "Sentence 2", "Sentence 3", ...]
33 | * "email_button": "Words inside button"
34 |
35 | */
36 |
37 | const Intro = () => {
38 | const introContent = contentData.intro_screen;
39 | const generalContent = contentData.general;
40 |
41 | const introBio = introContent.intro_bio.map((paragraph) => {
42 | return {paragraph}
;
43 | });
44 |
45 | return (
46 |
47 |
48 |
49 |
50 |
51 | {introContent.main_header}
52 |
53 | {introContent.main_subtitle}
54 |
55 |
56 |
57 | {introBio}
58 |
59 |
60 |
65 |
66 |
67 |
68 |
69 |
70 | );
71 | };
72 |
73 | export default Intro;
74 |
--------------------------------------------------------------------------------
/src/components/Linkbar.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import contentData from "../content.json";
3 | import { FaGithub, FaLinkedin, FaFileAlt } from "react-icons/fa";
4 | import { MdEmail } from "react-icons/md";
5 | import { Fade } from "react-awesome-reveal";
6 |
7 | /* ==========================================
8 | * JSON Template Example
9 | * ==========================================
10 |
11 | "general": {
12 | "first_name": "Kevin",
13 | "middle_name": "Huy",
14 | "last_name": "Trinh",
15 | "navbar_social_links": {
16 | "github": "https://github.com/KevinTrinh1227",
17 | "handshake": "https://app.joinhandshake.com/stu/users/32148581",
18 | "linkedin": "https://www.linkedin.com/in/kevintrinh1227",
19 | "email": "kevintrinh1227@gmail.com"
20 | }
21 |
22 | * Enter your name and links to your social accounts.
23 | * Note you can remove them from the social links below if
24 | * you wish to.
25 |
26 | */
27 |
28 | const Linkbar = () => {
29 | const general = contentData.general;
30 |
31 | const socialLinks = [
32 | {
33 | icon: MdEmail,
34 | href: general.navbar_social_links.email,
35 | target: "_blank",
36 | rel: "noreferrer",
37 | },
38 | {
39 | icon: FaLinkedin,
40 | href: general.navbar_social_links.linkedin,
41 | target: "_blank",
42 | rel: "noreferrer",
43 | },
44 | {
45 | icon: FaFileAlt,
46 | href: general.navbar_social_links.resume, // will open to "/resume"
47 | target: "_blank",
48 | rel: "noreferrer",
49 | },
50 | {
51 | icon: FaGithub,
52 | href: general.navbar_social_links.github,
53 | target: "_blank",
54 | rel: "noreferrer",
55 | },
56 | ];
57 |
58 | return (
59 |
60 |
61 | {socialLinks.map((link, index) => (
62 |
63 |
64 |
70 |
71 |
72 |
73 |
74 | ))}
75 |
76 |
77 | );
78 | };
79 |
80 | export default Linkbar;
81 |
--------------------------------------------------------------------------------
/src/components/Navbar.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import contentData from "../content.json";
3 | import { Fade } from "react-awesome-reveal";
4 |
5 | /*
6 | * This section checks if the section_enabled is true or false
7 | * and if true then the item inside the navbar is
8 | */
9 |
10 | const Navbar = () => {
11 | const sections = [
12 | {
13 | id: "aboutme",
14 | text: contentData.about_me.section.navbar_name,
15 | enable: contentData.about_me.section.enable_section,
16 | },
17 | {
18 | id: "education",
19 | text: contentData.academics.section.navbar_name,
20 | enable: contentData.academics.section.enable_section,
21 | },
22 | {
23 | id: "experience",
24 | text: contentData.experience.section.navbar_name,
25 | enable: contentData.experience.section.enable_section,
26 | },
27 | {
28 | id: "projects",
29 | text: contentData.projects.section.navbar_name,
30 | enable: contentData.projects.section.enable_section,
31 | },
32 | ];
33 |
34 | return (
35 |
36 |
37 |
38 |
39 | /Home
40 |
41 | {sections.map((section) => {
42 | return section.enable ? (
43 |
44 | {section.text}
45 |
46 | ) : null;
47 | })}
48 |
49 |
50 |
51 | );
52 | };
53 |
54 | export default Navbar;
55 |
--------------------------------------------------------------------------------
/src/components/RepoStats.js:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect } from "react";
2 | import contentData from "../content.json";
3 | import { Fade } from "react-awesome-reveal";
4 |
5 | /* ==========================================
6 | * JSON Template Example
7 | * ==========================================
8 |
9 | "repo_stats": {
10 | "section": {
11 | "enable_section": true
12 | },
13 | "repo_link": "https://github.com/KevinTrinh1227/Reactfolio",
14 | "api_link": "https://api.github.com/repos/KevinTrinh1227/Reactfolio"
15 | },
16 |
17 | * "enable_section": to enable/disable section
18 | * "repo_link": "Github link to your repo of choice (github.com/...)"
19 | * "api_link": "api link to that same repo (api.github.com/repos/...)"
20 |
21 | */
22 |
23 | const RepoStats = () => {
24 | const [repoData, setRepoData] = useState({});
25 | const [hidden, setHidden] = useState(false);
26 | const [totalCommits, setTotalCommits] = useState(0);
27 |
28 | useEffect(() => {
29 | // JSON API LINK MUST BE AN API LINK
30 | // AND NOT A REGULAR REPOSITORY GIT LINK
31 | // MAKE SURE REPO IS NOT PRIVATE
32 | const apiUrl = contentData.repo_stats.api_link;
33 |
34 | fetch(apiUrl)
35 | .then((response) => {
36 | if (!response.ok) {
37 | throw new Error("Network response was not ok");
38 | }
39 | return response.json();
40 | })
41 | .then((data) => {
42 | setRepoData(data);
43 | })
44 | .catch((error) => {
45 | console.error("Error fetching data:", error);
46 | });
47 |
48 | // Fetch commit statistics
49 | fetch(apiUrl + "/stats/participation")
50 | .then((response) => {
51 | if (!response.ok) {
52 | throw new Error("Network response was not ok");
53 | }
54 | return response.json();
55 | })
56 | .then((data) => {
57 | // Calculate the total number of commits
58 | const commitCounts = data.all;
59 | const total = commitCounts.reduce((acc, count) => acc + count, 0);
60 | setTotalCommits(total);
61 | })
62 | .catch((error) => {
63 | console.error("Error fetching commit statistics:", error);
64 | });
65 |
66 | // Event listener for scrolling
67 | window.addEventListener("scroll", handleScroll);
68 |
69 | // Cleanup the event listener when the component unmounts
70 | return () => {
71 | window.removeEventListener("scroll", handleScroll);
72 | };
73 | }, []);
74 |
75 | // Function to handle scroll behavior
76 | const handleScroll = () => {
77 | if (window.scrollY > 100) {
78 | setHidden(true);
79 | } else {
80 | setHidden(false);
81 | }
82 | };
83 |
84 | function commitUrl(input, attachment) {
85 | return `${input}${attachment}`;
86 | }
87 |
88 | const commit_link = commitUrl(
89 | contentData.repo_stats.repo_link,
90 | "/commits/master"
91 | );
92 | const last_commit_link = commitUrl(
93 | contentData.repo_stats.repo_link,
94 | "/commit"
95 | );
96 |
97 | return (
98 |
127 | );
128 | };
129 |
130 | export default RepoStats;
131 |
--------------------------------------------------------------------------------
/src/components/Resume.js:
--------------------------------------------------------------------------------
1 | import React, { useEffect } from "react";
2 |
3 | const Portfolio = () => {
4 | useEffect(() => {
5 | // Define a function to resize the embedded PDF
6 | const resizeEmbed = () => {
7 | // Retrieve the embedded PDF element by its ID
8 | const embed = document.getElementById("pdfEmbed");
9 | if (embed) {
10 | // Get the height of the window
11 | const windowHeight = window.innerHeight;
12 | // Set the height of the embedded PDF to match the window height
13 | embed.style.height = `${windowHeight}px`;
14 | }
15 | };
16 |
17 | resizeEmbed();
18 | window.addEventListener("resize", resizeEmbed);
19 | return () => window.removeEventListener("resize", resizeEmbed);
20 | }, []);
21 |
22 | return (
23 |
24 |
32 |
33 | );
34 | };
35 |
36 | export default Portfolio;
37 |
--------------------------------------------------------------------------------
/src/components/SideProjects.js:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect } from "react";
2 | import contentData from "../content.json";
3 | import { FaGithub, FaDownload, FaFilePdf } from "react-icons/fa";
4 | import { FiExternalLink, FiFolder } from "react-icons/fi";
5 | import { Fade } from "react-awesome-reveal";
6 | import ReactMarkdown from "react-markdown";
7 |
8 | /* ==========================================
9 | * JSON Template Example
10 | * ==========================================
11 |
12 | "projects": {
13 | "section": {
14 | "enable_section": true,
15 | "title": "~/Projects",
16 | "navbar_name": "/Projects",
17 | "description": "Some of my most recent or past favorite creations or work."
18 | },
19 |
20 | * "enable_section": to enable/disable section
21 | * "title": "Is what is displayed on the h3 tag to distinguish the section"
22 | * "navbar_name": "Is what is displayed on the navbar"
23 | * "description": "subtitle below the title element to distinguish the section"
24 |
25 |
26 | "project_items": [
27 | {
28 | "use_git_api": false,
29 | "api_github_repo_link": "https://api.github.com/repos/KevinTrinh1227/Hycord-Bot",
30 | "project_name": "Hycord Bot",
31 | "description": "Discord.py program that uses Hypixel & PlayerDB APIs to allow users to link and access in-game data. A versatile Hypixel Discord bot.",
32 | "resources_used": ["Python", "Discord.py", "Hypixel API"],
33 | "start_date": "Dec 2022",
34 | "end_date": "Present",
35 | "links": [
36 | {
37 | "href": "https://www.hycord.net",
38 | "icon": "FiExternalLink",
39 | "data_tooltip": "Information Page"
40 | },
41 | {
42 | "href": "https://github.com/KevinTrinh1227/Hycord-Bot",
43 | "icon": "FaGithub",
44 | "data_tooltip": "Visit GitHub Repo"
45 | },
46 | {
47 | "href": "https://github.com/KevinTrinh1227/Hycord-Bot/archive/main.zip",
48 | "icon": "FaDownload",
49 | "data_tooltip": "Download Project"
50 | }
51 | ]
52 | }
53 | ]
54 |
55 | * "use_git_api":
56 | * If true, the app will get your project repo name/title, description, and resources/languages used from the git API, if false then it will use data content inside json.
57 | * "api_github_repo_link": "API link of the repo (api.github.com/repos/...)"
58 |
59 | * "project_name": "Name of your project"
60 | * NOTE: It will display this if use_git_api is false
61 | * other wise it will match whatever is on github.
62 |
63 | * "description": "Your project description."
64 | * NOTE: It will display this if use_git_api is false
65 | * other wise it will match whatever is on github.
66 |
67 | * "resources_used": ["Lang 1", "Lang 2", ...]
68 | * IMPORTANT: If an item is on both the resources_used list and the github languages list, then
69 | * the item will not be displayed.
70 | * Example: Your repo project says that you used: ["JavaScript", "HTML", "CSS"]
71 | * Your JSON has the list: ["React.js", "NVM", "JavaScript", "HTML"]
72 | * The output will be: ["React.js", "NVM", "CSS"]
73 | * If you want it to only display whats on Github then set your JSON resources_used to empty []
74 |
75 | * links: [{link1}, {link2}, {link3}, ...]
76 | * Inside the list is a list of objects that represent a anchor tag for each external link you want your project to display.
77 |
78 | * "href": "link to your external link"
79 | * "icon": "name of your react icon"
80 | * "data_tooltip": "message that displays when a user hovers over item"
81 |
82 | [!] NOTE: make sure the icon your using is 1. Imported, AND 2. Inside the const iconComponents object variable below!
83 |
84 | } */
85 |
86 | // icons used will be listed below
87 | // make sure the react icon is imported
88 | const iconComponents = {
89 | FaGithub: FaGithub,
90 | FaDownload: FaDownload,
91 | FiExternalLink: FiExternalLink,
92 | FiFolder: FiFolder,
93 | FaFilePdf: FaFilePdf,
94 | };
95 |
96 | const SideProjects = () => {
97 | const [projectData, setProjectData] = useState({});
98 | const [projectLanguages, setProjectLanguages] = useState({});
99 |
100 | useEffect(() => {
101 | contentData.projects.project_items.forEach((project, index) => {
102 | if (project.use_git_api) {
103 | const apiUrl = project.api_github_repo_link;
104 |
105 | fetch(apiUrl)
106 | .then((response) => {
107 | if (!response.ok) {
108 | throw new Error("Network response was not ok");
109 | }
110 | return response.json();
111 | })
112 | .then((data) => {
113 | setProjectData((prevData) => ({
114 | ...prevData,
115 | [index]: data, // Store the data with an index as the key
116 | }));
117 |
118 | // Fetch languages data
119 | const languagesUrl = data.languages_url;
120 | fetch(languagesUrl)
121 | .then((response) => {
122 | if (!response.ok) {
123 | throw new Error("Network response was not ok");
124 | }
125 | return response.json();
126 | })
127 | .then((languagesData) => {
128 | setProjectLanguages((prevLanguages) => ({
129 | ...prevLanguages,
130 | [index]: Object.keys(languagesData), // Store language names as an array
131 | }));
132 | })
133 | .catch((error) => {
134 | console.error("Error fetching languages data:", error);
135 | });
136 | })
137 | .catch((error) => {
138 | console.error("Error fetching data:", error);
139 | });
140 | }
141 | });
142 | }, []);
143 |
144 | return (
145 |
146 |
147 |
148 |
149 |
150 |
151 |
152 | {contentData.projects.section.title}
153 |
154 |
155 |
156 |
157 | {contentData.projects.section.description}
158 |
159 |
160 |
161 |
162 |
163 |
164 |
165 |
166 |
167 | {contentData.projects.project_items.map((project, index) => (
168 |
169 |
175 |
176 |
177 |
178 |
179 |
180 |
181 |
182 |
183 |
184 | {project.use_git_api
185 | ? projectData[index]?.name || project.project_name
186 | : project.project_name}
187 |
188 |
189 | {project.use_git_api
190 | ? projectData[index]?.description ||
191 | project.description
192 | : project.description}
193 |
194 |
195 |
196 | {project.use_git_api
197 | ? [
198 | ...(projectLanguages[index] || []),
199 | ...project.resources_used,
200 | ]
201 | .filter(
202 | (item) =>
203 | !(
204 | projectLanguages[index]?.includes(item) &&
205 | project.resources_used.includes(item)
206 | )
207 | )
208 | .join(", ")
209 | : project.resources_used.join(", ")}
210 |
211 |
212 |
213 |
214 |
215 | {project.start_date} - {project.end_date}
216 |
217 |
218 |
235 |
236 |
237 |
238 |
239 | ))}
240 |
241 |
242 |
243 |
244 | );
245 | };
246 |
247 | export default SideProjects;
248 |
--------------------------------------------------------------------------------
/src/components/WorkExperience.js:
--------------------------------------------------------------------------------
1 | import React, { useState } from "react";
2 | import contentData from "../content.json";
3 | import { Fade } from "react-awesome-reveal";
4 | import ReactMarkdown from "react-markdown";
5 |
6 | /* ==========================================
7 | * JSON Template Example
8 | * ==========================================
9 |
10 | "experience": {
11 |
12 | "section": {
13 | "enable_section": true,
14 | "title": "~/Experience",
15 | "navbar_name": "/Experience",
16 | "description": "Positions, team projects, or organizations I am/have been a part of."
17 | },
18 |
19 | * "enable_section": to enable/disable section
20 | * "title": "Is what is displayed on the h3 tag to distinguish the section"
21 | * "navbar_name": "Is what is displayed on the navbar"
22 | * "description": "subtitle below the title element to distinguish the section"
23 |
24 | "experience_items": [
25 | {
26 | "organization": "McNair STMU",
27 | "title": "Research Intern",
28 | "start_date": "May 2023",
29 | "end_date": " July 2023",
30 | "description": [
31 | "Conducted thorough undergraduate research on compiler design and high-level to low-level language translation.",
32 | "Developed a lightweight translator by building a parser, lexer, and semantic analyzer module to convert custom code into assembly language for a virtual machine.",
33 | "Collaborated closely with mentors to refine development skills and deepen understanding of language design principles and compiler design methodologies."
34 | ]
35 | }
36 | ]
37 | },
38 |
39 | * "Experience_item": [{job object 1}, {job object 2}, ...]
40 |
41 | Each object is seperated by commas and has the following fields...
42 |
43 | * "organization": "Company or organization name"
44 | * "title": "Your job position title"
45 | * "start_date": "Job start date",
46 | * "end_date": "Job end date",
47 | * "description": ["Description 1", "Description 2"]
48 |
49 | */
50 |
51 | const WorkExperience = () => {
52 | const { experience } = contentData;
53 | const experienceItems = experience.experience_items;
54 |
55 | const [activeJobIndex, setActiveJobIndex] = useState(0);
56 |
57 | const handleTabClick = (index) => {
58 | setActiveJobIndex(index);
59 | };
60 |
61 | return (
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 | {experience.section.title}
70 |
71 |
72 |
73 |
74 | {experience.section.description}
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 | {experienceItems.map((job, index) => (
86 | handleTabClick(index)}
90 | data-target={job.organization.split(" ").join("-")}
91 | >
92 | {job.organization}
93 |
94 | ))}
95 |
96 |
97 |
98 |
99 | {experienceItems[activeJobIndex].title} @{" "}
100 | {experienceItems[activeJobIndex].organization}
101 |
102 |
103 | {experienceItems[activeJobIndex].start_date} -{" "}
104 | {experienceItems[activeJobIndex].end_date}
105 |
106 |
107 |
108 | {experienceItems[activeJobIndex].description.map(
109 | (paragraph, paraIndex) => (
110 |
111 | {paragraph}
112 |
113 | )
114 | )}
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 | );
124 | };
125 |
126 | export default WorkExperience;
127 |
--------------------------------------------------------------------------------
/src/content.json:
--------------------------------------------------------------------------------
1 | {
2 | "general": {
3 | "first_name": "Kevin",
4 | "middle_name": "Huy",
5 | "last_name": "Trinh",
6 | "navbar_social_links": {
7 | "github": "https://github.com/KevinTrinh1227",
8 | "resume": "/resume",
9 | "linkedin": "https://www.linkedin.com/in/kevintrinh1227",
10 | "email": "kevintrinh1227@gmail.com"
11 | }
12 | },
13 | "repo_stats": {
14 | "section": {
15 | "enable_section": true
16 | },
17 | "repo_link": "https://github.com/KevinTrinh1227/Reactfolio",
18 | "api_link": "https://api.github.com/repos/KevinTrinh1227/Reactfolio"
19 | },
20 | "intro_screen": {
21 | "section": {
22 | "enable_section": true
23 | },
24 | "main_header": "Hello, Kevin here.",
25 | "main_subtitle": "I like to build stuff occasionally.",
26 | "intro_bio": [
27 | "I'm currently a Freshman at the University of Houston,",
28 | "pursuing a Bachelor of Science in Computer Science.",
29 | "I have a profound interest in machine learning, operating",
30 | "systems, full-stack development, and everything in between."
31 | ],
32 | "email_button": "Say hello to me!"
33 | },
34 | "about_me": {
35 | "section": {
36 | "enable_section": true,
37 | "title": "~/About",
38 | "navbar_name": "/About",
39 | "description": "Brief information about me and some of my interests."
40 | },
41 | "headShotUrl": "../assets/portrait.png",
42 | "bio": [
43 | "I am currently an undergraduate student at the University of Houston pursuing a Bachelor of Science in Computer Science. I recently completed an internship at the McNair Research Program at STMU, where I conducted research on high-level to low-level language translation under the guidance of my mentor, Arthur Hanna, Ph.D. Some of my curent goals include gaining practical experience and cultivating valuable connections that will contribute to my personal and professional growth.",
44 | "I have a profound interest in various types of software development, including machine learning, operating systems, and full-stack development. I'm a huge enthusiast of desk setups and PCs. In my free time, I enjoy playing and analyzing chess games, thrifting, playing video games, strumming my guitar, and trying local foods."
45 | ],
46 | "skills_caption": "Some technologies I've been worked with:",
47 | "skills": [
48 | "JavaScript ES6+",
49 | "React.js & Node.js",
50 | "Python 3.x",
51 | "HTML & CSS",
52 | "MongoDB & MySQL",
53 | "Linux & Windows OS"
54 | ]
55 | },
56 | "academics": {
57 | "section": {
58 | "enable_section": false,
59 | "title": "~/Education",
60 | "navbar_name": "/Academics",
61 | "description": "Courses that I have completed or am on track to complete. Current GPA: 3.24"
62 | },
63 | "general": {
64 | "school": "St. Mary's University",
65 | "degree": "Bachelor of Science in Computer Science",
66 | "start_year": 2021,
67 | "end_year": 2025
68 | },
69 | "years": [
70 | {
71 | "year": 2021,
72 | "semester": "Fall",
73 | "courses": [
74 | {
75 | "abbreviation": "CS 1310",
76 | "name": "C Programming I",
77 | "credits": 3.0
78 | },
79 | {
80 | "abbreviation": "MT 1411",
81 | "name": "Pre Calculus",
82 | "credits": 4.0
83 | },
84 | {
85 | "abbreviation": "EN 1311",
86 | "name": "Rhetoric and Composition",
87 | "credits": 3.0
88 | },
89 | {
90 | "abbreviation": "PL 1301",
91 | "name": "Introduction to Philosophy",
92 | "credits": 3.0
93 | }
94 | ]
95 | },
96 | {
97 | "year": 2022,
98 | "semester": "Spring",
99 | "courses": [
100 | {
101 | "abbreviation": "CS102",
102 | "name": "C Programming II",
103 | "credits": 3.0
104 | },
105 | {
106 | "abbreviation": "CS 3300",
107 | "name": "Intro to Data Analytics Python Programming I",
108 | "credits": 3.0
109 | },
110 | {
111 | "abbreviation": "MT 2412",
112 | "name": "Calculus I",
113 | "credits": 4.0
114 | },
115 | {
116 | "abbreviation": "FYE 1301",
117 | "name": "First Year Seminar",
118 | "credits": 3.0
119 | },
120 | {
121 | "abbreviation": "MT 2412",
122 | "name": "Multicultural American Lit",
123 | "credits": 3.0
124 | }
125 | ]
126 | },
127 | {
128 | "year": 2022,
129 | "semester": "Fall",
130 | "courses": [
131 | {
132 | "abbreviation": "CS 2315",
133 | "name": "Algorithms and Data Structure",
134 | "credits": 3.0
135 | },
136 | {
137 | "abbreviation": "CS 2313",
138 | "name": "Object Oriented Programming I",
139 | "credits": 3.0
140 | },
141 | {
142 | "abbreviation": "CS 2110",
143 | "name": "Sophomore CS Seminar",
144 | "credits": 1.0
145 | },
146 | {
147 | "abbreviation": "CM 1341",
148 | "name": "Intro to Human Communication",
149 | "credits": 3.0
150 | },
151 | {
152 | "abbreviation": "AR 1300",
153 | "name": "Concepts in Visual Arts",
154 | "credits": 3.0
155 | }
156 | ]
157 | },
158 | {
159 | "year": 2023,
160 | "semester": "Spring",
161 | "courses": [
162 | {
163 | "abbreviation": "CS 2350",
164 | "name": "Computer Architecture",
165 | "credits": 3.0
166 | },
167 | {
168 | "abbreviation": "CS 2323",
169 | "name": "Object Oritented Programming II",
170 | "credits": 3.0
171 | },
172 | {
173 | "abbreviation": "CS 4375",
174 | "name": "Advanced Topics (Python Data Analytics)",
175 | "credits": 3.0
176 | },
177 | {
178 | "abbreviation": "MT 2323",
179 | "name": "Discrete Math Structures",
180 | "credits": 3.0
181 | },
182 | {
183 | "abbreviation": "TH 1301",
184 | "name": "Introduction to Thelogy",
185 | "credits": 3.0
186 | },
187 | {
188 | "abbreviation": "AC 2301",
189 | "name": "Financial Accounting",
190 | "credits": 3.0
191 | }
192 | ]
193 | }
194 | ]
195 | },
196 | "experience": {
197 | "section": {
198 | "enable_section": true,
199 | "title": "~/Experience",
200 | "navbar_name": "/Experience",
201 | "description": "My current/past relevant experience. Click [here](/resume) to view my resume."
202 | },
203 | "experience_items": [
204 | {
205 | "organization": "McNair STMU",
206 | "title": "Research Intern",
207 | "start_date": "May 2023",
208 | "end_date": " July 2023",
209 | "description": [
210 | "Conducted thorough undergraduate research on compiler design and high-level to low-level language translation.",
211 | "Developed a lightweight translator by building a parser, lexer, and semantic analyzer module to convert custom code into assembly language for a virtual machine.",
212 | "Collaborated closely with mentors to refine development skills and deepen understanding of language design principles and compiler design methodologies."
213 | ]
214 | },
215 | {
216 | "organization": "NP Engagement",
217 | "title": "Web Developer Intern",
218 | "start_date": "August 2022",
219 | "end_date": "November 2022",
220 | "description": [
221 | "Maintained and updated numerous partner websites and online services, ensuring peak performance and relevance with proficiency in HTML, CSS, and JavaScript.",
222 | "Integrated custom API scripts, optimized websites to fulfill unique client requirements and enhance user experience.",
223 | "Utilized my proficiency in WordPress, crafting visually stunning and highly functional platforms tailored to precise client specifications, ensuring optimal user engagement.",
224 | "Drove tangible business growth and significantly boosted client visibility and traffic by orchestrating cross-functional teamwork, applying my technical skills and strategic insights."
225 | ]
226 | }
227 | ]
228 | },
229 | "projects": {
230 | "section": {
231 | "enable_section": true,
232 | "title": "~/Projects",
233 | "navbar_name": "/Projects",
234 | "description": "Some of my most recent or past favorite creations or work."
235 | },
236 | "project_items": [
237 | {
238 | "use_git_api": true,
239 | "api_github_repo_link": "https://api.github.com/repos/KevinTrinh1227/Github-Followers-Bot",
240 | "project_name": "STMU Yelp",
241 | "description": "Application that showcases popular locations for college students specifically for St. Mary's students. collaborated with an 7 person team.",
242 | "resources_used": ["JavaScript", "Node.js", "GitHub API"],
243 | "start_date": "June 2024",
244 | "end_date": "July 2024",
245 | "links": [
246 | {
247 | "href": "https://github.com/KevinTrinh1227/Github-Followers-Bot",
248 | "icon": "FaGithub",
249 | "data_tooltip": "Visit GitHub Repo"
250 | },
251 | {
252 | "href": "https://github.com/KevinTrinh1227/Github-Followers-Bot/archive/main.zip",
253 | "icon": "FaDownload",
254 | "data_tooltip": "Download Project"
255 | }
256 | ]
257 | },
258 | {
259 | "use_git_api": true,
260 | "api_github_repo_link": "https://api.github.com/repos/KevinTrinh1227/Hycord-Client",
261 | "project_name": "Hycord Client",
262 | "description": "Discord.py client that uses Hypixel & PlayerDB APIs to allow users to link and access in-game data. Simply, a versatile Hypixel Discord bot.",
263 | "resources_used": ["HTML", "Discord.py", "Hypixel API"],
264 | "start_date": "Dec 2022",
265 | "end_date": "Present",
266 | "links": [
267 | {
268 | "href": "https://www.hycord.net",
269 | "icon": "FiExternalLink",
270 | "data_tooltip": "Information Page"
271 | },
272 | {
273 | "href": "https://github.com/KevinTrinh1227/Hycord-Client",
274 | "icon": "FaGithub",
275 | "data_tooltip": "Visit GitHub Repo"
276 | },
277 | {
278 | "href": "https://github.com/KevinTrinh1227/Hycord-Client/archive/main.zip",
279 | "icon": "FaDownload",
280 | "data_tooltip": "Download Project"
281 | }
282 | ]
283 | },
284 | {
285 | "use_git_api": true,
286 | "api_github_repo_link": "https://api.github.com/repos/KevinTrinh1227/Reactfolio",
287 | "project_name": "Reactfolio V2",
288 | "description": "An e-portfolio built with React.js that utilizes GitHub API. Designed for public use and easy configuration. The second iteration of my website.",
289 | "resources_used": [
290 | "React.js",
291 | "NVM",
292 | "HTML & CSS",
293 | "JavaScript",
294 | "HTML",
295 | "CSS"
296 | ],
297 | "start_date": "Sept 2023",
298 | "end_date": "Present",
299 | "links": [
300 | {
301 | "href": "https://github.com/KevinTrinh1227/Reactfolio",
302 | "icon": "FaGithub",
303 | "data_tooltip": "Visit GitHub Repo"
304 | }
305 | ]
306 | }
307 | ]
308 | },
309 | "footer": {
310 | "line_one": "Built & designed by",
311 | "copyright_line": "All rights reserved."
312 | },
313 | "error_page": {
314 | "title": "Error 404",
315 | "description": "The page you are looking for does not exist. Chose one of the following links to continue."
316 | }
317 | }
318 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import ReactDOM from "react-dom/client";
3 | import "./styles/bsstyles.css";
4 | import App from "./App";
5 | import reportWebVitals from "./reportWebVitals";
6 |
7 | const root = ReactDOM.createRoot(document.getElementById("root"));
8 | root.render(
9 |
10 |
11 |
12 | );
13 |
14 | // If you want to start measuring performance in your app, pass a function
15 | // to log results (for example: reportWebVitals(console.log))
16 | // or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
17 | reportWebVitals();
18 |
--------------------------------------------------------------------------------
/src/reportWebVitals.js:
--------------------------------------------------------------------------------
1 | const reportWebVitals = onPerfEntry => {
2 | if (onPerfEntry && onPerfEntry instanceof Function) {
3 | import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => {
4 | getCLS(onPerfEntry);
5 | getFID(onPerfEntry);
6 | getFCP(onPerfEntry);
7 | getLCP(onPerfEntry);
8 | getTTFB(onPerfEntry);
9 | });
10 | }
11 | };
12 |
13 | export default reportWebVitals;
14 |
--------------------------------------------------------------------------------
/src/setupTests.js:
--------------------------------------------------------------------------------
1 | // jest-dom adds custom jest matchers for asserting on DOM nodes.
2 | // allows you to do things like:
3 | // expect(element).toHaveTextContent(/react/i)
4 | // learn more: https://github.com/testing-library/jest-dom
5 | import '@testing-library/jest-dom';
6 |
--------------------------------------------------------------------------------
/src/styles/style.css:
--------------------------------------------------------------------------------
1 | :root {
2 | --dark-navy: #0b182c;
3 | --navy: #12223d;
4 | --light-navy: #233450;
5 | --light-lime: #64ff93;
6 | --white: #e9f1fc;
7 | --bone-white: #d4ddf8;
8 | --smoke: #8992ac;
9 | --light-smoke: #acb5cf;
10 | --lighter-smoke: #d0d8f3;
11 | --font-sans: "Calibre", "Inter", "San Francisco", "SF Pro Text", -apple-system,
12 | system-ui, sans-serif;
13 | --font-mono: "SF Mono", "Fira Code", "Fira Mono", "Roboto Mono", monospace;
14 | }
15 |
16 | /* This is for text highlight for multiple browsers */
17 | ::-moz-selection {
18 | background: var(--light-navy);
19 | }
20 | ::-webkit-selection {
21 | background: var(--light-navy);
22 | }
23 | ::selection {
24 | background: var(--light-navy);
25 | }
26 |
27 | body {
28 | background: var(--dark-navy);
29 | color: var(--white);
30 | cursor: default;
31 | }
32 |
33 | .p-r {
34 | position: relative;
35 | }
36 |
37 | .color-a {
38 | text-decoration: none;
39 | color: var(--light-lime);
40 | }
41 |
42 | .color-d {
43 | color: #f5f5f5;
44 | }
45 |
46 | .color-text-a {
47 | text-decoration: none;
48 | color: #4e4e4e;
49 | }
50 |
51 | a {
52 | text-decoration: none;
53 | }
54 |
55 | .box-shadow,
56 | .card-recentprojects,
57 | .work-box,
58 | .service-box,
59 | .paralax-mf {
60 | box-shadow: 0 13px 8px -10px rgba(0, 0, 0, 0.1);
61 | }
62 |
63 | .box-shadow-a,
64 | .button:hover {
65 | text-decoration: none;
66 | box-shadow: 0 0 0 4px #cde1f8;
67 | }
68 |
69 | @media (max-width: 1024px) {
70 | .bg-image {
71 | background-attachment: scroll;
72 | }
73 | }
74 |
75 | .overlay-mf {
76 | background-color: #17202a;
77 | }
78 |
79 | .overlay-mf {
80 | position: absolute;
81 | top: 0;
82 | left: 0px;
83 | padding: 0;
84 | height: 100%;
85 | width: 100%;
86 | opacity: 0.7;
87 | }
88 |
89 | .paralax-mf {
90 | position: relative;
91 | padding: 8rem 0;
92 | }
93 |
94 | .display-table {
95 | width: 100%;
96 | height: 100%;
97 | display: table;
98 | }
99 |
100 | .table-cell {
101 | display: table-cell;
102 | vertical-align: middle;
103 | }
104 |
105 | .sect-4 {
106 | padding: 4rem 0;
107 | }
108 |
109 | .sect-pt4 {
110 | padding-top: 4rem;
111 | }
112 |
113 | .sect-mt4 {
114 | margin-top: 4rem;
115 | }
116 |
117 | .title-s {
118 | font-weight: 600;
119 | color: #1e1e1e;
120 | font-size: 1.1rem;
121 | }
122 |
123 | .title-box {
124 | padding-top: 5rem;
125 | margin-bottom: 1.5rem;
126 | float: left;
127 | }
128 |
129 | .title-a {
130 | color: var(--light-lime);
131 | font-family: var(--font-mono);
132 | text-decoration: none;
133 | text-align: left;
134 | font-size: 2.5rem;
135 | font-weight: 550;
136 | padding-top: 2rem;
137 | }
138 |
139 | .subtitle-a a {
140 | color: var(--light-lime);
141 | text-decoration: none;
142 | font-weight: 500;
143 | font-family: var(--font-sans);
144 | font-size: 1.2rem;
145 | }
146 |
147 | .subtitle-a a:hover {
148 | color: var(--white);
149 | transition: color 0.5s ease-in-out;
150 | }
151 |
152 | .subtitle-a p {
153 | margin-top: 0;
154 | text-decoration: none;
155 | text-align: left;
156 | float: left;
157 | color: var(--light-smoke);
158 | font-weight: 500;
159 | font-family: var(--font-sans);
160 | font-size: 1.2rem;
161 | }
162 |
163 | .line-mf {
164 | display: none;
165 | }
166 |
167 | @media (min-width: 768px) {
168 | .line-mf {
169 | display: inline;
170 | width: 28rem;
171 | height: 1px;
172 | float: right;
173 | background-color: var(--light-navy);
174 | margin-top: 1.5rem;
175 | margin-left: 1rem;
176 | }
177 | }
178 |
179 | .box-pl2 {
180 | padding-left: 2rem;
181 | }
182 |
183 | .box-shadow-full {
184 | padding: 3rem 1.25rem;
185 | position: relative;
186 | background-color: var(--dark-navy);
187 | border-radius: 8px;
188 | margin-bottom: 3rem;
189 | z-index: 2;
190 | }
191 |
192 | @media (min-width: 768px) {
193 | .box-shadow-full {
194 | padding: 3rem;
195 | }
196 | }
197 |
198 | /*--------------------------------------------------------------
199 | # About me section
200 | --------------------------------------------------------------*/
201 |
202 | .about-bold {
203 | font-weight: bold;
204 | }
205 |
206 | .about-me-desc strong a {
207 | color: var(--light-lime);
208 | font-size: 1.1rem;
209 | }
210 |
211 | .about-me-desc strong a:hover {
212 | color: #abb2b9;
213 | text-decoration: none;
214 | }
215 |
216 | .myportrait {
217 | height: 375px;
218 | border-radius: 8px;
219 | box-shadow: 10px 10px 58px 6px rgba(0, 0, 0, 0.56);
220 | -webkit-box-shadow: 10px 10px 58px 6px rgba(0, 0, 0, 0.56);
221 | -moz-box-shadow: 10px 10px 58px 6px rgba(0, 0, 0, 0.56);
222 | }
223 |
224 | .col-md-6 img {
225 | padding-left: 0rem;
226 | }
227 |
228 | .about-me-desc {
229 | color: var(--smoke);
230 | font-size: 1.2rem;
231 | font-family: var(--font-sans);
232 | font-weight: 400;
233 | line-height: 1.3;
234 | padding-bottom: 1rem;
235 | }
236 |
237 | .languages-list {
238 | display: flex;
239 | flex-direction: row;
240 | justify-content: space-between;
241 | margin: 0;
242 | }
243 |
244 | .languages-list ul li {
245 | color: var(--light-smoke);
246 | padding-right: 0rem;
247 | white-space: nowrap;
248 | font-size: 0.9rem;
249 | }
250 |
251 | .languages-list ul {
252 | width: 50%;
253 | padding: 0 0px;
254 | }
255 |
256 | .languages-list ul li::before {
257 | content: "▹";
258 | padding-right: 0.8rem;
259 | color: var(--light-lime);
260 | }
261 |
262 | @media (min-width: 768px) {
263 | .col-md-6 .col-6-img-a {
264 | padding-left: 4rem;
265 | }
266 | }
267 |
268 | /*--------------------------------------------------------------
269 | # Revealing scroll effect
270 | --------------------------------------------------------------*/
271 |
272 | .reveal {
273 | position: relative;
274 | transform: transmokeY(150px);
275 | opacity: 0;
276 | transition: all 2s ease;
277 | }
278 |
279 | .reveal.active {
280 | transform: transmokeY(0px);
281 | opacity: 1;
282 | }
283 |
284 | /*--------------------------------------------------------------
285 | # Tooltips for courses tables
286 | --------------------------------------------------------------*/
287 |
288 | [data-tooltip] {
289 | position: relative;
290 | z-index: 0; /* Increase the z-index value to ensure tooltips appear above other elements */
291 | cursor: pointer;
292 | }
293 |
294 | /* Hide the tooltip content by default */
295 | [data-tooltip]:before,
296 | [data-tooltip]:after {
297 | visibility: hidden;
298 | -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=0)";
299 | filter: progid: DXImageTransform.Microsoft.Alpha(Opacity=0);
300 | opacity: 0;
301 | pointer-events: none;
302 | }
303 |
304 | /* Position tooltip above the element */
305 | [data-tooltip]:before {
306 | position: absolute;
307 | bottom: 50%;
308 | left: 20%;
309 | margin-bottom: 20px;
310 | margin-left: -80px;
311 | padding: 7px;
312 | width: 160px;
313 | -webkit-border-radius: 3px;
314 | -moz-border-radius: 3px;
315 | border-radius: 3px;
316 | background-color: var(--light-navy);
317 | color: var(--white);
318 | content: attr(data-tooltip);
319 | transition: all 0.15s ease-in;
320 | text-align: center;
321 | font-size: 1rem;
322 | font-weight: bold;
323 | line-height: 1.2;
324 | z-index: 2000;
325 | }
326 |
327 | /* Triangle hack to make tooltip look like a speech bubble */
328 | [data-tooltip]:after {
329 | position: absolute;
330 | bottom: 50%;
331 | left: 20%;
332 | margin-left: -5px;
333 | margin-bottom: 15px;
334 | width: 0;
335 | border-top: 5px solid var(--light-navy);
336 | border-right: 5px solid transparent;
337 | border-left: 5px solid transparent;
338 | content: " ";
339 | font-size: 0;
340 | line-height: 0;
341 | z-index: 1999;
342 | }
343 |
344 | /* Show tooltip content on hover */
345 | [data-tooltip]:hover:before,
346 | [data-tooltip]:hover:after {
347 | visibility: visible;
348 | -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=100)";
349 | filter: progid: DXImageTransform.Microsoft.Alpha(Opacity=100);
350 | opacity: 1;
351 | }
352 |
353 | /*--------------------------------------------------------------
354 | # Education Course Table styles
355 | --------------------------------------------------------------*/
356 |
357 | .education table,
358 | td,
359 | th {
360 | border: 1px solid var(--dark-navy);
361 | }
362 |
363 | .education td {
364 | padding-left: 0.7rem;
365 | }
366 |
367 | .education th {
368 | text-align: left;
369 | color: var(--bone-white);
370 | padding-top: 2.5rem;
371 | font-size: 1.1rem;
372 | }
373 |
374 | .education table {
375 | border-collapse: collapse;
376 | width: 100%;
377 | }
378 |
379 | .education td {
380 | height: 50px;
381 | color: var(--light-smoke);
382 | vertical-align: bottom;
383 | }
384 |
385 | /*--------------------------------------------------------------
386 | # Extraciricular Styles
387 | --------------------------------------------------------------*/
388 |
389 | .education-extras h4 {
390 | text-decoration: underline;
391 | color: var(--light-lime);
392 | text-shadow: 0 0 3px rgba(255, 255, 255, 0.2);
393 | text-align: center;
394 | }
395 |
396 | .education-extras table {
397 | width: 90%;
398 | }
399 |
400 | .education-extras p {
401 | color: #abb2b9;
402 | text-align: center;
403 | }
404 |
405 | .education-extras table th {
406 | text-align: center;
407 | color: #ecf0f1;
408 | }
409 |
410 | .education-extras table td {
411 | text-align: left;
412 | padding-left: 0.5rem;
413 | height: 50px;
414 | color: #abb2b9;
415 | vertical-align: bottom;
416 | }
417 |
418 | .education-extras a {
419 | text-decoration: none;
420 | color: var(--light-lime);
421 | text-shadow: 0 0 10px rgba(187, 153, 85, 0.9);
422 | }
423 |
424 | .education-extras a:hover {
425 | color: #ecf0f1;
426 | transition: all 0.5s ease-in-out;
427 | text-shadow: 0 0 2px rgba(255, 255, 255, 0.1);
428 | }
429 |
430 | .course-title strong {
431 | color: var(--light-lime);
432 | }
433 |
434 | /*--------------------------------------------------------------
435 | # Resume Buttons
436 | --------------------------------------------------------------*/
437 |
438 | .resumeButtons {
439 | text-align: center;
440 | padding-top: 1.5rem;
441 | font-size: 1.15rem;
442 | }
443 |
444 | .resumeButtons a {
445 | color: var(--light-lime);
446 | text-shadow: 0 0 5px rgba(187, 153, 85, 0.9);
447 | padding: 1rem;
448 | }
449 |
450 | .resumeButtons a:hover {
451 | color: #ecf0f1;
452 | transition: all 0.5s ease-in-out;
453 | text-shadow: 0 0 1px rgba(255, 255, 255, 0.1);
454 | }
455 |
456 | /*--------------------------------------------------------------
457 | # intro landing screen
458 | --------------------------------------------------------------*/
459 |
460 | .intro_section * {
461 | margin: 0;
462 | padding: 0;
463 | box-sizing: border-box;
464 | position: relative;
465 | list-style-type: none;
466 | }
467 |
468 | .intro_section {
469 | padding-top: 1rem;
470 | background: var(--dark-navy);
471 | color: var(--smoke);
472 | line-height: 1.4;
473 | font-family: "SF Mono", "Fira Code", "Fira Mono", "Roboto Mono", monospace;
474 | height: 100vh;
475 | }
476 |
477 | @media (min-width: 767px) {
478 | .intro_section {
479 | padding-top: 15%;
480 | }
481 | }
482 |
483 | /* adjusted padding from top for larger monirot/screens */
484 | @media (min-width: 1350px) {
485 | .intro_section {
486 | padding-top: 28%;
487 | }
488 | }
489 |
490 | .intro_section::-webkit-scrollbar {
491 | display: none;
492 | }
493 |
494 | .intro_section h1 {
495 | color: var(--lighter-smoke);
496 | font-family: "SF Mono", "Fira Code", "Fira Mono", "Roboto Mono", monospace;
497 | margin: 0 0 10px;
498 | font-size: 3rem;
499 | font-weight: 1000;
500 | }
501 |
502 | .intro_section a {
503 | color: var(--light-lime);
504 | text-shadow: 0 0 4px rgba(255, 255, 255, 0.2);
505 | font-size: 1.25rem;
506 | border: 1px solid var(--light-lime);
507 | padding: 1rem;
508 | border-radius: 4px;
509 | }
510 |
511 | .intro_section a:hover {
512 | color: var(--white);
513 | border: 1px solid var(--white);
514 | transition: all 0.3s ease-in-out;
515 | text-shadow: 0 0 2px rgba(255, 255, 255, 0.1);
516 | }
517 |
518 | .intro_section strong {
519 | color: var(--light-smoke);
520 | font-size: 2.25rem;
521 | font-weight: 1000;
522 | }
523 |
524 | .intro_section .introParagraph {
525 | color: var(--smoke);
526 | padding-top: 2.25rem;
527 | padding-bottom: 2.5rem;
528 | font-size: 3rem;
529 | }
530 |
531 | .intro_section .introParagraph p {
532 | font-family: "Calibre", "Inter", "San Francisco", "SF Pro Text", -apple-system,
533 | system-ui, sans-serif;
534 | word-spacing: 0.4rem;
535 | letter-spacing: 1.1px;
536 | font-size: 1.25rem;
537 | font-weight: 500;
538 | }
539 |
540 | .intro_section em {
541 | color: #dd925f;
542 | }
543 |
544 | .intro_section h3 {
545 | padding-top: 1rem;
546 | margin: 10px 0;
547 | }
548 |
549 | .intro_section h3 i {
550 | padding-right: 0.8rem;
551 | }
552 |
553 | .intro_section img {
554 | display: none;
555 | }
556 |
557 | .intro_section .comment {
558 | color: #4d4d4c;
559 | text-decoration: none;
560 | text-shadow: none;
561 | }
562 |
563 | .intro_section p:nth-of-type(8) {
564 | color: #b5bc67;
565 | }
566 |
567 | /*--------------------------------------------------------------
568 | # recentprojects
569 | --------------------------------------------------------------*/
570 |
571 | .card-recentprojects {
572 | background: var(--navy);
573 | color: var(--bone-white);
574 | border-radius: 8px;
575 | }
576 |
577 | .post-date a {
578 | color: var(--light-lime);
579 | text-decoration: none;
580 | transition: all 0.5s ease-in-out;
581 | font-size: 1.1rem;
582 | }
583 |
584 | .post-date a:hover {
585 | color: var(--white);
586 | text-decoration: none;
587 | transition: all 0.5s ease-in-out;
588 | }
589 |
590 | p,
591 | a {
592 | font-family: "SF Mono", "Fira Code", "Fira Mono", "Roboto Mono", monospace;
593 | margin: 0.8em 0;
594 | font-size: 1rem;
595 | font-weight: 400;
596 | color: var(--smoke);
597 | }
598 |
599 | .card-title {
600 | padding-top: 1rem;
601 | color: #ecf0f1;
602 | }
603 |
604 | @media (min-width: 767px) {
605 | .card-recentprojects {
606 | margin-bottom: 0rem;
607 | background: var(--navy);
608 | color: var(--white);
609 | }
610 | }
611 |
612 | .col-md-4 {
613 | padding-top: 1rem;
614 | padding-bottom: 1rem;
615 | }
616 |
617 | .card-recentprojects .card-body {
618 | position: relative;
619 | }
620 |
621 | /* fade in effect for card-body */
622 |
623 | .card-body:hover .card-description {
624 | color: var(--white);
625 | transition: color 0.5s ease-in-out;
626 | }
627 |
628 | .card-body:hover .resources-used {
629 | color: var(--white);
630 | transition: color 0.5s ease-in-out;
631 | }
632 |
633 | /* fade effect ends here */
634 |
635 | .card-recentprojects .card-category-box {
636 | position: absolute;
637 | text-align: center;
638 | top: -16px;
639 | left: 15px;
640 | right: 15px;
641 | line-height: 25px;
642 | overflow: hidden;
643 | }
644 |
645 | .card-title {
646 | color: var(--white);
647 | padding-bottom: 0.8rem;
648 | }
649 |
650 | .card-recentprojects .card-title {
651 | font-size: 1.3rem;
652 | margin-top: 0.6rem;
653 | color: var(--white);
654 | }
655 | .card-recentprojects .card-description {
656 | color: var(--light-smoke);
657 | }
658 | .card-recentprojects .post-author {
659 | display: inline-block;
660 | word-spacing: 0.2rem;
661 | color: var(--smoke);
662 | }
663 | .card-recentprojects .post-date {
664 | color: var(--light-lime);
665 | display: inline-block;
666 | }
667 |
668 | .card-footer {
669 | display: flex;
670 | justify-content: space-between; /* push the author to the left and post-date to the right */
671 | align-items: center; /* Vertically center the content */
672 | }
673 |
674 | .post-author {
675 | flex: 1; /* take the available space, pushing post-date to the right */
676 | word-spacing: 0.2rem;
677 | color: var(--smoke);
678 | }
679 |
680 | .post-date {
681 | color: var(--light-lime);
682 | }
683 |
684 | /*--------------------------------------------------------------
685 | # recentprojects Single
686 | --------------------------------------------------------------*/
687 |
688 | @media (min-width: 768px) {
689 | .post-box,
690 | .form-comments,
691 | .box-comments,
692 | .widget-sidebar {
693 | padding: 3rem;
694 | }
695 | }
696 |
697 | .recentprojects-wrapper .article-title {
698 | font-size: 1.5rem;
699 | }
700 | @media (min-width: 768px) {
701 | .recentprojects-wrapper .article-title {
702 | font-size: 1.9rem;
703 | }
704 | }
705 | .recentprojects-wrapper .post-meta {
706 | margin: 1rem 0;
707 | }
708 | .recentprojects-wrapper .post-meta ul {
709 | border-left: 4px solid var(--navy);
710 | margin-top: 1rem;
711 | }
712 | .recentprojects-wrapper .post-meta ul li {
713 | display: inline-block;
714 | margin-left: 15px;
715 | }
716 | .recentprojects-wrapper .post-meta ul a {
717 | color: var(--navy);
718 | }
719 | .recentprojects-wrapper .post-meta ul span {
720 | color: #1e1e1e;
721 | }
722 | .recentprojects-wrapper .blockquote {
723 | border-left: 4px solid var(--navy);
724 | padding: 18px;
725 | font-style: italic;
726 | }
727 |
728 | .card-recentprojects .resources-used {
729 | font-size: 0.85rem;
730 | }
731 |
732 | /*--------------------------------------------------------------
733 | # Certifications
734 | --------------------------------------------------------------*/
735 | .work-box {
736 | margin-bottom: 3rem;
737 | -webkit-backface-visibility: hidden;
738 | backface-visibility: hidden;
739 | background-color: var(--navy);
740 | border-radius: 8px;
741 | }
742 | .work-box:hover img {
743 | transform: scale(1.3);
744 | }
745 |
746 | .work-img {
747 | display: block;
748 | overflow: hidden;
749 | border-radius: 8px;
750 | }
751 | .work-img img {
752 | transition: all 1s;
753 | }
754 |
755 | .work-content {
756 | padding: 2rem 3% 1rem 4%;
757 | }
758 | .work-content .w-more {
759 | color: #abb2b9;
760 | font-size: 0.8rem;
761 | }
762 | .work-content .w-more .w-ctegory {
763 | color: var(--light-lime);
764 | }
765 |
766 | .w-title {
767 | font-size: 1.2rem;
768 | color: #ecf0f1;
769 | }
770 |
771 | /*--------------------------------------------------------------
772 | # My Certifications
773 | --------------------------------------------------------------*/
774 | .certifications-details {
775 | padding-top: 40px;
776 | }
777 | .certifications-details .certifications-details-slider img {
778 | width: 100%;
779 | }
780 | .certifications-details .certifications-details-slider .swiper-pagination {
781 | margin-top: 20px;
782 | position: relative;
783 | }
784 | .certifications-details
785 | .certifications-details-slider
786 | .swiper-pagination
787 | .swiper-pagination-bullet {
788 | width: 12px;
789 | height: 12px;
790 | background-color: #fff;
791 | opacity: 1;
792 | border: 1px solid var(--navy);
793 | }
794 | .certifications-details
795 | .certifications-details-slider
796 | .swiper-pagination
797 | .swiper-pagination-bullet-active {
798 | background-color: var(--navy);
799 | }
800 | .certifications-details .certifications-info {
801 | padding: 30px;
802 | box-shadow: 0px 0 30px rgba(78, 78, 78, 0.08);
803 | }
804 | .certifications-details .certifications-info h3 {
805 | font-size: 22px;
806 | font-weight: 700;
807 | margin-bottom: 20px;
808 | padding-bottom: 20px;
809 | border-bottom: 1px solid #eee;
810 | }
811 | .certifications-details .certifications-info ul {
812 | list-style: none;
813 | padding: 0;
814 | font-size: 15px;
815 | }
816 | .certifications-details .certifications-info ul li + li {
817 | margin-top: 10px;
818 | }
819 | .certifications-details .certifications-description {
820 | padding-top: 30px;
821 | }
822 | .certifications-details .certifications-description h2 {
823 | font-size: 26px;
824 | font-weight: 700;
825 | margin-bottom: 20px;
826 | }
827 | .certifications-details .certifications-description p {
828 | padding: 0;
829 | }
830 |
831 | /*--------------------------------------------------------------
832 | # experience section
833 | --------------------------------------------------------------*/
834 |
835 | li {
836 | list-style: none;
837 | }
838 |
839 | #experience {
840 | text-align: left;
841 | }
842 |
843 | .wrapper {
844 | display: flex;
845 | }
846 | .indicator {
847 | padding: 2.5rem 2rem;
848 | }
849 | .indicator li {
850 | display: flex;
851 | align-items: center;
852 | grid-gap: 0.5rem;
853 | padding: 1.5rem 4rem 1.5rem 0rem;
854 | cursor: pointer;
855 | font-size: 0.875rem;
856 | color: var(--white);
857 | border-right: 3px solid transparent;
858 | transition: border-right-color 0.3s ease; /* Add transition property */
859 | white-space: nowrap; /* Prevent text from wrapping */
860 | overflow: hidden; /* Hide overflow text */
861 | text-overflow: ellipsis; /* Show ellipsis (...) for overflow text */
862 | }
863 | .indicator li i {
864 | font-size: 1rem;
865 | }
866 | .indicator li:hover {
867 | color: var(--light-lime);
868 | }
869 | .indicator li.active {
870 | border-right-color: var(--light-lime);
871 | color: var(--light-lime); /* Set the text color of the active indicator */
872 | }
873 | .content {
874 | padding: 1.5rem 4rem;
875 | }
876 | .content li {
877 | display: none;
878 | }
879 | .content li.active {
880 | display: list-item;
881 | }
882 | .content h1 {
883 | font-size: 1.5rem;
884 | font-weight: 600;
885 | color: var(--white);
886 | }
887 |
888 | .content h4 {
889 | font-size: 1rem;
890 | color: var(--smoke);
891 | margin-bottom: 1.25rem;
892 | word-spacing: 0.25rem;
893 | }
894 |
895 | .content ul {
896 | padding-left: 1rem;
897 | }
898 |
899 | .content ul li.active {
900 | margin-bottom: 0.5rem;
901 | color: var(--smoke);
902 | font-size: 1rem;
903 | width: 100%;
904 | padding-left: 1rem;
905 | padding-bottom: 0.8rem; /* Distance from other li descriptions */
906 | position: relative; /* Set position to relative for absolute positioning */
907 | }
908 |
909 | /* Style the list item marker (absolute positioning) */
910 | .content ul li.active::before {
911 | content: "▹";
912 | color: var(--light-lime);
913 | position: absolute;
914 | left: -1rem; /* distance between symbol and text */
915 | }
916 |
917 | .content li li {
918 | margin-bottom: 0.5rem;
919 | color: var(--light-lime);
920 | font-size: 1rem;
921 | width: 50%;
922 | }
923 |
924 | @media screen and (max-width: 770px) {
925 | .wrapper {
926 | flex-direction: column;
927 | }
928 | .indicator {
929 | border-right: auto;
930 | }
931 | }
932 |
933 | /*--------------------------------------------------------------
934 | # navlist AND link bar AND Repo Stats Section
935 | --------------------------------------------------------------*/
936 |
937 | .repo-stats,
938 | .navlist,
939 | .linkbar {
940 | display: none;
941 | }
942 |
943 | .navlist ul li a {
944 | color: var(--smoke);
945 | text-decoration: none;
946 | font-weight: bold;
947 | font-size: 1.2rem;
948 | }
949 |
950 | .navlist ul li a:hover {
951 | color: #ecf0f1;
952 | text-decoration: none;
953 | transition: all 0.3s ease-in-out;
954 | text-shadow: 0 0 2px rgba(255, 255, 255, 0.1);
955 | }
956 |
957 | .linkbar a:hover {
958 | color: var(--white);
959 | transition: all 0.3s ease-in-out;
960 | text-shadow: 0 0 2px rgba(255, 255, 255, 0.1);
961 | }
962 |
963 | .repo-stats {
964 | color: var(--smoke);
965 | }
966 |
967 | .repo-stats a:hover {
968 | color: var(--white);
969 | transition: all 0.3s ease-in-out;
970 | text-shadow: 0 0 2px rgba(255, 255, 255, 0.1);
971 | }
972 |
973 | /* minimum screen width for the nav, links, etc to fit */
974 | @media (min-width: 1660px) {
975 | .repo-stats {
976 | display: block;
977 | position: fixed;
978 | top: 1rem;
979 | right: 10rem;
980 | background-color: var(--dark-navy);
981 | padding: 10px;
982 | transition: opacity 0.3s;
983 | z-index: 999;
984 | }
985 |
986 | .repo-stats.hidden {
987 | opacity: 0;
988 | pointer-events: none;
989 | }
990 |
991 | .navlist {
992 | font-size: 0.85rem;
993 | text-transform: lowercase;
994 | display: block;
995 | position: fixed;
996 | right: 0;
997 | bottom: 0;
998 | padding-right: 8.5rem;
999 | padding-bottom: 8rem;
1000 | text-align: right;
1001 | z-index: 999;
1002 | }
1003 |
1004 | .navlist ul li {
1005 | padding: 0.05rem;
1006 | color: var(--light-lime);
1007 | }
1008 |
1009 | .navlist li nav {
1010 | padding-top: 2.25rem;
1011 | }
1012 |
1013 | .navlist li a {
1014 | padding: 0.5rem;
1015 | }
1016 |
1017 | .linkbar {
1018 | display: block;
1019 | }
1020 |
1021 | .linkbar {
1022 | position: fixed;
1023 | right: 9rem;
1024 | top: 5rem;
1025 | padding: 10px;
1026 | width: 50px;
1027 | z-index: 1;
1028 | }
1029 |
1030 | .linkbar ul {
1031 | list-style: none;
1032 | padding: 0;
1033 | }
1034 |
1035 | .linkbar li {
1036 | display: flex;
1037 | align-items: center;
1038 | justify-content: center;
1039 | height: 4.25rem;
1040 | }
1041 |
1042 | .linkbar nav {
1043 | display: flex;
1044 | flex-direction: column;
1045 | }
1046 |
1047 | .linkbar a {
1048 | color: var(--smoke);
1049 | font-size: 1.4rem;
1050 | }
1051 | }
1052 |
1053 | /*--------------------------------------------------------------
1054 | # Footer
1055 | --------------------------------------------------------------*/
1056 | footer {
1057 | text-align: center;
1058 | color: var(--white);
1059 | padding: 4rem 0 3rem 0;
1060 | background: var(--dark-navy);
1061 | }
1062 | footer .copyright {
1063 | margin-bottom: 0.3rem;
1064 | }
1065 |
1066 | footer .copyright-box {
1067 | color: var(--smoke);
1068 | }
1069 |
1070 | footer .credits {
1071 | margin-bottom: 0.3rem;
1072 | }
1073 |
1074 | footer .credits a {
1075 | color: var(--smoke);
1076 | }
1077 |
1078 | footer a:hover {
1079 | color: var(--white);
1080 | transition: color 0.5s ease-in-out;
1081 | }
1082 |
1083 | /*--------------------------------------------------------------
1084 | # 404 ERROR PAGE
1085 | --------------------------------------------------------------*/
1086 |
1087 | .error-section {
1088 | background-color: var(--dark-navy);
1089 | text-align: center;
1090 | position: absolute;
1091 | top: 35%;
1092 | left: 50%;
1093 | transform: translate(-50%, -50%);
1094 | }
1095 |
1096 | .error-section h1 {
1097 | color: var(--light-lime);
1098 | text-align: center;
1099 | }
1100 |
1101 | .error-section p {
1102 | padding-bottom: 1rem;
1103 | text-align: center;
1104 | }
1105 |
1106 | .error-section a {
1107 | text-align: center;
1108 | color: var(--bone-white);
1109 | padding: 2rem;
1110 | text-decoration: none;
1111 | }
1112 |
1113 | .error-section a:hover {
1114 | color: var(--smoke);
1115 | transition: all 1s ease;
1116 | }
1117 |
--------------------------------------------------------------------------------