├── .env ├── .eslintrc.js ├── .gitignore ├── README.md ├── package-lock.json ├── package.json ├── public ├── favicon.ico ├── fonts │ ├── Quicksand-Bold.ttf │ ├── Quicksand-Medium.ttf │ ├── Quicksand-Regular.ttf │ └── quicksand.css ├── index.html ├── logo192.png ├── logo512.png ├── manifest.json └── robots.txt ├── src ├── @types │ └── emotion.ts ├── App.tsx ├── Portfolio │ ├── Animation.tsx │ ├── Intro.tsx │ ├── Playground │ │ ├── Playground.tsx │ │ └── ShadowPlay.tsx │ ├── Portfolio.tsx │ ├── Profile.tsx │ ├── Projects │ │ ├── Project.tsx │ │ └── Projects.tsx │ └── SocialMedia.tsx ├── components │ ├── Button │ │ ├── Button.tsx │ │ └── index.ts │ ├── Cards │ │ ├── Card.tsx │ │ ├── CardGroup.tsx │ │ └── index.tsx │ ├── Carousel │ │ ├── Carousel.tsx │ │ ├── fragments │ │ │ ├── CarouselItem.tsx │ │ │ └── ItemWindow.tsx │ │ ├── index.ts │ │ └── utils │ │ │ └── getComponents.ts │ ├── DirectionButton │ │ ├── DirectionButton.tsx │ │ └── index.ts │ ├── Scrollspy │ │ ├── Scrollspy.tsx │ │ ├── ScrollspyContent.tsx │ │ ├── ScrollspyExtra.tsx │ │ ├── fragments │ │ │ ├── ScrollspyMenu.tsx │ │ │ ├── ScrollspyMenuItem.tsx │ │ │ ├── ScrollspySeparator.tsx │ │ │ └── StyledScrollspy.ts │ │ ├── index.ts │ │ └── utils │ │ │ └── getComponents.ts │ ├── Section │ │ ├── Section.tsx │ │ └── index.ts │ ├── Theme │ │ ├── FiraCode-Light.woff │ │ ├── FiraCode-Regular.woff │ │ ├── ThemeProvider.tsx │ │ ├── getTheme.ts │ │ └── index.ts │ ├── Timeline │ │ ├── Timeline.tsx │ │ └── index.ts │ ├── Typer │ │ ├── Typer.tsx │ │ └── index.ts │ └── index.ts ├── data │ ├── media │ │ ├── avatar.webp │ │ ├── computer.svg │ │ ├── cookie.webp │ │ ├── fluidity.svg │ │ ├── gnrc.webp │ │ ├── go-audio.webp │ │ ├── kitty.webp │ │ ├── logo.svg │ │ ├── logo.webp │ │ ├── minigue.webp │ │ ├── missing-pic.png │ │ ├── react-startpage.svg │ │ ├── smartphone.svg │ │ ├── smug.webp │ │ ├── startpages.svg │ │ └── tablet.svg │ └── projects.ts ├── index.tsx ├── logo.svg ├── react-app-env.d.ts ├── reportWebVitals.ts └── setupTests.ts └── tsconfig.json /.env: -------------------------------------------------------------------------------- 1 | EXTEND_ESLINT=true 2 | SKIP_PREFLIGHT_CHECK=true -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | const baseConfigs = [ 2 | "eslint:recommended", 3 | "plugin:@typescript-eslint/eslint-recommended", 4 | "plugin:import/recommended", 5 | "plugin:import/typescript", 6 | "plugin:react-hooks/recommended", 7 | "plugin:jsx-a11y/recommended", 8 | "plugin:react/recommended", 9 | "plugin:react/jsx-runtime", 10 | "plugin:prettier/recommended", 11 | ] 12 | 13 | const plugins = [ 14 | "@emotion", 15 | "@typescript-eslint", 16 | "import", 17 | "react", 18 | "unused-imports", 19 | "jsx-a11y", 20 | ] 21 | 22 | const prettierRules = { 23 | tabWidth: 2, 24 | singleQuote: false, 25 | trailingComma: "es5", 26 | semi: false, 27 | bracketSpacing: true, 28 | arrowParens: "avoid", 29 | endOfLine: "auto", 30 | jsxSingleQuote: false, 31 | } 32 | 33 | const importOrderRules = { 34 | groups: ["builtin", "external", "internal"], 35 | pathGroups: [ 36 | { 37 | pattern: "react", 38 | group: "external", 39 | position: "before", 40 | }, 41 | ], 42 | pathGroupsExcludedImportTypes: ["react"], 43 | "newlines-between": "always", 44 | alphabetize: { 45 | order: "asc", 46 | caseInsensitive: true, 47 | }, 48 | } 49 | 50 | module.exports = { 51 | parser: "@typescript-eslint/parser", 52 | env: { 53 | node: true, 54 | }, 55 | parserOptions: { 56 | ecmaVersion: 2020, 57 | ecmaFeatures: { 58 | legacyDecorators: true, 59 | jsx: true, 60 | }, 61 | }, 62 | extends: baseConfigs, 63 | plugins: plugins, 64 | rules: { 65 | "@emotion/syntax-preference": [2, "string"], 66 | "unused-imports/no-unused-imports": "error", 67 | "@typescript-eslint/no-use-before-define": ["error"], 68 | "jsx-a11y/no-onchange": "off", // deprecated rule, will be deleted in a future release 69 | "jsx-a11y/no-autofocus": "off", 70 | "jsx-a11y/label-has-associated-control": "off", 71 | 72 | "import/order": ["error", importOrderRules], 73 | "prettier/prettier": ["error", prettierRules], 74 | }, 75 | settings: { 76 | react: { 77 | version: "detect", 78 | }, 79 | "import/parsers": { 80 | "@typescript-eslint/parser": [".ts", ".tsx"], 81 | }, 82 | }, 83 | } 84 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Getting Started with Create React App 2 | 3 | This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app). 4 | 5 | ## Available Scripts 6 | 7 | In the project directory, you can run: 8 | 9 | ### `npm start` 10 | 11 | Runs the app in the development mode.\ 12 | Open [http://localhost:3000](http://localhost:3000) to view it in the browser. 13 | 14 | The page will reload if you make edits.\ 15 | You will also see any lint errors in the console. 16 | 17 | ### `npm test` 18 | 19 | Launches the test runner in the interactive watch mode.\ 20 | See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information. 21 | 22 | ### `npm run build` 23 | 24 | Builds the app for production to the `build` folder.\ 25 | It correctly bundles React in production mode and optimizes the build for the best performance. 26 | 27 | The build is minified and the filenames include the hashes.\ 28 | Your app is ready to be deployed! 29 | 30 | See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information. 31 | 32 | ### `npm run eject` 33 | 34 | **Note: this is a one-way operation. Once you `eject`, you can’t go back!** 35 | 36 | If you aren’t satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project. 37 | 38 | Instead, it will copy all the configuration files and the transitive dependencies (webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you’re on your own. 39 | 40 | You don’t have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn’t feel obligated to use this feature. However we understand that this tool wouldn’t be useful if you couldn’t customize it when you are ready for it. 41 | 42 | ## Learn More 43 | 44 | You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started). 45 | 46 | To learn React, check out the [React documentation](https://reactjs.org/). 47 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "PrettyCoffee.github.io", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "start": "react-scripts start", 7 | "deploy": "npm run build && gh-pages -d build", 8 | "build": "react-scripts build", 9 | "lint": "eslint --ext .ts,.tsx src/", 10 | "lint:fix": "npm run lint -- --fix", 11 | "prettier": "prettier --config .prettierrc.js \"src/**/*.{js,jsx,ts,tsx}\" --write", 12 | "test": "react-scripts test", 13 | "eject": "react-scripts eject" 14 | }, 15 | "dependencies": { 16 | "@emotion/react": "^11.9.3", 17 | "@emotion/styled": "^11.9.3", 18 | "@fortawesome/fontawesome-svg-core": "^6.1.1", 19 | "@fortawesome/free-brands-svg-icons": "^6.1.1", 20 | "@fortawesome/free-regular-svg-icons": "^6.1.1", 21 | "@fortawesome/free-solid-svg-icons": "^6.1.1", 22 | "@fortawesome/react-fontawesome": "^0.2.0", 23 | "@react-hook/mouse-position": "^4.1.3", 24 | "@react-hook/window-scroll": "^1.3.0", 25 | "@testing-library/jest-dom": "^5.16.4", 26 | "@testing-library/react": "^13.3.0", 27 | "@testing-library/user-event": "^14.2.6", 28 | "@types/jest": "^28.1.6", 29 | "@types/node": "^18.0.6", 30 | "@types/react": "^18.0.15", 31 | "@types/react-dom": "^18.0.6", 32 | "react": "^18.2.0", 33 | "react-dom": "^18.2.0", 34 | "react-scripts": "5.0.1", 35 | "typescript": "^4.7.4", 36 | "web-vitals": "^2.1.4" 37 | }, 38 | "devDependencies": { 39 | "@emotion/eslint-plugin": "^11.7.0", 40 | "@typescript-eslint/eslint-plugin": "^5.30.7", 41 | "@typescript-eslint/parser": "^5.30.7", 42 | "eslint": "^8.20.0", 43 | "eslint-config-prettier": "^8.5.0", 44 | "eslint-import-resolver-typescript": "^3.2.7", 45 | "eslint-plugin-node": "^11.1.0", 46 | "eslint-plugin-prettier": "^4.2.1", 47 | "eslint-plugin-promise": "^6.0.0", 48 | "eslint-plugin-react": "^7.30.1", 49 | "eslint-plugin-unused-imports": "^2.0.0", 50 | 51 | "gh-pages": "^4.0.0", 52 | "prettier": "^2.7.1" 53 | }, 54 | "eslintConfig": { 55 | "extends": [ 56 | "react-app", 57 | "react-app/jest" 58 | ] 59 | }, 60 | "browserslist": { 61 | "production": [ 62 | ">0.2%", 63 | "not dead", 64 | "not op_mini all" 65 | ], 66 | "development": [ 67 | "last 1 chrome version", 68 | "last 1 firefox version", 69 | "last 1 safari version" 70 | ] 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PrettyCoffee/PrettyCoffee.github.io/a8d248920d3b20463c358af4ac41fb86b7bcfdab/public/favicon.ico -------------------------------------------------------------------------------- /public/fonts/Quicksand-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PrettyCoffee/PrettyCoffee.github.io/a8d248920d3b20463c358af4ac41fb86b7bcfdab/public/fonts/Quicksand-Bold.ttf -------------------------------------------------------------------------------- /public/fonts/Quicksand-Medium.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PrettyCoffee/PrettyCoffee.github.io/a8d248920d3b20463c358af4ac41fb86b7bcfdab/public/fonts/Quicksand-Medium.ttf -------------------------------------------------------------------------------- /public/fonts/Quicksand-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PrettyCoffee/PrettyCoffee.github.io/a8d248920d3b20463c358af4ac41fb86b7bcfdab/public/fonts/Quicksand-Regular.ttf -------------------------------------------------------------------------------- /public/fonts/quicksand.css: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: 'Quicksand'; 3 | font-style: normal; 4 | font-weight: 400; 5 | font-display: swap; 6 | src: url(./Quicksand-Regular.ttf) format('truetype'); 7 | } 8 | @font-face { 9 | font-family: 'Quicksand'; 10 | font-style: normal; 11 | font-weight: 500; 12 | font-display: swap; 13 | src: url(./Quicksand-Medium.ttf) format('truetype'); 14 | } 15 | @font-face { 16 | font-family: 'Quicksand'; 17 | font-style: normal; 18 | font-weight: 700; 19 | font-display: swap; 20 | src: url(./Quicksand-Bold.ttf) format('truetype'); 21 | } 22 | -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 13 | 14 | 18 | 19 | 28 | PrettyCoffee 29 | 30 | 31 | 32 |
33 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PrettyCoffee/PrettyCoffee.github.io/a8d248920d3b20463c358af4ac41fb86b7bcfdab/public/logo192.png -------------------------------------------------------------------------------- /public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PrettyCoffee/PrettyCoffee.github.io/a8d248920d3b20463c358af4ac41fb86b7bcfdab/public/logo512.png -------------------------------------------------------------------------------- /public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "PrettyCoffee.github.io", 3 | "name": "PrettyCoffee.github.io", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | }, 10 | { 11 | "src": "logo192.png", 12 | "type": "image/png", 13 | "sizes": "192x192" 14 | }, 15 | { 16 | "src": "logo512.png", 17 | "type": "image/png", 18 | "sizes": "512x512" 19 | } 20 | ], 21 | "start_url": ".", 22 | "display": "standalone", 23 | "theme_color": "#000000", 24 | "background_color": "#ffffff" 25 | } 26 | -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /src/@types/emotion.ts: -------------------------------------------------------------------------------- 1 | import "@emotion/react" 2 | 3 | declare module "@emotion/react" { 4 | export interface Theme { 5 | color: { 6 | dark: string 7 | light: string 8 | grey: string 9 | 10 | red: string 11 | green: string 12 | yellow: string 13 | blue: string 14 | purple: string 15 | cyan: string 16 | orange: string 17 | } 18 | space: { 19 | xxs: string 20 | xs: string 21 | sm: string 22 | md: string 23 | lg: string 24 | xl: string 25 | xxl: string 26 | } 27 | border: { 28 | light: { 29 | sm: string 30 | lg: string 31 | } 32 | dark: { 33 | sm: string 34 | lg: string 35 | } 36 | } 37 | shadow: { 38 | regular: string 39 | small: string 40 | } 41 | animation: { 42 | bouncy: string 43 | } 44 | breakpoints: { 45 | small: string 46 | mobile: string 47 | tablet: string 48 | laptop: string 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/App.tsx: -------------------------------------------------------------------------------- 1 | import styled from "@emotion/styled" 2 | 3 | import { ThemeProvider } from "./components" 4 | import { Portfolio } from "./Portfolio/Portfolio" 5 | 6 | const AppWrapper = styled.div` 7 | background-color: ${({ theme }) => theme.color.dark}; 8 | ` 9 | 10 | export const App = () => { 11 | return ( 12 | 13 | 14 | 15 | 16 | 17 | ) 18 | } 19 | -------------------------------------------------------------------------------- /src/Portfolio/Animation.tsx: -------------------------------------------------------------------------------- 1 | import { css } from "@emotion/react" 2 | import styled from "@emotion/styled" 3 | 4 | import computer from "../data/media/computer.svg" 5 | import smartphone from "../data/media/smartphone.svg" 6 | import tablet from "../data/media/tablet.svg" 7 | 8 | const StyledAnimation = styled.div` 9 | ${({ theme: { breakpoints } }) => css` 10 | display: grid; 11 | grid-gap: 40px; 12 | grid-template-columns: auto auto auto; 13 | align-items: flex-end; 14 | margin-bottom: 50px; 15 | 16 | @keyframes jump { 17 | 15% { 18 | transform: translateY(-50px); 19 | } 20 | 30%, 21 | 100% { 22 | transform: translateY(0px); 23 | } 24 | } 25 | 26 | ${breakpoints.tablet} { 27 | position: relative; 28 | display: block; 29 | height: 250px; 30 | > img { 31 | position: absolute; 32 | } 33 | @keyframes jump { 34 | 15% { 35 | transform: translateY(-20px); 36 | } 37 | 30%, 38 | 100% { 39 | transform: translateY(0px); 40 | } 41 | } 42 | } 43 | `} 44 | ` 45 | 46 | const Device = styled.img` 47 | ${({ theme: { animation, shadow } }) => css` 48 | animation: jump 3s ${animation.bouncy} infinite; 49 | filter: drop-shadow(${shadow.small}); 50 | `} 51 | ` 52 | 53 | const Computer = styled(Device)` 54 | ${({ theme: { breakpoints } }) => css` 55 | width: 500px; 56 | 57 | ${breakpoints.tablet} { 58 | left: -300px; 59 | bottom: 10px; 60 | } 61 | ${breakpoints.mobile} { 62 | width: 333px; 63 | left: -200px; 64 | bottom: 10px; 65 | } 66 | ${breakpoints.small} { 67 | left: -166px; 68 | } 69 | `} 70 | ` 71 | 72 | const Tablet = styled(Device)` 73 | ${({ theme: { breakpoints } }) => css` 74 | width: 150px; 75 | animation-delay: 0.5s; 76 | ${breakpoints.tablet} { 77 | left: 100px; 78 | bottom: 0; 79 | } 80 | ${breakpoints.mobile} { 81 | width: 100px; 82 | left: 70px; 83 | bottom: 0; 84 | } 85 | ${breakpoints.small} { 86 | left: 0px; 87 | } 88 | `} 89 | ` 90 | 91 | const Smartphone = styled(Device)` 92 | ${({ theme: { breakpoints } }) => css` 93 | width: 70px; 94 | animation-delay: 1s; 95 | 96 | ${breakpoints.tablet} { 97 | left: 220px; 98 | bottom: -10px; 99 | } 100 | ${breakpoints.mobile} { 101 | width: 46px; 102 | left: 150px; 103 | bottom: -10px; 104 | } 105 | ${breakpoints.small} { 106 | left: 70px; 107 | } 108 | `} 109 | ` 110 | 111 | export const Animation = () => { 112 | return ( 113 | 114 | 115 | 116 | 117 | 118 | ) 119 | } 120 | -------------------------------------------------------------------------------- /src/Portfolio/Intro.tsx: -------------------------------------------------------------------------------- 1 | import styled from "@emotion/styled" 2 | 3 | import { Typer } from "../components" 4 | import { Animation } from "./Animation" 5 | 6 | const TyperContainer = styled.div` 7 | width: 500px; 8 | font-size: ${({ theme }) => theme.space.xl}; 9 | ${({ theme }) => theme.breakpoints.small} { 10 | width: auto; 11 | font-size: 2.5rem; 12 | margin: 0 auto; 13 | } 14 | margin: 0 auto; 15 | ` 16 | 17 | export const Intro = () => ( 18 | <> 19 | 20 | 21 | 32 | 33 | 34 | ) 35 | -------------------------------------------------------------------------------- /src/Portfolio/Playground/Playground.tsx: -------------------------------------------------------------------------------- 1 | import { css } from "@emotion/react" 2 | import styled from "@emotion/styled" 3 | 4 | import { Carousel, CarouselItem } from "../../components" 5 | import { ShadowPlay } from "./ShadowPlay" 6 | 7 | const Text = styled.div` 8 | ${({ theme: { color } }) => css` 9 | color: ${color.dark}; 10 | > h3 { 11 | font-weight: 500; 12 | } 13 | font-weight: 400; 14 | max-width: 300px; 15 | `} 16 | ` 17 | 18 | export const Playground = () => { 19 | return ( 20 | 21 | 22 | 23 |

24 | I couldnt think of a fitting text yet, take this cat ipsum instead: 25 |

26 |
27 | Instantly break out into full speed gallop across the house for no 28 | reason. Cry for no apparent reason scream at teh bath yet destroy the 29 | blinds. Take a big fluffing crap 💩 purr sleep all day whilst slave is 30 | at work, play all night whilst slave is sleeping and run at 3am yet 31 | purr. 32 |
33 |
34 | 35 | 36 | 37 |
38 | ) 39 | } 40 | -------------------------------------------------------------------------------- /src/Portfolio/Playground/ShadowPlay.tsx: -------------------------------------------------------------------------------- 1 | import { css } from "@emotion/react" 2 | import styled from "@emotion/styled" 3 | 4 | const Animations = styled.div` 5 | display: grid; 6 | grid-gap: 2rem; 7 | grid-template: repeat(3, 8rem) / repeat(2, 8rem); 8 | > * { 9 | align-self: center; 10 | justify-self: center; 11 | } 12 | ` 13 | 14 | const Square = styled.div` 15 | ${({ theme: { color } }) => css` 16 | position: relative; 17 | width: 3rem; 18 | height: 3rem; 19 | transform: rotate(-45deg); 20 | 21 | ::after, 22 | ::before { 23 | position: absolute; 24 | content: ""; 25 | display: block; 26 | border-radius: 0.5rem; 27 | width: 1rem; 28 | height: 1rem; 29 | box-shadow: -0.2rem 0.2rem 0.4rem ${color.grey}, 30 | 0.1rem -0.1rem 0.2rem white, -0.2rem 0.2rem 0.3rem white inset, 31 | 0.15rem -0.15rem 0.2rem ${color.grey} inset; 32 | animation: squareLoad 1.5s linear infinite; 33 | } 34 | ::before { 35 | animation-delay: -750ms; 36 | } 37 | 38 | @keyframes squareLoad { 39 | 0% { 40 | width: 1rem; 41 | height: 1rem; 42 | margin: 0 0 0 0; 43 | } 44 | 12% { 45 | width: 3rem; 46 | height: 1rem; 47 | margin: 0 0 0 0; 48 | } 49 | 25% { 50 | width: 1rem; 51 | height: 1rem; 52 | margin: 0 0 0 2rem; 53 | } 54 | 37% { 55 | width: 1rem; 56 | height: 3rem; 57 | margin: 0 0 0 2rem; 58 | } 59 | 50% { 60 | width: 1rem; 61 | height: 1rem; 62 | margin: 2rem 0 0 2rem; 63 | } 64 | 62% { 65 | width: 3rem; 66 | height: 1rem; 67 | margin: 2rem 0 0 0; 68 | } 69 | 75% { 70 | width: 1rem; 71 | height: 1rem; 72 | margin: 2rem 0 0 0; 73 | } 74 | 87% { 75 | width: 1rem; 76 | height: 3rem; 77 | margin: 0 0 0 0; 78 | } 79 | } 80 | `} 81 | ` 82 | const FillingSquare = styled.div` 83 | position: relative; 84 | width: 4rem; 85 | height: 4rem; 86 | transform: rotate(-45deg); 87 | display: flex; 88 | justify-content: center; 89 | align-items: center; 90 | ` 91 | const SquareFill = styled.div` 92 | ${({ theme: { color } }) => css` 93 | position: absolute; 94 | content: ""; 95 | display: block; 96 | border-radius: 0.3rem; 97 | box-shadow: -0.2rem 0.2rem 0.4rem ${color.grey}, 0.1rem -0.1rem 0.2rem white, 98 | -0.2rem 0.2rem 0.3rem white inset, 99 | 0.15rem -0.15rem 0.2rem ${color.grey} inset; 100 | animation: squareFillLoad 2s linear infinite; 101 | 102 | :nth-of-type(2) { 103 | animation-delay: 0.5s; 104 | } 105 | :nth-of-type(3) { 106 | animation-delay: 1.5s; 107 | } 108 | :nth-of-type(4) { 109 | animation-delay: 1s; 110 | } 111 | 112 | @keyframes squareFillLoad { 113 | 0% { 114 | width: 0rem; 115 | height: 0rem; 116 | box-shadow: -0.2rem 0.2rem 0.4rem ${color.grey}, 117 | 0.1rem -0.1rem 0.2rem white, -0.2rem 0.2rem 0.3rem white inset, 118 | 0.15rem -0.15rem 0.2rem ${color.grey} inset; 119 | } 120 | 50% { 121 | width: 2rem; 122 | height: 2rem; 123 | box-shadow: 0 0.2rem 0.4rem ${color.grey}, 0 -0.1rem 0.2rem white, 124 | 0 0.2rem 0.3rem white inset, 0 -0.15rem 0.2rem ${color.grey} inset; 125 | } 126 | 100% { 127 | width: 4rem; 128 | height: 4rem; 129 | box-shadow: none; 130 | } 131 | } 132 | `} 133 | ` 134 | 135 | const Waves = styled.div` 136 | position: relative; 137 | display: flex; 138 | justify-content: center; 139 | align-items: center; 140 | ` 141 | 142 | const Wave = styled.div` 143 | ${({ theme: { color } }) => css` 144 | position: absolute; 145 | display: flex; 146 | justify-content: center; 147 | align-items: center; 148 | border-radius: 50%; 149 | transition: 1s; 150 | 151 | ::before { 152 | content: ''; 153 | border-radius: 50%; 154 | animation: inner 2s ease-in-out infinite, innerSize 2s ease-in-out infinite; 155 | } 156 | 157 | animation: outer 2s ease-in-out infinite, outerSize 2s ease-in-out infinite; 158 | :nth-of-type(2), 159 | :nth-of-type(2)::before { 160 | animation-delay: 600ms; 161 | } 162 | :nth-of-type(3), 163 | :nth-of-type(3)::before { 164 | animation-delay: 1200ms; 165 | } 166 | } 167 | 168 | @keyframes outer { 169 | 0% { 170 | box-shadow: 1em 1em 0.5em ${color.grey}, -0.1em -0.1em 0.1em white, 171 | 0.1em 0.1em 0.2em white inset, 172 | -0.2em -0.2em 0.2em ${color.grey} inset; 173 | } 174 | 40% { 175 | box-shadow: 0.3em 0.3em 0.3em ${color.grey}, 176 | -0.2em -0.2em 0.2em white, 0.1em 0.1em 0.2em white inset, 177 | -0.1em -0.1em 0.2em ${color.grey} inset; 178 | } 179 | 70% { 180 | box-shadow: 0.4em 0.4em 0.4em ${color.grey}, 181 | -0.4em -0.4em 0.4em white, 0.1em 0.1em 0.2em white inset, 182 | -0.1em -0.1em 0.2em ${color.grey} inset; 183 | } 184 | 90%, 185 | 100% { 186 | box-shadow: 0 0 0 ${color.grey}, 0 0 0 white, 187 | 0 0 0 white inset, 0 0 0 ${color.grey} inset; 188 | } 189 | } 190 | @keyframes inner { 191 | 0%, 192 | 40% { 193 | box-shadow: 0.1em 0.1em 0.2em white, 194 | -0.1em -0.1em 0.2em ${color.grey}, 0.1em 0.1em 0.2em ${color.grey} inset, 195 | -0.1em -0.1em 0.2em white inset; 196 | } 197 | 70% { 198 | box-shadow: 0.2em 0.2em 0.4em white, 199 | -0.2em -0.2em 0.4em ${color.grey}, 0.1em 0.1em 0.2em ${color.grey} inset, 200 | -0.1em -0.1em 0.2em white inset; 201 | } 202 | 90%, 203 | 100% { 204 | box-shadow: 0 0 0 ${color.grey}, 0 0 0 white, 205 | 0 0 0 white inset, 0 0 0 ${color.grey} inset; 206 | } 207 | } 208 | @keyframes innerSize { 209 | 15% { 210 | width: 0rem; 211 | height: 0rem; 212 | } 213 | 100% { 214 | width: 5rem; 215 | height: 5rem; 216 | } 217 | } 218 | @keyframes outerSize { 219 | 0% { 220 | width: 0rem; 221 | height: 0rem; 222 | } 223 | 100% { 224 | width: 5rem; 225 | height: 5rem; 226 | } 227 | } 228 | `} 229 | ` 230 | 231 | const Balls = styled.div` 232 | position: relative; 233 | display: grid; 234 | height: 3rem; 235 | grid-template-columns: repeat(4, 2rem); 236 | align-items: flex-end; 237 | > * { 238 | justify-self: center; 239 | } 240 | ` 241 | const LoadingBar = styled.div` 242 | ${({ theme: { color } }) => css` 243 | border-radius: 0.5rem; 244 | 245 | width: 1rem; 246 | height: 2.5rem; 247 | box-shadow: 0.2rem 0.2rem 0.4rem ${color.grey}, -0.1rem -0.1rem 0.2rem white, 248 | 0.2rem 0.2rem 0.3rem white inset, 249 | -0.15rem -0.15rem 0.2rem ${color.grey} inset; 250 | animation: barsLoad 1.5s ease-in-out infinite alternate-reverse; 251 | 252 | :nth-of-type(2) { 253 | animation-delay: 200ms; 254 | } 255 | :nth-of-type(3) { 256 | animation-delay: 400ms; 257 | } 258 | :nth-of-type(4) { 259 | animation-delay: 600ms; 260 | } 261 | 262 | @keyframes barsLoad { 263 | 0%, 264 | 20% { 265 | height: 1rem; 266 | margin-bottom: 1.5rem; 267 | } 268 | 50% { 269 | height: 2.5rem; 270 | margin-bottom: 0rem; 271 | margin-top: 0rem; 272 | } 273 | 80%, 274 | 100% { 275 | height: 1rem; 276 | margin-top: 1.5rem; 277 | } 278 | } 279 | `} 280 | ` 281 | 282 | const BouncingBall = styled.div` 283 | ${({ theme: { color } }) => css` 284 | border-radius: 50%; 285 | width: 1rem; 286 | height: 1rem; 287 | box-shadow: 0.2rem 0.2rem 0.4rem ${color.grey}, -0.1rem -0.1rem 0.2rem white, 288 | 0.2rem 0.2rem 0.3rem white inset, 289 | -0.15rem -0.15rem 0.2rem ${color.grey} inset; 290 | animation: bounceLoad 0.75s ease-in-out infinite alternate; 291 | 292 | :nth-of-type(1) { 293 | animation-delay: -600ms; 294 | } 295 | :nth-of-type(2) { 296 | animation-delay: -400ms; 297 | } 298 | :nth-of-type(3) { 299 | animation-delay: -200ms; 300 | } 301 | 302 | @keyframes bounceLoad { 303 | 0% { 304 | height: 0.9rem; 305 | width: 1.2rem; 306 | } 307 | 20% { 308 | width: 1rem; 309 | height: 1rem; 310 | box-shadow: 0.2rem 0.2rem 0.4rem ${color.grey}, 311 | -0.1rem -0.1rem 0.2rem white, 0.2rem 0.2rem 0.3rem white inset, 312 | -0.15rem -0.15rem 0.2rem ${color.grey} inset; 313 | } 314 | 100% { 315 | box-shadow: 0.2rem 2.2rem 0.4rem ${color.grey}, 316 | -0.1rem -0.1rem 0.2rem white, 0.2rem 0.2rem 0.3rem white inset, 317 | -0.15rem -0.15rem 0.2rem ${color.grey} inset; 318 | transform: translate(0px, -2rem); 319 | } 320 | } 321 | `} 322 | ` 323 | 324 | const SpinningBalls = styled.div` 325 | ${({ theme: { color } }) => { 326 | const easing = "cubic-bezier(0.65, -0.45, 0.35, 1.45)" 327 | return css` 328 | position: relative; 329 | display: flex; 330 | align-items: center; 331 | width: 3rem; 332 | height: 3rem; 333 | animation: squareSpin 1s ${easing} infinite; 334 | 335 | ::before, 336 | ::after { 337 | content: ""; 338 | position: absolute; 339 | z-index: 1; 340 | width: 1rem; 341 | height: 1rem; 342 | border-radius: 50%; 343 | box-shadow: 0.2rem 0.2rem 0.4rem ${color.grey}, 344 | -0.1rem -0.1rem 0.2rem white, 0.2rem 0.2rem 0.3rem white inset, 345 | -0.15rem -0.15rem 0.2rem ${color.grey} inset; 346 | animation: ballSpin 1s ${easing} infinite; 347 | } 348 | 349 | ::after { 350 | left: 0; 351 | top: 0; 352 | } 353 | ::before { 354 | right: 0; 355 | bottom: 0; 356 | } 357 | 358 | @keyframes squareSpin { 359 | from { 360 | transform: rotate(-45deg); 361 | } 362 | to { 363 | transform: rotate(315deg); 364 | } 365 | } 366 | 367 | @keyframes ballSpin { 368 | from { 369 | transform: rotate(45deg); 370 | } 371 | to { 372 | transform: rotate(-315deg); 373 | } 374 | } 375 | ` 376 | }} 377 | ` 378 | 379 | export const ShadowPlay = () => { 380 | return ( 381 | 382 | 383 | 384 | 385 | 386 | 387 | 388 | 389 | 390 | 391 | 392 | 393 | 394 | 395 | 396 | 397 | 398 | 399 | 400 | 401 | 402 | 403 | 404 | 405 | 406 | 407 | 408 | ) 409 | } 410 | -------------------------------------------------------------------------------- /src/Portfolio/Portfolio.tsx: -------------------------------------------------------------------------------- 1 | import { css, useTheme } from "@emotion/react" 2 | import styled from "@emotion/styled" 3 | import { faGithubAlt } from "@fortawesome/free-brands-svg-icons" 4 | import { 5 | faTerminal, 6 | faAddressCard, 7 | faHashtag, 8 | faPaintBrush, 9 | } from "@fortawesome/free-solid-svg-icons" 10 | 11 | import { 12 | Scrollspy, 13 | ScrollspyExtra, 14 | ScrollspyContent, 15 | Section, 16 | } from "../components" 17 | import logo from "../data/media/logo.webp" 18 | import { Intro } from "./Intro" 19 | import { Playground } from "./Playground/Playground" 20 | import { Profile } from "./Profile" 21 | import { Projects } from "./Projects/Projects" 22 | import { SocialMedia } from "./SocialMedia" 23 | 24 | const Square = styled.div<{ num: number }>` 25 | ${({ num }) => css` 26 | display: flex; 27 | align-items: center; 28 | justify-content: space-between; 29 | height: 1.414rem; 30 | width: 2rem; 31 | ::before { 32 | content: ""; 33 | display: block; 34 | box-sizing: border-box; 35 | position: relative; 36 | z-index: ${num}; 37 | background-color: ${num !== 1 ? "white" : "black"}; 38 | border: 2px solid white; 39 | height: 2rem; 40 | width: 2rem; 41 | transform: rotate(45deg); 42 | } 43 | `} 44 | ` 45 | 46 | const Avatar = styled.div` 47 | ${({ theme: { border } }) => css` 48 | width: 50px; 49 | height: 50px; 50 | padding: 5px; 51 | border: ${border.light.sm}; 52 | > img { 53 | object-fit: contain; 54 | width: 100%; 55 | height: 100%; 56 | mix-blend-mode: normal; 57 | } 58 | `} 59 | ` 60 | 61 | export const Portfolio = () => { 62 | const { color } = useTheme() 63 | 64 | return ( 65 | 66 | 67 | 68 | {"avatar"} 69 | 70 | 71 | 72 | 73 |
74 | 75 |
76 |
77 | 78 | 79 |
80 | 81 |
82 |
83 | 84 | 85 |
86 | 87 |
88 |
89 | 90 | 91 |
92 | 93 |
94 |
95 | 96 | 97 |
98 | 99 |
100 |
101 | 102 | 103 | 104 | 105 | 106 | 107 |
108 | ) 109 | } 110 | -------------------------------------------------------------------------------- /src/Portfolio/Profile.tsx: -------------------------------------------------------------------------------- 1 | import { css } from "@emotion/react" 2 | import styled from "@emotion/styled" 3 | 4 | import avatar from "../data/media/avatar.webp" 5 | 6 | const gap = 40 7 | const width = 800 8 | const height = width / 1.618 9 | const textWidth = height - gap * 2 10 | const picWidth = width - textWidth - gap * 3 11 | 12 | const StyledProfile = styled.div` 13 | ${({ theme: { color, breakpoints, shadow } }) => css` 14 | box-sizing: border-box; 15 | width: ${width}px; 16 | min-height: ${height}px; 17 | 18 | display: flex; 19 | 20 | background-color: ${color.dark}; 21 | box-shadow: ${shadow.regular}; 22 | padding: ${gap}px; 23 | 24 | display: grid; 25 | grid-template-columns: ${picWidth}px ${textWidth}px; 26 | grid-gap: ${gap}px; 27 | 28 | ${breakpoints.tablet} { 29 | grid-template-columns: auto; 30 | width: 100%; 31 | } 32 | `} 33 | ` 34 | 35 | const LeftColumn = styled.div` 36 | ${({ theme: { breakpoints } }) => css` 37 | ${breakpoints.tablet} { 38 | display: grid; 39 | grid-template-columns: auto 1fr; 40 | grid-gap: ${gap}px; 41 | > div { 42 | margin: auto 0; 43 | } 44 | } 45 | @media only screen and (max-width: 850px) { 46 | grid-template-columns: auto; 47 | > img { 48 | justify-self: center; 49 | } 50 | } 51 | `} 52 | ` 53 | 54 | const Picture = styled.img` 55 | ${({ theme: { color, shadow, breakpoints } }) => css` 56 | width: ${picWidth}px; 57 | height: ${picWidth}px; 58 | border: 4px solid ${color.light}; 59 | padding: 20px; 60 | object-fit: contain; 61 | box-sizing: border-box; 62 | box-shadow: ${shadow.regular}; 63 | ${breakpoints.small} { 64 | width: 100%; 65 | height: unset; 66 | } 67 | `} 68 | ` 69 | 70 | const Skills = styled.div` 71 | ${({ theme: { space } }) => css` 72 | margin-top: ${gap}px; 73 | font-weight: 400; 74 | line-height: ${space.md}; 75 | `} 76 | ` 77 | 78 | const Text = styled.div` 79 | ${({ theme: { color, space, breakpoints } }) => css` 80 | width: ${textWidth}px; 81 | color: ${color.light}; 82 | font-size: ${space.md}; 83 | text-align: justify; 84 | > p { 85 | margin: 0; 86 | } 87 | > h2 { 88 | font-size: ${space.lg}; 89 | margin-top: 0; 90 | margin-bottom: ${space.sm}; 91 | } 92 | 93 | ${breakpoints.tablet} { 94 | width: 100%; 95 | } 96 | ${breakpoints.small} { 97 | font-size: ${space.sm}; 98 | > h2 { 99 | font-size: ${space.md}; 100 | } 101 | } 102 | `} 103 | ` 104 | 105 | const K = styled.span` 106 | ${({ theme: { color } }) => 107 | css` 108 | color: ${color.red !== color.dark ? color.red : color.light}; 109 | `}; 110 | ` 111 | 112 | export const Profile = () => ( 113 | 114 | 115 | 116 | 117 | B Sc Informatics 118 |
119 | German | English 120 |
121 | Skills: CSS, TypeScript, React, Redux, Web Design 122 |
123 |
124 | 125 |

About Me

126 |

127 | I am a 25 year old professional web developer which acquired his{" "} 128 | informatics degree in 2020 and is employed since then as a web 129 | developer. 130 |
131 |
132 | Most of the time I develop frontend apps with React and{" "} 133 | Typescript, try to design my own stuff and build cool web 134 | apps while learning new things. 135 |

136 |
137 |
138 | ) 139 | -------------------------------------------------------------------------------- /src/Portfolio/Projects/Project.tsx: -------------------------------------------------------------------------------- 1 | import { css } from "@emotion/react" 2 | import styled from "@emotion/styled" 3 | import { faGithub } from "@fortawesome/free-brands-svg-icons" 4 | import { faEye } from "@fortawesome/free-regular-svg-icons" 5 | import { faBook } from "@fortawesome/free-solid-svg-icons" 6 | 7 | import { Button } from "../../components" 8 | import { Project as ProjectProps } from "../../data/projects" 9 | 10 | const StyledProject = styled.div` 11 | ${({ theme: { breakpoints } }) => css` 12 | display: grid; 13 | grid-template-columns: auto 1fr; 14 | grid-gap: 40px; 15 | margin-bottom: 40px; 16 | ${breakpoints.mobile} { 17 | grid-template-columns: auto; 18 | width: calc(100vw - 40px); 19 | position: relative; 20 | } 21 | `} 22 | ` 23 | 24 | const ProjectAvatar = styled.img` 25 | ${({ theme: { color, shadow, breakpoints, space } }) => css` 26 | height: 200px; 27 | width: 200px; 28 | border: 4px solid ${color.light}; 29 | padding: 20px; 30 | object-fit: contain; 31 | box-sizing: border-box; 32 | box-shadow: ${shadow.small}; 33 | background-color: ${color.dark}; 34 | ${breakpoints.mobile} { 35 | justify-self: center; 36 | margin-top: ${space.xl}; 37 | } 38 | `} 39 | ` 40 | 41 | const Description = styled.div` 42 | ${({ theme: { space, shadow, color } }) => css` 43 | display: grid; 44 | grid-template-rows: auto 1fr auto; 45 | padding: 20px; 46 | box-shadow: ${shadow.small}; 47 | background-color: ${color.dark}; 48 | > h3 { 49 | margin: 0 0 ${space.xs} 0; 50 | font-weight: 500; 51 | } 52 | `} 53 | ` 54 | const ButtonRow = styled.div` 55 | ${({ theme: { space } }) => css` 56 | text-align: right; 57 | > a:nth-of-type(2) { 58 | margin-left: ${space.sm}; 59 | } 60 | `} 61 | ` 62 | 63 | const Paragraph = styled.p` 64 | white-space: break-spaces; 65 | ` 66 | 67 | export const Project = ({ 68 | title, 69 | description, 70 | demoUrl, 71 | repoUrl, 72 | docsUrl, 73 | image, 74 | }: ProjectProps) => ( 75 | 76 | 77 | 78 |

{title}

79 | {description} 80 | 81 | {repoUrl && ( 82 | 90 | )} 91 | {demoUrl && ( 92 | 95 | )} 96 | {docsUrl && ( 97 | 100 | )} 101 | 102 |
103 |
104 | ) 105 | -------------------------------------------------------------------------------- /src/Portfolio/Projects/Projects.tsx: -------------------------------------------------------------------------------- 1 | import { css } from "@emotion/react" 2 | import styled from "@emotion/styled" 3 | 4 | import { Timeline } from "../../components" 5 | import { projects } from "../../data/projects" 6 | import { Project } from "./Project" 7 | 8 | const ProjectsContainer = styled.div` 9 | ${({ theme: { breakpoints } }) => css` 10 | width: 100%; 11 | max-width: 800px; 12 | ${breakpoints.mobile} { 13 | align-self: flex-start; 14 | width: calc(100% - 110px); 15 | } 16 | `} 17 | ` 18 | 19 | export const Projects = () => ( 20 | 21 | {projects.map(({ projects, ...timelineProps }) => ( 22 | 23 | {projects.map(project => ( 24 | 25 | ))} 26 | 27 | ))} 28 | 29 | ) 30 | -------------------------------------------------------------------------------- /src/Portfolio/SocialMedia.tsx: -------------------------------------------------------------------------------- 1 | import { useTheme } from "@emotion/react" 2 | import { 3 | faYoutube, 4 | faGithub, 5 | faGitlab, 6 | faReddit, 7 | faNpm, 8 | faSteam, 9 | } from "@fortawesome/free-brands-svg-icons" 10 | 11 | import { Card, CardGroup } from "../components" 12 | 13 | export const SocialMedia = () => { 14 | const { color } = useTheme() 15 | return ( 16 | 17 | 22 | PrettyCoffee 23 | 24 | 29 | PrettyCoffee 30 | 31 | 36 | ~prettycoffee 37 | 38 | 43 | PrettyCoffee 44 | 45 | 50 | u/SpinatMixxer 51 | 52 | 57 | PrettyCoffee 58 | 59 | 60 | ) 61 | } 62 | -------------------------------------------------------------------------------- /src/components/Button/Button.tsx: -------------------------------------------------------------------------------- 1 | import React from "react" 2 | 3 | import { css } from "@emotion/react" 4 | import styled from "@emotion/styled" 5 | import { 6 | FontAwesomeIcon, 7 | FontAwesomeIconProps, 8 | } from "@fortawesome/react-fontawesome" 9 | 10 | const StyledButton = styled.button` 11 | ${({ theme: { color, border, space } }) => css` 12 | display: inline-flex; 13 | justify-content: center; 14 | align-items: center; 15 | cursor: pointer; 16 | color: ${color.light}; 17 | background-color: transparent; 18 | border: ${border.light.sm}; 19 | padding: 0 ${space.sm}; 20 | height: ${space.lg}; 21 | transition: 0.3s; 22 | font-weight: 400; 23 | font-size: ${space.sm}; 24 | :hover, 25 | :active, 26 | :focus { 27 | color: ${color.dark}; 28 | background-color: ${color.light}; 29 | outline: none; 30 | } 31 | 32 | > svg { 33 | margin-left: ${space.xs}; 34 | } 35 | `} 36 | ` 37 | 38 | type ButtonProps = { 39 | icon?: FontAwesomeIconProps["icon"] 40 | onClick?: () => void 41 | href?: string 42 | target?: string 43 | rel?: string 44 | } 45 | 46 | export const Button = ({ 47 | children, 48 | icon, 49 | ...buttonProps 50 | }: React.PropsWithChildren) => ( 51 | 52 | {children} 53 | {icon && } 54 | 55 | ) 56 | -------------------------------------------------------------------------------- /src/components/Button/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./Button" 2 | -------------------------------------------------------------------------------- /src/components/Cards/Card.tsx: -------------------------------------------------------------------------------- 1 | import React from "react" 2 | 3 | import { css } from "@emotion/react" 4 | import styled from "@emotion/styled" 5 | import { IconDefinition } from "@fortawesome/fontawesome-svg-core" 6 | import { FontAwesomeIcon } from "@fortawesome/react-fontawesome" 7 | 8 | const CardWrapper = styled.a<{ color: string }>` 9 | ${({ theme: { shadow }, color }) => css` 10 | position: relative; 11 | width: 14.14rem; 12 | height: 10rem; 13 | background-color: ${color}; 14 | box-shadow: ${shadow.regular}; 15 | float: left; 16 | overflow: hidden; 17 | 18 | ::before, 19 | ::after { 20 | content: ""; 21 | position: absolute; 22 | right: 0; 23 | left: 0; 24 | z-index: 1; 25 | 26 | background-color: black; 27 | transition: 0.4s ease; 28 | } 29 | ::before { 30 | top: 0; 31 | bottom: 0; 32 | opacity: 0.6; 33 | } 34 | 35 | ::after { 36 | top: 10rem; 37 | bottom: 0; 38 | opacity: 0.4; 39 | } 40 | [datatype="text"] { 41 | } 42 | 43 | :hover, 44 | :active, 45 | :focus { 46 | outline: none; 47 | ::before { 48 | bottom: 10rem; 49 | } 50 | ::after { 51 | top: 0; 52 | } 53 | [datatype="icon"] { 54 | bottom: 10rem; 55 | top: -10rem; 56 | } 57 | [datatype="text"] { 58 | top: 0; 59 | } 60 | } 61 | `} 62 | ` 63 | 64 | const CardContent = styled.div` 65 | color: ${({ theme }) => theme.color.light}; 66 | position: absolute; 67 | height: 100%; 68 | width: 100%; 69 | z-index: 10; 70 | 71 | display: flex; 72 | justify-content: center; 73 | align-items: center; 74 | transition: 0.4s ease; 75 | ` 76 | 77 | const CardLabel = styled(CardContent)` 78 | top: 10rem; 79 | font-weight: 500; 80 | font-size: ${({ theme }) => theme.space.md}; 81 | ` 82 | 83 | const CardIcon = styled(CardContent)` 84 | top: 0; 85 | right: 0; 86 | font-size: ${({ theme }) => theme.space.xl}; 87 | ` 88 | 89 | type CardProps = { 90 | url: string 91 | icon: IconDefinition 92 | color: string 93 | } 94 | 95 | export const Card = ({ 96 | url, 97 | children, 98 | icon, 99 | color, 100 | }: React.PropsWithChildren) => { 101 | return ( 102 | 103 | 104 | 105 | 106 | {children} 107 | 108 | ) 109 | } 110 | -------------------------------------------------------------------------------- /src/components/Cards/CardGroup.tsx: -------------------------------------------------------------------------------- 1 | import { css } from "@emotion/react" 2 | import styled from "@emotion/styled" 3 | 4 | export const CardGroup = styled.div` 5 | ${({ theme: { breakpoints, space } }) => 6 | css` 7 | display: grid; 8 | grid-gap: 40px; 9 | grid-template-columns: auto auto auto; 10 | ${breakpoints.tablet} { 11 | grid-template-columns: auto auto; 12 | } 13 | ${breakpoints.small} { 14 | grid-gap: 20px; 15 | > a { 16 | height: 6.4rem; 17 | width: 9rem; 18 | > [datatype="text"] { 19 | font-size: ${space.sm}; 20 | } 21 | > [datatype="icon"] { 22 | font-size: 3rem; 23 | } 24 | } 25 | } 26 | `} 27 | ` 28 | -------------------------------------------------------------------------------- /src/components/Cards/index.tsx: -------------------------------------------------------------------------------- 1 | export * from "./Card" 2 | export * from "./CardGroup" 3 | -------------------------------------------------------------------------------- /src/components/Carousel/Carousel.tsx: -------------------------------------------------------------------------------- 1 | import React from "react" 2 | 3 | import { css } from "@emotion/react" 4 | import styled from "@emotion/styled" 5 | 6 | import { DirectionButton } from "../DirectionButton" 7 | import { ItemWindow } from "./fragments/ItemWindow" 8 | import { getComponents } from "./utils/getComponents" 9 | 10 | const Wrapper = styled.div<{ headline?: string }>` 11 | ${({ theme: { color }, headline }) => css` 12 | position: relative; 13 | width: 100%; 14 | max-width: 800px; 15 | height: 500px; 16 | display: flex; 17 | justify-content: center; 18 | ::before { 19 | content: "${headline}"; 20 | position: absolute; 21 | top: -2.5rem; 22 | left: 0; 23 | color: ${color.dark}; 24 | font-weight: 400; 25 | font-size: 1.5rem; 26 | } 27 | `} 28 | ` 29 | 30 | const CarouselButton = styled.div` 31 | ${({ theme: { breakpoints } }) => css` 32 | position: absolute; 33 | bottom: 50%; 34 | height: 0; 35 | display: flex; 36 | align-items: center; 37 | transition: 0.5s; 38 | ${breakpoints.mobile} { 39 | bottom: -3rem; 40 | } 41 | `} 42 | ` 43 | 44 | const RightButton = styled(CarouselButton)` 45 | ${({ theme: { breakpoints } }) => css` 46 | right: -2rem; 47 | ${breakpoints.mobile} { 48 | right: 20%; 49 | } 50 | `} 51 | ` 52 | const LeftButton = styled(CarouselButton)` 53 | ${({ theme: { breakpoints } }) => css` 54 | left: -2rem; 55 | 56 | ${breakpoints.mobile} { 57 | left: 20%; 58 | } 59 | `} 60 | ` 61 | 62 | const ItemHider = styled.div<{ hide?: "left" | "right" }>` 63 | ${({ hide }) => css` 64 | transition: 0.5s; 65 | position: absolute; 66 | display: flex; 67 | align-items: center; 68 | justify-content: center; 69 | width: 100%; 70 | ${ 71 | hide === "left" && 72 | css` 73 | transform: translateX(-100%); 74 | ` 75 | } 76 | ${ 77 | hide === "right" && 78 | css` 79 | transform: translateX(100%); 80 | ` 81 | }} 82 | `} 83 | ` 84 | 85 | export type CarouselProps = { 86 | children: React.ReactElement[] 87 | } 88 | 89 | export const Carousel = ({ children }: CarouselProps) => { 90 | const [current, setCurrent] = React.useState(0) 91 | const items = getComponents(children) 92 | 93 | const handleChange = (direction: "left" | "right") => { 94 | if (direction === "left") setCurrent(current - 1) 95 | else setCurrent(current + 1) 96 | } 97 | 98 | return ( 99 | 100 | 101 | 106 | 107 | 108 | {items.map((item, index) => ( 109 | current ? "right" : undefined 114 | } 115 | > 116 | {item.content} 117 | 118 | ))} 119 | 120 | 121 | = items.length - 1} 125 | /> 126 | 127 | 128 | ) 129 | } 130 | -------------------------------------------------------------------------------- /src/components/Carousel/fragments/CarouselItem.tsx: -------------------------------------------------------------------------------- 1 | import React from "react" 2 | 3 | export type CarouselItemProps = { 4 | headline?: string 5 | } 6 | 7 | export const CarouselItem = ({ 8 | children, 9 | }: React.PropsWithChildren) => <>{children} 10 | -------------------------------------------------------------------------------- /src/components/Carousel/fragments/ItemWindow.tsx: -------------------------------------------------------------------------------- 1 | import React from "react" 2 | 3 | import { css } from "@emotion/react" 4 | import styled from "@emotion/styled" 5 | 6 | const Wrapper = styled.div` 7 | position: relative; 8 | height: 100%; 9 | width: 100%; 10 | display: flex; 11 | align-items: center; 12 | justify-content: center; 13 | overflow: hidden; 14 | ` 15 | 16 | const Decorator = styled.div` 17 | position: absolute; 18 | height: 1rem; 19 | left: 0; 20 | right: 0; 21 | opacity: 0.7; 22 | ` 23 | 24 | const TopDecorator = styled(Decorator)` 25 | ${({ theme: { color } }) => css` 26 | top: 0; 27 | box-shadow: 0 0.2rem 0 ${color.dark} inset, 0.2rem 0 0 ${color.dark} inset, 28 | -0.2rem 0 0 ${color.dark} inset; 29 | `} 30 | ` 31 | const BottomDecorator = styled(Decorator)` 32 | ${({ theme: { color } }) => css` 33 | display: flex; 34 | justify-content: center; 35 | bottom: 0; 36 | box-shadow: 0 -0.2rem 0 ${color.dark} inset, 0.2rem 0 0 ${color.dark} inset, 37 | -0.2rem 0 0 ${color.dark} inset; 38 | 39 | ::before { 40 | content: ""; 41 | position: absolute; 42 | bottom: -0.4rem; 43 | width: 5rem; 44 | height: 1rem; 45 | background-color: ${color.dark}; 46 | clip-path: polygon( 47 | 0 50%, 48 | 0.5rem 0, 49 | calc(100% - 0.5rem) 0, 50 | 100% 50%, 51 | calc(100% - 0.5rem) 100%, 52 | 0.5rem 100% 53 | ); 54 | } 55 | `} 56 | ` 57 | 58 | export const ItemWindow = ({ children }: React.PropsWithChildren) => ( 59 | <> 60 | 61 | {children} 62 | 63 | 64 | ) 65 | -------------------------------------------------------------------------------- /src/components/Carousel/index.ts: -------------------------------------------------------------------------------- 1 | export { Carousel } from "./Carousel" 2 | export { CarouselItem } from "./fragments/CarouselItem" 3 | -------------------------------------------------------------------------------- /src/components/Carousel/utils/getComponents.ts: -------------------------------------------------------------------------------- 1 | import React from "react" 2 | 3 | import { CarouselItem } from "../fragments/CarouselItem" 4 | 5 | export type Components = { headline?: string; content: React.ReactElement }[] 6 | 7 | export function getComponents(children: React.ReactElement[]): Components { 8 | const result: Components = [] 9 | if (children) { 10 | React.Children.forEach(children, child => { 11 | switch (child.type) { 12 | case CarouselItem: 13 | result.push({ 14 | headline: child.props.headline, 15 | content: child.props.children, 16 | }) 17 | break 18 | default: 19 | console.error( 20 | `An incompatible component was passed to the carousel, it won't be displayed.` 21 | ) 22 | } 23 | }) 24 | } 25 | if (result.length === 0) 26 | console.error(`You didnt pass any content to the carousel.`) 27 | 28 | return result 29 | } 30 | -------------------------------------------------------------------------------- /src/components/DirectionButton/DirectionButton.tsx: -------------------------------------------------------------------------------- 1 | import { css, keyframes } from "@emotion/react" 2 | import styled from "@emotion/styled" 3 | 4 | const hoverAnimation = keyframes` 5 | from { 6 | padding: 5%; 7 | } to { 8 | padding: 5% 10%; 9 | } 10 | ` 11 | 12 | const Wrapper = styled.div` 13 | ${({ theme: { space }, size = "medium" }) => { 14 | const width = 15 | size === "small" ? "3rem" : size === "medium" ? space.xl : space.xxl 16 | return css` 17 | position: relative; 18 | display: flex; 19 | justify-content: center; 20 | align-items: center; 21 | height: ${width}; 22 | width: ${width}; 23 | transition: 0.5s; 24 | ` 25 | }} 26 | ` 27 | const Seperator = styled.div` 28 | ${({ theme: { color } }) => css` 29 | background-color: ${color.dark}; 30 | transform: rotate(45deg); 31 | opacity: 0.7; 32 | height: 25%; 33 | width: 25%; 34 | `} 35 | ` 36 | 37 | const ArrowButton = styled.button` 38 | ${({ theme: { color } }) => css` 39 | position: absolute; 40 | box-sizing: content-box; 41 | height: 100%; 42 | width: 50%; 43 | padding: 5%; 44 | cursor: pointer; 45 | border: none; 46 | background-color: transparent; 47 | outline: none; 48 | ::before { 49 | content: ""; 50 | display: block; 51 | height: 100%; 52 | width: 100%; 53 | background-color: ${color.dark}; 54 | } 55 | :hover, 56 | :focus { 57 | animation: ${hoverAnimation} 400ms infinite ease-in-out alternate; 58 | } 59 | `} 60 | ` 61 | const ArrowRight = styled(ArrowButton)` 62 | left: 50%; 63 | ::before { 64 | clip-path: polygon(0 0, 100% 50%, 0 100%, 0 75%, 50% 50%, 0 25%); 65 | } 66 | ` 67 | const ArrowLeft = styled(ArrowButton)` 68 | right: 50%; 69 | ::before { 70 | clip-path: polygon(0 0, 100% 50%, 0 100%, 0 75%, 50% 50%, 0 25%); 71 | clip-path: polygon(100% 0, 0 50%, 100% 100%, 100% 75%, 50% 50%, 100% 25%); 72 | } 73 | ` 74 | 75 | export type DirectionButtonProps = { 76 | onClick?: (direction: "left" | "right") => void 77 | hideLeft?: boolean 78 | hideRight?: boolean 79 | size?: "small" | "medium" | "large" 80 | } 81 | 82 | export const DirectionButton = ({ 83 | onClick, 84 | hideLeft, 85 | hideRight, 86 | size, 87 | }: DirectionButtonProps) => ( 88 | 89 | 90 | {!hideLeft && onClick?.("left")} />} 91 | {!hideRight && onClick?.("right")} />} 92 | 93 | ) 94 | -------------------------------------------------------------------------------- /src/components/DirectionButton/index.ts: -------------------------------------------------------------------------------- 1 | export { DirectionButton } from "./DirectionButton" 2 | -------------------------------------------------------------------------------- /src/components/Scrollspy/Scrollspy.tsx: -------------------------------------------------------------------------------- 1 | import React from "react" 2 | 3 | import useScrollPosition from "@react-hook/window-scroll" 4 | 5 | import { ScrollspyMenu } from "./fragments/ScrollspyMenu" 6 | import { ScrollspyMenuItem } from "./fragments/ScrollspyMenuItem" 7 | import { ScrollspySeparator } from "./fragments/ScrollspySeparator" 8 | import { StyledScrollspy } from "./fragments/StyledScrollspy" 9 | import { getComponents } from "./utils/getComponents" 10 | 11 | type ScreenSection = { 12 | ystart: number 13 | scrollTo: () => void 14 | } 15 | 16 | type ScrollspyProps = { children: React.ReactElement[] } 17 | 18 | export const Scrollspy = ({ children }: ScrollspyProps) => { 19 | const [currentSection, setCurrentSection] = React.useState(0) 20 | const { extras, content } = getComponents(children) 21 | const screenSections: ScreenSection[] = React.useMemo(() => [], []) 22 | const scrollPosition = useScrollPosition() 23 | 24 | React.useEffect(() => { 25 | screenSections.forEach((section, index) => { 26 | if (section.ystart <= scrollPosition + 100) setCurrentSection(index) 27 | }) 28 | }, [scrollPosition, screenSections]) 29 | 30 | const addScrollSection = (ref: HTMLDivElement | null, index: number) => { 31 | if (!screenSections[index]) { 32 | screenSections[index] = { 33 | ystart: ref?.offsetTop ?? 0, 34 | scrollTo: () => { 35 | ref?.scrollIntoView({ behavior: "smooth" }) 36 | setCurrentSection(index) 37 | }, 38 | } 39 | } 40 | } 41 | 42 | return ( 43 | <> 44 | 45 | {extras[0]} 46 | 47 | 48 | 49 | {content.map((child, index) => ( 50 | screenSections[index]?.scrollTo()} 52 | active={currentSection === index} 53 | icon={child.icon} 54 | label={child.label} 55 | key={"spyitem" + index} 56 | /> 57 | ))} 58 | 59 | 60 | 61 | {extras.slice(1)} 62 | 63 | 64 | {content.map((child, index) => ( 65 |
addScrollSection(ref, index)} 68 | > 69 | {child.children} 70 |
71 | ))} 72 | 73 | ) 74 | } 75 | -------------------------------------------------------------------------------- /src/components/Scrollspy/ScrollspyContent.tsx: -------------------------------------------------------------------------------- 1 | import React from "react" 2 | 3 | import { IconDefinition } from "@fortawesome/fontawesome-svg-core" 4 | 5 | export type ScrollspyContentProps = { 6 | icon: IconDefinition 7 | label: string 8 | } 9 | 10 | export const ScrollspyContent = ({ 11 | children, 12 | }: React.PropsWithChildren) =>
{children}
13 | -------------------------------------------------------------------------------- /src/components/Scrollspy/ScrollspyExtra.tsx: -------------------------------------------------------------------------------- 1 | import styled from "@emotion/styled" 2 | 3 | export const ScrollspyExtra = styled.div` 4 | ${({ theme }) => theme.breakpoints.mobile} { 5 | display: none; 6 | } 7 | ` 8 | -------------------------------------------------------------------------------- /src/components/Scrollspy/fragments/ScrollspyMenu.tsx: -------------------------------------------------------------------------------- 1 | import styled from "@emotion/styled" 2 | 3 | export const ScrollspyMenu = styled.div` 4 | display: flex; 5 | flex-direction: column; 6 | ${({ theme }) => theme.breakpoints.mobile} { 7 | flex-direction: row; 8 | } 9 | ` 10 | -------------------------------------------------------------------------------- /src/components/Scrollspy/fragments/ScrollspyMenuItem.tsx: -------------------------------------------------------------------------------- 1 | import { css } from "@emotion/react" 2 | import styled from "@emotion/styled" 3 | import { IconDefinition } from "@fortawesome/fontawesome-svg-core" 4 | import { FontAwesomeIcon } from "@fortawesome/react-fontawesome" 5 | 6 | const WIDTH = "8rem" 7 | 8 | const StyledMenuItem = styled.button<{ active: boolean }>` 9 | ${({ theme: { color, space, animation, breakpoints }, active }) => css` 10 | position: relative; 11 | display: flex; 12 | flex-direction: row; 13 | justify-content: center; 14 | 15 | width: ${WIDTH}; 16 | height: calc(${space.md} * 2); 17 | cursor: pointer; 18 | border: none; 19 | border-radius: 0; 20 | outline: none; 21 | overflow-y: clip; 22 | 23 | background-color: transparent; 24 | color: inherit; 25 | font-size: ${space.sm}; 26 | white-space: nowrap; 27 | 28 | > svg, 29 | > span { 30 | position: absolute; 31 | transition: 0.7s ${animation.bouncy}; 32 | } 33 | > svg { 34 | top: calc(50% - ${space.sm}); 35 | font-size: calc(${space.sm} * 1.2); 36 | } 37 | 38 | > span { 39 | top: -50%; 40 | } 41 | 42 | :hover, 43 | :focus, 44 | :active { 45 | > svg { 46 | top: 150%; 47 | } 48 | 49 | > span { 50 | top: calc(50% - ${space.sm}); 51 | } 52 | } 53 | ${active && 54 | css` 55 | > svg { 56 | top: 150%; 57 | } 58 | 59 | > span { 60 | top: calc(50% - ${space.sm}); 61 | } 62 | `} 63 | 64 | ${breakpoints.mobile} { 65 | width: calc(${space.md} * 2); 66 | > span { 67 | display: none; 68 | } 69 | 70 | > svg { 71 | transition: 0.3s ${animation.bouncy}; 72 | } 73 | 74 | :focus, 75 | :active { 76 | > svg { 77 | top: calc(50% - ${space.sm}); 78 | } 79 | } 80 | :hover { 81 | > svg { 82 | color: ${color.red}; 83 | top: 0; 84 | } 85 | } 86 | ${active && 87 | css` 88 | > svg { 89 | color: ${color.blue}; 90 | top: 0; 91 | } 92 | :focus, 93 | :active { 94 | > svg { 95 | color: ${color.blue}; 96 | top: 0; 97 | } 98 | } 99 | `} 100 | } 101 | `} 102 | ` 103 | 104 | type ScrollspyMenuItemProps = { 105 | icon: IconDefinition 106 | label: string 107 | active: boolean 108 | onClick: () => void 109 | } 110 | 111 | export const ScrollspyMenuItem = ({ 112 | icon, 113 | label, 114 | ...props 115 | }: ScrollspyMenuItemProps) => ( 116 | 117 | 118 | {label} 119 | 120 | ) 121 | -------------------------------------------------------------------------------- /src/components/Scrollspy/fragments/ScrollspySeparator.tsx: -------------------------------------------------------------------------------- 1 | import styled from "@emotion/styled" 2 | 3 | export const ScrollspySeparator = styled.div` 4 | flex: auto; 5 | border-left: ${({ theme }) => theme.border.light.sm}; 6 | width: 0; 7 | margin: ${({ theme }) => theme.space.md} 0; 8 | ` 9 | -------------------------------------------------------------------------------- /src/components/Scrollspy/fragments/StyledScrollspy.ts: -------------------------------------------------------------------------------- 1 | import styled from "@emotion/styled" 2 | 3 | export const StyledScrollspy = styled.div` 4 | position: fixed; 5 | top: 50px; 6 | left: 0; 7 | bottom: 50px; 8 | z-index: 100; 9 | 10 | display: flex; 11 | flex-direction: column; 12 | justify-content: center; 13 | align-items: center; 14 | 15 | color: white; 16 | mix-blend-mode: difference; 17 | 18 | ${({ theme }) => theme.breakpoints.mobile} { 19 | flex-direction: row; 20 | justify-content: space-between; 21 | padding: 20px 20px 10px 20px; 22 | top: 0px; 23 | right: 0px; 24 | bottom: unset; 25 | left: 0px; 26 | background-color: ${({ theme }) => theme.color.dark}; 27 | mix-blend-mode: unset; 28 | } 29 | ` 30 | -------------------------------------------------------------------------------- /src/components/Scrollspy/index.ts: -------------------------------------------------------------------------------- 1 | export { Scrollspy } from "./Scrollspy" 2 | export { ScrollspyContent } from "./ScrollspyContent" 3 | export { ScrollspyExtra } from "./ScrollspyExtra" 4 | -------------------------------------------------------------------------------- /src/components/Scrollspy/utils/getComponents.ts: -------------------------------------------------------------------------------- 1 | import React from "react" 2 | 3 | import { ScrollspyExtra, ScrollspyContent } from "../" 4 | import { ScrollspyContentProps } from "../ScrollspyContent" 5 | 6 | type ContentElements = React.PropsWithChildren 7 | 8 | export type Components = { 9 | extras: React.ReactElement[] 10 | content: ContentElements[] 11 | } 12 | 13 | export function getComponents(children: React.ReactElement[]): Components { 14 | const result: Components = { content: [], extras: [] } 15 | if (children) { 16 | React.Children.forEach(children, child => { 17 | switch (child.type) { 18 | case ScrollspyExtra: 19 | result.extras.push(child) 20 | break 21 | case ScrollspyContent: 22 | result.content.push(child.props) 23 | break 24 | default: 25 | console.error( 26 | `An incompatible component was passed to the scrollspy, it won't be displayed.` 27 | ) 28 | } 29 | }) 30 | } 31 | if (result.content.length === 0) 32 | console.error(`You didnt pass any content to the scrollspy.`) 33 | 34 | return result 35 | } 36 | -------------------------------------------------------------------------------- /src/components/Section/Section.tsx: -------------------------------------------------------------------------------- 1 | import React from "react" 2 | 3 | import { css } from "@emotion/react" 4 | import styled from "@emotion/styled" 5 | 6 | const StyledSection = styled.section<{ bgcolor?: string }>` 7 | ${({ theme: { color, breakpoints }, bgcolor = color.dark }) => css` 8 | min-height: 100vh; 9 | position: relative; 10 | background-color: ${bgcolor}; 11 | 12 | display: flex; 13 | justify-content: center; 14 | align-items: center; 15 | flex-direction: column; 16 | 17 | padding: 150px; 18 | box-sizing: border-box; 19 | 20 | ${breakpoints.mobile} { 21 | padding-left: 20px; 22 | padding-right: 20px; 23 | } 24 | `} 25 | ` 26 | 27 | type SectionProps = { 28 | bgcolor?: string 29 | } 30 | 31 | export const Section = ({ 32 | children, 33 | ...props 34 | }: React.PropsWithChildren) => { 35 | return {children} 36 | } 37 | -------------------------------------------------------------------------------- /src/components/Section/index.ts: -------------------------------------------------------------------------------- 1 | export { Section } from "./Section" 2 | -------------------------------------------------------------------------------- /src/components/Theme/FiraCode-Light.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PrettyCoffee/PrettyCoffee.github.io/a8d248920d3b20463c358af4ac41fb86b7bcfdab/src/components/Theme/FiraCode-Light.woff -------------------------------------------------------------------------------- /src/components/Theme/FiraCode-Regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PrettyCoffee/PrettyCoffee.github.io/a8d248920d3b20463c358af4ac41fb86b7bcfdab/src/components/Theme/FiraCode-Regular.woff -------------------------------------------------------------------------------- /src/components/Theme/ThemeProvider.tsx: -------------------------------------------------------------------------------- 1 | import React from "react" 2 | 3 | import { 4 | ThemeProvider as EmotionThemeProvider, 5 | Global, 6 | css, 7 | } from "@emotion/react" 8 | 9 | import { getTheme } from "./getTheme" 10 | 11 | const { color } = getTheme() 12 | 13 | const globalStyles = css` 14 | ::-webkit-scrollbar { 15 | width: 15px; 16 | height: 15px; 17 | } 18 | ::-webkit-scrollbar-track { 19 | background-color: transparent; 20 | } 21 | ::-webkit-scrollbar-thumb { 22 | background-color: ${color.grey}; 23 | border-radius: 10px; 24 | } 25 | body { 26 | margin: 0; 27 | overflow: overlay; 28 | color: ${color.light}; 29 | } 30 | * { 31 | font-family: "Quicksand"; 32 | font-weight: 300; 33 | } 34 | 35 | a { 36 | text-decoration: none; 37 | ::visited { 38 | color: unset; 39 | } 40 | color: unset; 41 | } 42 | ` 43 | 44 | export const ThemeProvider = ({ 45 | children, 46 | }: React.PropsWithChildren) => { 47 | return ( 48 | <> 49 | 50 | {children} 51 | 52 | ) 53 | } 54 | -------------------------------------------------------------------------------- /src/components/Theme/getTheme.ts: -------------------------------------------------------------------------------- 1 | import { Theme } from "@emotion/react" 2 | 3 | // for grey: https://www.colorhexa.com/282a36-to-f8f8f2 -> 4th from light 4 | 5 | const schemes = { 6 | atom: { 7 | dark: "#282C34", 8 | light: "#DCDFE4", 9 | grey: "#AFB2B8", 10 | 11 | red: "#E06C75", 12 | green: "#98C379", 13 | yellow: "#E5C07B", 14 | blue: "#61AFEF", 15 | purple: "#C678DD", 16 | cyan: "#56B6C2", 17 | orange: "#FFBB7C", 18 | }, 19 | 20 | // https://github.com/dracula/dracula-theme 21 | dracula: { 22 | dark: "#282a36", 23 | light: "#f8f8f2", 24 | grey: "#c4c4c3", 25 | 26 | red: "#ff5555", 27 | green: "#50fa7b", 28 | yellow: "#f1fa8c", 29 | blue: "#6272a4", 30 | purple: "#bd93f9", 31 | cyan: "#8be9fd", 32 | orange: "#ffb86c", 33 | }, 34 | 35 | //https://github.com/arcticicestudio/nord 36 | nord: { 37 | dark: "#2E3541", 38 | light: "#ECEFF4", 39 | grey: "#bdc1c7", 40 | 41 | red: "#BF616A", 42 | green: "#A3BE8C", 43 | yellow: "#EBCB8B", 44 | blue: "#5E81AC", 45 | purple: "#B48EAD", 46 | cyan: "#88C0D0", 47 | orange: "#D08770", 48 | }, 49 | 50 | //https://material.io/design/color/the-color-system.html#tools-for-picking-colors 51 | material: { 52 | dark: "#263238", 53 | light: "#eceff1", 54 | grey: "#b0bec5", 55 | 56 | red: "#ef5350", 57 | green: "#66bb6a", 58 | yellow: "#ffee58", 59 | blue: "#42a5f5", 60 | purple: "#ab47bc", 61 | cyan: "#26c6da", 62 | orange: "#ffa726", 63 | }, 64 | } 65 | 66 | const currentTheme = schemes.atom 67 | 68 | const space = { 69 | xxs: "0.125rem", 70 | xs: "0.5rem", 71 | sm: "1rem", 72 | md: "1.5rem", 73 | lg: "2rem", 74 | xl: "4rem", 75 | xxl: "6rem", 76 | } 77 | 78 | const border = { 79 | light: { 80 | sm: space.xxs + " solid " + currentTheme.light, 81 | lg: space.xs + " solid " + currentTheme.light, 82 | }, 83 | dark: { 84 | sm: space.xxs + " solid " + currentTheme.dark, 85 | lg: space.xs + " solid " + currentTheme.dark, 86 | }, 87 | } 88 | 89 | const shadow = { 90 | regular: "0 20px 50px rgba(0, 0, 0, 0.8)", 91 | small: "0 10px 20px rgba(0, 0, 0, 0.8)", 92 | } 93 | 94 | const animation = { 95 | bouncy: "cubic-bezier(0.65, -0.85, 0.35, 1.85)", 96 | } 97 | 98 | const breakpoints = { 99 | small: "@media only screen and (max-width: 550px)", 100 | mobile: "@media only screen and (max-width: 800px)", 101 | tablet: "@media only screen and (max-width: 1050px)", 102 | laptop: "@media only screen and (max-width: 1440px)", 103 | } 104 | 105 | export const getTheme = () => 106 | ({ 107 | color: currentTheme, 108 | space: space, 109 | border: border, 110 | shadow: shadow, 111 | animation: animation, 112 | breakpoints: breakpoints, 113 | } as Theme) 114 | -------------------------------------------------------------------------------- /src/components/Theme/index.ts: -------------------------------------------------------------------------------- 1 | export { ThemeProvider } from "./ThemeProvider" 2 | export { getTheme } from "./getTheme" 3 | -------------------------------------------------------------------------------- /src/components/Timeline/Timeline.tsx: -------------------------------------------------------------------------------- 1 | import React from "react" 2 | 3 | import { css } from "@emotion/react" 4 | import styled from "@emotion/styled" 5 | 6 | const bubbleSize = 70 7 | 8 | const Wrapper = styled.div` 9 | ${({ theme: { shadow } }) => css` 10 | position: absolute; 11 | right: -100px; 12 | top: 0; 13 | height: 100%; 14 | width: ${bubbleSize}px; 15 | filter: drop-shadow(${shadow.small}); 16 | `} 17 | ` 18 | const Bubble = styled.div` 19 | ${({ theme: { color: themeColors, space, breakpoints }, color, year }) => css` 20 | position: sticky; 21 | top: ${space.xs}; 22 | height: ${bubbleSize}px; 23 | width: ${bubbleSize}px; 24 | 25 | display: flex; 26 | justify-content: center; 27 | align-items: center; 28 | 29 | border-radius: 50%; 30 | background-color: ${color}; 31 | ::before { 32 | content: "${year}"; 33 | color: ${themeColors.dark}; 34 | font-weight: 600; 35 | } 36 | 37 | ${breakpoints.mobile} { 38 | top: 5rem; 39 | } 40 | `} 41 | ` 42 | const Line = styled.div>` 43 | ${({ color }) => css` 44 | margin: 0 auto; 45 | height: calc(100% - ${bubbleSize}px); 46 | background-color: ${color}; 47 | width: 4px; 48 | `} 49 | ` 50 | const StyledTimeline = styled.div` 51 | ${({ theme: { space } }) => css` 52 | position: relative; 53 | margin-bottom: ${space.md}; 54 | padding-top: ${bubbleSize / 2}px; 55 | `} 56 | ` 57 | 58 | type TimelineProps = { 59 | year: number 60 | color: string 61 | } 62 | export const Timeline = ({ 63 | year, 64 | color, 65 | children, 66 | }: React.PropsWithChildren) => ( 67 | 68 | 69 | 70 | 71 | 72 | {children} 73 | 74 | ) 75 | -------------------------------------------------------------------------------- /src/components/Timeline/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./Timeline" 2 | -------------------------------------------------------------------------------- /src/components/Typer/Typer.tsx: -------------------------------------------------------------------------------- 1 | import React from "react" 2 | 3 | import { css } from "@emotion/react" 4 | import styled from "@emotion/styled" 5 | 6 | const StyledTyper = styled.div` 7 | width: 100%; 8 | box-sizing: border-box; 9 | white-space: break-spaces; 10 | ` 11 | 12 | const Carret = styled.span` 13 | ${({ theme: { color, space } }) => css` 14 | height: ${space.xxs}; 15 | width: ${space.md}; 16 | margin-left: ${space.xs}; 17 | display: inline-block; 18 | background-color: ${color.light}; 19 | animation: blink 0.7s infinite; 20 | transition: 0.5s; 21 | } 22 | 23 | @keyframes blink { 24 | 0% { 25 | opacity: 1; 26 | } 27 | 25% { 28 | opacity: 1; 29 | } 30 | 75% { 31 | opacity: 0; 32 | } 33 | 100% { 34 | opacity: 0; 35 | } 36 | } 37 | `} 38 | ` 39 | 40 | type TyperProps = { 41 | text: string 42 | textCarousel?: string[] 43 | timing: { 44 | typeStrokes: number 45 | deleteStrokes: number 46 | waiting: number 47 | } 48 | } 49 | 50 | export const Typer = ({ 51 | text, 52 | textCarousel, 53 | timing: { typeStrokes, deleteStrokes, waiting }, 54 | }: TyperProps) => { 55 | const [content, setContent] = React.useState("") 56 | const intervalId = React.useRef() 57 | let initialCopy = "" 58 | let carouselCopy = "" 59 | 60 | const addCarouselWord = (carouselIndex: number) => { 61 | if (textCarousel) { 62 | const currentText = textCarousel[carouselIndex] 63 | if (currentText) { 64 | carouselCopy += currentText[carouselCopy.length] 65 | setContent(initialCopy + carouselCopy) 66 | if (intervalId.current && carouselCopy.length >= currentText.length) { 67 | clearInterval(intervalId.current) 68 | setTimeout( 69 | () => 70 | (intervalId.current = setInterval( 71 | // eslint-disable-next-line @typescript-eslint/no-use-before-define 72 | () => deleteCarouselWord(carouselIndex), 73 | deleteStrokes 74 | )), 75 | waiting 76 | ) 77 | } 78 | } 79 | } 80 | } 81 | 82 | const getNextCarouselIndex = (index: number) => { 83 | if (textCarousel && index >= textCarousel.length - 1) return 0 84 | return index + 1 85 | } 86 | 87 | const deleteCarouselWord = (carouselIndex: number) => { 88 | if (textCarousel) { 89 | const currentText = textCarousel[carouselIndex] 90 | if (currentText) { 91 | carouselCopy = carouselCopy.slice(0, carouselCopy.length - 1) 92 | setContent(initialCopy + carouselCopy) 93 | if (intervalId.current && carouselCopy.length === 0) { 94 | clearInterval(intervalId.current) 95 | intervalId.current = setInterval( 96 | () => addCarouselWord(getNextCarouselIndex(carouselIndex)), 97 | typeStrokes 98 | ) 99 | } 100 | } 101 | } 102 | } 103 | 104 | const intervalUpdater = () => { 105 | initialCopy += text[initialCopy.length] 106 | setContent(initialCopy) 107 | if (intervalId.current && initialCopy.length === text.length) { 108 | clearInterval(intervalId.current) 109 | if (textCarousel) 110 | intervalId.current = setInterval(() => addCarouselWord(0), typeStrokes) 111 | } 112 | } 113 | 114 | React.useEffect(() => { 115 | intervalId.current = setInterval(intervalUpdater, typeStrokes) 116 | // eslint-disable-next-line react-hooks/exhaustive-deps 117 | }, []) 118 | 119 | return ( 120 | 121 | {content} 122 | 123 | 124 | ) 125 | } 126 | -------------------------------------------------------------------------------- /src/components/Typer/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./Typer" 2 | -------------------------------------------------------------------------------- /src/components/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./Button" 2 | export * from "./Carousel" 3 | export * from "./Cards" 4 | export * from "./DirectionButton" 5 | export * from "./Scrollspy" 6 | export * from "./Section" 7 | export * from "./Theme" 8 | export * from "./Timeline" 9 | export * from "./Typer" 10 | -------------------------------------------------------------------------------- /src/data/media/avatar.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PrettyCoffee/PrettyCoffee.github.io/a8d248920d3b20463c358af4ac41fb86b7bcfdab/src/data/media/avatar.webp -------------------------------------------------------------------------------- /src/data/media/computer.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | 289 | 290 | 291 | 292 | 293 | 294 | 295 | 296 | 297 | 298 | 299 | 300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308 | 309 | 310 | 311 | 312 | 313 | 314 | 315 | 316 | 317 | 318 | 319 | 320 | 321 | 322 | 323 | 324 | 325 | 326 | 327 | -------------------------------------------------------------------------------- /src/data/media/cookie.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PrettyCoffee/PrettyCoffee.github.io/a8d248920d3b20463c358af4ac41fb86b7bcfdab/src/data/media/cookie.webp -------------------------------------------------------------------------------- /src/data/media/fluidity.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /src/data/media/gnrc.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PrettyCoffee/PrettyCoffee.github.io/a8d248920d3b20463c358af4ac41fb86b7bcfdab/src/data/media/gnrc.webp -------------------------------------------------------------------------------- /src/data/media/go-audio.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PrettyCoffee/PrettyCoffee.github.io/a8d248920d3b20463c358af4ac41fb86b7bcfdab/src/data/media/go-audio.webp -------------------------------------------------------------------------------- /src/data/media/kitty.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PrettyCoffee/PrettyCoffee.github.io/a8d248920d3b20463c358af4ac41fb86b7bcfdab/src/data/media/kitty.webp -------------------------------------------------------------------------------- /src/data/media/logo.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PrettyCoffee/PrettyCoffee.github.io/a8d248920d3b20463c358af4ac41fb86b7bcfdab/src/data/media/logo.webp -------------------------------------------------------------------------------- /src/data/media/minigue.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PrettyCoffee/PrettyCoffee.github.io/a8d248920d3b20463c358af4ac41fb86b7bcfdab/src/data/media/minigue.webp -------------------------------------------------------------------------------- /src/data/media/missing-pic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PrettyCoffee/PrettyCoffee.github.io/a8d248920d3b20463c358af4ac41fb86b7bcfdab/src/data/media/missing-pic.png -------------------------------------------------------------------------------- /src/data/media/react-startpage.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /src/data/media/smartphone.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | -------------------------------------------------------------------------------- /src/data/media/smug.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PrettyCoffee/PrettyCoffee.github.io/a8d248920d3b20463c358af4ac41fb86b7bcfdab/src/data/media/smug.webp -------------------------------------------------------------------------------- /src/data/media/startpages.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | _______________ 9 | < /R/STARTPAGES > 10 | --------------- 11 | \ 12 | 13 | \ ^^ 14 | (OO)\________ 15 | 16 | 17 | ()\        )\ 18 | 19 | 20 | 21 | W 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | _______________ 33 | < /R/STARTPAGES > 34 | --------------- 35 | \ 36 | 37 | \ ^^ 38 | (OO)\________ 39 | 40 | 41 | ()\        )\ 42 | 43 | 44 | 45 | W 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | -------------------------------------------------------------------------------- /src/data/media/tablet.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | -------------------------------------------------------------------------------- /src/data/projects.ts: -------------------------------------------------------------------------------- 1 | import { getTheme } from "../components" 2 | import avatar from "./media/avatar.webp" 3 | import cookie from "./media/cookie.webp" 4 | import fluidity from "./media/fluidity.svg" 5 | import gnrc from "./media/gnrc.webp" 6 | import goAudio from "./media/go-audio.webp" 7 | import kitty from "./media/kitty.webp" 8 | import minigue from "./media/minigue.webp" 9 | import stpg from "./media/react-startpage.svg" 10 | import smug from "./media/smug.webp" 11 | 12 | const { color } = getTheme() 13 | 14 | export type Project = { 15 | title: string 16 | description: string 17 | image: string 18 | repoUrl: string 19 | demoUrl?: string 20 | docsUrl?: string 21 | } 22 | 23 | /* 24 | _______________ 25 | < /r/startpages > 26 | --------------- 27 | \ 28 | \ ^__^ 29 | (oo)\________ 30 | (__)\ )\/\ 31 | ||----W || 32 | || || 33 | */ 34 | 35 | type Year = { year: number; color: string; projects: Project[] } 36 | 37 | export const projects: Year[] = [ 38 | { 39 | year: 2022, 40 | color: color.orange, 41 | projects: [ 42 | { 43 | title: "[WIP] - Cookie Credit Card", 44 | description: 45 | "My first fullstack web app that helps keeping track of your cookie debts. For a colleague to whom I owe some cookies. A lot actually.", 46 | image: cookie, 47 | repoUrl: "https://github.com/PrettyCoffee/cookie-credit-card", 48 | }, 49 | { 50 | title: "MiniguePT (Frontend)", 51 | description: 52 | "Web chat client for MiniguePT, a chatbot created by a lovely friend <3 \nThis is also my first SolidJs project.", 53 | image: minigue, 54 | demoUrl: "https://chatty.rhostruct.de/", 55 | repoUrl: "https://github.com/Eeeeelias/miniguept-chat", 56 | }, 57 | { 58 | title: "Yet another generic startpage", 59 | description: 60 | "The fourth startpage in the list, demonstrating the usage of the @startpage library.", 61 | image: gnrc, 62 | repoUrl: 63 | "https://github.com/PrettyCoffee/yet-another-generic-startpage", 64 | demoUrl: "https://prettycoffee.github.io/yet-another-generic-startpage", 65 | }, 66 | ], 67 | }, 68 | { 69 | year: 2021, 70 | color: color.red, 71 | projects: [ 72 | { 73 | title: "@startpage", 74 | description: 75 | "A library that provides tools and components to facilitate the process of creating a browser start page.", 76 | image: stpg, 77 | repoUrl: "https://github.com/PrettyCoffee/startpage", 78 | docsUrl: "https://prettycoffee.github.io/startpage/", 79 | }, 80 | { 81 | title: "Portfolio v3", 82 | description: 83 | "The latest version of my portfolio. You are currently looking at it.", 84 | image: avatar, 85 | repoUrl: "https://github.com/PrettyCoffee/PrettyCoffee.github.io", 86 | }, 87 | { 88 | title: "Fluidity", 89 | description: 90 | "My third browser startpage featuring a unique design and many options for customizability such as appearance, bookmarks and keyword forwarding.", 91 | image: fluidity, 92 | repoUrl: "https://github.com/PrettyCoffee/fluidity", 93 | demoUrl: "https://prettycoffee.github.io/fluidity/", 94 | }, 95 | ], 96 | }, 97 | { 98 | year: 2020, 99 | color: color.purple, 100 | projects: [ 101 | { 102 | title: "B/W Kitty", 103 | description: 104 | "My second browser startpage, featuring bookmarks, a searchbar and color theming.", 105 | image: kitty, 106 | repoUrl: "https://github.com/PrettyCoffee/b-w-kitty", 107 | demoUrl: "https://prettycoffee.github.io/b-w-kitty/", 108 | }, 109 | { 110 | title: "Raspberry Pi Audio-API", 111 | description: 112 | "A go library which allows to programmatically time and mix the playback of sound files.", 113 | image: goAudio, 114 | repoUrl: "https://gitlab.com/PrettyCoffee/raspberry-pi-audio-api", 115 | }, 116 | ], 117 | }, 118 | { 119 | year: 2019, 120 | color: color.blue, 121 | projects: [ 122 | { 123 | title: "smugLoader", 124 | description: 'A browser startpage based on the "smug dancin" meme.', 125 | image: smug, 126 | repoUrl: "https://gitlab.com/PrettyCoffee/smugloader", 127 | }, 128 | ], 129 | }, 130 | ] 131 | -------------------------------------------------------------------------------- /src/index.tsx: -------------------------------------------------------------------------------- 1 | import React from "react" 2 | 3 | import ReactDOM from "react-dom" 4 | 5 | import { App } from "./App" 6 | import reportWebVitals from "./reportWebVitals" 7 | 8 | ReactDOM.render( 9 | 10 | 11 | , 12 | document.getElementById("root") 13 | ) 14 | 15 | // If you want to start measuring performance in your app, pass a function 16 | // to log results (for example: reportWebVitals(console.log)) 17 | // or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals 18 | reportWebVitals() 19 | -------------------------------------------------------------------------------- /src/logo.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/react-app-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | "typeRoots": [ 27 | "./src/@types", 28 | "./node_modules/@types/" 29 | ] 30 | } 31 | --------------------------------------------------------------------------------