203 | #ifndef FLAT_SHADED // Normal computed with derivatives when FLAT_SHADED
204 | vNormal = normalize(transformedNormal);
205 | #endif
206 |
207 | vViewPosition = - mvPosition.xyz;
208 |
209 | vUv = uv;
210 |
211 | noise = turbulence(0.01 * position + normal + time * 0.8);
212 | vec3 displacement = vec3((position.x) * noise, position.y * noise, position.z * noise);
213 | gl_Position = projectionMatrix * modelViewMatrix * vec4((position + normal) + displacement, 1.0);
214 | }
215 | `;
216 |
--------------------------------------------------------------------------------
/src/components/content/Content.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { Typography, Container } from "@material-ui/core";
3 | import { makeStyles } from "@material-ui/core/styles";
4 | import { TextDecrypt } from "./TextDecrypt";
5 | import Resume from "../../settings/resume.json";
6 | import { FirstName } from "../../utils/getName";
7 |
8 | const useStyles = makeStyles((theme) => ({
9 | main: {
10 | marginTop: "auto",
11 | marginBottom: "auto",
12 | "@media (max-width: 768px)": {
13 | marginLeft: theme.spacing(4),
14 | },
15 | },
16 | }));
17 |
18 | export const Content = () => {
19 | const classes = useStyles();
20 |
21 | return (
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 | );
32 | };
33 |
--------------------------------------------------------------------------------
/src/components/content/SocialIcons.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Link, Tooltip, IconButton, Zoom } from '@material-ui/core';
3 | import { makeStyles } from '@material-ui/core/styles';
4 | import Resume from '../../settings/resume.json';
5 |
6 | const useStyles = makeStyles((theme) => ({
7 | socialIcons: {
8 | position: 'absolute',
9 | top: theme.spacing(6),
10 | right: theme.spacing(6),
11 | },
12 | iconButton: {
13 | height: '2.5rem',
14 | width: '2.5rem',
15 | display: 'block',
16 | marginBottom: theme.spacing(2),
17 | },
18 | icon: {
19 | fontSize: '1.25rem',
20 | },
21 | }));
22 |
23 | export const SocialIcons = () => {
24 | const classes = useStyles();
25 |
26 | const socialItems = Resume.basics.profiles.map((socialItem) => (
27 |
35 |
40 |
45 |
46 |
47 |
48 |
49 | ));
50 |
51 | return {socialItems}
;
52 | };
53 |
--------------------------------------------------------------------------------
/src/components/content/SponsorButton.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { makeStyles } from '@material-ui/core/styles';
3 |
4 | const useStyles = makeStyles((theme) => ({
5 | svgHover: {
6 | fill: theme.palette.secondary.main,
7 | '&:hover': {
8 | transform: 'scale(1.1)',
9 | },
10 | '&:focus': {
11 | transform: 'scale(1.1)',
12 | },
13 | transition: 'transform 0.15s cubic-bezier(0.2, 0, 0.13, 2)',
14 | transform: 'scale(1)',
15 | overflow: 'visible !important',
16 | },
17 | }));
18 |
19 | export const HeartIcon = () => {
20 | const classes = useStyles();
21 |
22 | return (
23 |
35 | );
36 | };
37 |
38 | export const HeartIconFilled = () => {
39 | const classes = useStyles();
40 |
41 | return (
42 |
54 | );
55 | };
56 |
--------------------------------------------------------------------------------
/src/components/content/TextDecrypt.js:
--------------------------------------------------------------------------------
1 | import React, { useEffect } from "react";
2 | import { useDencrypt } from "use-dencrypt-effect";
3 |
4 | const decryptOptions = {
5 | chars: [
6 | "-",
7 | ".",
8 | "/",
9 | "*",
10 | "!",
11 | "?",
12 | "#",
13 | "%",
14 | "&",
15 | "@",
16 | "$",
17 | "€",
18 | "(",
19 | ")",
20 | "[",
21 | "]",
22 | "{",
23 | "}",
24 | "<",
25 | ">",
26 | "~",
27 | "0",
28 | "1",
29 | "2",
30 | "3",
31 | "4",
32 | "5",
33 | "6",
34 | "7",
35 | "8",
36 | "9",
37 | "a",
38 | "b",
39 | "c",
40 | "d",
41 | "e",
42 | "f",
43 | "g",
44 | "h",
45 | "i",
46 | "j",
47 | "k",
48 | "l",
49 | "m",
50 | "n",
51 | "o",
52 | "p",
53 | "q",
54 | "r",
55 | "s",
56 | "t",
57 | "u",
58 | "v",
59 | "w",
60 | "x",
61 | "y",
62 | "z",
63 | ],
64 | interval: 50,
65 | };
66 |
67 | export const TextDecrypt = (props) => {
68 | const { result, dencrypt } = useDencrypt(decryptOptions);
69 |
70 | useEffect(() => {
71 | const updateText = () => {
72 | dencrypt(props.text || "");
73 | };
74 |
75 | const action = setTimeout(updateText, 0);
76 |
77 | return () => clearTimeout(action);
78 | }, [dencrypt, props.text]);
79 |
80 | return (
81 |
82 | {result}
83 | {" "}
84 |
85 | );
86 | };
87 |
--------------------------------------------------------------------------------
/src/components/content/Today.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { Typography } from "@material-ui/core";
3 |
4 | export const Today = () => {
5 | var date = new Date();
6 | var hour = date.getHours();
7 | var time = `${
8 | (hour < 4 && "night") ||
9 | (hour < 12 && "morning") ||
10 | (hour < 18 && "afternoon") ||
11 | (hour < 22 && "evening") ||
12 | "night"
13 | }`;
14 | var days = [
15 | "weekend",
16 | "Monday",
17 | "Tuesday",
18 | "Wednesday",
19 | "Thursday",
20 | "Friday",
21 | "weekend",
22 | ];
23 | var day = days[date.getDay()];
24 |
25 | return (
26 |
27 | Have a good {day === "weekend" ? day : time}.
28 |
29 | );
30 | };
31 |
--------------------------------------------------------------------------------
/src/components/footer/FooterText.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { makeStyles } from '@material-ui/core/styles';
3 | import { Typography, Link } from '@material-ui/core';
4 | import { TextDecrypt } from '../content/TextDecrypt';
5 | import { HeartIcon } from '../content/SponsorButton';
6 |
7 | const useStyles = makeStyles((theme) => ({
8 | footerText: {
9 | position: 'absolute',
10 | bottom: theme.spacing(6),
11 | left: theme.spacing(6),
12 | '&:hover': {
13 | color: theme.palette.primary.main,
14 | },
15 | transition: 'all 0.5s ease',
16 | display: 'flex',
17 | alignItems: 'center',
18 | flexWrap: 'wrap',
19 | },
20 | }));
21 |
22 | export const FooterText = () => {
23 | const classes = useStyles();
24 |
25 | return (
26 |
34 |
35 |
36 |
37 |
38 |
39 | );
40 | };
41 |
--------------------------------------------------------------------------------
/src/components/logo/Logo.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { makeStyles } from "@material-ui/core/styles";
3 |
4 | const useStyles = makeStyles((theme) => ({
5 | svgHover: {
6 | fill: theme.palette.foreground.default,
7 | "&:hover": {
8 | fill: theme.palette.primary.main,
9 | },
10 | transition: "all 0.5s ease",
11 | },
12 | }));
13 |
14 | export const Logo = () => {
15 | const classes = useStyles();
16 |
17 | return (
18 |
36 | );
37 | };
38 |
--------------------------------------------------------------------------------
/src/components/logo/LogoLink.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { Link, Tooltip, Zoom } from "@material-ui/core";
3 | import { makeStyles } from "@material-ui/core/styles";
4 | import Resume from "../../settings/resume.json";
5 | import { Logo } from "./Logo";
6 |
7 | const useStyles = makeStyles((theme) => ({
8 | svg: {
9 | width: "40px",
10 | height: "40px",
11 | position: "absolute",
12 | top: theme.spacing(6),
13 | left: theme.spacing(6),
14 | boxShadow:
15 | "0px 3px 5px -1px rgba(0,0,0,0.2),0px 6px 10px 0px rgba(0,0,0,0.14),0px 1px 18px 0px rgba(0,0,0,0.12)",
16 | borderRadius: "50%",
17 | },
18 | }));
19 |
20 | export const LogoLink = () => {
21 | const classes = useStyles();
22 |
23 | return (
24 |
29 |
37 |
38 |
39 |
40 | );
41 | };
42 |
--------------------------------------------------------------------------------
/src/components/speedDial/SpeedDial.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { makeStyles } from "@material-ui/core/styles";
3 | import { SpeedDial, SpeedDialIcon, SpeedDialAction } from "@material-ui/lab";
4 | import Resume from "../../settings/resume.json";
5 |
6 | const useStyles = makeStyles((theme) => ({
7 | speedDial: {
8 | position: "absolute",
9 | top: theme.spacing(6),
10 | right: theme.spacing(6),
11 | },
12 | iconColor: {
13 | color: theme.palette.foreground.default,
14 | },
15 | }));
16 |
17 | export const SpeedDials = () => {
18 | const classes = useStyles();
19 |
20 | const [open, setOpen] = React.useState(false);
21 |
22 | const handleClose = () => {
23 | setOpen(false);
24 | };
25 |
26 | const handleOpen = () => {
27 | setOpen(true);
28 | };
29 |
30 | const actionIcons = Resume.basics.profiles.map((action) => (
31 | }
34 | tooltipTitle={action.network}
35 | onClick={handleClose}
36 | href={action.url}
37 | target="_blank"
38 | rel="noopener noreferrer"
39 | underline="none"
40 | color="inherit"
41 | />
42 | ));
43 |
44 | return (
45 | <>
46 | }
51 | onClose={handleClose}
52 | onOpen={handleOpen}
53 | open={open}
54 | direction="down"
55 | >
56 | {actionIcons}
57 |
58 | >
59 | );
60 | };
61 |
--------------------------------------------------------------------------------
/src/components/theme/ThemeProvider.js:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useState, createContext } from "react";
2 | import { LightTheme, DarkTheme } from "./Themes";
3 | import { MuiThemeProvider } from "@material-ui/core/styles";
4 |
5 | export const ThemeContext = createContext();
6 |
7 | export const ThemeProvider = ({ children }) => {
8 | const getInitialMode = () => {
9 | if (typeof localStorage === "undefined") return true;
10 | const isReturningUser = "dark" in localStorage;
11 | const savedMode = JSON.parse(localStorage.getItem("dark"));
12 | const userPrefersDark = getPrefColorScheme();
13 | if (isReturningUser) {
14 | return savedMode;
15 | }
16 | return !!userPrefersDark;
17 | };
18 |
19 | const getPrefColorScheme = () => {
20 | if (!window.matchMedia) return;
21 |
22 | return window.matchMedia("(prefers-color-scheme: dark)").matches;
23 | };
24 |
25 | const [theme, setTheme] = useState(getInitialMode() ? "dark" : "light");
26 |
27 | const toggleTheme = () => {
28 | if (theme === "light") {
29 | setTheme("dark");
30 | } else {
31 | setTheme("light");
32 | }
33 | };
34 |
35 | useEffect(() => {
36 | typeof localStorage !== "undefined" &&
37 | localStorage.setItem("dark", JSON.stringify(theme === "dark"));
38 | }, [theme]);
39 |
40 | return (
41 |
47 |
50 | {children}
51 |
52 |
53 | );
54 | };
55 |
--------------------------------------------------------------------------------
/src/components/theme/ThemeToggle.js:
--------------------------------------------------------------------------------
1 | import React, { useContext } from "react";
2 | import { ThemeContext } from "./ThemeProvider";
3 | import { Tooltip, IconButton, Zoom } from "@material-ui/core";
4 | import { makeStyles } from "@material-ui/core/styles";
5 | import { Brightness4, Brightness7 } from "@material-ui/icons";
6 |
7 | const useStyles = makeStyles((theme) => ({
8 | iconButton: {
9 | position: "absolute",
10 | bottom: theme.spacing(6),
11 | right: theme.spacing(6),
12 | height: "2.5rem",
13 | width: "2.5rem",
14 | },
15 | icon: {
16 | fontSize: "1.25rem",
17 | },
18 | }));
19 |
20 | export const ThemeToggle = () => {
21 | const { theme, toggleTheme } = useContext(ThemeContext);
22 | const classes = useStyles();
23 |
24 | return (
25 |
30 |
36 | {theme === "light" ? (
37 |
38 | ) : (
39 |
40 | )}
41 |
42 |
43 | );
44 | };
45 |
--------------------------------------------------------------------------------
/src/components/theme/Themes.js:
--------------------------------------------------------------------------------
1 | import { createMuiTheme, responsiveFontSizes } from '@material-ui/core';
2 | import Settings from '../../settings/settings.json';
3 |
4 | export const primary = `${Settings.colors.primary}`;
5 | export const secondary = `${Settings.colors.secondary}`;
6 | export const black = `${Settings.colors.black}`;
7 | export const white = `${Settings.colors.white}`;
8 |
9 | export const LightTheme = responsiveFontSizes(
10 | createMuiTheme({
11 | palette: {
12 | type: 'light',
13 | primary: {
14 | main: primary,
15 | },
16 | secondary: {
17 | main: secondary,
18 | },
19 | background: {
20 | default: white,
21 | },
22 | foreground: {
23 | default: black,
24 | },
25 | },
26 | typography: {
27 | fontSize: 16,
28 | htmlFontSize: 16,
29 | h2: {
30 | fontWeight: 500,
31 | },
32 | h5: {
33 | fontWeight: 500,
34 | fontFamily: 'Roboto Mono, monospace',
35 | },
36 | body1: {
37 | fontWeight: 500,
38 | fontFamily: 'Roboto Mono, monospace',
39 | },
40 | },
41 | overrides: {
42 | MuiCssBaseline: {
43 | '@global': {
44 | body: {
45 | color: black,
46 | backgroundColor: white,
47 | },
48 | },
49 | },
50 | MuiIconButton: {
51 | root: {
52 | boxShadow:
53 | '0px 3px 5px -1px rgba(0,0,0,0.2),0px 6px 10px 0px rgba(0,0,0,0.14),0px 1px 18px 0px rgba(0,0,0,0.12)',
54 | '&:hover': {
55 | backgroundColor: primary,
56 | },
57 | transition: 'all 0.5s ease',
58 | },
59 | },
60 | MuiFab: {
61 | root: {
62 | width: '2.5rem',
63 | height: '2.5rem',
64 | fontSize: '1.25rem',
65 | },
66 | primary: {
67 | color: black,
68 | backgroundColor: 'transparent',
69 | '&:hover': {
70 | color: black,
71 | backgroundColor: primary,
72 | },
73 | transition: 'all 0.5s ease !important',
74 | },
75 | },
76 | MuiSpeedDialAction: {
77 | fab: {
78 | color: white,
79 | backgroundColor: 'transparent',
80 | '&:hover': {
81 | color: white,
82 | backgroundColor: primary,
83 | },
84 | transition: 'all 0.5s ease',
85 | margin: '0px',
86 | marginBottom: '16px',
87 | },
88 | },
89 | MuiTooltip: {
90 | tooltip: {
91 | fontFamily: 'Roboto Mono, monospace',
92 | backgroundColor: primary,
93 | color: black,
94 | fontSize: 11,
95 | },
96 | },
97 | },
98 | })
99 | );
100 |
101 | export const DarkTheme = responsiveFontSizes(
102 | createMuiTheme({
103 | palette: {
104 | type: 'dark',
105 | primary: {
106 | main: primary,
107 | },
108 | secondary: {
109 | main: secondary,
110 | },
111 | background: {
112 | default: black,
113 | },
114 | foreground: {
115 | default: white,
116 | },
117 | },
118 | typography: {
119 | fontSize: 16,
120 | htmlFontSize: 16,
121 | h2: {
122 | fontWeight: 500,
123 | },
124 | h5: {
125 | fontWeight: 500,
126 | fontFamily: 'Roboto Mono, monospace',
127 | },
128 | body1: {
129 | fontWeight: 500,
130 | fontFamily: 'Roboto Mono, monospace',
131 | },
132 | },
133 | overrides: {
134 | MuiCssBaseline: {
135 | '@global': {
136 | body: {
137 | color: white,
138 | backgroundColor: black,
139 | },
140 | },
141 | },
142 | MuiIconButton: {
143 | root: {
144 | boxShadow:
145 | '0px 3px 5px -1px rgba(0,0,0,0.2),0px 6px 10px 0px rgba(0,0,0,0.14),0px 1px 18px 0px rgba(0,0,0,0.12)',
146 | '&:hover': {
147 | backgroundColor: primary,
148 | },
149 | transition: 'all 0.5s ease',
150 | },
151 | },
152 | MuiFab: {
153 | root: {
154 | width: '2.5rem',
155 | height: '2.5rem',
156 | fontSize: '1.25rem',
157 | },
158 | primary: {
159 | color: white,
160 | backgroundColor: 'transparent',
161 | '&:hover': {
162 | color: white,
163 | backgroundColor: primary,
164 | },
165 | transition: 'all 0.5s ease !important',
166 | },
167 | },
168 | MuiSpeedDialAction: {
169 | fab: {
170 | color: white,
171 | backgroundColor: 'transparent',
172 | '&:hover': {
173 | color: white,
174 | backgroundColor: primary,
175 | },
176 | transition: 'all 0.5s ease',
177 | margin: '0px',
178 | marginBottom: '16px',
179 | },
180 | },
181 | MuiTooltip: {
182 | tooltip: {
183 | fontFamily: 'Roboto Mono, monospace',
184 | backgroundColor: primary,
185 | color: white,
186 | fontSize: 11,
187 | },
188 | },
189 | },
190 | })
191 | );
192 |
--------------------------------------------------------------------------------
/src/hooks/useInViewport.js:
--------------------------------------------------------------------------------
1 | import { useEffect, useState } from "react";
2 |
3 | export const useInViewport = (
4 | elementRef,
5 | unobserveOnIntersect,
6 | options = {}
7 | ) => {
8 | const [intersect, setIntersect] = useState(false);
9 | const [isUnobserved, setIsUnobserved] = useState(false);
10 |
11 | useEffect(() => {
12 | if (!elementRef?.current) return;
13 |
14 | const observer = new IntersectionObserver(([entry]) => {
15 | const { isIntersecting, target } = entry;
16 |
17 | setIntersect(isIntersecting);
18 |
19 | if (isIntersecting && unobserveOnIntersect) {
20 | observer.unobserve(target);
21 | setIsUnobserved(true);
22 | }
23 | }, options);
24 |
25 | if (!isUnobserved) {
26 | observer.observe(elementRef.current);
27 | }
28 |
29 | return () => observer.disconnect();
30 | }, [elementRef, unobserveOnIntersect, options, isUnobserved]);
31 |
32 | return intersect;
33 | };
34 |
--------------------------------------------------------------------------------
/src/hooks/usePrefersReducedMotion.js:
--------------------------------------------------------------------------------
1 | export const usePrefersReducedMotion = () => {
2 | if (!window.matchMedia) return false;
3 |
4 | return window.matchMedia("(prefers-reduced-motion: reduce)").matches;
5 | };
6 |
--------------------------------------------------------------------------------
/src/index.css:
--------------------------------------------------------------------------------
1 | ::-moz-selection {
2 | background: #00bfbf;
3 | color: #fafafa;
4 | text-shadow: none;
5 | }
6 | ::selection {
7 | background: #00bfbf;
8 | color: #fafafa;
9 | text-shadow: none;
10 | }
11 | ::-webkit-scrollbar {
12 | width: 0px;
13 | background: transparent;
14 | }
15 | html {
16 | overflow: scroll;
17 | overflow-x: hidden;
18 | font-size: 16px;
19 | }
20 | body {
21 | transition: all 0.5s ease;
22 | }
23 | p {
24 | margin-block-start: 0.5em;
25 | margin-block-end: 0.5em;
26 | }
27 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { render } from "react-snapshot";
3 | import { App } from "./app/App";
4 | import "./index.css";
5 |
6 | render(, document.getElementById("root"));
7 |
--------------------------------------------------------------------------------
/src/pages/Home.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { LogoLink } from '../components/logo/LogoLink';
3 | import { Content } from '../components/content/Content';
4 | import { Hidden } from '@material-ui/core';
5 | import { makeStyles } from '@material-ui/core/styles';
6 | import DisplacementSphere from '../components/background/DisplacementSphere';
7 | import { ThemeToggle } from '../components/theme/ThemeToggle';
8 | import { FooterText } from '../components/footer/FooterText';
9 | import { SocialIcons } from '../components/content/SocialIcons';
10 | import { SpeedDials } from '../components/speedDial/SpeedDial';
11 |
12 | const useStyles = makeStyles(() => ({
13 | root: {
14 | display: 'flex',
15 | flexDirection: 'column',
16 | minHeight: '100vh',
17 | },
18 | }));
19 |
20 | export const Home = () => {
21 | const classes = useStyles();
22 |
23 | return (
24 | <>
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 | >
39 | );
40 | };
41 |
--------------------------------------------------------------------------------
/src/pages/PageNotFound.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | export const PageNotFound = () => {
4 | return Page not found...
;
5 | };
6 |
--------------------------------------------------------------------------------
/src/pages/Resume.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | export const Resume = () => {
4 | return Resume...
;
5 | };
6 |
--------------------------------------------------------------------------------
/src/settings/resume.json:
--------------------------------------------------------------------------------
1 | {
2 | "basics": {
3 | "name": "Jo Lienhoop",
4 | "label": "Computer Science student",
5 | "email": "hey@jolienhoop.com",
6 | "phone": "(XXX) XXX-XXXX",
7 | "picture": "jl.png",
8 | "x_title": "Hey, I'm",
9 | "job": "CS student",
10 | "description": "Hey, I'm Jo Lienhoop, a Computer Science student based in Bremen, Germany. I love all things technical. Talk soon!",
11 | "summary": "based in Bremen, Germany.",
12 | "keywords": "personal,website,portfolio,template,kit,jo,lienhoop,jolienhoop,computer,science,bremen,germany,react,github,linkedin,google,gitlab,telegram,material,design,ui,webapp",
13 | "url": "https://jolienhoop.com",
14 | "location": {
15 | "address": "XX XXXXXstreet",
16 | "postalCode": "XXXXX",
17 | "city": "Bremen",
18 | "country": "Germany",
19 | "countryCode": "DEU",
20 | "region": "Bremen"
21 | },
22 | "profiles": [
23 | {
24 | "network": "Google",
25 | "username": "hey@jolienhoop.com",
26 | "url": "mailto:hey@jolienhoop.com",
27 | "x_icon": "fab fa-google"
28 | },
29 | {
30 | "network": "LinkedIn",
31 | "username": "jolienhoop",
32 | "url": "https://www.linkedin.com/in/jolienhoop/",
33 | "x_icon": "fab fa-linkedin-in"
34 | },
35 | {
36 | "network": "GitHub",
37 | "username": "johoop",
38 | "url": "https://github.com/JoHoop",
39 | "x_icon": "fab fa-github"
40 | },
41 | {
42 | "network": "GitLab",
43 | "username": "joli",
44 | "url": "https://gitlab.informatik.uni-bremen.de/joli",
45 | "x_icon": "fab fa-gitlab"
46 | }
47 | ]
48 | },
49 | "work": [
50 | {
51 | "company": "Company",
52 | "position": "President",
53 | "website": "http://company.com",
54 | "startDate": "2013-01-01",
55 | "endDate": "2014-01-01",
56 | "summary": "Description...",
57 | "highlights": ["Started the company"]
58 | }
59 | ],
60 | "volunteer": [
61 | {
62 | "organization": "Organization",
63 | "position": "Volunteer",
64 | "website": "http://organization.com/",
65 | "startDate": "2012-01-01",
66 | "endDate": "2013-01-01",
67 | "summary": "Description...",
68 | "highlights": ["Awarded 'Volunteer of the Month'"]
69 | }
70 | ],
71 | "education": [
72 | {
73 | "institution": "University",
74 | "area": "Software Development",
75 | "studyType": "Bachelor",
76 | "startDate": "2011-01-01",
77 | "endDate": "2013-01-01",
78 | "gpa": "4.0",
79 | "courses": ["DB1101 - Basic SQL"]
80 | }
81 | ],
82 | "awards": [
83 | {
84 | "title": "Award",
85 | "date": "2014-11-01",
86 | "awarder": "Company",
87 | "summary": "There is no spoon."
88 | }
89 | ],
90 | "publications": [
91 | {
92 | "name": "Publication",
93 | "publisher": "Company",
94 | "releaseDate": "2014-10-01",
95 | "website": "http://publication.com",
96 | "summary": "Description..."
97 | }
98 | ],
99 | "skills": [
100 | {
101 | "name": "Web Development",
102 | "level": "Master",
103 | "keywords": ["HTML", "CSS", "Javascript"]
104 | }
105 | ],
106 | "languages": [
107 | {
108 | "language": "German",
109 | "fluency": "Native speaker"
110 | },
111 | {
112 | "language": "English",
113 | "fluency": "Fluent speaker"
114 | },
115 | {
116 | "language": "French",
117 | "fluency": "Good knowledge"
118 | }
119 | ],
120 | "interests": [
121 | {
122 | "name": "Wildlife",
123 | "keywords": ["Ferrets", "Unicorns"]
124 | }
125 | ],
126 | "references": [
127 | {
128 | "name": "Jane Doe",
129 | "reference": "Reference..."
130 | }
131 | ]
132 | }
133 |
--------------------------------------------------------------------------------
/src/settings/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors": {
3 | "primary": "#00bfbf",
4 | "secondary": "#db61a2",
5 | "black": "#111111",
6 | "white": "#fafafa"
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/src/utils/getName.js:
--------------------------------------------------------------------------------
1 | import Resume from "../settings/resume.json";
2 |
3 | const names = Resume.basics.name.split(" ");
4 |
5 | export const FirstName = names[0];
6 |
7 | export const LastName = names[names.length - 1];
8 |
9 | export const Initials = FirstName.charAt(0)
10 | .toUpperCase()
11 | .concat(LastName.charAt(0).toUpperCase());
12 |
--------------------------------------------------------------------------------
/src/utils/logCredits.js:
--------------------------------------------------------------------------------
1 | import { primary } from "../components/theme/Themes";
2 |
3 | export const logCredits = () => {
4 | const pieceEmoji = String.fromCodePoint(0x270c);
5 |
6 | const logStyle = [
7 | `color: ${primary}`,
8 | "font-size: 3em",
9 | "font-weight: 300",
10 | "padding: 100px 0px 100px 0px",
11 | ].join(";");
12 |
13 | return console.log(
14 | `%c © ${new Date().getFullYear()} github.com/johoop ${pieceEmoji}`,
15 | logStyle
16 | );
17 | };
18 |
--------------------------------------------------------------------------------
/src/utils/style.js:
--------------------------------------------------------------------------------
1 | import { Color } from 'three/src/math/Color';
2 |
3 | /**
4 | * Media query breakpoints
5 | */
6 | export const media = {
7 | desktop: 1600,
8 | laptop: 1280,
9 | tablet: 1024,
10 | mobile: 696,
11 | mobileS: 320,
12 | };
13 |
14 | /**
15 | * Convert a px string to a number
16 | */
17 | export const pxToNum = px => Number(px.replace('px', ''));
18 |
19 | /**
20 | * Convert a number to a px string
21 | */
22 | export const numToPx = num => `${num}px`;
23 |
24 | /**
25 | * Convert pixel values to rem for a11y
26 | */
27 | export const pxToRem = px => `${px / 16}rem`;
28 |
29 | /**
30 | * Convert ms token values to a raw numbers for ReactTransitionGroup
31 | * Transition delay props
32 | */
33 | export const msToNum = msString => Number(msString.replace('ms', ''));
34 |
35 | /**
36 | * Convert a number to an ms string
37 | */
38 | export const numToMs = num => `${num}ms`;
39 |
40 | /**
41 | * Convert an rgb theme property (e.g. rgbBlack: '0 0 0')
42 | * to a ThreeJS Color class
43 | */
44 | export const rgbToThreeColor = rgb =>
45 | new Color(...rgb.split(' ').map(value => Number(value) / 255));
46 |
--------------------------------------------------------------------------------
/src/utils/three.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Clean up a scene's materials and geometry
3 | */
4 | export const cleanScene = scene => {
5 | scene.traverse(object => {
6 | if (!object.isMesh) return;
7 |
8 | object.geometry.dispose();
9 |
10 | if (object.material.isMaterial) {
11 | cleanMaterial(object.material);
12 | } else {
13 | for (const material of object.material) {
14 | cleanMaterial(material);
15 | }
16 | }
17 | });
18 |
19 | scene.dispose();
20 | };
21 |
22 | /**
23 | * Clean up and dispose of a material
24 | */
25 | export const cleanMaterial = material => {
26 | material.dispose();
27 |
28 | for (const key of Object.keys(material)) {
29 | const value = material[key];
30 | if (value && typeof value === 'object' && 'minFilter' in value) {
31 | value.dispose();
32 | }
33 | }
34 | };
35 |
36 | /**
37 | * Clean up and dispose of a renderer
38 | */
39 | export const cleanRenderer = renderer => {
40 | renderer.dispose();
41 | renderer.forceContextLoss();
42 | renderer = null;
43 | };
44 |
45 | /**
46 | * Clean up lights by removing them from their parent
47 | */
48 | export const removeLights = lights => {
49 | for (const light of lights) {
50 | light.parent.remove(light);
51 | }
52 | };
53 |
54 | /**
55 | * A reasonable default pixel ratio
56 | */
57 | export const renderPixelRatio = 2;
58 |
--------------------------------------------------------------------------------
/src/utils/transition.js:
--------------------------------------------------------------------------------
1 | const visibleStatus = ['entering', 'entered'];
2 |
3 | /**
4 | * Is the given TransitionStatus visible?
5 | */
6 | export const isVisible = status => visibleStatus.includes(status);
7 |
8 | /**
9 | * Is the given TransitionStatus hidden?
10 | */
11 | export const isHidden = status => !visibleStatus.includes(status);
12 |
13 | /**
14 | * Forces a reflow to trigger transitions on enter
15 | */
16 | export const reflow = node => node && node.offsetHeight;
17 |
--------------------------------------------------------------------------------
/vercel.json:
--------------------------------------------------------------------------------
1 | {
2 | "github": {
3 | "silent": true
4 | }
5 | }
6 |
--------------------------------------------------------------------------------