├── .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 | You need to enable JavaScript to run this app.
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 |
69 |
70 |
71 |
72 |
73 |
76 |
77 |
78 |
79 |
82 |
83 |
84 |
85 |
88 |
89 |
90 |
91 |
94 |
95 |
96 |
97 |
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 |
88 | Repo
89 |
90 | )}
91 | {demoUrl && (
92 |
93 | Demo
94 |
95 | )}
96 | {docsUrl && (
97 |
98 | Docs
99 |
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 |
--------------------------------------------------------------------------------