├── public ├── _redirects ├── robots.txt ├── favicon.ico ├── logo192.png ├── logo512.png ├── manifest.json └── index.html ├── src ├── react-app-env.d.ts ├── assets │ ├── images │ │ ├── bg-dark.png │ │ ├── mock01.png │ │ ├── mock02.png │ │ ├── mock03.png │ │ ├── mock04.png │ │ ├── mock05.png │ │ ├── mock06.png │ │ ├── mock07.png │ │ ├── mock08.png │ │ ├── mock09.png │ │ ├── mock10.png │ │ ├── bg-light.png │ │ └── screenshot.png │ └── styles │ │ ├── Footer.scss │ │ ├── Timeline.scss │ │ ├── Contact.scss │ │ ├── Project.scss │ │ ├── Expertise.scss │ │ └── Main.scss ├── setupTests.ts ├── App.test.tsx ├── components │ ├── index.js │ ├── Footer.tsx │ ├── Main.tsx │ ├── FadeIn.tsx │ ├── Timeline.tsx │ ├── Expertise.tsx │ ├── Contact.tsx │ ├── Navigation.tsx │ └── Project.tsx ├── reportWebVitals.ts ├── index.tsx ├── App.tsx └── index.scss ├── .gitignore ├── tsconfig.json ├── LICENSE ├── package.json └── README.md /public/_redirects: -------------------------------------------------------------------------------- 1 | /* /index.html 200 -------------------------------------------------------------------------------- /src/react-app-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NightStar1208/React-Portfolio/HEAD/public/favicon.ico -------------------------------------------------------------------------------- /public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NightStar1208/React-Portfolio/HEAD/public/logo192.png -------------------------------------------------------------------------------- /public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NightStar1208/React-Portfolio/HEAD/public/logo512.png -------------------------------------------------------------------------------- /src/assets/images/bg-dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NightStar1208/React-Portfolio/HEAD/src/assets/images/bg-dark.png -------------------------------------------------------------------------------- /src/assets/images/mock01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NightStar1208/React-Portfolio/HEAD/src/assets/images/mock01.png -------------------------------------------------------------------------------- /src/assets/images/mock02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NightStar1208/React-Portfolio/HEAD/src/assets/images/mock02.png -------------------------------------------------------------------------------- /src/assets/images/mock03.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NightStar1208/React-Portfolio/HEAD/src/assets/images/mock03.png -------------------------------------------------------------------------------- /src/assets/images/mock04.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NightStar1208/React-Portfolio/HEAD/src/assets/images/mock04.png -------------------------------------------------------------------------------- /src/assets/images/mock05.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NightStar1208/React-Portfolio/HEAD/src/assets/images/mock05.png -------------------------------------------------------------------------------- /src/assets/images/mock06.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NightStar1208/React-Portfolio/HEAD/src/assets/images/mock06.png -------------------------------------------------------------------------------- /src/assets/images/mock07.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NightStar1208/React-Portfolio/HEAD/src/assets/images/mock07.png -------------------------------------------------------------------------------- /src/assets/images/mock08.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NightStar1208/React-Portfolio/HEAD/src/assets/images/mock08.png -------------------------------------------------------------------------------- /src/assets/images/mock09.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NightStar1208/React-Portfolio/HEAD/src/assets/images/mock09.png -------------------------------------------------------------------------------- /src/assets/images/mock10.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NightStar1208/React-Portfolio/HEAD/src/assets/images/mock10.png -------------------------------------------------------------------------------- /src/assets/images/bg-light.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NightStar1208/React-Portfolio/HEAD/src/assets/images/bg-light.png -------------------------------------------------------------------------------- /src/assets/images/screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NightStar1208/React-Portfolio/HEAD/src/assets/images/screenshot.png -------------------------------------------------------------------------------- /src/setupTests.ts: -------------------------------------------------------------------------------- 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/App.test.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { render, screen } from '@testing-library/react'; 3 | import App from './App'; 4 | 5 | test('renders learn react link', () => { 6 | render(); 7 | const linkElement = screen.getByText(/learn react/i); 8 | expect(linkElement).toBeInTheDocument(); 9 | }); 10 | -------------------------------------------------------------------------------- /src/assets/styles/Footer.scss: -------------------------------------------------------------------------------- 1 | footer { 2 | text-align: center; 3 | padding-top: 50px; 4 | padding-bottom: 50px; 5 | 6 | svg { 7 | width: 1.1em !important; 8 | height: 1.1em !important; 9 | margin-right: 10px; 10 | } 11 | 12 | p { 13 | font-size: 0.8em; 14 | } 15 | } -------------------------------------------------------------------------------- /src/components/index.js: -------------------------------------------------------------------------------- 1 | export { default as Navigation } from "./Navigation"; 2 | export { default as Footer } from "./Footer"; 3 | export { default as Main } from "./Main"; 4 | export { default as Expertise } from "./Expertise"; 5 | export { default as Timeline } from "./Timeline"; 6 | export { default as Project } from "./Project"; 7 | export { default as Contact } from "./Contact"; -------------------------------------------------------------------------------- /.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.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | -------------------------------------------------------------------------------- /src/reportWebVitals.ts: -------------------------------------------------------------------------------- 1 | import { ReportHandler } from 'web-vitals'; 2 | 3 | const reportWebVitals = (onPerfEntry?: ReportHandler) => { 4 | if (onPerfEntry && onPerfEntry instanceof Function) { 5 | import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => { 6 | getCLS(onPerfEntry); 7 | getFID(onPerfEntry); 8 | getFCP(onPerfEntry); 9 | getLCP(onPerfEntry); 10 | getTTFB(onPerfEntry); 11 | }); 12 | } 13 | }; 14 | 15 | export default reportWebVitals; 16 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /src/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom/client'; 3 | import './index.scss'; 4 | import App from './App'; 5 | import reportWebVitals from './reportWebVitals'; 6 | 7 | const root = ReactDOM.createRoot( 8 | document.getElementById('root') as HTMLElement 9 | ); 10 | root.render( 11 | 12 | 13 | 14 | ); 15 | 16 | // If you want to start measuring performance in your app, pass a function 17 | // to log results (for example: reportWebVitals(console.log)) 18 | // or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals 19 | reportWebVitals(); 20 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": [ 5 | "dom", 6 | "dom.iterable", 7 | "esnext" 8 | ], 9 | "allowJs": true, 10 | "skipLibCheck": true, 11 | "esModuleInterop": true, 12 | "allowSyntheticDefaultImports": true, 13 | "strict": true, 14 | "forceConsistentCasingInFileNames": true, 15 | "noFallthroughCasesInSwitch": true, 16 | "module": "esnext", 17 | "moduleResolution": "node", 18 | "resolveJsonModule": true, 19 | "isolatedModules": true, 20 | "noEmit": true, 21 | "jsx": "react-jsx" 22 | }, 23 | "include": [ 24 | "src" 25 | ] 26 | } 27 | -------------------------------------------------------------------------------- /src/components/Footer.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import GitHubIcon from '@mui/icons-material/GitHub'; 3 | import LinkedInIcon from '@mui/icons-material/LinkedIn'; 4 | import '../assets/styles/Footer.scss' 5 | 6 | function Footer() { 7 | return ( 8 |
9 |
10 | 11 | 12 |
13 |

A portfolio designed & built by Yuji Sato with 💜

14 |
15 | ); 16 | } 17 | 18 | export default Footer; -------------------------------------------------------------------------------- /src/assets/styles/Timeline.scss: -------------------------------------------------------------------------------- 1 | .vertical-timeline span { 2 | color: white; 3 | font-family: 'Courier Prime', monospace; 4 | } 5 | 6 | .vertical-timeline p { 7 | color: rgb(39, 40, 34); 8 | } 9 | 10 | .vertical-timeline-element p { 11 | color: rgb(39, 40, 34); 12 | } 13 | 14 | .vertical-timeline-element h3 { 15 | color: rgb(39, 40, 34); 16 | } 17 | 18 | .vertical-timeline-element h4 { 19 | color: rgb(39, 40, 34); 20 | } 21 | 22 | .vertical-timeline-element span { 23 | color: rgb(39, 40, 34); 24 | } 25 | 26 | .vertical-timeline-element .vertical-timeline-element-date { 27 | color: white; 28 | } 29 | 30 | .svg-inline--fa { 31 | color: white; 32 | } 33 | 34 | @media screen and (max-width: 1170px) { 35 | .vertical-timeline-element .vertical-timeline-element-date { 36 | color: rgb(39, 40, 34); 37 | } 38 | } -------------------------------------------------------------------------------- /src/assets/styles/Contact.scss: -------------------------------------------------------------------------------- 1 | .contact_wrapper svg { 2 | font-size: 1em; 3 | vertical-align: middle; 4 | margin-right: 5px; 5 | } 6 | 7 | .contact-form { 8 | padding-top: 20px; 9 | 10 | button { 11 | float: right; 12 | background-color: white; 13 | color: #050f0b; 14 | } 15 | 16 | button:hover { 17 | background-color: #5000ca; 18 | color: white; 19 | } 20 | 21 | fieldset { 22 | background-color: white; 23 | } 24 | } 25 | 26 | label { 27 | font-size: 1.2rem; 28 | font-family: 'DomaineDispNar-Medium', sans-serif; 29 | } 30 | 31 | .form-flex { 32 | display: flex; 33 | gap: 15px; 34 | padding-bottom: 15px; 35 | } 36 | 37 | .form-flex .MuiFormControl-root { 38 | width: 100%; 39 | } 40 | 41 | form { 42 | width: 100% !important; 43 | } 44 | 45 | .body-form { 46 | width: 100% !important; 47 | margin-bottom: 15px !important; 48 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Yuji Sato 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 | -------------------------------------------------------------------------------- /src/App.tsx: -------------------------------------------------------------------------------- 1 | import React, {useState, useEffect} from "react"; 2 | import { 3 | Main, 4 | Timeline, 5 | Expertise, 6 | Project, 7 | Contact, 8 | Navigation, 9 | Footer, 10 | } from "./components"; 11 | import FadeIn from './components/FadeIn'; 12 | import './index.scss'; 13 | 14 | function App() { 15 | const [mode, setMode] = useState('dark'); 16 | 17 | const handleModeChange = () => { 18 | if (mode === 'dark') { 19 | setMode('light'); 20 | } else { 21 | setMode('dark'); 22 | } 23 | } 24 | 25 | useEffect(() => { 26 | window.scrollTo({top: 0, left: 0, behavior: 'smooth'}); 27 | }, []); 28 | 29 | return ( 30 |
31 | 32 | 33 |
34 | 35 | 36 | 37 | 38 | 39 |
40 |
41 | ); 42 | } 43 | 44 | export default App; -------------------------------------------------------------------------------- /src/assets/styles/Project.scss: -------------------------------------------------------------------------------- 1 | .projects-container { 2 | display: flex; 3 | flex-direction: column; 4 | padding: 5% 10%; 5 | text-align: left; 6 | } 7 | 8 | .projects-container .projects-grid { 9 | display: grid; 10 | grid-template-columns: repeat(2, 1fr); 11 | grid-gap: 50px; 12 | } 13 | 14 | .projects-container .projects-grid .project { 15 | text-align: left; 16 | } 17 | 18 | .project h2:hover { 19 | text-decoration: underline; 20 | transition: color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out; 21 | } 22 | 23 | .zoom { 24 | border-radius: 5px; 25 | transition: transform .2s; 26 | margin: 0 auto; 27 | } 28 | 29 | .zoom:hover { 30 | transform: scale(1.05); 31 | } 32 | 33 | @media screen { 34 | @media (max-width: 700px) { 35 | .projects-container { 36 | display: block; 37 | padding-left: 5%; 38 | padding-right: 5%; 39 | } 40 | 41 | .projects-container .projects-grid { 42 | display: block; 43 | } 44 | 45 | .projects-container .projects-grid .project { 46 | padding-bottom: 5%; 47 | } 48 | } 49 | } -------------------------------------------------------------------------------- /src/components/Main.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import GitHubIcon from '@mui/icons-material/GitHub'; 3 | import LinkedInIcon from '@mui/icons-material/LinkedIn'; 4 | import '../assets/styles/Main.scss'; 5 | 6 | function Main() { 7 | 8 | return ( 9 |
10 |
11 |
12 | Avatar 13 |
14 |
15 |
16 | 17 | 18 |
19 |

Yuji Sato

20 |

Full Stack Engineer

21 | 22 |
23 | 24 | 25 |
26 |
27 |
28 |
29 | ); 30 | } 31 | 32 | export default Main; -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-portfolio-template", 3 | "version": "0.1.0", 4 | "private": true, 5 | "homepage": "https://yujisatojr.github.io/react-portfolio-template", 6 | "dependencies": { 7 | "@emotion/react": "^11.11.4", 8 | "@emotion/styled": "^11.11.5", 9 | "@fortawesome/free-brands-svg-icons": "^6.5.2", 10 | "@fortawesome/free-regular-svg-icons": "^6.5.2", 11 | "@fortawesome/free-solid-svg-icons": "^6.5.2", 12 | "@fortawesome/react-fontawesome": "^0.2.2", 13 | "@mui/icons-material": "^5.15.19", 14 | "@mui/material": "^5.15.19", 15 | "@testing-library/jest-dom": "^5.17.0", 16 | "@testing-library/react": "^13.4.0", 17 | "@testing-library/user-event": "^13.5.0", 18 | "@types/jest": "^27.5.2", 19 | "@types/node": "^16.18.98", 20 | "@types/react": "^18.3.3", 21 | "@types/react-dom": "^18.3.0", 22 | "react": "^18.3.1", 23 | "react-dom": "^18.3.1", 24 | "react-scripts": "5.0.1", 25 | "react-vertical-timeline-component": "^3.6.0", 26 | "sass": "^1.77.4", 27 | "typescript": "^4.9.5", 28 | "web-vitals": "^2.1.4" 29 | }, 30 | "scripts": { 31 | "start": "react-scripts start", 32 | "build": "react-scripts build", 33 | "test": "react-scripts test", 34 | "eject": "react-scripts eject", 35 | "predeploy": "npm run build", 36 | "deploy": "gh-pages -d build" 37 | }, 38 | "eslintConfig": { 39 | "extends": [ 40 | "react-app", 41 | "react-app/jest" 42 | ] 43 | }, 44 | "browserslist": { 45 | "production": [ 46 | ">0.2%", 47 | "not dead", 48 | "not op_mini all" 49 | ], 50 | "development": [ 51 | "last 1 chrome version", 52 | "last 1 firefox version", 53 | "last 1 safari version" 54 | ] 55 | }, 56 | "devDependencies": { 57 | "@types/react-vertical-timeline-component": "^3.3.6" 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 17 | 18 | 27 | Yuji Sato 28 | 29 | 30 | 31 |
32 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /src/assets/styles/Expertise.scss: -------------------------------------------------------------------------------- 1 | .skills-container { 2 | padding: 5% 10%; 3 | display: flex; 4 | flex-direction: column; 5 | text-align: left; 6 | 7 | svg { 8 | color: white; 9 | } 10 | } 11 | 12 | .skills-container .skills-grid { 13 | display: grid; 14 | grid-template-columns: repeat(3, 1fr); 15 | grid-gap: 50px; 16 | } 17 | 18 | .skills-container .skills-grid .skill { 19 | align-items: center; 20 | text-align: left; 21 | } 22 | 23 | .chip { 24 | color: rgb(39, 40, 34) !important; 25 | background-color: white !important; 26 | font-size: 0.8rem !important; 27 | height: 25px !important; 28 | 29 | span { 30 | font-family: 'Courier Prime', monospace; 31 | color: rgb(39, 40, 34); 32 | } 33 | 34 | svg { 35 | height: 15px !important; 36 | } 37 | } 38 | 39 | .flex-chips { 40 | line-height: 2; 41 | margin: 0.5; 42 | 43 | .chip-title { 44 | color: white; 45 | padding-right: 10px; 46 | } 47 | } 48 | 49 | .flex-chips .MuiChip-root { 50 | margin-right: 0.5em; 51 | } 52 | 53 | .flex-chips .MuiChip-outlined { 54 | background-color: #e0e0e0; 55 | font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New", 56 | monospace; 57 | } 58 | 59 | .flex-chips .MuiChip-outlined span { 60 | color: rgb(39, 40, 34); 61 | } 62 | 63 | @media screen { 64 | @media (max-width: 768px) { 65 | .skills-container { 66 | display: block; 67 | padding-left: 5%; 68 | padding-right: 5%; 69 | } 70 | 71 | .skills-container .skills-grid { 72 | display: initial; 73 | } 74 | 75 | .skills-container .skills-grid .skill { 76 | padding-bottom: 5%; 77 | } 78 | } 79 | 80 | @media (max-width: 567px) { 81 | .skills { 82 | font-size: 12px; 83 | } 84 | } 85 | } -------------------------------------------------------------------------------- /src/components/FadeIn.tsx: -------------------------------------------------------------------------------- 1 | import React, { 2 | JSXElementConstructor, 3 | PropsWithChildren, 4 | useEffect, 5 | useState, 6 | } from "react"; 7 | 8 | interface Props { 9 | delay?: number; 10 | transitionDuration?: number; 11 | wrapperTag?: JSXElementConstructor; 12 | childTag?: JSXElementConstructor; 13 | className?: string; 14 | childClassName?: string; 15 | visible?: boolean; 16 | onComplete?: () => any; 17 | } 18 | 19 | export default function FadeIn(props: PropsWithChildren) { 20 | const [maxIsVisible, setMaxIsVisible] = useState(0); 21 | const transitionDuration = props.transitionDuration || 400; 22 | const delay = props.delay || 50; 23 | const WrapperTag = props.wrapperTag || "div"; 24 | const ChildTag = props.childTag || "div"; 25 | const visible = typeof props.visible === "undefined" ? true : props.visible; 26 | 27 | useEffect(() => { 28 | let count = React.Children.count(props.children); 29 | if (!visible) { 30 | // Animate all children out 31 | count = 0; 32 | } 33 | 34 | if (count === maxIsVisible) { 35 | // We're done updating maxVisible, notify when animation is done 36 | const timeout = setTimeout(() => { 37 | if (props.onComplete) props.onComplete(); 38 | }, transitionDuration); 39 | return () => clearTimeout(timeout); 40 | } 41 | 42 | // Move maxIsVisible toward count 43 | const increment = count > maxIsVisible ? 1 : -1; 44 | const timeout = setTimeout(() => { 45 | setMaxIsVisible(maxIsVisible + increment); 46 | }, delay); 47 | return () => clearTimeout(timeout); 48 | // eslint-disable-next-line 49 | }, [ 50 | // eslint-disable-next-line 51 | React.Children.count(props.children), 52 | delay, 53 | maxIsVisible, 54 | visible, 55 | transitionDuration, 56 | ]); 57 | 58 | return ( 59 | 60 | {React.Children.map(props.children, (child, i) => { 61 | return ( 62 | i ? "none" : "translateY(20px)", 67 | opacity: maxIsVisible > i ? 1 : 0, 68 | }} 69 | > 70 | {child} 71 | 72 | ); 73 | })} 74 | 75 | ); 76 | } 77 | -------------------------------------------------------------------------------- /src/components/Timeline.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import '@fortawesome/free-regular-svg-icons' 3 | import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; 4 | import { faBriefcase } from '@fortawesome/free-solid-svg-icons'; 5 | import { VerticalTimeline, VerticalTimelineElement } from 'react-vertical-timeline-component'; 6 | import 'react-vertical-timeline-component/style.min.css'; 7 | import '../assets/styles/Timeline.scss' 8 | 9 | function Timeline() { 10 | return ( 11 |
12 |
13 |

Career History

14 | 15 | } 22 | > 23 |

Technology Consultant

24 |

Dallas, TX

25 |

26 | Full-stack Web Development, GenAI/LLM, Project Management, Business Development 27 |

28 |
29 | } 34 | > 35 |

Full Stack Engineer

36 |

Laie, HI

37 |

38 | Frontend Development, Backend Development, User Experience, Team Leading 39 |

40 |
41 | } 46 | > 47 |

Staff Engineer Intern

48 |

Laie, HI

49 |

50 | Full-stack Development, API Development, User Experience 51 |

52 |
53 | } 58 | > 59 |

Data Analyst Intern

60 |

Tokyo, Japan

61 |

62 | Automation, Data Governance, Statistical Analysis 63 |

64 |
65 |
66 |
67 |
68 | ); 69 | } 70 | 71 | export default Timeline; -------------------------------------------------------------------------------- /src/components/Expertise.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import '@fortawesome/free-regular-svg-icons' 3 | import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; 4 | import { faReact, faDocker, faPython } from '@fortawesome/free-brands-svg-icons'; 5 | import Chip from '@mui/material/Chip'; 6 | import '../assets/styles/Expertise.scss'; 7 | 8 | const labelsFirst = [ 9 | "React", 10 | "TypeScript", 11 | "JavaScript", 12 | "HTML5", 13 | "CSS3", 14 | "SASS", 15 | "Flask", 16 | "Python", 17 | "SQL", 18 | "PostgreSQL", 19 | "Postman" 20 | ]; 21 | 22 | const labelsSecond = [ 23 | "Git", 24 | "GitHub Actions", 25 | "Docker", 26 | "AWS", 27 | "Azure", 28 | "Linux", 29 | "Snowflake", 30 | "Pandas", 31 | "Selenium", 32 | ]; 33 | 34 | const labelsThird = [ 35 | "OpenAI", 36 | "Groq", 37 | "LangChain", 38 | "Qdrant", 39 | "Hugging Face", 40 | "LlamaIndex", 41 | "Streamlit", 42 | ]; 43 | 44 | function Expertise() { 45 | return ( 46 |
47 |
48 |

Expertise

49 |
50 |
51 | 52 |

Full Stack Web Development

53 |

I have built a diverse array of web applications from scratch using modern technologies such as React and Flask. I have a strong proficiency in the SDLC process and frontend + backend development.

54 |
55 | Tech stack: 56 | {labelsFirst.map((label, index) => ( 57 | 58 | ))} 59 |
60 |
61 | 62 |
63 | 64 |

DevOps & Automation

65 |

Once the application is built, I help clients set up DevOps testing, CI/CD pipelines, and deployment automation to support the successful Go-Live.

66 |
67 | Tech stack: 68 | {labelsSecond.map((label, index) => ( 69 | 70 | ))} 71 |
72 |
73 | 74 |
75 | 76 |

GenAI & LLM

77 |

Stay relevant in the market by leveraging the latest AI models in your projects. I have professional experience building enterprise grade GenAI-enabled solutions to empower intelligent decision making.

78 |
79 | Tech stack: 80 | {labelsThird.map((label, index) => ( 81 | 82 | ))} 83 |
84 |
85 |
86 |
87 |
88 | ); 89 | } 90 | 91 | export default Expertise; -------------------------------------------------------------------------------- /src/assets/styles/Main.scss: -------------------------------------------------------------------------------- 1 | .dark-mode .about-section { 2 | background-color: rgba(0, 0, 0, 0.2); 3 | } 4 | 5 | .about-section { 6 | display: flex; 7 | gap: 30px; 8 | justify-content: flex-start; 9 | align-items: center; 10 | width: 100%; 11 | padding: 0px 15%; 12 | min-height: 700px; 13 | background-image: url("../images/bg-dark.png"); 14 | background-repeat: no-repeat; 15 | background-size: cover; 16 | background-attachment: fixed; 17 | background-position: center; 18 | } 19 | 20 | .about-section .content { 21 | text-align: left; 22 | z-index: 2; 23 | 24 | .social_icons { 25 | display: flex; 26 | flex-direction: row; 27 | gap: 10px; 28 | color: white; 29 | 30 | svg { 31 | font-size: 1.8em; 32 | } 33 | } 34 | 35 | .mobile_social_icons { 36 | display: none; 37 | } 38 | 39 | p { 40 | font-size: 1.5em; 41 | margin-block-start: 0px; 42 | margin-block-end: 0px; 43 | } 44 | 45 | h1 { 46 | font-size: 5em; 47 | margin-block-start: 0px; 48 | margin-block-end: 0px; 49 | } 50 | } 51 | 52 | .about-section .image-wrapper { 53 | z-index: 2; 54 | img { 55 | width: 150px; 56 | border-radius: 50%; 57 | } 58 | } 59 | 60 | .about p { 61 | text-align: center; 62 | padding-top: 30%; 63 | padding-bottom: 30%; 64 | } 65 | 66 | @media screen { 67 | @media (max-width: 567px) { 68 | .nav-left{ 69 | padding-top: 20px; 70 | float:left; 71 | } 72 | 73 | .nav-left img { 74 | height: 35px 75 | } 76 | 77 | .nav-right { 78 | float:right; 79 | } 80 | 81 | .nav-right li { 82 | padding: 10px 15px; 83 | list-style:none; 84 | font-size:13px; 85 | color:white; 86 | width: 10%; 87 | } 88 | 89 | .nav-right ul { 90 | list-style-type: none; 91 | margin: 0px; 92 | padding: 0px; 93 | } 94 | 95 | body { 96 | width: 100%; 97 | } 98 | 99 | .about { 100 | padding-left: 5%; 101 | padding-right: 5%; 102 | font-size: 12px; 103 | } 104 | } 105 | 106 | @media (max-width:768px) { 107 | .about-section h1 { 108 | font-size: 4em !important; 109 | text-align: left; 110 | } 111 | 112 | .about-section p { 113 | text-align: left; 114 | } 115 | 116 | .container { 117 | display: initial; 118 | } 119 | 120 | .social_icons { 121 | display: none !important; 122 | } 123 | 124 | .mobile_social_icons { 125 | padding-top: 10px; 126 | display: flex !important; 127 | flex-direction: row; 128 | gap: 10px; 129 | color: white; 130 | 131 | svg { 132 | font-size: 1.8em; 133 | } 134 | } 135 | 136 | .about-section { 137 | padding-top: 150px; 138 | display: block; 139 | padding-left: 0px; 140 | padding-right: 0px; 141 | } 142 | 143 | .about-section .content { 144 | width: 90%; 145 | padding-left: 5%; 146 | padding-right: 5%; 147 | padding-bottom: 5%; 148 | } 149 | 150 | .about-section .image-wrapper { 151 | padding-left: 5%; 152 | width: 100%; 153 | } 154 | } 155 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Developer Portfolio Template 🚀 2 | 3 | ![React](https://img.shields.io/badge/React-20232A?style=for-the-badge&logo=react&logoColor=61DAFB) ![Node.js](https://img.shields.io/badge/Node%20js-339933?style=for-the-badge&logo=nodedotjs&logoColor=white) ![npm](https://img.shields.io/badge/npm-CB3837?style=for-the-badge&logo=npm&logoColor=white) ![TypeScript](https://img.shields.io/badge/typescript-%23007ACC.svg?style=for-the-badge&logo=typescript&logoColor=white) ![JavaScript](https://img.shields.io/badge/JavaScript-323330?style=for-the-badge&logo=javascript&logoColor=F7DF1E) ![HTML5](https://img.shields.io/badge/HTML5-E34F26?style=for-the-badge&logo=html5&logoColor=white) ![Sass](https://img.shields.io/badge/Sass-CC6699?style=for-the-badge&logo=sass&logoColor=white) 4 | 5 | ## What is this? 6 | 7 | This simple portfolio template is designed to showcase your past projects, career history, skill sets, and more. 8 | 9 | View the [Demo](https://yujisatojr.github.io/react-portfolio-template/). 10 | 11 | **This template is free to use, and no attribution is required.** You can fork or download this repository to customize it for your own use. Please don't forget to leave a ⭐ if you like this portfolio! 12 | 13 | ![screenshot](./src/assets//images/screenshot.png) 14 | 15 | ## Features 16 | 17 | ✅ Open source (free to use, no attribution required) 18 | ✅ Responsive design & mobile-friendly 19 | ✅ Supports both dark and light modes 20 | ✅ Highly customizable multi-component layout 21 | ✅ Built with modern technologies (React, TypeScript, JavaScript, and SCSS) 22 | 23 | ## Quick Setup 24 | 25 | 1. Ensure you have [Node.js](https://nodejs.org/) installed. Check your installation by running: 26 | 27 | ```bash 28 | node -v 29 | ``` 30 | 31 | 2. In the project directory, install dependencies: 32 | 33 | ```bash 34 | npm install 35 | ``` 36 | 37 | 3. Start the development server: 38 | 39 | ```bash 40 | npm start 41 | ``` 42 | 43 | 4. Open [http://localhost:3000](http://localhost:3000) to view the app in the browser. 44 | 45 | 5. Customize the template by navigating to the `/src/components` directory. Modify texts, pictures, and other information as needed. 46 | 47 | The page will reload if you make edits, and you will see any lint errors in the console. 48 | 49 | If you are interested in creating a mockup image like the ones from the personal projects section, I recommend [Genmoo](https://gemoo.com/tools/browser-mockup-generator/). This website lets you generate sleek looking browser mockups for free. 50 | 51 | ## Deployment 52 | 53 | You can choose your preferred service (e.g., [Netlify](https://www.netlify.com/), [Render](https://render.com/), [Heroku](https://www.heroku.com/)) for deployment. One of the easiest ways to host this portfolio is using GitHub Pages. Follow the instructions below for a production deploy. 54 | 55 | 1. **Set Up GitHub Repository** 56 | 57 | Create a new repository on GitHub for your portfolio app. 58 | 59 | 2. **Configure `package.json`** 60 | 61 | Edit the following properties in your `package.json` file: 62 | 63 | ```json 64 | { 65 | "homepage": "https://yourusername.github.io/your-repo-name", 66 | "scripts": { 67 | "predeploy": "npm run build", 68 | "deploy": "gh-pages -d build", 69 | ... 70 | } 71 | } 72 | ``` 73 | 74 | Replace `yourusername` with your GitHub username and `your-repo-name` with the name of your GitHub repository. 75 | 76 | 3. **Deploy to GitHub Pages** 77 | 78 | Run the following command to deploy your app: 79 | 80 | ```bash 81 | npm run deploy 82 | ``` 83 | 84 | 4. **Access Your Deployed App** 85 | 86 | After successfully deploying, you can access your app at `https://yourusername.github.io/your-repo-name`. -------------------------------------------------------------------------------- /src/index.scss: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", 4 | "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", 5 | sans-serif; 6 | -webkit-font-smoothing: antialiased; 7 | -moz-osx-font-smoothing: grayscale; 8 | font-size: 1.1rem !important; 9 | } 10 | 11 | code { 12 | font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', monospace !important; 13 | font-size: 1rem !important; 14 | } 15 | 16 | .main-container { 17 | position: relative; 18 | background-color: #0d1116; 19 | 20 | } 21 | 22 | a { 23 | text-decoration: none; 24 | } 25 | 26 | a:hover { 27 | color: #5000ca; 28 | transition: color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out; 29 | } 30 | 31 | p, a, h1, h2, h3, h4, h5, label, button { 32 | color: white; 33 | font-family: "Lato", sans-serif; 34 | } 35 | 36 | .navigation-bar { 37 | justify-content: space-between; 38 | background-color: #0d1116; 39 | 40 | button { 41 | font-size: 1.1rem; 42 | font-family: "Lato", sans-serif; 43 | text-transform: initial; 44 | padding: 6px 10px; 45 | } 46 | 47 | svg { 48 | cursor: pointer; 49 | } 50 | } 51 | 52 | .navigation-bar-responsive { 53 | span, p { 54 | color: #0d1116; 55 | font-family: "Lato", sans-serif; 56 | } 57 | } 58 | 59 | .container { 60 | margin-top: 64px; 61 | width: 100%; 62 | display: flex; 63 | flex-direction: column; 64 | align-items: center; 65 | justify-content: center; 66 | } 67 | 68 | .header-image { 69 | width: 100%; 70 | height: 450px; 71 | 72 | img { 73 | width: 100%; 74 | height: 450px; 75 | object-fit: cover; 76 | } 77 | } 78 | 79 | .mobile-menu-top { 80 | display: flex; 81 | justify-content: center; 82 | align-items: center; 83 | gap: 5px; 84 | font-weight: bold; 85 | } 86 | 87 | .items-container { 88 | padding: 5% 10%; 89 | text-align: left; 90 | display: flex; 91 | flex-direction: column; 92 | justify-content: center; 93 | } 94 | 95 | .light-mode { 96 | background-color: #f8f9fa; 97 | 98 | .navigation-bar { 99 | background-color: #f8f9fa; 100 | } 101 | 102 | p, a, h1, h2, h3, h4, h5, label, button { 103 | color: #0d1116; 104 | } 105 | 106 | a:hover { 107 | color: #5000ca; 108 | transition: color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out; 109 | } 110 | 111 | .navigation-bar svg { 112 | color: #0d1116; 113 | } 114 | 115 | .about-section { 116 | background-image: url("./assets/images/bg-light.png"); 117 | } 118 | 119 | .skills-container svg { 120 | color: #0d1116; 121 | } 122 | 123 | .chip { 124 | background-color: rgba(0, 0, 0, 0.08); 125 | box-shadow: 0px 3px 1px -2px rgba(0, 0, 0, 0.2), 0px 2px 2px 0px rgba(0, 0, 0, 0.14), 0px 1px 5px 0px rgba(0, 0, 0, 0.12); 126 | } 127 | 128 | .vertical-timeline::before { 129 | background-color: rgba(0, 0, 0, 0.08); 130 | } 131 | 132 | .vertical-timeline--animate .vertical-timeline-element-content.bounce-in { 133 | box-shadow: 0px 3px 1px -2px rgba(0, 0, 0, 0.2), 0px 2px 2px 0px rgba(0, 0, 0, 0.14), 0px 1px 5px 0px rgba(0, 0, 0, 0.12); 134 | } 135 | 136 | .vertical-timeline--two-columns .vertical-timeline-element-content .vertical-timeline-element-date { 137 | color: gray; 138 | } 139 | 140 | .vertical-timeline-element-content-arrow { 141 | border-right: 7px solid rgba(0, 0, 0, 0.08) !important; 142 | } 143 | 144 | .flex-chips .chip-title { 145 | color: #0d1116; 146 | } 147 | } 148 | 149 | @media screen { 150 | @media (max-width: 768px) { 151 | .items-container { 152 | padding: 5% 5%; 153 | } 154 | } 155 | } -------------------------------------------------------------------------------- /src/components/Contact.tsx: -------------------------------------------------------------------------------- 1 | import React, { useRef, useState } from 'react'; 2 | import '../assets/styles/Contact.scss'; 3 | // import emailjs from '@emailjs/browser'; 4 | import Box from '@mui/material/Box'; 5 | import Button from '@mui/material/Button'; 6 | import SendIcon from '@mui/icons-material/Send'; 7 | import TextField from '@mui/material/TextField'; 8 | 9 | function Contact() { 10 | 11 | const [name, setName] = useState(''); 12 | const [email, setEmail] = useState(''); 13 | const [message, setMessage] = useState(''); 14 | 15 | const [nameError, setNameError] = useState(false); 16 | const [emailError, setEmailError] = useState(false); 17 | const [messageError, setMessageError] = useState(false); 18 | 19 | const form = useRef(); 20 | 21 | const sendEmail = (e: any) => { 22 | e.preventDefault(); 23 | 24 | setNameError(name === ''); 25 | setEmailError(email === ''); 26 | setMessageError(message === ''); 27 | 28 | /* Uncomment below if you want to enable the emailJS */ 29 | 30 | // if (name !== '' && email !== '' && message !== '') { 31 | // var templateParams = { 32 | // name: name, 33 | // email: email, 34 | // message: message 35 | // }; 36 | 37 | // console.log(templateParams); 38 | // emailjs.send('service_id', 'template_id', templateParams, 'api_key').then( 39 | // (response) => { 40 | // console.log('SUCCESS!', response.status, response.text); 41 | // }, 42 | // (error) => { 43 | // console.log('FAILED...', error); 44 | // }, 45 | // ); 46 | // setName(''); 47 | // setEmail(''); 48 | // setMessage(''); 49 | // } 50 | }; 51 | 52 | return ( 53 |
54 |
55 |
56 |

Contact Me

57 |

Got a project waiting to be realized? Let's collaborate and make it happen!

58 | 65 |
66 | { 73 | setName(e.target.value); 74 | }} 75 | error={nameError} 76 | helperText={nameError ? "Please enter your name" : ""} 77 | /> 78 | { 85 | setEmail(e.target.value); 86 | }} 87 | error={emailError} 88 | helperText={emailError ? "Please enter your email or phone number" : ""} 89 | /> 90 |
91 | { 101 | setMessage(e.target.value); 102 | }} 103 | error={messageError} 104 | helperText={messageError ? "Please enter the message" : ""} 105 | /> 106 | 109 |
110 |
111 |
112 |
113 | ); 114 | } 115 | 116 | export default Contact; -------------------------------------------------------------------------------- /src/components/Navigation.tsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from "react"; 2 | import AppBar from '@mui/material/AppBar'; 3 | import Box from '@mui/material/Box'; 4 | import Button from '@mui/material/Button'; 5 | import CssBaseline from '@mui/material/CssBaseline'; 6 | import DarkModeIcon from '@mui/icons-material/DarkMode'; 7 | import Divider from '@mui/material/Divider'; 8 | import Drawer from '@mui/material/Drawer'; 9 | import IconButton from '@mui/material/IconButton'; 10 | import LightModeIcon from '@mui/icons-material/LightMode'; 11 | import List from '@mui/material/List'; 12 | import ListIcon from '@mui/icons-material/List'; 13 | import ListItem from '@mui/material/ListItem'; 14 | import ListItemButton from '@mui/material/ListItemButton'; 15 | import ListItemText from '@mui/material/ListItemText'; 16 | import MenuIcon from '@mui/icons-material/Menu'; 17 | import Toolbar from '@mui/material/Toolbar'; 18 | 19 | const drawerWidth = 240; 20 | const navItems = [['Expertise', 'expertise'], ['History', 'history'], ['Projects', 'projects'], ['Contact', 'contact']]; 21 | 22 | function Navigation({parentToChild, modeChange}: any) { 23 | 24 | const {mode} = parentToChild; 25 | 26 | const [mobileOpen, setMobileOpen] = useState(false); 27 | const [scrolled, setScrolled] = useState(false); 28 | 29 | const handleDrawerToggle = () => { 30 | setMobileOpen((prevState) => !prevState); 31 | }; 32 | 33 | useEffect(() => { 34 | const handleScroll = () => { 35 | const navbar = document.getElementById("navigation"); 36 | if (navbar) { 37 | const scrolled = window.scrollY > navbar.clientHeight; 38 | setScrolled(scrolled); 39 | } 40 | }; 41 | 42 | window.addEventListener('scroll', handleScroll); 43 | 44 | return () => { 45 | window.removeEventListener('scroll', handleScroll); 46 | }; 47 | }, []); 48 | 49 | const scrollToSection = (section: string) => { 50 | console.log(section) 51 | const expertiseElement = document.getElementById(section); 52 | if (expertiseElement) { 53 | expertiseElement.scrollIntoView({ behavior: 'smooth' }); 54 | console.log('Scrolling to:', expertiseElement); // Debugging: Ensure the element is found 55 | } else { 56 | console.error('Element with id "expertise" not found'); // Debugging: Log error if element is not found 57 | } 58 | }; 59 | 60 | const drawer = ( 61 | 62 |

Menu

63 | 64 | 65 | {navItems.map((item) => ( 66 | 67 | scrollToSection(item[1])}> 68 | 69 | 70 | 71 | ))} 72 | 73 |
74 | ); 75 | 76 | return ( 77 | 78 | 79 | 80 | 81 | 88 | 89 | 90 | {mode === 'dark' ? ( 91 | modeChange()}/> 92 | ) : ( 93 | modeChange()}/> 94 | )} 95 | 96 | {navItems.map((item) => ( 97 | 100 | ))} 101 | 102 | 103 | 104 | 120 | 121 | ); 122 | } 123 | 124 | export default Navigation; -------------------------------------------------------------------------------- /src/components/Project.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import mock01 from '../assets/images/mock01.png'; 3 | import mock02 from '../assets/images/mock02.png'; 4 | import mock03 from '../assets/images/mock03.png'; 5 | import mock04 from '../assets/images/mock04.png'; 6 | import mock05 from '../assets/images/mock05.png'; 7 | import mock06 from '../assets/images/mock06.png'; 8 | import mock07 from '../assets/images/mock07.png'; 9 | import mock08 from '../assets/images/mock08.png'; 10 | import mock09 from '../assets/images/mock09.png'; 11 | import mock10 from '../assets/images/mock10.png'; 12 | import '../assets/styles/Project.scss'; 13 | 14 | function Project() { 15 | return( 16 |
17 |

Personal Projects

18 |
19 |
20 | thumbnail 21 |

Filmate AI

22 |

Developed movie finder app with semantic search and sentiment analysis using OpenAI GPT-3.5 Turbo, Qdrant, React, and Flask.

23 |
24 |
25 | thumbnail 26 |

High Speed Chase

27 |

Designed, developed, and launched a 3D multiplayer racing game with C# and Unity. This is available on Itch.io for gamers worldwide to enjoy.

28 |
29 |
30 | thumbnail 31 |

Astro Raiders

32 |

Developed and released a 2D shooting game with C# and Unity. This project is hosted on the Itch.io public marketplace.

33 |
34 |
35 | thumbnail 36 |

Datum: Integrated Learning Platform

37 |

This is an online educational platform that provides high-quality, data science-focused learning resources in the Japanese language. I created the entire platform from scratch using Ruby on Rails.

38 |
39 |
40 | thumbnail 41 |

WeManage: Real Estate Asset Management

42 |

This mobile application allows realtors in Japan to securely manage their property information and view future income predictions. This app is built with Ruby on Rails and JavaScript.

43 |
44 |
45 | thumbnail 46 |

COVID-19 Case Management

47 |

Built official charts for COVID/vaccination tracking for an educational institution using JavaScript and the Google Sheets API v4. The dashboard served the university's leadership in their decision-making processes.

48 |
49 |
50 | thumbnail 51 |

Multiple Regression Property Analysis

52 |

Analyzed the real estate market in Japan and predicted property prices by implementing statistical methods such as OLS and multi-regression analysis. This project leveraged Python and various libraries such as Pandas, NumPy, Matplotlib, and Scikit-Learn.

53 |
54 |
55 | thumbnail 56 |

Programs of Study

57 |

Designed and developed a custom component for a CMS-based platform (e.g., 'Brightspot') using Java, Handlebars, and LESS. University students can find their majors of interest through this module.

58 |
59 |
60 | thumbnail 61 |

Transfer Evaluation Matrix

62 |

Created an interactive CSV table generator with Java, Handlebars, and LESS. This project helps transfer students to quickly identify eligible credits.

63 |
64 |
65 | thumbnail 66 |

Submeowrine

67 |

Developed and released an Android mobile application using Java and Android Studio that runs a 2D shooting game.

68 |
69 |
70 |
71 | ); 72 | } 73 | 74 | export default Project; --------------------------------------------------------------------------------