├── .gitignore ├── .nvmrc ├── .swp ├── .vscode └── settings.json ├── LICENSE ├── README.md ├── gatsby-config.js ├── jsconfig.json ├── package-lock.json ├── package.json ├── src ├── components │ ├── about.js │ ├── article │ │ ├── contentwrapper.js │ │ ├── coverimage.js │ │ ├── fullsizeimage.js │ │ ├── header.js │ │ ├── largeimage.js │ │ └── twoimage.js │ ├── footer.js │ ├── global-wrapper.js │ ├── header.js │ ├── hero.js │ ├── icons.js │ ├── project.js │ ├── projectlogo.js │ ├── section.js │ ├── sectionHeading.js │ ├── spirograph.js │ ├── twoColumns.js │ └── work.js ├── fonts │ ├── TTNBold.woff │ ├── TTNBold.woff2 │ ├── TTNRegular.woff │ └── TTNRegular.woff2 ├── img │ ├── articles │ │ └── project1 │ │ │ ├── img1.jpg │ │ │ ├── img2.jpg │ │ │ ├── img3.jpg │ │ │ └── img4.jpg │ └── icon-templates │ │ ├── arrow.svg │ │ ├── dribbble.svg │ │ ├── mail.svg │ │ └── twitter.svg ├── pages │ ├── 404.js │ ├── index.js │ ├── project1.js │ ├── project2.js │ └── project3.js ├── styles │ ├── global-style.js │ └── theme.js └── utils │ └── media-queries.js └── static ├── android-chrome-192x192.png ├── android-chrome-256x256.png ├── apple-touch-icon.png ├── browserconfig.xml ├── favicon-16x16.png ├── favicon-32x32.png ├── favicon.ico ├── manifest.json ├── mstile-150x150.png └── safari-pinned-tab.svg /.gitignore: -------------------------------------------------------------------------------- 1 | # Project dependencies 2 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git 3 | node_modules 4 | .cache/ 5 | # Build directory 6 | public/ 7 | .DS_Store 8 | yarn-error.log 9 | yarn.lock 10 | portfolio.code-workspace 11 | .netlify/ 12 | .env.development 13 | -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | v16.4.1 2 | -------------------------------------------------------------------------------- /.swp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gabdorf/gatsby-portfolio/2c468cd143e5231da9a94e76a62023ecd425f6d3/.swp -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "eslint.enable": true 3 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 gatsbyjs 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # gatsby-portfolio 2 | 3 | This is a template for a portfolio page built with [gatsby](https://github.com/gatsbyjs/gatsby) and [styled components](https://github.com/styled-components/styled-components). 4 | 5 | Check it out here: [demo-gatsby-portfolio.netlify.app](https://demo-gatsby-portfolio.netlify.app/) 6 | -------------------------------------------------------------------------------- /gatsby-config.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | 3 | module.exports = { 4 | siteMetadata: { 5 | title: `John Doe`, 6 | }, 7 | plugins: [ 8 | { 9 | resolve: 'gatsby-plugin-root-import', 10 | options: { 11 | src: path.join(__dirname, 'src'), 12 | components: path.join(__dirname, 'src/components'), 13 | fonts: path.join(__dirname, 'src/fonts'), 14 | img: path.join(__dirname, 'src/img'), 15 | layouts: path.join(__dirname, 'src/layouts'), 16 | pages: path.join(__dirname, 'src/pages'), 17 | styles: path.join(__dirname, 'src/styles'), 18 | utils: path.join(__dirname, 'src/utils'), 19 | }, 20 | }, 21 | `gatsby-plugin-react-helmet`, 22 | `gatsby-plugin-styled-components`, 23 | ], 24 | } 25 | -------------------------------------------------------------------------------- /jsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es6" 4 | }, 5 | "exclude": ["node_modules"] 6 | } 7 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "gatsby-portfolio", 3 | "description": "A personal page built with gatsby", 4 | "version": "1.0.0", 5 | "author": "Gabriel Schneider", 6 | "dependencies": { 7 | "gatsby": "^4.16.0", 8 | "gatsby-link": "^4.16.0", 9 | "gatsby-plugin-react-helmet": "^5.16.0", 10 | "gatsby-plugin-splitbee": "^0.2.0", 11 | "gatsby-plugin-styled-components": "^5.16.0", 12 | "lodash": "^4.17.21", 13 | "react": "^18.2.0", 14 | "react-dom": "^18.2.0", 15 | "react-helmet": "^6.1.0", 16 | "styled-components": "^5.3.5" 17 | }, 18 | "keywords": [ 19 | "gatsby" 20 | ], 21 | "license": "MIT", 22 | "main": "n/a", 23 | "scripts": { 24 | "build": "gatsby build", 25 | "develop": "gatsby develop", 26 | "format": "prettier --trailing-comma es5 --no-semi --single-quote --write 'src/**/*.js'", 27 | "test": "echo \"Error: no test specified\" && exit 1" 28 | }, 29 | "devDependencies": { 30 | "babel-plugin-styled-components": "^2.0.2", 31 | "gatsby-plugin-root-import": "^2.0.8", 32 | "prettier": "^2.5.1" 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/components/about.js: -------------------------------------------------------------------------------- 1 | import React, { Fragment } from 'react' 2 | import Link from 'gatsby-link' 3 | import styled from 'styled-components' 4 | import media from 'utils/media-queries' 5 | 6 | import { color, fontSize } from 'styles/theme' 7 | 8 | import TwoColumns from 'components/twoColumns' 9 | import SectionHeading from 'components/sectionHeading' 10 | 11 | const Big = styled.span` 12 | font-size: ${fontSize.f6}; 13 | color: ${color.grey900}; 14 | font-weight: 700; 15 | letter-spacing: -0.4px; 16 | line-height: 1.35; 17 | ${media.lg` 18 | font-size: ${fontSize.f5}; 19 | letter-spacing: -0.3px; 20 | `} 21 | ${media.sm` 22 | font-size: ${fontSize.f5}; 23 | `} 24 | ` 25 | 26 | const About = () => { 27 | return ( 28 | About} 30 | rightColumn={ 31 | 32 | Hi. I'm John. I love to design stuff. 33 |

Currently I'm designing for Acme.

34 |

35 | Outside of work I'm passionate about climbing, snowboarding and 36 | mountain biking. 37 |

38 |
39 | } 40 | /> 41 | ) 42 | } 43 | 44 | export default About 45 | -------------------------------------------------------------------------------- /src/components/article/contentwrapper.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import styled from 'styled-components' 3 | import media from 'utils/media-queries' 4 | 5 | const Container = styled.div` 6 | max-width: 700px; 7 | margin-left: auto; 8 | margin-right: auto; 9 | ${media.lg` 10 | max-width: 600px; 11 | `} 12 | ` 13 | const Content = styled.div` 14 | p + h3 { 15 | margin-top: 42px; 16 | } 17 | padding: 56px 24px 26px 24px; 18 | ${media.lg` 19 | padding: 24px 24px 26px 24px; 20 | `} 21 | ${media.xs` 22 | padding: 56px 16px 26px 16px; 23 | `} 24 | ` 25 | 26 | const ContentWrapper = (props) => { 27 | return ( 28 | 29 | {props.children} 30 | 31 | ) 32 | } 33 | 34 | export default ContentWrapper 35 | -------------------------------------------------------------------------------- /src/components/article/coverimage.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import styled from 'styled-components' 3 | 4 | import media from 'utils/media-queries' 5 | 6 | const Image = styled.div` 7 | background: #cdcdcd; 8 | background-image: url(${(props) => props.src}); 9 | background-size: cover; 10 | background-position: ${(props) => props.focusX} ${(props) => props.focusY}; 11 | width: 100%; 12 | height: calc(100vh - 170px); 13 | ${media.lg` 14 | height: calc(100vh - 148px); 15 | `} 16 | ${media.sm` 17 | height: calc(100vh - 184px); 18 | `} 19 | ` 20 | 21 | const LargeImage = (props) => { 22 | return 23 | } 24 | 25 | export default LargeImage 26 | -------------------------------------------------------------------------------- /src/components/article/fullsizeimage.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import styled from 'styled-components' 3 | import media from 'utils/media-queries' 4 | 5 | const Image = styled.img` 6 | display: block; 7 | margin: 16px auto; 8 | max-height: 750px; 9 | max-width: 100%; 10 | border-radius: 16px; 11 | ${media.lg` 12 | max-height: 600px; 13 | `} 14 | ${media.md` 15 | border-radius: 0px; 16 | `} 17 | ` 18 | 19 | const FullSizeImage = (props) => { 20 | return {props.alt} 21 | } 22 | 23 | export default FullSizeImage 24 | -------------------------------------------------------------------------------- /src/components/article/header.js: -------------------------------------------------------------------------------- 1 | import React, { Fragment } from 'react' 2 | import Link from 'gatsby-link' 3 | import styled from 'styled-components' 4 | import media from 'utils/media-queries' 5 | 6 | import { color } from 'styles/theme' 7 | 8 | import Header from 'components/header' 9 | import Icon from 'components/icons' 10 | 11 | const TitleBar = styled.div`` 12 | 13 | const BackArrow = styled(Link)` 14 | position: absolute; 15 | left: 10px; 16 | transform: rotate(90deg); 17 | max-width: 64px; 18 | max-height: 64px; 19 | color: ${color.grey900}; 20 | ${media.sm` 21 | display: none; 22 | `}; 23 | &:visited { 24 | color: ${color.grey900}; 25 | } 26 | &:hover { 27 | color: ${color.grey900}; 28 | } 29 | &:active { 30 | color: ${color.grey900}; 31 | } 32 | ` 33 | 34 | const Title = styled.div` 35 | width: 100%; 36 | text-align: center; 37 | ` 38 | 39 | const ArticleHeader = (props) => { 40 | return ( 41 | 42 |
43 | 44 | 45 | 46 | 47 | 48 | <h1>{props.title}</h1> 49 | 50 | 51 | 52 | ) 53 | } 54 | 55 | export default ArticleHeader 56 | -------------------------------------------------------------------------------- /src/components/article/largeimage.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import styled from 'styled-components' 3 | 4 | const Image = styled.img` 5 | max-height: 500px; 6 | display: block; 7 | margin-left: auto; 8 | margin-right: auto; 9 | max-width: 100%; 10 | ` 11 | 12 | const LargeImage = (props) => { 13 | return {props.alt} 14 | } 15 | 16 | export default LargeImage 17 | -------------------------------------------------------------------------------- /src/components/article/twoimage.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import styled from 'styled-components' 3 | 4 | const TwoImageWrapper = styled.div` 5 | display: flex; 6 | justify-content: center; 7 | flex-wrap: wrap; 8 | max-width: 100%; 9 | margin-top: 16px; 10 | margin-bottom: 16px; 11 | ` 12 | 13 | const Image = styled.img` 14 | max-height: ${(props) => props.maxHeight}; 15 | align-self: center; 16 | max-width: 100%; 17 | ` 18 | 19 | const TwoImage = (props) => { 20 | return ( 21 | 22 | {props.alt1} 23 | {props.alt2} 24 | 25 | ) 26 | } 27 | 28 | export default TwoImage 29 | -------------------------------------------------------------------------------- /src/components/footer.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import styled from 'styled-components' 3 | import media from 'utils/media-queries' 4 | 5 | import { fontSize } from 'styles/theme' 6 | 7 | import Section from 'components/section' 8 | 9 | const FooterText = styled.div` 10 | text-align: center; 11 | font-size: ${fontSize.f2}; 12 | ${media.lg` 13 | font-size: ${fontSize.f1}; 14 | `} 15 | ${media.sm` 16 | text-align: left; 17 | font-size: ${fontSize.f1}; 18 | `} 19 | ` 20 | 21 | const Footer = () => { 22 | return ( 23 |
24 | 25 | This page is open source, Check it out on  26 | Github 27 |
© {new Date().getFullYear()} John Doe 28 |
29 |
30 | ) 31 | } 32 | 33 | export default Footer 34 | -------------------------------------------------------------------------------- /src/components/global-wrapper.js: -------------------------------------------------------------------------------- 1 | import React, { Fragment, useState, useEffect } from 'react' 2 | import { Helmet } from 'react-helmet' 3 | import GlobalStyle from 'styles/global-style' 4 | 5 | const GlobalWrapper = (props) => { 6 | const [displayOutlines, setDisplayOutlines] = useState(false) 7 | 8 | const handleKeyboardInput = (e) => { 9 | const key = e.keyCode || e.charCode 10 | // Tab 11 | if (key === 9) { 12 | setDisplayOutlines(true) 13 | } 14 | } 15 | 16 | useEffect(() => { 17 | window.addEventListener('keydown', (e) => handleKeyboardInput(e)) 18 | }) 19 | 20 | return ( 21 | 22 | 23 | 24 | John Doe 25 | 26 | 30 | 34 | 35 | 36 | 37 | 38 | 39 | 44 | 50 | 56 | 57 | 58 | 59 | 60 | 61 | {props.children} 62 | 63 | ) 64 | } 65 | 66 | export default GlobalWrapper 67 | -------------------------------------------------------------------------------- /src/components/header.js: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react' 2 | import styled from 'styled-components' 3 | import media from 'utils/media-queries' 4 | import { color, fontSize } from 'styles/theme' 5 | 6 | import Icon from 'components/icons' 7 | 8 | const Div = styled.div` 9 | display: flex; 10 | justify-content: space-between; 11 | flex-direction: row; 12 | flex: 0 0 auto; 13 | ${media.lg` 14 | height: ${props => (props.article ? '64px' : '')}; 15 | `} 16 | ${media.sm` 17 | display: ${props => (props.article ? 'flex' : 'block')}; 18 | `}; 19 | z-index: 10; 20 | height: ${props => (props.article ? '74px' : '')}; 21 | ${media.xs` 22 | display: block; 23 | height: 112px; 24 | `} 25 | ` 26 | 27 | const LogoWrapper = styled.div` 28 | padding: 24px 0 24px 24px; 29 | ${media.sm` 30 | padding: ${props => (props.article ? '24px 0 24px 24px' : '24px 0 0 0')}; 31 | `}; 32 | ${media.xs` 33 | padding: 24px 0 0 0; 34 | `} 35 | ` 36 | 37 | const NameLink = styled.a` 38 | text-decoration: none; 39 | ` 40 | 41 | const Name = styled.h1` 42 | white-space: nowrap; 43 | font-size: ${fontSize.f6}; 44 | text-align: left; 45 | margin: 0; 46 | line-height: 1.2; 47 | letter-spacing: -0.8px; 48 | ${media.sm` 49 | text-align: center; 50 | font-size: ${fontSize.f7}; 51 | `}; 52 | ` 53 | 54 | const NameArticle = styled.div` 55 | white-space: nowrap; 56 | font-size: ${fontSize.f5}; 57 | font-weight: 700; 58 | text-align: left; 59 | margin: 0; 60 | line-height: 1.2; 61 | letter-spacing: -0.6px; 62 | color: ${color.grey900}; 63 | ${media.xs` 64 | text-align: center; 65 | `}; 66 | ` 67 | 68 | const Role = styled.div` 69 | color: ${color.grey700}; 70 | text-align: left; 71 | line-height: 1.4; 72 | font-size: ${fontSize.f4}; 73 | ${media.sm` 74 | text-align: center; 75 | font-size: ${fontSize.f6}; 76 | `}; 77 | ` 78 | 79 | const SocialLinks = styled.div` 80 | display: grid; 81 | grid-column-gap: 4px; 82 | grid-template-columns: auto auto auto; 83 | padding: 15px 24px 0 24px; 84 | ${media.sm` 85 | padding: ${props => (props.article ? '15px 12px 0 8px' : '4px 0 0 0')}; 86 | grid-column-gap: 0; 87 | `} 88 | ${media.xs` 89 | padding: 0; 90 | `} 91 | justify-content: center; 92 | ` 93 | 94 | const SocialLink = styled.a` 95 | display: flex; 96 | justify-content: center; 97 | align-items: center; 98 | width: 40px; 99 | height: 40px; 100 | background: white; 101 | border-radius: 20px; 102 | color: ${color.grey900}; 103 | border: 1px solid white; 104 | &:hover { 105 | border: 1px solid ${color.grey150}; 106 | background: ${color.grey150}; 107 | color: ${color.grey900}; 108 | } 109 | &:active { 110 | color: ${color.grey900}; 111 | } 112 | &:visited { 113 | color: ${color.grey900}; 114 | } 115 | ` 116 | 117 | export const SvgWrapper = styled.div` 118 | display: flex; 119 | justify-content: center; 120 | align-items: center; 121 | min-width: 24px; 122 | min-height: 24px; 123 | color: inherit; 124 | background: inherit; 125 | ` 126 | 127 | export const InlineSvg = styled.svg` 128 | height: 24px; 129 | width: 24px; 130 | color: inherit; 131 | fill: currentColor; 132 | ` 133 | 134 | const Tooltip = styled.div` 135 | padding: 2px 24px 0 24px; 136 | display: flex; 137 | justify-content: flex-end; 138 | align-items: center; 139 | opacity: ${props => (props.visible ? '1' : '0')}; 140 | transition: opacity 300ms; 141 | ${media.sm` 142 | justify-content: center; 143 | padding-top: 6px; 144 | `} 145 | ` 146 | 147 | const TooltipIcon = styled.div` 148 | transform: rotate(270deg); 149 | margin-left: 8px; 150 | ` 151 | 152 | const TooltipText = styled.div`` 153 | 154 | const Header = props => { 155 | const [tooltipIsVisible, setTooltipIsVisible] = useState(false) 156 | const [tooltipText, setTooltipText] = useState('') 157 | 158 | const showTooltip = tooltipText => { 159 | setTooltipIsVisible(true) 160 | setTooltipText(tooltipText) 161 | } 162 | 163 | const hideTooltip = () => { 164 | setTooltipIsVisible(false) 165 | } 166 | 167 | return ( 168 |
169 | 170 | {props.article && ( 171 | 172 | John Doe 173 | 174 | )} 175 | {!props.article && John Doe} 176 | {!props.article && Designer} 177 | 178 |
179 | 180 | showTooltip('Mail')} 183 | onFocus={() => showTooltip('Mail')} 184 | onMouseLeave={hideTooltip} 185 | onBlur={hideTooltip} 186 | aria-label="Send an email to John" 187 | > 188 | 189 | 190 | 191 | 192 | 193 | 194 | showTooltip('Twitter')} 198 | onFocus={() => showTooltip('Twitter')} 199 | onMouseLeave={hideTooltip} 200 | onBlur={hideTooltip} 201 | aria-label="John's twitter profile" 202 | > 203 | 204 | 205 | 206 | 207 | 208 | 209 | showTooltip('Dribbble')} 213 | onFocus={() => showTooltip('Dribbble')} 214 | onMouseLeave={hideTooltip} 215 | onBlur={hideTooltip} 216 | aria-label="John's dribbble profile" 217 | > 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | {!props.article && ( 226 | 227 | {tooltipText} 228 | 229 | 230 | 231 | 232 | )} 233 |
234 |
235 | ) 236 | } 237 | 238 | export default Header 239 | -------------------------------------------------------------------------------- /src/components/hero.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import styled from 'styled-components' 3 | import media from 'utils/media-queries' 4 | 5 | import Header from 'components/header' 6 | import Spirograph from 'components/spirograph' 7 | 8 | const HeroSection = styled.section` 9 | height: 100vh; 10 | max-height: 100vh; 11 | display: flex; 12 | flex-direction: column; 13 | overflow: hidden; 14 | width: 100%; 15 | ${media.sm` 16 | height: calc(100vh - 76px); 17 | `} 18 | ` 19 | 20 | const Hero = () => { 21 | return ( 22 | 23 | 24 |
25 | 26 | ) 27 | } 28 | export default Hero 29 | -------------------------------------------------------------------------------- /src/components/icons.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import styled from 'styled-components' 3 | 4 | import { color } from 'styles/theme' 5 | 6 | export const InlineSvg = styled.svg` 7 | position: absolute; 8 | top: 0; 9 | right: 0; 10 | bottom: 0; 11 | left: 0; 12 | height: 100%; 13 | width: 100%; 14 | color: inherit; 15 | fill: currentColor; 16 | ` 17 | 18 | export const SvgWrapper = styled.div` 19 | display: inline-block; 20 | flex: 0 0 ${(props) => (props.size ? `${props.size}px` : '32px')}; 21 | width: ${(props) => (props.size ? `${props.size}px` : '32px')}; 22 | height: ${(props) => (props.size ? `${props.size}px` : '32px')}; 23 | min-width: ${(props) => (props.size ? `${props.size}px` : '32px')}; 24 | min-height: ${(props) => (props.size ? `${props.size}px` : '32px')}; 25 | position: relative; 26 | color: inherit; 27 | ` 28 | 29 | export const Glyph = ({ glyph }) => { 30 | switch (glyph) { 31 | case 'arrow': 32 | return ( 33 | 37 | ) 38 | case 'external-link': 39 | return ( 40 | 41 | 45 | 46 | ) 47 | case 'close': 48 | return ( 49 | 50 | {/* */} 51 | 55 | 56 | ) 57 | default: 58 | } 59 | } 60 | 61 | const Icon = (props) => { 62 | const { size = 32, glyph } = props 63 | 64 | return ( 65 | 66 | 78 | {glyph} 79 | 80 | 81 | 82 | ) 83 | } 84 | 85 | export default Icon 86 | -------------------------------------------------------------------------------- /src/components/project.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import styled from 'styled-components' 3 | import media from 'utils/media-queries' 4 | 5 | import { fontSize } from 'styles/theme' 6 | 7 | import Logo from 'components/projectlogo' 8 | 9 | const ProjectWrapper = styled.div` 10 | margin-bottom: 100px; 11 | display: flex; 12 | @media (max-width: 660px) { 13 | } 14 | &:last-of-type { 15 | margin-bottom: 0px; 16 | } 17 | ${media.lg` 18 | margin-bottom: 92px; 19 | `} 20 | ${media.sm` 21 | flex-wrap: wrap; 22 | margin-bottom: 68px; 23 | `} 24 | ` 25 | 26 | const Description = styled.div` 27 | @media (max-width: 660px) { 28 | margin-top: 32px; 29 | } 30 | ` 31 | 32 | const Title = styled.h3` 33 | font-size: ${fontSize.f6}; 34 | font-weight: 700; 35 | ${media.lg` 36 | font-size: ${fontSize.f5}; 37 | letter-spacing: -0.3px; 38 | `} 39 | ${media.md` 40 | font-size: ${fontSize.f6}; 41 | `} 42 | margin: 0 0 16px 0; 43 | ` 44 | 45 | const Abstract = styled.div` 46 | margin-bottom: 16px; 47 | ` 48 | 49 | const Project = (props) => { 50 | return ( 51 | 52 | {props.logo} 53 | 54 | {props.title} 55 | {props.abstract} 56 | {props.link} 57 | 58 | 59 | ) 60 | } 61 | 62 | export default Project 63 | -------------------------------------------------------------------------------- /src/components/projectlogo.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import styled from 'styled-components' 3 | import media from 'utils/media-queries' 4 | 5 | import Project1Logo from 'img/articles/project1/img1.jpg' 6 | 7 | const Image = styled.div` 8 | background: #eee; 9 | background-size: cover; 10 | background-position: center center; 11 | flex: 0 0 120px; 12 | height: 120px; 13 | margin-right: 40px; 14 | border-radius: 8px; 15 | ` 16 | 17 | const ProjectLogo = props => { 18 | return 19 | } 20 | 21 | export default ProjectLogo 22 | -------------------------------------------------------------------------------- /src/components/section.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import styled from 'styled-components' 3 | import media from 'utils/media-queries' 4 | 5 | import { color } from 'styles/theme' 6 | 7 | const Section = styled.section` 8 | margin: 0 96px; 9 | ${media.lg` 10 | margin: 0 40px; 11 | `} 12 | ${media.sm` 13 | margin: 0 24px; 14 | `} 15 | display: flex; 16 | justify-content: center; 17 | ` 18 | 19 | const Container = styled.div` 20 | border-top: 1px solid ${color.grey400}; 21 | max-width: 916px; 22 | padding: 128px 0; 23 | ${media.lg` 24 | padding: 112px 0; 25 | `} 26 | ${media.sm` 27 | padding: 64px 0; 28 | `} 29 | width: 100%; 30 | ` 31 | 32 | const SectionComponent = (props) => { 33 | return ( 34 |
35 | {props.children} 36 |
37 | ) 38 | } 39 | 40 | export default SectionComponent 41 | -------------------------------------------------------------------------------- /src/components/sectionHeading.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import styled from 'styled-components' 3 | import media from 'utils/media-queries' 4 | import { color, fontSize } from 'styles/theme' 5 | 6 | const H2 = styled.h2` 7 | border-top: 2px solid ${color.grey900}; 8 | display: inline-block; 9 | font-size: ${fontSize.f5}; 10 | color: ${color.grey900}; 11 | letter-spacing: 5px; 12 | text-transform: uppercase; 13 | font-weight: 700; 14 | margin: 6px 0 0 0; 15 | padding: 16px 0 0 0; 16 | line-height: 1.1; 17 | ${media.lg` 18 | font-size: ${fontSize.f4}; 19 | padding: 12px 0 0 0; 20 | margin: 2px 0 0 0; 21 | `} 22 | ${media.md` 23 | border-top: none; 24 | margin-bottom: 72px; 25 | padding-bottom: 12px; 26 | border-bottom: 2px solid ${color.grey900}; 27 | `} 28 | ${media.sm` 29 | margin-bottom: 60px; 30 | padding-bottom: 8px; 31 | `} 32 | ` 33 | 34 | const SectionHeading = (props) => { 35 | return

{props.children}

36 | } 37 | 38 | export default SectionHeading 39 | -------------------------------------------------------------------------------- /src/components/spirograph.js: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useRef } from 'react' 2 | import styled from 'styled-components' 3 | import { color } from 'styles/theme' 4 | import debounce from 'lodash/debounce' 5 | 6 | const Div = styled.div` 7 | position: absolute; 8 | top: 0; 9 | left: 0; 10 | bottom: 0; 11 | right: 0; 12 | width: 100%; 13 | height: 100vh; 14 | overflow: hidden; 15 | ` 16 | 17 | const CanvasWrapper = styled.div` 18 | cursor: pointer; 19 | position: absolute; 20 | width: 100%; 21 | height: 100%; 22 | display: flex; 23 | justify-content: center; 24 | align-items: center; 25 | ` 26 | 27 | const Canvas = styled.canvas`` 28 | 29 | const Spirograph = () => { 30 | const movingCanvasContainer = useRef() 31 | const plottingCanvasContainer = useRef() 32 | let movingCanvas 33 | let plottingCanvas 34 | let isMounted 35 | 36 | //canvas 37 | let windowWidth 38 | let size 39 | let mctx 40 | let pctx 41 | 42 | // config 43 | const dotSize = 7 44 | const spiroColor = color.grey800 45 | const circleColor = color.grey400 46 | const lowSpeed = 0.012 47 | const highSpeed = 0.07 48 | const lineWidth = 1.2 49 | 50 | // seed values 51 | let randomM 52 | let randomN 53 | let f 54 | 55 | // gear values 56 | let m 57 | let n 58 | let speed = lowSpeed 59 | let gearRadius 60 | let angle 61 | let centerX 62 | let centerY 63 | let gearX 64 | let gearY 65 | let spiroX 66 | let spiroY 67 | 68 | useEffect(() => { 69 | isMounted = true 70 | window.addEventListener('resize', resizeHandler) 71 | movingCanvas = movingCanvasContainer.current 72 | plottingCanvas = plottingCanvasContainer.current 73 | mctx = movingCanvas.getContext('2d') 74 | pctx = plottingCanvas.getContext('2d') 75 | mctx.lineWidth = lineWidth 76 | pctx.lineWidth = lineWidth 77 | windowWidth = window.innerWidth 78 | setCanvasSize() 79 | initSpirograph() 80 | window.requestAnimationFrame(draw) 81 | return function cleanup() { 82 | window.removeEventListener('resize', resizeHandler) 83 | isMounted = false 84 | } 85 | }) 86 | 87 | const resizeHandler = debounce(() => { 88 | //check if window size has actually changed bc of iOS Safari Bug 89 | if (window.innerWidth !== windowWidth) { 90 | setCanvasSize() 91 | initSpirograph() 92 | windowWidth = window.innerWidth 93 | } else { 94 | return 95 | } 96 | }, 500) 97 | 98 | const setCanvasSize = () => { 99 | const canvasSize = Math.min( 100 | 1000, 101 | window.innerWidth / 1.4 + 2, 102 | window.innerHeight / 1.4 + 2 103 | ) 104 | movingCanvas.width = canvasSize 105 | movingCanvas.height = canvasSize 106 | plottingCanvas.width = canvasSize 107 | plottingCanvas.height = canvasSize 108 | } 109 | 110 | const reduceFraction = (numerator, denominator) => { 111 | var gcd = function (a, b) { 112 | return b ? gcd(b, a % b) : a 113 | } 114 | gcd = gcd(numerator, denominator) 115 | return [numerator / gcd, denominator / gcd] 116 | } 117 | 118 | const speedUp = () => { 119 | speed = highSpeed 120 | } 121 | 122 | const speedDown = () => { 123 | speed = lowSpeed 124 | } 125 | 126 | const delay = (t) => new Promise((resolve) => setTimeout(resolve, t)) 127 | 128 | const initSpirograph = () => { 129 | // calculate size based on window size 130 | size = Math.min( 131 | 498, 132 | Math.min(window.innerWidth / 2.8, window.innerHeight / 2.8) 133 | ) 134 | 135 | // clear canvas 136 | mctx.clearRect(0, 0, movingCanvas.width, movingCanvas.height) 137 | pctx.clearRect(0, 0, movingCanvas.width, movingCanvas.height) 138 | 139 | // get seed values 140 | randomM = Math.floor(Math.random() * (100 - 3)) + 3 141 | randomN = 142 | Math.floor(Math.random() * (randomM / 2 - randomM / 10)) + 143 | Math.floor(randomM / 10 + 1) 144 | n = reduceFraction(randomN, randomM)[0] 145 | m = reduceFraction(randomN, randomM)[1] 146 | gearRadius = n / m 147 | f = Math.random() * (0.9 - 0.2) + 0.2 148 | 149 | // initial gear values 150 | angle = 0 151 | centerX = movingCanvas.width / 2 152 | centerY = movingCanvas.height / 2 153 | gearX = (1 - gearRadius) * Math.cos(angle) * size 154 | gearY = (1 - gearRadius) * Math.sin(angle) * size 155 | spiroX = 156 | centerX + 157 | ((1 - gearRadius) * Math.cos(angle) + 158 | f * gearRadius * Math.cos((1 / gearRadius - 1) * angle)) * 159 | size 160 | spiroY = 161 | centerY + 162 | ((1 - gearRadius) * Math.sin(angle) - 163 | f * gearRadius * Math.sin((1 / gearRadius - 1) * angle)) * 164 | size 165 | mctx.strokeStyle = circleColor 166 | 167 | // plot outer circle 168 | pctx.strokeStyle = circleColor 169 | pctx.beginPath() 170 | pctx.arc(centerX, centerY, size, 0, 2 * Math.PI) 171 | pctx.stroke() 172 | pctx.strokeStyle = spiroColor 173 | } 174 | 175 | const draw = () => { 176 | if (isMounted === true) { 177 | // ----------- MOVING CANVAS ---------- 178 | // clear moving canvas 179 | mctx.clearRect(0, 0, movingCanvas.width, movingCanvas.height) 180 | 181 | // gear 182 | mctx.beginPath() 183 | mctx.arc( 184 | centerX + gearX, 185 | centerY + gearY, 186 | gearRadius * size, 187 | 0, 188 | 2 * Math.PI 189 | ) 190 | mctx.stroke() 191 | 192 | // inner gear 193 | mctx.beginPath() 194 | mctx.arc( 195 | centerX + gearX, 196 | centerY + gearY, 197 | f * gearRadius * size, 198 | 0, 199 | 2 * Math.PI 200 | ) 201 | mctx.stroke() 202 | 203 | // drawing dot 204 | mctx.beginPath() 205 | mctx.arc( 206 | centerX + 207 | ((1 - gearRadius) * Math.cos(angle) + 208 | f * gearRadius * Math.cos((1 / gearRadius - 1) * angle)) * 209 | size, 210 | centerY + 211 | ((1 - gearRadius) * Math.sin(angle) - 212 | f * gearRadius * Math.sin((1 / gearRadius - 1) * angle)) * 213 | size, 214 | dotSize, 215 | 0, 216 | 2 * Math.PI 217 | ) 218 | mctx.stroke() 219 | 220 | // ---------- PLOTTING CANVAS ---------- 221 | // check if spirograph is uncomplete 222 | if (angle - speed < n * 2 * Math.PI) { 223 | pctx.beginPath() 224 | pctx.moveTo(spiroX, spiroY) 225 | spiroX = 226 | centerX + 227 | ((1 - gearRadius) * Math.cos(angle) + 228 | f * gearRadius * Math.cos((1 / gearRadius - 1) * angle)) * 229 | size 230 | spiroY = 231 | centerY + 232 | ((1 - gearRadius) * Math.sin(angle) - 233 | f * gearRadius * Math.sin((1 / gearRadius - 1) * angle)) * 234 | size 235 | pctx.lineTo(spiroX, spiroY) 236 | pctx.stroke() 237 | } else { 238 | // if completed, start new spirograph 239 | mctx.clearRect(0, 0, movingCanvas.width, movingCanvas.height) 240 | delay(3000) 241 | .then(() => initSpirograph()) 242 | .then(() => window.requestAnimationFrame(draw)) 243 | return 244 | } 245 | 246 | // ---------- INCREMENT ---------- 247 | gearX = (1 - gearRadius) * Math.cos(angle) * size 248 | gearY = (1 - gearRadius) * Math.sin(angle) * size 249 | angle += speed 250 | 251 | // draw next frame 252 | window.requestAnimationFrame(draw) 253 | } else { 254 | return 255 | } 256 | } 257 | 258 | return ( 259 |
260 | 261 | 262 | 263 | 269 | 270 | 271 |
272 | ) 273 | } 274 | 275 | export default Spirograph 276 | -------------------------------------------------------------------------------- /src/components/twoColumns.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import styled from 'styled-components' 3 | 4 | import media from 'utils/media-queries' 5 | 6 | const Div = styled.div` 7 | display: grid; 8 | grid-template-columns: 100px auto; 9 | justify-content: space-between; 10 | width: 100%; 11 | ${media.md` 12 | grid-template-columns: auto; 13 | justify-content: center; 14 | `} 15 | ` 16 | 17 | const LeftColumn = styled.div`` 18 | 19 | const RightColumn = styled.div` 20 | max-width: ${(props) => (props.wide ? '620px' : '544px')}; 21 | ${media.md` 22 | max-width: 544px; 23 | `} 24 | ` 25 | 26 | const TwoColumns = (props) => { 27 | return ( 28 |
29 | {props.leftColumn} 30 | {props.rightColumn} 31 |
32 | ) 33 | } 34 | 35 | export default TwoColumns 36 | -------------------------------------------------------------------------------- /src/components/work.js: -------------------------------------------------------------------------------- 1 | import React, { Fragment } from 'react' 2 | import Link from 'gatsby-link' 3 | 4 | import TwoColumns from 'components/twoColumns' 5 | import SectionHeading from 'components/sectionHeading' 6 | import Project from 'components/project' 7 | 8 | import Project1Logo from 'img/articles/project1/img1.jpg' 9 | 10 | const Project1Link = Read More 11 | const Project2Link = Read More 12 | const Project3Link = Read More 13 | 14 | const Work = () => { 15 | return ( 16 | Work} 19 | rightColumn={ 20 | 21 | 27 | 33 | 39 | 40 | } 41 | /> 42 | ) 43 | } 44 | 45 | export default Work 46 | -------------------------------------------------------------------------------- /src/fonts/TTNBold.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gabdorf/gatsby-portfolio/2c468cd143e5231da9a94e76a62023ecd425f6d3/src/fonts/TTNBold.woff -------------------------------------------------------------------------------- /src/fonts/TTNBold.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gabdorf/gatsby-portfolio/2c468cd143e5231da9a94e76a62023ecd425f6d3/src/fonts/TTNBold.woff2 -------------------------------------------------------------------------------- /src/fonts/TTNRegular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gabdorf/gatsby-portfolio/2c468cd143e5231da9a94e76a62023ecd425f6d3/src/fonts/TTNRegular.woff -------------------------------------------------------------------------------- /src/fonts/TTNRegular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gabdorf/gatsby-portfolio/2c468cd143e5231da9a94e76a62023ecd425f6d3/src/fonts/TTNRegular.woff2 -------------------------------------------------------------------------------- /src/img/articles/project1/img1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gabdorf/gatsby-portfolio/2c468cd143e5231da9a94e76a62023ecd425f6d3/src/img/articles/project1/img1.jpg -------------------------------------------------------------------------------- /src/img/articles/project1/img2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gabdorf/gatsby-portfolio/2c468cd143e5231da9a94e76a62023ecd425f6d3/src/img/articles/project1/img2.jpg -------------------------------------------------------------------------------- /src/img/articles/project1/img3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gabdorf/gatsby-portfolio/2c468cd143e5231da9a94e76a62023ecd425f6d3/src/img/articles/project1/img3.jpg -------------------------------------------------------------------------------- /src/img/articles/project1/img4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gabdorf/gatsby-portfolio/2c468cd143e5231da9a94e76a62023ecd425f6d3/src/img/articles/project1/img4.jpg -------------------------------------------------------------------------------- /src/img/icon-templates/arrow.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/img/icon-templates/dribbble.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/img/icon-templates/mail.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/img/icon-templates/twitter.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/pages/404.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import styled from 'styled-components' 3 | import Link from 'gatsby-link' 4 | import GlobalWrapper from 'components/global-wrapper' 5 | 6 | const Div = styled.div` 7 | margin: 24px; 8 | ` 9 | 10 | const NotFoundPage = () => { 11 | return ( 12 | 13 |
14 |

404

15 |

You just hit a route that doesn't exist... the sadness.

16 | Back to Homepage 17 |
18 |
19 | ) 20 | } 21 | 22 | export default NotFoundPage 23 | -------------------------------------------------------------------------------- /src/pages/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | import GlobalWrapper from 'components/global-wrapper' 4 | import Hero from 'components/hero' 5 | import Section from 'components/section' 6 | import About from 'components/about' 7 | import Work from 'components/work' 8 | import Footer from 'components/footer' 9 | 10 | const App = () => { 11 | return ( 12 | 13 | 14 |
15 | 16 |
17 |
18 | 19 |
20 |