├── .gitignore ├── LICENSE.md ├── README.md ├── netlify.toml ├── package-lock.json ├── package.json ├── public ├── index.html ├── manifest.json ├── projectImages │ ├── P1.png │ ├── bike.png │ ├── cyclingfamily.jpeg │ ├── f1.jpeg │ ├── javascript.jpeg │ ├── logo.svg │ ├── mobile-screenshot.png │ ├── mobile-screenshot2.png │ ├── mobile-screenshot3.png │ ├── mobile-screenshot4.png │ ├── p13.png │ ├── planner.png │ ├── planner3.png │ ├── quiz.png │ ├── quiz3.png │ ├── readme.png │ ├── routes3.png │ ├── screenshot.png │ ├── screenshot2.png │ ├── screenshot3.png │ ├── screenshot4.png │ ├── storm.jpeg │ ├── team.png │ ├── team3.png │ ├── teamcelebrating.jpeg │ ├── weather.png │ ├── weather3.png │ └── workplanner.jpeg └── robots.txt └── src ├── App.jsx ├── _data └── projects.json ├── components ├── AboutMe.jsx ├── AnimatedRoutes.jsx ├── Button.jsx ├── ContactInfo.jsx ├── Draw.jsx ├── Footer.jsx ├── Form.jsx ├── Header.jsx ├── Hero.jsx ├── Image.jsx ├── LightDarkToggle.jsx ├── NavLinks.jsx ├── PageHeader.jsx ├── ProjectCard.jsx ├── ProjectList.jsx └── SocialIcons.jsx ├── images ├── aboutme.jpeg ├── close.svg ├── logo.svg ├── me.svg ├── mobile-screenshot.png ├── mobile-screenshot2.png ├── mobile-screenshot3.png ├── mobile-screenshot4.png ├── open.svg ├── screenshot.png ├── screenshot2.png ├── screenshot3.png ├── screenshot4.png └── suit.png ├── index.css ├── index.js └── pages ├── 404 ├── PageNotFound.jsx └── pageNotFound.css ├── contact ├── Contact.js └── contact.css ├── landing ├── Landing.jsx ├── about.css └── hero.css ├── loader └── loader.jsx ├── portfolio ├── Portfolio.jsx ├── [project] │ └── ProjectDetails.jsx └── portfolio.css └── resume ├── Resume.js └── resume.css /.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 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /build 13 | 14 | # misc 15 | .DS_Store 16 | .env 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 | 26 | # Local Netlify folder 27 | .netlify 28 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Michael Yeates 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 | [![Contributors][contributors-shield]][contributors-url] 6 | [![Forks][forks-shield]][forks-url] 7 | [![Stargazers][stars-shield]][stars-url] 8 | [![Issues][issues-shield]][issues-url] 9 | [![MIT License][license-shield]][license-url] 10 | [![LinkedIn][linkedin-shield]][linkedin-url] 11 | 12 | 13 | 14 |
15 |
16 | 17 | Logo 18 | 19 |

michaelyeates.co.uk

20 |

21 |
22 | Crafting Connections through Code: Explore my journey, projects, and passions in this immersive portfolio powered by React.
23 |
24 | Built by: Michael Yeates 25 |
26 |
27 | Explore the docs » 28 |
29 |
30 | View Live Website 31 | · 32 | Report Bug 33 | · 34 | Request Feature 35 |

36 |
37 | 38 | 39 | 40 |
41 | Table of Contents 42 |
    43 |
  1. 44 | About The Project 45 | 50 |
  2. 51 |
  3. 52 | Getting Started 53 | 57 |
  4. 58 |
  5. License
  6. 59 |
  7. Questions
  8. 60 |
61 |
62 | 63 | 64 | 65 | ## About The Project 66 | 67 | ### Photos 68 | 69 | [![My React Portfolio Screen Shot][product-screenshot]](https://michaelyeates.co.uk) 70 | 71 | [![My React Portfolio Screen Shot][product-screenshot2]](https://michaelyeates.co.uk) 72 | 73 | [![My React Portfolio Screen Shot][product-screenshot3]](https://michaelyeates.co.uk) 74 | 75 | [![My React Portfolio Screen Shot][product-screenshot4]](https://michaelyeates.co.uk) 76 | 77 |

back to top

78 | 79 | ### Built With 80 | 81 | ![React](https://img.shields.io/badge/React-20232A?style=for-the-badge&logo=React&logoColor=61DAFB) 82 | 83 |

back to top

84 | 85 | ### Description 86 | 87 | This is more than a portfolio; it's an invitation to explore, learn, and connect. So, immerse yourself and enjoy the journey. 88 | 89 | While I wholeheartedly encourage you to utilise my portfolio as a springboard for your own creative ventures, I would like to kindly ask that, in the spirit of fairness and recognition, you provide attribution to michaelyeates.co.uk. Your support in acknowledging the origin of this portfolio is greatly appreciated. 90 | 91 | I kindly urge you to abstain from portraying either the portfolio or the projects as products of your own authorship. The energy and time devoted to shaping this portfolio reflect my personal journey and aspirations. 92 | 93 | Thank you for your understanding, and for embracing the ethos of creativity, authenticity, and shared respect that underpins our digital community. Your appreciation means a lot! 94 | 95 | If you like what you see, I'd be truly grateful if you consider giving it a star 🌟 96 | 97 |

TL;DR

98 | Feel free to fork this repo for your own purposes, provided you give me credit. 99 | 100 |

back to top

101 | 102 | 103 | 104 | ## Getting Started 105 | 106 | To get a local copy up and running follow these simple example steps. 107 | 108 | ### Prerequisites 109 | 110 | - npm 111 | ```sh 112 | npm install npm@latest -g 113 | ``` 114 | 115 | ### Installation 116 | 117 | 1. Clone the repo 118 | ```sh 119 | git clone https://github.com/mdyeates/my-portfolio.git 120 | ``` 121 | 2. Install NPM packages 122 | ```sh 123 | npm install 124 | ``` 125 | 126 |

back to top

127 | 128 | 129 | 130 | 131 | ## License 132 | 133 | Distributed under the MIT License. See `LICENSE.md` for more information. 134 | 135 |

back to top

136 | 137 | 138 | 139 | 140 | ## Questions 141 | 142 | If you have any inquiries, don't hesitate to reach out to me via socials or by sending an email to michael-yeates@outlook.com 143 | 144 | LinkedIn | GitHub 145 | 146 | Project Link: [https://github.com/mdyeates/my-portfolio](https://github.com/mdyeates/my-portfolio) 147 | 148 |

back to top

149 | 150 | 151 | 152 | [contributors-shield]: https://img.shields.io/github/contributors/mdyeates/my-portfolio.svg?style=for-the-badge 153 | [contributors-url]: https://github.com/mdyeates/my-portfolio/graphs/contributors 154 | [forks-shield]: https://img.shields.io/github/forks/mdyeates/my-portfolio.svg?style=for-the-badge 155 | [forks-url]: https://github.com/mdyeates/my-portfolio/network/members 156 | [stars-shield]: https://img.shields.io/github/stars/mdyeates/my-portfolio.svg?style=for-the-badge 157 | [stars-url]: https://github.com/mdyeates/my-portfolio/stargazers 158 | [issues-shield]: https://img.shields.io/github/issues/mdyeates/my-portfolio.svg?style=for-the-badge 159 | [issues-url]: https://github.com/mdyeates/my-portfolio/issues 160 | [license-shield]: https://img.shields.io/github/license/mdyeates/my-portfolio.svg?style=for-the-badge 161 | [license-url]: https://github.com/mdyeates/my-portfolio/blob/main/LICENSE.md 162 | [linkedin-shield]: https://img.shields.io/badge/-LinkedIn-black.svg?style=for-the-badge&logo=linkedin&colorB=555 163 | [linkedin-url]: https://linkedin.com/in/mdyeates 164 | 165 | 166 | 167 | [product-screenshot]: src/images/screenshot.png 168 | [product-screenshot2]: src/images/screenshot2.png 169 | [product-screenshot3]: src/images/screenshot3.png 170 | [product-screenshot4]: src/images/screenshot4.png 171 | [responsive-screenshot]: src/images/mobile-screenshot.png 172 | [responsive-screenshot2]: src/images/mobile-screenshot2.png 173 | [responsive-screenshot3]: src/images/mobile-screenshot3.png 174 | [responsive-screenshot4]: src/images/mobile-screenshot4.png 175 | -------------------------------------------------------------------------------- /netlify.toml: -------------------------------------------------------------------------------- 1 | [[redirects]] 2 | from = "/*" 3 | to = "/index.html" 4 | status = 200 -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "my-portfolio", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@testing-library/jest-dom": "^5.16.5", 7 | "@testing-library/react": "^13.4.0", 8 | "@testing-library/user-event": "^13.5.0", 9 | "dotenv": "^16.0.3", 10 | "email-validator": "^2.0.4", 11 | "framer-motion": "^10.0.1", 12 | "react": "^18.2.0", 13 | "react-dom": "^18.2.0", 14 | "react-icons": "^4.10.1", 15 | "react-intersection-observer": "^9.4.3", 16 | "react-modal": "^3.16.1", 17 | "react-router-dom": "^6.8.2", 18 | "react-scripts": "5.0.1", 19 | "typewriter-effect": "^2.19.0", 20 | "web-vitals": "^2.1.4" 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 | } 47 | -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 14 | 21 | 28 | Michael Yeates | Software Engineer | Portfolio 29 | 30 | 31 |
32 | 33 | 34 | -------------------------------------------------------------------------------- /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/projectImages/P1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdyeates/my-portfolio/a31294e81b19e6048f38cf764e8f5a0b43d4064c/public/projectImages/P1.png -------------------------------------------------------------------------------- /public/projectImages/bike.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdyeates/my-portfolio/a31294e81b19e6048f38cf764e8f5a0b43d4064c/public/projectImages/bike.png -------------------------------------------------------------------------------- /public/projectImages/cyclingfamily.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdyeates/my-portfolio/a31294e81b19e6048f38cf764e8f5a0b43d4064c/public/projectImages/cyclingfamily.jpeg -------------------------------------------------------------------------------- /public/projectImages/f1.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdyeates/my-portfolio/a31294e81b19e6048f38cf764e8f5a0b43d4064c/public/projectImages/f1.jpeg -------------------------------------------------------------------------------- /public/projectImages/javascript.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdyeates/my-portfolio/a31294e81b19e6048f38cf764e8f5a0b43d4064c/public/projectImages/javascript.jpeg -------------------------------------------------------------------------------- /public/projectImages/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /public/projectImages/mobile-screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdyeates/my-portfolio/a31294e81b19e6048f38cf764e8f5a0b43d4064c/public/projectImages/mobile-screenshot.png -------------------------------------------------------------------------------- /public/projectImages/mobile-screenshot2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdyeates/my-portfolio/a31294e81b19e6048f38cf764e8f5a0b43d4064c/public/projectImages/mobile-screenshot2.png -------------------------------------------------------------------------------- /public/projectImages/mobile-screenshot3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdyeates/my-portfolio/a31294e81b19e6048f38cf764e8f5a0b43d4064c/public/projectImages/mobile-screenshot3.png -------------------------------------------------------------------------------- /public/projectImages/mobile-screenshot4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdyeates/my-portfolio/a31294e81b19e6048f38cf764e8f5a0b43d4064c/public/projectImages/mobile-screenshot4.png -------------------------------------------------------------------------------- /public/projectImages/p13.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdyeates/my-portfolio/a31294e81b19e6048f38cf764e8f5a0b43d4064c/public/projectImages/p13.png -------------------------------------------------------------------------------- /public/projectImages/planner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdyeates/my-portfolio/a31294e81b19e6048f38cf764e8f5a0b43d4064c/public/projectImages/planner.png -------------------------------------------------------------------------------- /public/projectImages/planner3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdyeates/my-portfolio/a31294e81b19e6048f38cf764e8f5a0b43d4064c/public/projectImages/planner3.png -------------------------------------------------------------------------------- /public/projectImages/quiz.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdyeates/my-portfolio/a31294e81b19e6048f38cf764e8f5a0b43d4064c/public/projectImages/quiz.png -------------------------------------------------------------------------------- /public/projectImages/quiz3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdyeates/my-portfolio/a31294e81b19e6048f38cf764e8f5a0b43d4064c/public/projectImages/quiz3.png -------------------------------------------------------------------------------- /public/projectImages/readme.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdyeates/my-portfolio/a31294e81b19e6048f38cf764e8f5a0b43d4064c/public/projectImages/readme.png -------------------------------------------------------------------------------- /public/projectImages/routes3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdyeates/my-portfolio/a31294e81b19e6048f38cf764e8f5a0b43d4064c/public/projectImages/routes3.png -------------------------------------------------------------------------------- /public/projectImages/screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdyeates/my-portfolio/a31294e81b19e6048f38cf764e8f5a0b43d4064c/public/projectImages/screenshot.png -------------------------------------------------------------------------------- /public/projectImages/screenshot2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdyeates/my-portfolio/a31294e81b19e6048f38cf764e8f5a0b43d4064c/public/projectImages/screenshot2.png -------------------------------------------------------------------------------- /public/projectImages/screenshot3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdyeates/my-portfolio/a31294e81b19e6048f38cf764e8f5a0b43d4064c/public/projectImages/screenshot3.png -------------------------------------------------------------------------------- /public/projectImages/screenshot4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdyeates/my-portfolio/a31294e81b19e6048f38cf764e8f5a0b43d4064c/public/projectImages/screenshot4.png -------------------------------------------------------------------------------- /public/projectImages/storm.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdyeates/my-portfolio/a31294e81b19e6048f38cf764e8f5a0b43d4064c/public/projectImages/storm.jpeg -------------------------------------------------------------------------------- /public/projectImages/team.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdyeates/my-portfolio/a31294e81b19e6048f38cf764e8f5a0b43d4064c/public/projectImages/team.png -------------------------------------------------------------------------------- /public/projectImages/team3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdyeates/my-portfolio/a31294e81b19e6048f38cf764e8f5a0b43d4064c/public/projectImages/team3.png -------------------------------------------------------------------------------- /public/projectImages/teamcelebrating.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdyeates/my-portfolio/a31294e81b19e6048f38cf764e8f5a0b43d4064c/public/projectImages/teamcelebrating.jpeg -------------------------------------------------------------------------------- /public/projectImages/weather.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdyeates/my-portfolio/a31294e81b19e6048f38cf764e8f5a0b43d4064c/public/projectImages/weather.png -------------------------------------------------------------------------------- /public/projectImages/weather3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdyeates/my-portfolio/a31294e81b19e6048f38cf764e8f5a0b43d4064c/public/projectImages/weather3.png -------------------------------------------------------------------------------- /public/projectImages/workplanner.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdyeates/my-portfolio/a31294e81b19e6048f38cf764e8f5a0b43d4064c/public/projectImages/workplanner.jpeg -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /src/App.jsx: -------------------------------------------------------------------------------- 1 | import { useState, useEffect } from "react"; 2 | import { Routes, Route, useLocation, Navigate } from "react-router-dom"; 3 | 4 | // Components 5 | import Loader from "./pages/loader/loader"; 6 | import Header from "./components/Header"; 7 | import Landing from "./pages/landing/Landing"; 8 | import Portfolio from "./pages/portfolio/Portfolio"; 9 | import ProjectDetails from "./pages/portfolio/[project]/ProjectDetails"; 10 | import Resume from "./pages/resume/Resume"; 11 | import Contact from "./pages/contact/Contact"; 12 | import PageNotFound from "./pages/404/PageNotFound"; 13 | 14 | /** 15 | * Instructions for Customizing the Portfolio 16 | * ------------------------------------------ 17 | * 1. Adding Your Own Projects: 18 | * - Navigate to the "_data" folder and modify the "projects.json" file to include your projects. 19 | * 20 | * 2. Replacing Project Images: 21 | * - Access the "public/projectImages" directory to replace the my project images with your own. 22 | * 23 | * 3. Handling Form Submissions: 24 | * - To receive form submissions, obtain an API Key. Refer to the Form component for detailed instructions. 25 | * 26 | * For Assistance or Questions: 27 | * ----------------------------- 28 | * If you require assistance or have questions, don't hesitate to reach out via LinkedIn or email. 29 | * 30 | * Support and Star: 31 | * ------------------ 32 | * Enjoying this project? Please consider giving it a star (🌟). 33 | * I'm committed to ongoing updates and feature additions. 34 | * Your suggestions and feedback are invaluable and highly encouraged! 35 | */ 36 | 37 | function App() { 38 | // Personal details for the user 39 | const personalDetails = { 40 | name: "Michael Yeates", 41 | location: "United Kingdom", 42 | email: "michael-yeates@outlook.com", 43 | brand: 44 | "My unique blend of technical expertise, creative thinking, and background in psychology allows me to approach each project with a deep understanding of the end user's perspective, resulting in highly effective user-centred digital products.", 45 | }; 46 | 47 | const location = useLocation(); 48 | 49 | // State to manage loader visibility 50 | const [showLoader, setShowLoader] = useState(true); 51 | 52 | const [originalTitle, setOriginalTitle] = useState(); 53 | 54 | useEffect(() => { 55 | // Hide loader when initial route is loaded 56 | if (location.pathname !== "/") { 57 | setShowLoader(false); 58 | } 59 | 60 | // Store the original document title 61 | if (!originalTitle) { 62 | setOriginalTitle(document.title); 63 | } 64 | 65 | // Handle document title change when tab visibility changes 66 | const handleTabChange = () => { 67 | if (document.hidden) { 68 | document.title = "👋🏻 Git pulling you back in!"; 69 | } else { 70 | document.title = originalTitle; 71 | } 72 | }; 73 | 74 | // Listen for visibility change events 75 | window.addEventListener("visibilitychange", handleTabChange); 76 | return () => window.removeEventListener("visibilitychange", handleTabChange); 77 | }, [location, originalTitle]); 78 | 79 | return ( 80 | <> 81 | {showLoader ? ( 82 | // Show loader until initial route is loaded 83 | 84 | ) : ( 85 | <> 86 | {/* Header */} 87 |
88 | {/* Define routes */} 89 | 90 | } /> 91 | } /> 92 | } /> 93 | 94 | 102 | } 103 | /> 104 | } /> 105 | } /> 106 | {/* Fallback route for unknown paths */} 107 | } /> 108 | 109 | 110 | )} 111 | 112 | ); 113 | } 114 | 115 | export default App; 116 | -------------------------------------------------------------------------------- /src/_data/projects.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "title": "Routes", 4 | "description": "Plan Safe Routes", 5 | "image": "/projectImages/bike.png", 6 | "image2": "/projectImages/cyclingfamily.jpeg", 7 | "image3": "/projectImages/routes3.png", 8 | "body": "During my immersive coding bootcamp experience, I collaborated with a team to develop a sophisticated project — a MVP version with a distinct emphasis on route optimisation. Demonstrating a comprehensive understanding of the project's intricacies, I took charge of the entire route management aspect.\nThe purpose of this initiative was to enhance the safety of young cyclists. Leveraged Leaflet.js and TomTom's Routing/Reverse Geolocation APIs.\nIn practical terms, this translates to the creation of a interactive map. Users can intricately plan their routes, considering factors such as real-time traffic updates, designated bike lanes, and terrain gradients. The result is a visually appealing mapping experience. An adventure plotted with utmost consideration for safety and enjoyment.", 9 | "technologies": ["Leaflet", "jQuery", "Bootstrap", "HTML", "CSS"], 10 | "github": "https://github.com/mdyeates/bike-buddy", 11 | "deployed": "https://norrinradd8.github.io/bike_buddy/", 12 | "bgcolor": "var(--hl2-color)", 13 | "id": "1" 14 | }, 15 | { 16 | "title": "Weather", 17 | "description": "Weather Forecast", 18 | "image": "/projectImages/weather.png", 19 | "image2": "/projectImages/storm.jpeg", 20 | "image3": "/projectImages/weather3.png", 21 | "technologies": ["JavaScript", "jQuery", "HTML", "CSS"], 22 | "body": "This mobile web application leverages the OpenWeatherMap API to deliver real-time weather data for any location. Users can view current conditions including temperature, humidity, and wind speed, as well as a 5-day forecast.\nThe interface automatically adapts between light and dark modes based on the time of day at the given location. Recent searches are stored to enable quick access to frequently checked cities. Geolocation functionality determines the user's position for quick access to local weather.\nBy combining a modern, responsive design with robust weather data pulled from OpenWeatherMap's API, this application aims to provide an intuitive experience for staying up-to-date on the latest weather conditions. The project demonstrates proficiency in integrating third-party APIs, building adaptive interfaces, and crafting performant mobile web applications.", 23 | "github": "https://github.com/mdyeates/weather-app", 24 | "deployed": "https://mdyeates.github.io/weather-app/", 25 | "bgcolor": "var(--hl-color)", 26 | "id": "2" 27 | }, 28 | { 29 | "title": "Generator", 30 | "description": "Effortless web pages", 31 | "image": "/projectImages/team.png", 32 | "image2": "/projectImages/teamcelebrating.jpeg", 33 | "image3": "/projectImages/team3.png", 34 | "technologies": ["JavaScript", "Node", "Bootstrap", "Testing"], 35 | "body": "This Node.js command-line application generates an interactive team dashboard from user-provided data. It accepts information about team members and outputs a dynamic HTML page profiling the entire group.\nRather than relying on static spreadsheets, this tool creates a customizable web page that brings employee data to life. The dashboard auto-populates with profiles for each team member, containing details provided via the command-line input.\nBuilt on Node.js, this project demonstrates skills in crafting web applications using JavaScript-based server-side frameworks. It handles input data dynamically to render an animated, engaging profile of the entire team. The end result is an shareable HTML page that serves as a creative alternative to traditional spreadsheet-based team overviews.", 36 | "github": "https://github.com/mdyeates/team-dashboard", 37 | "deployed": "https://drive.google.com/file/d/1_atUXTM-L9r4NccsrPaaWqPyNu37ftwH/view", 38 | "bgcolor": "#6c4bf4", 39 | "id": "3" 40 | }, 41 | { 42 | "title": "Quiz", 43 | "description": "Test your knowledge", 44 | "image": "/projectImages/quiz.png", 45 | "image2": "/projectImages/javascript.jpeg", 46 | "image3": "/projectImages/quiz3.png", 47 | "technologies": ["JavaScript", "HTML", "CSS"], 48 | "body": "This application tests JavaScript knowledge through a timed, question-based quiz. Users are presented with 5 random questions and have 50 seconds to complete the quiz. The remaining time at completion determines the final score, rewarding both accuracy and speed.\nTo add challenge, the timer is reduced by 10 seconds for each incorrect answer. This creates urgency to answer carefully within the time limit. Questions are drawn randomly from a pool to ensure variety upon each attempt.\nBuilt using JavaScript, this project provides hands-on experience creating a responsive quiz application. Key learning outcomes include: \n➤ Generating random questions from a pool of options\n➤ Implementing a countdown timer with penalty deductions\n➤ Calculating scores based on time remaining\n➤ Providing interactive quiz feedback to users\nOverall, this application allows users to test their JavaScript skills through an engaging, time-pressured quiz interface. Both code knowledge and quick thinking are required to achieve a top score.", 49 | "github": "https://github.com/mdyeates/coding-quiz", 50 | "deployed": "https://mdyeates.github.io/coding-quiz/", 51 | "bgcolor": "#f85781", 52 | "id": "4" 53 | }, 54 | 55 | { 56 | "title": "News", 57 | "description": "F1 Hub", 58 | "image": "/projectImages/P1.png", 59 | "image2": "/projectImages/f1.jpeg", 60 | "image3": "/projectImages/p13.png", 61 | "body": "P1 is a web application designed to be a hub for Formula 1 fans. It brings together news, standings, and an interactive card game in one platform.\nThe app features a responsive layout with animated, polished visuals to provide an immersive user experience. The news section stays up-to-date on the latest happenings in the F1 world.\nThe centerpiece is a card game that puts users' F1 knowledge to the test in a competitive, engaging format. Game mechanics reward expertise in drivers, teams, and racing trivia.\nBuilt using modern web development tools, this project shows skills in:\n➤ Creating visually appealing, animated interfaces\n➤ Integrating real-time data feeds and news\n➤ Developing interactive game mechanics with competitive elements\n➤ Providing an all-in-one web app for a niche audience\nOverall, P1 aims to establish an exciting online space for Formula 1 fans to follow the sport, connect with a community, and test their expert knowledge.", 62 | "technologies": ["React", "Framer Motion", "Bootstrap", "HTML", "CSS"], 63 | "github": "https://github.com/Jake-W95/P1", 64 | "deployed": "https://p1-heroes.netlify.app/", 65 | "bgcolor": "#e47911", 66 | "id": "5" 67 | }, 68 | 69 | { 70 | "title": "Planner", 71 | "description": "Conquer your day", 72 | "image": "/projectImages/planner.png", 73 | "image2": "/projectImages/workplanner.jpeg", 74 | "image3": "/projectImages/planner3.png", 75 | "technologies": ["JavaScript", "Moment", "Bootstrap", "HTML", "CSS"], 76 | "body": "The Daily Planner App aims to help users maximize productivity and organization. Its intuitive interface enables easy scheduling of appointments and meetings, ensuring nothing gets missed during busy work days.\nThe app goes beyond basic calendar features to set the right mood for different times of day. It automatically transitions between color schemes from morning to evening. To-do items persist in the app, allowing users to revisit pending tasks when needed.\nBuilt with modern web development tools, this project demonstrates skills in crafting responsive web applications with dynamic interfaces. Key learning outcomes include implementing time-based design changes, enabling persistent data storage, and creating an intuitive user experience for scheduling and task management.\nBy combining useful calendar functionality with ambient visual changes and persistent to-dos, the Daily Planner App strives to keep users on track, in the right headspace, and focused on conquering each day more effectively. The end result is a tool to help make the most of every hour.", 77 | "github": "https://github.com/mdyeates/daily-planner", 78 | "deployed": "https://mdyeates.github.io/daily-planner/", 79 | "bgcolor": "#3e67ff", 80 | "id": "6" 81 | } 82 | ] 83 | -------------------------------------------------------------------------------- /src/components/AboutMe.jsx: -------------------------------------------------------------------------------- 1 | import { motion } from "framer-motion"; 2 | import { useInView } from "react-intersection-observer"; 3 | import { NavLink } from "react-router-dom"; 4 | import Button from "./Button"; 5 | import aboutMeImg from "../images/aboutme.jpeg"; 6 | 7 | /** 8 | * Represents the About Me section. 9 | * Displays information about the user. 10 | * Not currently in use. 11 | * 12 | * @component 13 | * @param {string} name - The name of the user. 14 | */ 15 | 16 | const AboutMe = ({ name }) => { 17 | // Using react-intersection-observer to determine if the component is in view 18 | const [ref, inView] = useInView({ 19 | threshold: 0.4, 20 | triggerOnce: true, 21 | }); 22 | 23 | // Variants for staggered animations 24 | const staggerVariants = { 25 | initial: { opacity: 0 }, 26 | animate: { 27 | opacity: 1, 28 | transition: { 29 | staggerChildren: 0.3, 30 | }, 31 | }, 32 | }; 33 | 34 | // Variants for paragraph animations 35 | const paragraphVariants = { 36 | initial: { y: 20, opacity: 0 }, 37 | animate: { y: 0, opacity: 1 }, 38 | }; 39 | 40 | return ( 41 |
42 |
43 |
44 | 52 | {/* Display the personal image */} 53 | 54 | 55 |
56 | 57 | {/* Display greeting and job title with animation */} 58 | Nice to meet you! 👋🏻 59 | I'm a Software Engineer at COMPANY. 60 | 61 | {/* Display content description with animation */} 62 | 68 | {/* Paragraphs with animation */} 69 | 70 | Today, I find myself knee-deep in an exhilarating chapter of my journey as a POSITION at the tech 71 | titan, COMPANY. My playground? The captivating 72 | universe of DEPARTMENT. 73 | 74 |
75 | 76 | Here, I don my problem-solving 77 | cape and dive headfirst into real-world challenges, all while relentlessly pursuing a{" "} 78 | DEGREE NAME degree from the UNIVERSITY. So here I 79 | am, juggling bits of binary and real-life conundrums, all while crafting my own success story. 80 | 81 |
82 | 83 | Life is a kaleidoscope of experiences, far beyond the confines of work. When code isn't my focus, I'm 84 | conquering HOBBY, HOBBY, and fueling my love for HOBBY. 85 | 86 |
87 | 88 | {/* Button to view the portfolio */} 89 | 90 |
94 |
95 |
96 |
97 | ); 98 | }; 99 | 100 | export default AboutMe; 101 | -------------------------------------------------------------------------------- /src/components/AnimatedRoutes.jsx: -------------------------------------------------------------------------------- 1 | /** 2 | * No need to seperate this logic at the present time 3 | */ 4 | 5 | // import { Routes, Route, useLocation, Navigate } from "react-router-dom"; 6 | 7 | // import Landing from "../pages/landing/Landing"; 8 | // import About from "../pages/about/About"; 9 | // import Portfolio from "../pages/portfolio/Portfolio"; 10 | // import Contact from "../pages/contact/Contact"; 11 | // import PageNotFound from "../pages/404/PageNotFound"; 12 | 13 | // const AnimatedRoutes = ({ personalDetails }) => { 14 | // const location = useLocation(); 15 | 16 | // return ( 17 | // 18 | // } /> 19 | // 29 | // } 30 | // /> 31 | // } /> 32 | // 36 | // } 37 | // /> 38 | // } /> 39 | // } /> 40 | // 41 | // ); 42 | // }; 43 | 44 | // export default AnimatedRoutes; 45 | -------------------------------------------------------------------------------- /src/components/Button.jsx: -------------------------------------------------------------------------------- 1 | import { motion } from "framer-motion"; 2 | import { FiArrowUpRight } from "react-icons/fi"; 3 | 4 | /** 5 | * Represents a button component with hover and tap animations. 6 | * Displays a name and an arrow icon. 7 | * 8 | * @component 9 | * @param {string} name - The text to display on the button. 10 | * @param {string} color - The background color of the button. 11 | */ 12 | 13 | const Button = ({ name, color }) => { 14 | return ( 15 | 23 | {/* Display the button name */} 24 |

{name}

25 |
26 | {/* Display the arrow icon */} 27 | 28 |
29 |
30 | ); 31 | }; 32 | 33 | export default Button; 34 | -------------------------------------------------------------------------------- /src/components/ContactInfo.jsx: -------------------------------------------------------------------------------- 1 | import { useInView } from "react-intersection-observer"; 2 | import { motion } from "framer-motion"; 3 | 4 | /** 5 | * Represents the contact information section. 6 | * Displays the user's contact details. 7 | * 8 | * @component 9 | * @param {string} name - The name of the user. 10 | * @param {string} email - The email address of the user. 11 | * @param {string} location - The location of the user. 12 | */ 13 | 14 | const ContactInfo = ({ name, email, location }) => { 15 | // Using react-intersection-observer to determine if the component is in view 16 | const [ref, inView] = useInView({ 17 | threshold: 0, 18 | triggerOnce: true, 19 | }); 20 | 21 | return ( 22 | 31 |

Connect With Me

32 |

33 | Looking to build connections and share perspectives with talented developers working to create positive change. 34 |

35 |
    36 | {/* Display Name */} 37 |
  • 38 |
    39 | 40 | {/* Icon for Name */} 41 | {" "} 42 | 43 |
    44 |
    Name
    45 | {name} 46 |
    47 |
    48 |
  • 49 | {/* Display Location */} 50 |
  • 51 |
    52 | 53 | {/* Icon for Location */} 54 | {" "} 55 | 56 |
    57 |
    Location
    58 | {location} 59 |
    60 |
    61 |
  • 62 | {/* Display Email */} 63 |
  • 64 |
    65 | 66 | {/* Icon for Email */} 67 | {" "} 68 | 69 |
    70 |
    Email
    71 | {/* Link to email */} 72 | 73 | {email} 74 | 75 |
    76 |
    77 |
  • 78 |
79 |
80 | ); 81 | }; 82 | 83 | export default ContactInfo; 84 | -------------------------------------------------------------------------------- /src/components/Draw.jsx: -------------------------------------------------------------------------------- 1 | import { useRef, useEffect } from "react"; 2 | 3 | /** 4 | * Represents a drawing canvas that creates trails as the user moves the mouse. 5 | * 6 | * @component 7 | */ 8 | 9 | const Draw = () => { 10 | // Create a reference to the canvas element 11 | const canvasRef = useRef(null); 12 | // Store the last position of the mouse 13 | const lastPositionRef = useRef(null); 14 | 15 | useEffect(() => { 16 | // Get the canvas and its context 17 | const canvas = canvasRef.current; 18 | const ctx = canvas.getContext("2d"); 19 | 20 | // Add a mousemove event listener to the canvas 21 | canvas.addEventListener("mousemove", (e) => { 22 | // Set the drawing settings 23 | ctx.lineWidth = 0.2; 24 | ctx.lineCap = "round"; 25 | ctx.strokeStyle = "#fff"; 26 | ctx.globalAlpha = 1; 27 | 28 | const { pageX, pageY } = e; 29 | 30 | if (lastPositionRef.current) { 31 | const { x, y } = lastPositionRef.current; 32 | // Move the pen to the last position and draw a line to the current position 33 | ctx.moveTo(x - canvas.offsetLeft, y - canvas.offsetTop); 34 | ctx.lineTo(pageX - canvas.offsetLeft, pageY - canvas.offsetTop); 35 | ctx.stroke(); 36 | } 37 | 38 | // Update the last position 39 | lastPositionRef.current = { x: pageX, y: pageY }; 40 | }); 41 | }, []); // Empty dependency array ensures this effect runs only once 42 | 43 | return ( 44 | 50 | ); 51 | }; 52 | 53 | export default Draw; 54 | -------------------------------------------------------------------------------- /src/components/Footer.jsx: -------------------------------------------------------------------------------- 1 | import { useState, useEffect } from "react"; 2 | import { AiOutlineStar } from "react-icons/ai"; 3 | import { BiGitRepoForked } from "react-icons/bi"; 4 | import { motion } from "framer-motion"; 5 | import SocialIcons from "./SocialIcons"; 6 | 7 | /** 8 | * Represents the footer section of the website. 9 | * 10 | * @component 11 | */ 12 | 13 | const Footer = () => { 14 | const date = new Date(); 15 | const currentYear = date.getFullYear(); 16 | 17 | // State to hold GitHub information 18 | const [gitHubInfo, setGitHubInfo] = useState({ 19 | stars: null, 20 | forks: null, 21 | }); 22 | 23 | useEffect(() => { 24 | // Fetch GitHub repository information 25 | fetch("https://api.github.com/repos/mdyeates/my-portfolio") 26 | .then((res) => res.json()) 27 | .then((json) => { 28 | const { stargazers_count, forks_count } = json; 29 | setGitHubInfo({ 30 | stars: stargazers_count, 31 | forks: forks_count, 32 | }); 33 | }) 34 | .catch((e) => console.error(e)); 35 | }, []); 36 | 37 | // Variants for button animation 38 | const buttonVariants = { 39 | hover: { 40 | scale: 1.05, 41 | }, 42 | tap: { 43 | scale: 1, 44 | }, 45 | }; 46 | 47 | return ( 48 | 86 | ); 87 | }; 88 | 89 | export default Footer; 90 | -------------------------------------------------------------------------------- /src/components/Form.jsx: -------------------------------------------------------------------------------- 1 | import { useInView } from "react-intersection-observer"; 2 | import { motion } from "framer-motion"; 3 | import { useState } from "react"; 4 | import validator from "email-validator"; 5 | import Button from "./Button"; 6 | 7 | /** 8 | * Contact Form Component 9 | * ---------------------- 10 | * This component represents a fully functional contact form. 11 | * 12 | * @component 13 | * 14 | * Form Submission API Key: 15 | * ------------------------ 16 | * To enable form submissions, obtain your API Key from https://web3forms.com/ 17 | * 18 | * Follow these steps: 19 | * 1. Create a .env file in the root directory. 20 | * 2. Copy and paste the following line into your .env file, replacing with your API key: 21 | * REACT_APP_ACCESS_KEY="Your API Key" 22 | * 23 | */ 24 | 25 | const Form = () => { 26 | const [ref, inView] = useInView({ 27 | threshold: 0, 28 | triggerOnce: true, 29 | }); 30 | 31 | // State for handling form submission statuses and errors 32 | const [success, setSuccess] = useState(false); 33 | const [sending, setSending] = useState(false); 34 | const [failed, setFailed] = useState(false); 35 | const [nameError, setNameError] = useState(false); 36 | const [emailError, setEmailError] = useState(false); 37 | const [subjectError, setSubjectError] = useState(false); 38 | const [messageError, setMessageError] = useState(false); 39 | 40 | // State for form data 41 | const [formData, setFormData] = useState({ 42 | name: "", 43 | email: "", 44 | subject: "", 45 | message: "", 46 | access_key: process.env.REACT_APP_ACCESS_KEY, 47 | }); 48 | 49 | // Handle input change 50 | const handleChange = (e) => { 51 | setFormData({ 52 | ...formData, 53 | [e.target.name]: e.target.value, 54 | }); 55 | }; 56 | 57 | // Handle input focus to reset error state 58 | const handleInputFocus = (errorStateSetter) => { 59 | errorStateSetter(false); 60 | }; 61 | 62 | // Handle form submission 63 | const handleSubmit = (e) => { 64 | e.preventDefault(); 65 | 66 | // Validate and set error states 67 | formData.name === "" ? setNameError(true) : setNameError(false); 68 | formData.email === "" || !validator.validate(formData.email) ? setEmailError(true) : setEmailError(false); 69 | formData.subject === "" ? setSubjectError(true) : setSubjectError(false); 70 | formData.message === "" ? setMessageError(true) : setMessageError(false); 71 | 72 | // Handle invalid form 73 | if ( 74 | nameError || 75 | emailError || 76 | messageError || 77 | subjectError || 78 | !validator.validate(formData.email) || 79 | formData.name === "" || 80 | formData.email === "" || 81 | formData.subject === "" || 82 | formData.message === "" 83 | ) { 84 | setFormData({ 85 | ...formData, 86 | email: "", 87 | }); 88 | setSending(false); 89 | setFailed(true); 90 | return; 91 | } 92 | 93 | // Form submission in progress 94 | setSending(true); 95 | 96 | const data = JSON.stringify(formData); 97 | 98 | // Send form data to an API endpoint 99 | fetch("https://api.web3forms.com/submit", { 100 | method: "POST", 101 | headers: { 102 | "Content-Type": "application/json", 103 | Accept: "application/json", 104 | }, 105 | body: data, 106 | }) 107 | .then((res) => res.json()) 108 | .then((data) => { 109 | // Form submission success 110 | setSending(false); 111 | setSuccess(true); 112 | setFailed(false); 113 | setFormData({ 114 | ...formData, 115 | name: "", 116 | email: "", 117 | subject: "", 118 | message: "", 119 | }); 120 | setTimeout(() => { 121 | setSuccess(false); 122 | }, 3000); 123 | }) 124 | .catch((err) => { 125 | // Form submission failed 126 | console.log(err); 127 | setSending(false); 128 | setFailed(true); 129 | }); 130 | }; 131 | 132 | // Determine button text based on status 133 | const handleButtonText = () => { 134 | if (sending) { 135 | return "Please wait..."; 136 | } else if (success) { 137 | return "Message Sent"; 138 | } else if (failed || nameError || messageError || emailError || subjectError) { 139 | return "Try again"; 140 | } else { 141 | return "Send Message"; 142 | } 143 | }; 144 | 145 | return ( 146 | 155 |

Send a Message

156 | {/* Input fields */} 157 |
158 | { 162 | handleInputFocus(setNameError); 163 | }} 164 | onChange={handleChange} 165 | value={formData.name} 166 | id="contactName" 167 | name="name" 168 | placeholder={`${nameError ? "Please enter your name" : "Name"}`} 169 | autoComplete="name" 170 | /> 171 |
172 |
173 | { 177 | handleInputFocus(setEmailError); 178 | }} 179 | onChange={handleChange} 180 | value={formData.email} 181 | id="contactEmail" 182 | name="email" 183 | placeholder={`${emailError ? "Please enter a valid email" : "Email"}`} 184 | autoComplete="email" 185 | /> 186 |
187 |
188 | { 192 | handleInputFocus(setSubjectError); 193 | }} 194 | onChange={handleChange} 195 | value={formData.subject} 196 | id="contactSubject" 197 | name="subject" 198 | placeholder={`${subjectError ? "Please enter a subject" : "Subject"}`} 199 | autoComplete="off" 200 | /> 201 |
202 |
203 | 216 |
217 | {/* Form submission button */} 218 | 219 | 67 | ); 68 | }; 69 | 70 | export default LightDarkToggle; 71 | -------------------------------------------------------------------------------- /src/components/NavLinks.jsx: -------------------------------------------------------------------------------- 1 | import { useState } from "react"; 2 | import { NavLink } from "react-router-dom"; 3 | import { motion } from "framer-motion"; 4 | import LightDarkToggle from "./LightDarkToggle"; 5 | import openMenu from "../images/open.svg"; 6 | import closeMenu from "../images/close.svg"; 7 | 8 | /** 9 | * Represents navigation links and menu toggles. 10 | * 11 | * @component 12 | */ 13 | 14 | const NavLinks = () => { 15 | // State to track whether the menu is open or closed 16 | const [isMenuOpen, setIsMenuOpen] = useState(false); 17 | 18 | return ( 19 | <> 20 | {/* Menu toggle button */} 21 | 28 | {/* Navigation links */} 29 | 71 | 72 | ); 73 | }; 74 | 75 | export default NavLinks; 76 | -------------------------------------------------------------------------------- /src/components/PageHeader.jsx: -------------------------------------------------------------------------------- 1 | import { motion } from "framer-motion"; 2 | 3 | /** 4 | * Represents a page header with title and description. 5 | * 6 | * @component 7 | * @param {string} title - The title of the page. 8 | * @param {string} description - The description of the page. 9 | */ 10 | 11 | const PageHeader = ({ title, description }) => ( 12 | <> 13 | {/* Page description */} 14 | 20 | {description} 21 | 22 | {/* Page title */} 23 | 29 | {title} 30 | 31 | 32 | ); 33 | 34 | export default PageHeader; 35 | -------------------------------------------------------------------------------- /src/components/ProjectCard.jsx: -------------------------------------------------------------------------------- 1 | import { motion } from "framer-motion"; 2 | import { useInView } from "react-intersection-observer"; 3 | import { Link } from "react-router-dom"; 4 | import { FiArrowUpRight } from "react-icons/fi"; 5 | import Image from "./Image"; 6 | 7 | /** 8 | * Represents a project card component. 9 | * 10 | * @component 11 | * @param {string} title - The title of the project. 12 | * @param {string} image - The image source for the project thumbnail. 13 | * @param {string} color - The background color of the project card. 14 | * @param {number} id - The unique identifier of the project. 15 | */ 16 | 17 | const ProjectCard = ({ title, image, color, id }) => { 18 | const [ref, inView] = useInView({ 19 | threshold: 0.5, 20 | triggerOnce: true, 21 | }); 22 | 23 | const variants = { 24 | hidden: { y: "10vw", opacity: 0 }, 25 | visible: { y: 0, opacity: 1 }, 26 | }; 27 | 28 | return ( 29 | 30 | 38 |
{}} 42 | > 43 |
44 |

{title}

45 | 46 | View Work 47 | 48 |
49 |
50 | Laptop displaying the application 51 |
52 |
53 |
54 | 55 | ); 56 | }; 57 | 58 | export default ProjectCard; 59 | -------------------------------------------------------------------------------- /src/components/ProjectList.jsx: -------------------------------------------------------------------------------- 1 | import ProjectCard from "./ProjectCard"; 2 | import projects from "../_data/projects.json"; 3 | 4 | /** 5 | * Represents a list of project cards. 6 | * 7 | * This component maps over the projects data and generates 8 | * a ProjectCard component for each project. 9 | * 10 | * @component 11 | */ 12 | 13 | const ProjectList = () => 14 | projects.map((project) => ( 15 | 16 | )); 17 | 18 | export default ProjectList; 19 | -------------------------------------------------------------------------------- /src/components/SocialIcons.jsx: -------------------------------------------------------------------------------- 1 | import { motion } from "framer-motion"; 2 | 3 | /** 4 | * Represents a component displaying social media icons. 5 | * 6 | * This component displays social media icons with animation effects. 7 | * 8 | * @component 9 | */ 10 | 11 | const SocialIcons = () => { 12 | // Define styles for the icons 13 | const styles = { 14 | icon: { 15 | textDecoration: "none", 16 | fontSize: "22px", 17 | padding: "10px", 18 | transition: "0.2s ease-in", 19 | }, 20 | }; 21 | 22 | return ( 23 | 69 | ); 70 | }; 71 | 72 | export default SocialIcons; 73 | -------------------------------------------------------------------------------- /src/images/aboutme.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdyeates/my-portfolio/a31294e81b19e6048f38cf764e8f5a0b43d4064c/src/images/aboutme.jpeg -------------------------------------------------------------------------------- /src/images/close.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /src/images/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /src/images/me.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/images/mobile-screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdyeates/my-portfolio/a31294e81b19e6048f38cf764e8f5a0b43d4064c/src/images/mobile-screenshot.png -------------------------------------------------------------------------------- /src/images/mobile-screenshot2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdyeates/my-portfolio/a31294e81b19e6048f38cf764e8f5a0b43d4064c/src/images/mobile-screenshot2.png -------------------------------------------------------------------------------- /src/images/mobile-screenshot3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdyeates/my-portfolio/a31294e81b19e6048f38cf764e8f5a0b43d4064c/src/images/mobile-screenshot3.png -------------------------------------------------------------------------------- /src/images/mobile-screenshot4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdyeates/my-portfolio/a31294e81b19e6048f38cf764e8f5a0b43d4064c/src/images/mobile-screenshot4.png -------------------------------------------------------------------------------- /src/images/open.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /src/images/screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdyeates/my-portfolio/a31294e81b19e6048f38cf764e8f5a0b43d4064c/src/images/screenshot.png -------------------------------------------------------------------------------- /src/images/screenshot2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdyeates/my-portfolio/a31294e81b19e6048f38cf764e8f5a0b43d4064c/src/images/screenshot2.png -------------------------------------------------------------------------------- /src/images/screenshot3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdyeates/my-portfolio/a31294e81b19e6048f38cf764e8f5a0b43d4064c/src/images/screenshot3.png -------------------------------------------------------------------------------- /src/images/screenshot4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdyeates/my-portfolio/a31294e81b19e6048f38cf764e8f5a0b43d4064c/src/images/screenshot4.png -------------------------------------------------------------------------------- /src/images/suit.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdyeates/my-portfolio/a31294e81b19e6048f38cf764e8f5a0b43d4064c/src/images/suit.png -------------------------------------------------------------------------------- /src/index.css: -------------------------------------------------------------------------------- 1 | @import url("https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;700&display=swap"); 2 | 3 | /* --- VARIABLES --- */ 4 | :root { 5 | --bg-color: #131a22; 6 | --bg2-color: #232f3e19; 7 | --hl-color: #48a3c6; 8 | --hl2-color: #ea5b5c; 9 | --text-color: #f3f3f3; 10 | --secondary-text-color: #999999; 11 | --grey: #9f9f9f76; 12 | --spacing: 50px; 13 | --sm-spacing: 30px; 14 | } 15 | 16 | /* --- ANIMATIONS --- */ 17 | 18 | @keyframes slideDown { 19 | 0% { 20 | transform: translateY(0); 21 | opacity: 0; 22 | } 23 | 100% { 24 | transform: translateY(1%); 25 | opacity: 1; 26 | } 27 | } 28 | 29 | @keyframes fadeInAnimation { 30 | from { 31 | opacity: 0; 32 | } 33 | to { 34 | opacity: 0.5; 35 | } 36 | } 37 | @keyframes spin { 38 | 0% { 39 | transform: rotate(0deg); 40 | } 41 | 100% { 42 | transform: rotate(360deg); 43 | } 44 | } 45 | 46 | /* --- RESETS --- */ 47 | 48 | * { 49 | margin: 0; 50 | padding: 0; 51 | box-sizing: border-box; 52 | } 53 | 54 | html, 55 | body, 56 | #root { 57 | height: 100%; 58 | background-color: var(--bg-color); 59 | } 60 | 61 | body { 62 | font-family: "Poppins", sans-serif; 63 | } 64 | 65 | /* --- GLOBAL STYLES --- */ 66 | 67 | ::selection { 68 | background-color: var(--hl-color); 69 | color: var(--text-color); 70 | } 71 | .pageTitle { 72 | color: var(--text-color); 73 | font-size: 36px; 74 | border-bottom: 3px solid var(--hl-color); 75 | margin-bottom: var(--spacing); 76 | letter-spacing: -1.5px; 77 | } 78 | 79 | .pageDescription { 80 | color: var(--grey); 81 | font-size: 14px; 82 | letter-spacing: 1px; 83 | } 84 | 85 | .socialIcons .icon { 86 | color: var(--text-color); 87 | } 88 | 89 | .socialIcons .icon:hover { 90 | scale: 1.2; 91 | color: var(--hl-color); 92 | } 93 | 94 | .btn { 95 | color: #f9f9f9; 96 | background-color: var(--hl-color); 97 | border: none; 98 | font-size: 14px; 99 | padding: 12px var(--sm-spacing); 100 | border-radius: 30px; 101 | margin-top: 10px; 102 | cursor: pointer; 103 | display: flex; 104 | align-items: center; 105 | justify-content: center; 106 | } 107 | 108 | .btn:hover { 109 | background-color: var(--hl2-color); 110 | } 111 | 112 | .btn:hover .arrow-icon { 113 | transform: translate(3px, -3px); 114 | } 115 | 116 | a { 117 | text-decoration: none; 118 | } 119 | 120 | .spinner { 121 | border: 4px solid rgba(255, 255, 255, 0.1); 122 | border-top: 4px solid var(--text-color); 123 | border-radius: 50%; 124 | width: 30px; 125 | height: 30px; 126 | animation: spin 1s linear infinite; 127 | margin: 0 auto; 128 | } 129 | 130 | .fadeIn { 131 | animation: fadeInAnimation 0.5s ease-in; 132 | } 133 | 134 | /* --- MOBILE STYLES --- */ 135 | 136 | /* --- HEADER --- */ 137 | header { 138 | padding: var(--sm-spacing) var(--spacing); 139 | color: var(--text-color); 140 | display: flex; 141 | justify-content: space-between; 142 | align-items: center; 143 | margin-left: 10px; 144 | } 145 | 146 | .logo { 147 | height: 30px; 148 | } 149 | 150 | .links { 151 | display: none; 152 | position: absolute; 153 | top: 93px; 154 | right: 0px; 155 | background-color: var(--bg-color); 156 | width: 100%; 157 | padding: 10px; 158 | z-index: 999; 159 | border-radius: 10px; 160 | } 161 | 162 | .links a { 163 | color: var(--text-color); 164 | font-size: 15px; 165 | display: flex; 166 | justify-content: center; 167 | padding: 20px 0; 168 | position: relative; 169 | } 170 | 171 | .links a:hover { 172 | color: white; 173 | } 174 | 175 | .links.open { 176 | display: block; 177 | animation: slideDown 0.5s ease-in-out forwards; 178 | } 179 | 180 | .dropdown-toggle { 181 | background: none; 182 | border: none; 183 | font-size: 16px; 184 | cursor: pointer; 185 | } 186 | 187 | .openMenu, 188 | .closeMenu { 189 | height: 20px; 190 | transition: transform 0.5s ease-in-out; 191 | } 192 | 193 | .closeMenu { 194 | transform: rotate(180deg); 195 | cursor: pointer; 196 | } 197 | 198 | .links .closed { 199 | background-color: transparent; 200 | } 201 | 202 | .toggleMode { 203 | background: none; 204 | border: none; 205 | outline: none; 206 | color: var(--text-color); 207 | margin-left: 10px; 208 | display: none; 209 | } 210 | 211 | .toggleIcon { 212 | scale: 2; 213 | cursor: pointer; 214 | } 215 | 216 | .toggleIcon:hover { 217 | color: var(--hl-color); 218 | } 219 | 220 | /* --- FOOTER --- */ 221 | footer { 222 | font-size: 14px; 223 | display: flex; 224 | flex-direction: column; 225 | align-items: center; 226 | justify-content: center; 227 | padding: var(--spacing); 228 | text-align: center; 229 | color: var(--text-color); 230 | } 231 | 232 | footer p span { 233 | color: var(--text-color); 234 | } 235 | 236 | footer .footer-link { 237 | text-decoration: none; 238 | color: var(--hl-color); 239 | padding-top: 10px; 240 | } 241 | footer .footer-link:hover { 242 | color: var(--hl2-color); 243 | } 244 | 245 | footer .socialIcons { 246 | padding-top: 10px; 247 | } 248 | 249 | /* --- MEDIA QUERIES --- */ 250 | @media (min-width: 750px) { 251 | .links { 252 | display: flex; 253 | background-color: var(--bg-color); 254 | justify-content: flex-end; 255 | position: relative; 256 | top: auto; 257 | width: 0; 258 | left: 0; 259 | padding: 0 10px; 260 | } 261 | 262 | .links a { 263 | padding: 0px; 264 | margin-left: 3px; 265 | } 266 | 267 | .links div:not(:last-child) { 268 | margin-right: 20px; 269 | } 270 | 271 | .links.open { 272 | display: flex; 273 | } 274 | 275 | .dropdown-toggle { 276 | display: none; 277 | } 278 | 279 | .links a:before { 280 | content: ""; 281 | position: absolute; 282 | bottom: -5px; 283 | left: 0; 284 | width: 0; 285 | height: 1px; 286 | background-color: var(--text-color); 287 | transition: width 0.3s ease-in-out; 288 | } 289 | 290 | .links a:hover:before { 291 | width: 100%; 292 | } 293 | 294 | .links a.active:before { 295 | content: ""; 296 | position: absolute; 297 | bottom: -5px; 298 | left: 0; 299 | width: 0; 300 | height: 1px; 301 | background-color: var(--text-color); 302 | transition: width 0.3s ease-in-out; 303 | width: 100%; 304 | } 305 | 306 | .closed { 307 | background-color: transparent; 308 | } 309 | 310 | .toggleMode { 311 | display: inline-block; 312 | } 313 | } 314 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import ReactDOM from "react-dom/client"; 3 | import "./index.css"; 4 | import "./pages/landing/hero.css"; 5 | import "./pages/landing/about.css"; 6 | import "./pages/portfolio/portfolio.css"; 7 | import "./pages/resume/resume.css"; 8 | import "./pages/contact/contact.css"; 9 | import "./pages/404/pageNotFound.css"; 10 | 11 | import App from "./App"; 12 | 13 | import { BrowserRouter } from "react-router-dom"; 14 | 15 | const root = ReactDOM.createRoot(document.getElementById("root")); 16 | root.render( 17 | 18 | 19 | 20 | 21 | 22 | ); 23 | -------------------------------------------------------------------------------- /src/pages/404/PageNotFound.jsx: -------------------------------------------------------------------------------- 1 | import { Link } from "react-router-dom"; 2 | import PageHeader from "../../components/PageHeader"; 3 | import Button from "../../components/Button"; 4 | 5 | /** 6 | * Represents the 404 Page Not Found component. 7 | * This component is displayed when a user tries to access a non-existent page. 8 | * 9 | * @component 10 | */ 11 | 12 | const PageNotFound = () => { 13 | return ( 14 |
15 | {/* Display the page header */} 16 | 17 | 18 |
19 |
20 |
21 | {/* Display a message indicating the page was not found */} 22 |

Sorry, the page you are looking for does not exist.

23 |

Please double-check the URL or navigate to another section of the site.

24 | 25 | {/* Provide a link back to the home page */} 26 | 27 |
30 |
31 |
32 |
33 | ); 34 | }; 35 | 36 | export default PageNotFound; 37 | -------------------------------------------------------------------------------- /src/pages/404/pageNotFound.css: -------------------------------------------------------------------------------- 1 | .error { 2 | display: flex; 3 | flex-direction: column; 4 | align-items: center; 5 | margin-top: var(--spacing); 6 | } 7 | 8 | .error .pageTitle { 9 | border-bottom-color: var(--hl2-color); 10 | } 11 | 12 | .error-description { 13 | color: var(--hl2-color); 14 | } 15 | 16 | .error .btn { 17 | margin-top: 32px; 18 | } 19 | -------------------------------------------------------------------------------- /src/pages/contact/Contact.js: -------------------------------------------------------------------------------- 1 | import PageHeader from "../../components/PageHeader"; 2 | import Form from "../../components/Form"; 3 | import ContactInfo from "../../components/ContactInfo"; 4 | import Footer from "../../components/Footer"; 5 | 6 | /** 7 | * Represents the Contact page component. 8 | * Displays a contact form and contact information side by side. 9 | * 10 | * @component 11 | * @param {string} name - The name of the contact person. 12 | * @param {string} email - The email address of the contact person. 13 | * @param {string} location - The location of the contact person. 14 | */ 15 | 16 | const Contact = ({ name, email, location }) => { 17 | return ( 18 | <> 19 | {/* Main Contact Page */} 20 |
21 | {/* Display the page header */} 22 | 23 | 24 |
25 |
26 | {/* Display the contact form */} 27 |
28 |
29 |
30 | 31 | {/* Display the contact information */} 32 |
33 | 34 |
35 |
36 |
37 |
38 |