├── .env.development.template ├── .github ├── FUNDING.YML └── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md ├── .gitignore ├── .nvmrc ├── .vscode └── settings.json ├── LICENSE.md ├── README.md ├── api └── contact.js ├── gatsby-browser.js ├── gatsby-config.js ├── gatsby-node.js ├── gatsby-ssr.js ├── jsconfig.json ├── package.json ├── powered-by-vercel.svg ├── src ├── assets │ ├── icons │ │ ├── moon.svg │ │ └── sun.svg │ ├── illustrations │ │ ├── contact-overlay.svg │ │ ├── contact.svg │ │ ├── details.svg │ │ ├── dev.svg │ │ ├── footer.svg │ │ ├── overlay.svg │ │ └── skills.svg │ └── thumbnail │ │ └── thumbnail.png ├── components │ ├── common │ │ ├── Button │ │ │ └── index.js │ │ ├── Card │ │ │ └── index.js │ │ ├── Container │ │ │ └── index.js │ │ ├── Icons │ │ │ ├── Fork.js │ │ │ └── Star.js │ │ ├── Input │ │ │ └── index.js │ │ ├── Layout │ │ │ ├── fonts.css │ │ │ ├── fonts │ │ │ │ ├── roboto-v18-latin-700.eot │ │ │ │ ├── roboto-v18-latin-700.svg │ │ │ │ ├── roboto-v18-latin-700.ttf │ │ │ │ ├── roboto-v18-latin-700.woff │ │ │ │ ├── roboto-v18-latin-700.woff2 │ │ │ │ ├── roboto-v18-latin-regular.eot │ │ │ │ ├── roboto-v18-latin-regular.svg │ │ │ │ ├── roboto-v18-latin-regular.ttf │ │ │ │ ├── roboto-v18-latin-regular.woff │ │ │ │ └── roboto-v18-latin-regular.woff2 │ │ │ ├── index.jsx │ │ │ └── styles.js │ │ ├── Seo │ │ │ └── index.jsx │ │ └── index.js │ ├── landing │ │ ├── Contact │ │ │ ├── ContactForm │ │ │ │ ├── index.jsx │ │ │ │ └── styles.js │ │ │ ├── index.jsx │ │ │ └── styles.js │ │ ├── Intro │ │ │ ├── index.jsx │ │ │ └── styles.js │ │ ├── Projects │ │ │ ├── index.jsx │ │ │ └── styles.js │ │ ├── Skills │ │ │ ├── index.jsx │ │ │ └── styles.js │ │ └── index.js │ └── theme │ │ ├── Footer │ │ ├── index.jsx │ │ ├── social.json │ │ └── styles.js │ │ ├── Header │ │ ├── Hamburger │ │ │ ├── index.jsx │ │ │ └── styles.js │ │ ├── Navbar │ │ │ ├── index.jsx │ │ │ └── styles.js │ │ ├── NavbarLinks │ │ │ ├── index.jsx │ │ │ └── styles.js │ │ ├── Sidebar │ │ │ ├── index.jsx │ │ │ └── styles.js │ │ ├── ToggleTheme │ │ │ ├── index.jsx │ │ │ └── styles.js │ │ ├── index.jsx │ │ └── styles.js │ │ └── index.js ├── data │ └── config.js ├── hooks │ ├── useDarkMode.js │ └── useMedia.js ├── pages │ ├── 404.js │ └── index.js └── providers │ └── ThemeProvider.jsx └── static ├── _redirects ├── favicon └── favicon-512.png └── icons ├── github.svg ├── stackoverflow.svg ├── telegram.svg └── twitter.svg /.env.development.template: -------------------------------------------------------------------------------- 1 | PORTFOLIO_GITHUB_TOKEN= 2 | PORTFOLIO_FORMIUM_ENDPOINT= 3 | GATSBY_PORTFOLIO_RECAPTCHA_KEY= -------------------------------------------------------------------------------- /.github/FUNDING.YML: -------------------------------------------------------------------------------- 1 | # repo: https://github.com/shinevue/gatsby-portfolio-dev 2 | # filename: FUNDING.YML 3 | 4 | github: shinevue 5 | patreon: shinevue 6 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: "" 5 | labels: bug 6 | assignees: shinevue 7 | --- 8 | 9 | **Describe the bug** 10 | A clear and concise description of what the bug is. 11 | 12 | **To Reproduce** 13 | Steps to reproduce the behavior: 14 | 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **(please complete the following information):** 27 | 28 | - OS: [e.g. iOS] 29 | - Browser [e.g. chrome, safari] 30 | - Version [e.g. 22] 31 | 32 | **Additional context** 33 | Add any other context about the problem here. 34 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: "" 5 | labels: enhancement 6 | assignees: shinevue 7 | --- 8 | 9 | **Is your feature request related to a problem? Please describe.** 10 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 11 | 12 | **Describe the solution you'd like** 13 | A clear and concise description of what you want to happen. 14 | 15 | **Describe alternatives you've considered** 16 | A clear and concise description of any alternative solutions or features you've considered. 17 | 18 | **Additional context** 19 | Add any other context or screenshots about the feature request here. 20 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | 8 | # Runtime data 9 | pids 10 | *.pid 11 | *.seed 12 | *.pid.lock 13 | 14 | # Directory for instrumented libs generated by jscoverage/JSCover 15 | lib-cov 16 | 17 | # Coverage directory used by tools like istanbul 18 | coverage 19 | 20 | # nyc test coverage 21 | .nyc_output 22 | 23 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 24 | .grunt 25 | 26 | # Bower dependency directory (https://bower.io/) 27 | bower_components 28 | 29 | # node-waf configuration 30 | .lock-wscript 31 | 32 | # Compiled binary addons (http://nodejs.org/api/addons.html) 33 | build/Release 34 | 35 | # Dependency directories 36 | node_modules/ 37 | jspm_packages/ 38 | 39 | # Typescript v1 declaration files 40 | typings/ 41 | 42 | # Optional npm cache directory 43 | .npm 44 | package-lock.json 45 | 46 | # Optional eslint cache 47 | .eslintcache 48 | 49 | # Optional REPL history 50 | .node_repl_history 51 | 52 | # Output of 'npm pack' 53 | *.tgz 54 | 55 | # dotenv environment variables file 56 | .env 57 | .env.development 58 | .env.production 59 | 60 | # gatsby files 61 | .cache/ 62 | public 63 | 64 | # Mac files 65 | .DS_Store 66 | 67 | # Yarn 68 | yarn-error.log 69 | .pnp/ 70 | .pnp.js 71 | # Yarn Integrity file 72 | .yarn-integrity 73 | 74 | .now 75 | -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | v12.13.0 2 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.formatOnSave": true, 3 | // turn it off for JS and JSX, we will do this via eslint 4 | "[javascript]": { 5 | "editor.formatOnSave": true 6 | }, 7 | "[javascriptreact]": { 8 | "editor.formatOnSave": true 9 | }, 10 | // tell the ESLint plugin to run on save 11 | "editor.codeActionsOnSave": { 12 | "source.fixAll.eslint": "explicit" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Portfolio for developers 2 | 3 | [](https://vercel.com/new/git/external?repository-url=https%3A%2F%2Fgithub.com%2Fsmakosh%2Fgatsby-portfolio-dev&env=PORTFOLIO_GITHUB_TOKEN,PORTFOLIO_FORMIUM_ENDPOINT,GATSBY_PORTFOLIO_RECAPTCHA_KEY&envDescription=All%20env%20variables%20are%20required%20to%20deploy%20the%20project&envLink=https%3A%2F%2Fgithub.com%2Fsmakosh%2Fgatsby-portfolio-dev%2Fblob%2Fmaster%2F.env.development.template&project-name=my-portfolio&repo-name=my-portfolio&demo-title=Portfolio%20demo&demo-description=A%20simple%20portfolio%20for%20developers&demo-url=https%3A%2F%2Fportfolio.smakosh.com&demo-image=https%3A%2F%2Fportfolio.smakosh.com%2Fstatic%2Fthumbnail-16a70559ab07712f83d3ce412dfbb5a6.png) 4 | 5 | [](https://vercel.com?utm_source=smakosh&utm_campaign=oss) 6 | 7 | ## Next js version? 8 | 9 | [There you go](https://github.com/smakosh/next-portfolio-dev) 10 | 11 | ## Theme 12 | 13 | [Gatsby-theme-portfolio](https://github.com/smakosh/gatsby-theme-portfolio) 14 | 15 | ## Features 16 | 17 | - Eslint/Prettier configured 18 | - Scores 100% on a11y / Performance / PWA / SEO 19 | - PWA (desktop & mobile) 20 | - Easy to customize 21 | - Nice project structure 22 | - Amazing illustrations by [Undraw.co](https://undraw.co) 23 | - Tablet & mobile friendly 24 | - Continuous deployment with [Vercel](https://vercel.com/?utm_source=smakosh) 25 | - Or with Netlify, check [Netlify branch](https://github.com/smakosh/gatsby-portfolio-dev/tree/netlify) 26 | - A contact form protected by Google Recaptcha 27 | - Can be deployed with one click 28 | - Functional components with ~~Recompose~~ React Hooks! ~~ready to migrate to React hooks!~~ 29 | - Fetches your Github pinned projects with most stars (You could customize this if you wish) 30 | - One click deployment to Vercel 31 | 32 | ## Design 33 | 34 | Project on [Behance](https://www.behance.net/gallery/74172961/Free-Gatsby-portfolio-for-developers) 35 | 36 | ## Structure 37 | 38 | ```bash 39 | . 40 | ├── data 41 | │ └── config # SEO related tags 42 | ├── src 43 | │ └── assets # Assets 44 | │ │ │── icons # icons 45 | │ │ │── illustrations # illustrations from (undraw.co) 46 | │ │ └── thumbnail # cover of your website when it's shared to social media 47 | │ ├── components # Components 48 | │ │ │── common # Common components 49 | │ │ │── landing # Components used on the landing page 50 | │ │ └── theme # Header & Footer 51 | │ └── pages # Pages 52 | └── static # favicon & Netlify redirects 53 | ``` 54 | 55 | ## Prerequisites 56 | 57 | ### Online 58 | 59 | 1. Create an account at [Formium](https://formium.io/?utm_source=smakosh) and grab your form endpoint url 60 | 2. Grab a Google recaptcha key from [Google Recaptcha](https://www.google.com/recaptcha/admin) 61 | > Make sure to select V2 checkbox 62 | 4. Grab your Github token from [GitHub](https://github.com/settings/tokens/new?scopes=repo&description=portfolio-dev) 63 | 5. Click [](https://vercel.com/new/git/external?repository-url=https%3A%2F%2Fgithub.com%2Fsmakosh%2Fgatsby-portfolio-dev&env=PORTFOLIO_GITHUB_TOKEN,PORTFOLIO_FORMIUM_ENDPOINT,GATSBY_PORTFOLIO_RECAPTCHA_KEY&envDescription=All%20env%20variables%20are%20required%20to%20deploy%20the%20project&envLink=https%3A%2F%2Fgithub.com%2Fsmakosh%2Fgatsby-portfolio-dev%2Fblob%2Fmaster%2F.env.development.template&project-name=my-portfolio&repo-name=my-portfolio&demo-title=Portfolio%20demo&demo-description=A%20simple%20portfolio%20for%20developers&demo-url=https%3A%2F%2Fportfolio.smakosh.com&demo-image=https%3A%2F%2Fportfolio.smakosh.com%2Fstatic%2Fthumbnail-16a70559ab07712f83d3ce412dfbb5a6.png) and pass in your: 64 | 65 | - Formium form endpoint 66 | - Google recaptcha public key 67 | - Github token 68 | 69 | To Env variables section. 70 | 71 | > For the contact form to work, you will need to update the `url` in [here](https://github.com/smakosh/gatsby-portfolio-dev/blob/master/src/data/config.js#L5) 72 | 73 | ### Locally 74 | 75 | 1. Create an account at [Formium](https://formium.io/?utm_source=smakosh) 76 | 2. Grab a Google recaptcha key from [Google Recaptcha](https://www.google.com/recaptcha/admin) 77 | 3. Grab your Github token from GitHub 78 | 4. Run `cp .env.development.template .env.development` 79 | 5. Run `npm i && npm start` 80 | 81 | > You could run `vercel env pull` to get your env variables from Vercel. 82 | 83 | ### Deploying locally to Vercel 84 | 85 | I highly recommend that you push to GitHub/GitLab and deploy your repository to Vercel instead or just hit the Deploy button. 86 | 87 | ### Clean the cache 88 | 89 | This removes the `.cache/` & `public/` folders 90 | 91 | ```bash 92 | yarn reset 93 | ``` 94 | 95 | ## Built with 96 | 97 | - Adobe XD 98 | - Gatsby 99 | - React & GraphQL 100 | - Formium 101 | - Google recaptcha 102 | - VSCode 103 | - And these useful of JavaScript libraries & Gatsby plugins [package.json](package.json) 104 | 105 | ## License 106 | 107 | This project is licensed under the MIT License - see the [LICENSE.md](LICENSE.md) file for more details 108 | 109 | ## Contributors 110 | 111 | - [Ajay NS](https://github.com/ajayns) https://github.com/smakosh/gatsby-portfolio-dev/pull/3 112 | - [Ryan Lee](https://github.com/drdgvhbh) https://github.com/smakosh/gatsby-portfolio-dev/pull/6 113 | - [David](https://github.com/davidavz) https://github.com/smakosh/gatsby-portfolio-dev/pull/8 114 | - [Léu Almeida](https://github.com/LeuAlmeida) https://github.com/smakosh/gatsby-portfolio-dev/pull/21 115 | - [Kudakwashe Mupeni](https://github.com/2wce) https://github.com/smakosh/gatsby-portfolio-dev/pull/20 116 | - [sasannnn](https://github.com/sasannnn) https://github.com/smakosh/gatsby-portfolio-dev/pull/22 117 | - [Michael Seifarth](https://github.com/Kageetai) https://github.com/smakosh/gatsby-portfolio-dev/pull/27 118 | - [Hugo](https://github.com/Kronicom) https://github.com/smakosh/gatsby-portfolio-dev/pull/34 https://github.com/smakosh/gatsby-portfolio-dev/pull/35 119 | - [manula thejan](https://github.com/manula2004) https://github.com/smakosh/gatsby-portfolio-dev/pull/38 120 | - [Benjamin Lo](https://github.com/benji011) https://github.com/smakosh/gatsby-portfolio-dev/pull/40 121 | - [Yassine Rais](https://github.com/yassinrais) https://github.com/smakosh/gatsby-portfolio-dev/pull/41 122 | - [Juan Manuel Combetto](https://github.com/omniwired) https://github.com/smakosh/gatsby-portfolio-dev/pull/54 123 | - [Smakosh](https://smakosh.com) 124 | 125 | ## Support 126 | 127 | If you love this Gatsby template and want to support me, you can do so through my GitHub profile. 128 | -------------------------------------------------------------------------------- /api/contact.js: -------------------------------------------------------------------------------- 1 | import axios from "axios"; 2 | import * as Yup from "yup"; 3 | 4 | const schema = Yup.object().shape({ 5 | name: Yup.string().required(), 6 | email: Yup.string().email(), 7 | message: Yup.string().required(), 8 | }); 9 | 10 | export default async (req, res) => { 11 | try { 12 | const data = await schema.validate(req.body); 13 | 14 | await axios({ 15 | method: "POST", 16 | url: `${process.env.PORTFOLIO_FORMIUM_ENDPOINT}`, 17 | headers: { 18 | "Content-Type": "application/json", 19 | }, 20 | data, 21 | }); 22 | 23 | res.status(200).json({ message: "Submission has been sent successfully" }); 24 | } catch (err) { 25 | console.log(err); 26 | res.status(400).json({ message: "Submission has failed" }); 27 | } 28 | }; 29 | -------------------------------------------------------------------------------- /gatsby-browser.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ThemeProvider from 'providers/ThemeProvider'; 3 | 4 | export const onServiceWorkerUpdateReady = () => window.location.reload(true); 5 | 6 | export const wrapRootElement = ({ element }) => {element}; 7 | -------------------------------------------------------------------------------- /gatsby-config.js: -------------------------------------------------------------------------------- 1 | const config = require("./src/data/config"); 2 | 3 | require("dotenv").config({ 4 | path: `.env`, 5 | }); 6 | 7 | module.exports = { 8 | siteMetadata: { 9 | title: config.defaultTitle, 10 | description: config.defaultDescription, 11 | author: config.author, 12 | }, 13 | plugins: [ 14 | "gatsby-plugin-react-helmet", 15 | "gatsby-plugin-styled-components", 16 | { 17 | resolve: `gatsby-plugin-canonical-urls`, 18 | options: { 19 | siteUrl: config.url, 20 | }, 21 | }, 22 | { 23 | resolve: "gatsby-source-graphql", 24 | options: { 25 | typeName: "GitHub", 26 | fieldName: "github", 27 | url: "https://api.github.com/graphql", 28 | headers: { 29 | Authorization: `bearer ${process.env.PORTFOLIO_GITHUB_TOKEN}`, 30 | }, 31 | fetchOptions: {}, 32 | }, 33 | }, 34 | { 35 | resolve: "gatsby-plugin-nprogress", 36 | options: { 37 | color: config.themeColor, 38 | showSpinner: false, 39 | }, 40 | }, 41 | { 42 | resolve: "gatsby-plugin-google-analytics", 43 | options: { 44 | trackingId: config.googleAnalyticsID, 45 | head: true, 46 | }, 47 | }, 48 | { 49 | resolve: "gatsby-plugin-manifest", 50 | options: { 51 | name: config.defaultTitle, 52 | short_name: "starter", 53 | start_url: "/", 54 | background_color: config.backgroundColor, 55 | theme_color: config.themeColor, 56 | display: "minimal-ui", 57 | icon: "./static/favicon/favicon-512.png", 58 | }, 59 | }, 60 | "gatsby-plugin-offline", 61 | ], 62 | }; 63 | -------------------------------------------------------------------------------- /gatsby-node.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | exports.onCreateWebpackConfig = ({ actions }) => { 4 | actions.setWebpackConfig({ 5 | resolve: { 6 | modules: [path.resolve(__dirname, 'src'), 'node_modules'], 7 | }, 8 | }); 9 | }; 10 | -------------------------------------------------------------------------------- /gatsby-ssr.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ThemeProvider from 'providers/ThemeProvider'; 3 | 4 | export const wrapRootElement = ({ element }) => {element}; 5 | -------------------------------------------------------------------------------- /jsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES6", 4 | "module": "commonjs", 5 | "baseUrl": "src", 6 | "experimentalDecorators": true, 7 | "allowSyntheticDefaultImports": true, 8 | "jsx": "react" 9 | }, 10 | "include": ["src/**/*"] 11 | } 12 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "portfolio", 3 | "private": true, 4 | "description": "A simple portfolio for developers", 5 | "version": "0.1.2", 6 | "author": "Shine ", 7 | "dependencies": { 8 | "axios": "^0.21.1", 9 | "dotenv": "^10.0.0", 10 | "formik": "^2.1.4", 11 | "gatsby": "^3.6.1", 12 | "gatsby-plugin-canonical-urls": "^3.6.0", 13 | "gatsby-plugin-google-analytics": "^3.6.0", 14 | "gatsby-plugin-manifest": "^3.6.0", 15 | "gatsby-plugin-nprogress": "^3.6.0", 16 | "gatsby-plugin-offline": "^4.6.0", 17 | "gatsby-plugin-react-helmet": "^4.6.0", 18 | "gatsby-plugin-sitemap": "^4.2.0", 19 | "gatsby-plugin-styled-components": "^4.6.0", 20 | "gatsby-source-graphql": "^3.6.0", 21 | "prop-types": "^15.6.2", 22 | "react": "^17.0.2", 23 | "react-anchor-link-smooth-scroll": "^1.0.12", 24 | "react-dom": "^17.0.2", 25 | "react-google-recaptcha": "^2.1.0", 26 | "react-helmet": "^6.1.0", 27 | "styled-components": "^5.3.0", 28 | "yup": "^0.32.9" 29 | }, 30 | "keywords": [ 31 | "gatsby", 32 | "portfolio", 33 | "developer" 34 | ], 35 | "license": "MIT", 36 | "scripts": { 37 | "build": "gatsby build", 38 | "start": "npx vercel dev && gatsby develop -o", 39 | "reset": "rm -rf .cache/ public/", 40 | "test": "echo \"Write tests! -> https://gatsby.app/unit-testing\"" 41 | }, 42 | "devDependencies": { 43 | "babel-eslint": "^10.1.0", 44 | "babel-plugin-styled-components": "^1.10.6", 45 | "eslint": "^7.27.0" 46 | }, 47 | "repository": { 48 | "type": "git", 49 | "url": "https://github.com/shinevue/next-portfolio" 50 | }, 51 | "bugs": { 52 | "url": "https://github.com/shinevue/next-portfolio/issues" 53 | }, 54 | "resolutions": { 55 | "graphql": "^15.4.0", 56 | "graphql-compose": "^7.25.0", 57 | "webpack": "^5.24.2", 58 | "formik/deepmerge": "4.2.2" 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /powered-by-vercel.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /src/assets/icons/moon.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/icons/sun.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/assets/illustrations/contact-overlay.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /src/assets/illustrations/details.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /src/assets/illustrations/dev.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/illustrations/footer.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /src/assets/illustrations/overlay.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/assets/illustrations/skills.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 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 | -------------------------------------------------------------------------------- /src/assets/thumbnail/thumbnail.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shinevue/next-portfolio/005eb14a489ceb5260499e6927999dd7ee88bdda/src/assets/thumbnail/thumbnail.png -------------------------------------------------------------------------------- /src/components/common/Button/index.js: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components'; 2 | 3 | export const Button = styled.button` 4 | cursor: pointer; 5 | border-radius: 3px; 6 | padding: 0.7rem 2.5rem; 7 | border: none; 8 | -webkit-appearance: none; 9 | -webkit-touch-callout: none; 10 | -webkit-user-select: none; 11 | -khtml-user-select: none; 12 | -moz-user-select: none; 13 | -ms-user-select: none; 14 | user-select: none; 15 | color: #fff; 16 | background: #0074d9; 17 | 18 | &:focus { 19 | outline: none; 20 | } 21 | 22 | &:disabled { 23 | background: gray; 24 | } 25 | 26 | ${({ secondary }) => 27 | secondary && 28 | ` 29 | background: #001F3F; 30 | `} 31 | `; 32 | -------------------------------------------------------------------------------- /src/components/common/Card/index.js: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components'; 2 | 3 | export const Card = styled.div` 4 | padding: 1rem; 5 | background: ${({ theme }) => (theme === 'light' ? '#fff' : '#181717')}; 6 | height: 100%; 7 | `; 8 | 9 | export const TitleWrap = styled.div` 10 | display: flex; 11 | flex-direction: row; 12 | justify-content: space-between; 13 | `; 14 | -------------------------------------------------------------------------------- /src/components/common/Container/index.js: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components'; 2 | 3 | export const Container = styled.div` 4 | max-width: 1280px; 5 | margin: 0 auto; 6 | width: 90%; 7 | 8 | @media (min-width: 601px) { 9 | width: 90%; 10 | } 11 | 12 | @media (min-width: 993px) { 13 | width: 80%; 14 | } 15 | `; 16 | -------------------------------------------------------------------------------- /src/components/common/Icons/Fork.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | const Fork = ({ width = 16, height = 16, color = '#000' }) => ( 4 | 5 | 10 | 15 | 16 | 17 | ); 18 | 19 | export default Fork; 20 | -------------------------------------------------------------------------------- /src/components/common/Icons/Star.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | const Star = ({ width = 16, height = 16, color = '#000' }) => ( 4 | 5 | 10 | 11 | ); 12 | 13 | export default Star; 14 | -------------------------------------------------------------------------------- /src/components/common/Input/index.js: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components'; 2 | 3 | export const Input = styled.input` 4 | width: 100%; 5 | box-sizing: border-box; 6 | border: 2px solid #6c63ff; 7 | padding: 0.8rem 1rem; 8 | border-radius: 7px; 9 | margin-bottom: 0.5rem; 10 | transition: 0.3s; 11 | 12 | ${({ error }) => 13 | error && 14 | ` 15 | border-color: #ff4136; 16 | `} 17 | 18 | &::placeholder { 19 | color: #a7a7a7; 20 | } 21 | `; 22 | -------------------------------------------------------------------------------- /src/components/common/Layout/fonts.css: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: 'Roboto'; 3 | font-style: normal; 4 | font-display: fallback; 5 | font-weight: 400; 6 | src: url('./fonts/roboto-v18-latin-regular.eot'); 7 | src: local('Roboto'), local('Roboto-Regular'), 8 | url('./fonts/roboto-v18-latin-regular.eot?#iefix') 9 | format('embedded-opentype'), 10 | url('./fonts/roboto-v18-latin-regular.woff2') format('woff2'), 11 | url('./fonts/roboto-v18-latin-regular.woff') format('woff'), 12 | url('./fonts/roboto-v18-latin-regular.ttf') format('truetype'), 13 | url('./fonts/roboto-v18-latin-regular.svg#Roboto') format('svg'); 14 | } 15 | 16 | @font-face { 17 | font-family: 'Roboto'; 18 | font-style: normal; 19 | font-display: fallback; 20 | font-weight: 700; 21 | src: url('./fonts/roboto-v18-latin-700.eot'); 22 | src: local('Roboto Bold'), local('Roboto-Bold'), 23 | url('./fonts/roboto-v18-latin-700.eot?#iefix') format('embedded-opentype'), 24 | url('./fonts/roboto-v18-latin-700.woff2') format('woff2'), 25 | url('./fonts/roboto-v18-latin-700.woff') format('woff'), 26 | url('./fonts/roboto-v18-latin-700.ttf') format('truetype'), 27 | url('./fonts/roboto-v18-latin-700.svg#Roboto') format('svg'); 28 | } 29 | -------------------------------------------------------------------------------- /src/components/common/Layout/fonts/roboto-v18-latin-700.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shinevue/next-portfolio/005eb14a489ceb5260499e6927999dd7ee88bdda/src/components/common/Layout/fonts/roboto-v18-latin-700.eot -------------------------------------------------------------------------------- /src/components/common/Layout/fonts/roboto-v18-latin-700.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shinevue/next-portfolio/005eb14a489ceb5260499e6927999dd7ee88bdda/src/components/common/Layout/fonts/roboto-v18-latin-700.ttf -------------------------------------------------------------------------------- /src/components/common/Layout/fonts/roboto-v18-latin-700.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shinevue/next-portfolio/005eb14a489ceb5260499e6927999dd7ee88bdda/src/components/common/Layout/fonts/roboto-v18-latin-700.woff -------------------------------------------------------------------------------- /src/components/common/Layout/fonts/roboto-v18-latin-700.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shinevue/next-portfolio/005eb14a489ceb5260499e6927999dd7ee88bdda/src/components/common/Layout/fonts/roboto-v18-latin-700.woff2 -------------------------------------------------------------------------------- /src/components/common/Layout/fonts/roboto-v18-latin-regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shinevue/next-portfolio/005eb14a489ceb5260499e6927999dd7ee88bdda/src/components/common/Layout/fonts/roboto-v18-latin-regular.eot -------------------------------------------------------------------------------- /src/components/common/Layout/fonts/roboto-v18-latin-regular.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 12 | 13 | 14 | 15 | 16 | 18 | 21 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 35 | 36 | 37 | 39 | 40 | 42 | 44 | 45 | 47 | 49 | 50 | 51 | 52 | 53 | 54 | 56 | 59 | 60 | 62 | 64 | 65 | 66 | 67 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 78 | 79 | 81 | 82 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 99 | 100 | 102 | 103 | 105 | 106 | 108 | 109 | 110 | 111 | 112 | 113 | 115 | 116 | 118 | 120 | 122 | 123 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 140 | 142 | 144 | 145 | 146 | 149 | 150 | 153 | 155 | 156 | 157 | 158 | 161 | 162 | 164 | 165 | 166 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 176 | 177 | 178 | 180 | 183 | 185 | 186 | 187 | 188 | 190 | 192 | 194 | 195 | 197 | 198 | 199 | 200 | 202 | 203 | 204 | 205 | 206 | 207 | 209 | 211 | 213 | 215 | 218 | 221 | 222 | 224 | 225 | 226 | 228 | 230 | 231 | 232 | 234 | 236 | 238 | 240 | 243 | 246 | 249 | 252 | 254 | 256 | 258 | 260 | 262 | 263 | 264 | 265 | 266 | 268 | 270 | 272 | 274 | 276 | 279 | 281 | 283 | 285 | 286 | 287 | 288 | 290 | 291 | 293 | 295 | 296 | 297 | 298 | 299 | 300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308 | 309 | -------------------------------------------------------------------------------- /src/components/common/Layout/fonts/roboto-v18-latin-regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shinevue/next-portfolio/005eb14a489ceb5260499e6927999dd7ee88bdda/src/components/common/Layout/fonts/roboto-v18-latin-regular.ttf -------------------------------------------------------------------------------- /src/components/common/Layout/fonts/roboto-v18-latin-regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shinevue/next-portfolio/005eb14a489ceb5260499e6927999dd7ee88bdda/src/components/common/Layout/fonts/roboto-v18-latin-regular.woff -------------------------------------------------------------------------------- /src/components/common/Layout/fonts/roboto-v18-latin-regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shinevue/next-portfolio/005eb14a489ceb5260499e6927999dd7ee88bdda/src/components/common/Layout/fonts/roboto-v18-latin-regular.woff2 -------------------------------------------------------------------------------- /src/components/common/Layout/index.jsx: -------------------------------------------------------------------------------- 1 | import React, { useContext } from 'react'; 2 | import { ThemeContext } from 'providers/ThemeProvider'; 3 | import { Footer } from 'components/theme'; 4 | import { Global } from './styles'; 5 | import './fonts.css'; 6 | 7 | export const Layout = ({ children }) => { 8 | const { theme } = useContext(ThemeContext); 9 | 10 | return ( 11 | <> 12 | 13 | {children} 14 | 15 | > 16 | ); 17 | }; 18 | -------------------------------------------------------------------------------- /src/components/common/Layout/styles.js: -------------------------------------------------------------------------------- 1 | import { createGlobalStyle } from 'styled-components'; 2 | 3 | export const Global = createGlobalStyle` 4 | html { 5 | font-family: 'Roboto', Helvetica, sans-serif; 6 | -ms-text-size-adjust: 100%; 7 | -webkit-text-size-adjust: 100%; 8 | 9 | article, 10 | aside, 11 | details, 12 | figcaption, 13 | figure, 14 | footer, 15 | header, 16 | main, 17 | menu, 18 | nav, 19 | section, 20 | summary { 21 | display: block; 22 | } 23 | audio, 24 | canvas, 25 | progress, 26 | video { 27 | display: inline-block; 28 | } 29 | audio:not([controls]) { 30 | display: none; 31 | height: 0; 32 | } 33 | progress { 34 | vertical-align: baseline; 35 | } 36 | [hidden], 37 | template { 38 | display: none; 39 | } 40 | a { 41 | background-color: transparent; 42 | -webkit-text-decoration-skip: objects; 43 | } 44 | a:active, 45 | a:hover { 46 | outline-width: 0; 47 | } 48 | abbr[title] { 49 | border-bottom: none; 50 | text-decoration: underline; 51 | text-decoration: underline dotted; 52 | } 53 | b, 54 | strong { 55 | font-weight: inherit; 56 | font-weight: bolder; 57 | } 58 | dfn { 59 | font-style: italic; 60 | } 61 | h1 { 62 | font-size: 2em; 63 | margin: 0.67em 0; 64 | } 65 | mark { 66 | background-color: #ff0; 67 | color: #000; 68 | } 69 | small { 70 | font-size: 80%; 71 | } 72 | sub, 73 | sup { 74 | font-size: 75%; 75 | line-height: 0; 76 | position: relative; 77 | vertical-align: baseline; 78 | } 79 | sub { 80 | bottom: -0.25em; 81 | } 82 | sup { 83 | top: -0.5em; 84 | } 85 | img { 86 | border-style: none; 87 | } 88 | svg:not(:root) { 89 | overflow: hidden; 90 | } 91 | code, 92 | kbd, 93 | pre, 94 | samp { 95 | font-family: monospace, monospace; 96 | font-size: 1em; 97 | } 98 | figure { 99 | margin: 1em 40px; 100 | } 101 | hr { 102 | box-sizing: content-box; 103 | height: 0; 104 | overflow: visible; 105 | } 106 | button, 107 | input, 108 | optgroup, 109 | select, 110 | textarea { 111 | font: inherit; 112 | margin: 0; 113 | } 114 | optgroup { 115 | font-weight: 700; 116 | } 117 | button, 118 | input { 119 | overflow: visible; 120 | } 121 | button, 122 | select { 123 | text-transform: none; 124 | } 125 | [type='reset'], 126 | [type='submit'], 127 | button, 128 | html [type='button'] { 129 | -webkit-appearance: button; 130 | } 131 | [type='button']::-moz-focus-inner, 132 | [type='reset']::-moz-focus-inner, 133 | [type='submit']::-moz-focus-inner, 134 | button::-moz-focus-inner { 135 | border-style: none; 136 | padding: 0; 137 | } 138 | [type='button']:-moz-focusring, 139 | [type='reset']:-moz-focusring, 140 | [type='submit']:-moz-focusring, 141 | button:-moz-focusring { 142 | outline: 1px dotted ButtonText; 143 | } 144 | fieldset { 145 | border: 1px solid silver; 146 | margin: 0 2px; 147 | padding: 0.35em 0.625em 0.75em; 148 | } 149 | legend { 150 | box-sizing: border-box; 151 | color: inherit; 152 | display: table; 153 | max-width: 100%; 154 | padding: 0; 155 | white-space: normal; 156 | } 157 | textarea { 158 | overflow: auto; 159 | } 160 | [type='checkbox'], 161 | [type='radio'] { 162 | box-sizing: border-box; 163 | padding: 0; 164 | } 165 | [type='number']::-webkit-inner-spin-button, 166 | [type='number']::-webkit-outer-spin-button { 167 | height: auto; 168 | } 169 | [type='search'] { 170 | -webkit-appearance: textfield; 171 | outline-offset: -2px; 172 | } 173 | [type='search']::-webkit-search-cancel-button, 174 | [type='search']::-webkit-search-decoration { 175 | -webkit-appearance: none; 176 | } 177 | ::-webkit-input-placeholder { 178 | color: inherit; 179 | opacity: 0.54; 180 | } 181 | ::-webkit-file-upload-button { 182 | -webkit-appearance: button; 183 | font: inherit; 184 | } 185 | html { 186 | font: 112.5%/1.45em georgia, serif; 187 | box-sizing: border-box; 188 | overflow-y: scroll; 189 | } 190 | * { 191 | box-sizing: inherit; 192 | } 193 | *:before { 194 | box-sizing: inherit; 195 | } 196 | *:after { 197 | box-sizing: inherit; 198 | } 199 | img { 200 | max-width: 100%; 201 | margin-left: 0; 202 | margin-right: 0; 203 | margin-top: 0; 204 | padding-bottom: 0; 205 | padding-left: 0; 206 | padding-right: 0; 207 | padding-top: 0; 208 | margin-bottom: 1.45rem; 209 | } 210 | h1 { 211 | margin-left: 0; 212 | margin-right: 0; 213 | margin-top: 0; 214 | padding-bottom: 0; 215 | padding-left: 0; 216 | padding-right: 0; 217 | padding-top: 0; 218 | margin-bottom: 1.45rem; 219 | color: inherit; 220 | font-weight: bold; 221 | text-rendering: optimizeLegibility; 222 | font-size: 2.25rem; 223 | line-height: 1.1; 224 | } 225 | h2 { 226 | margin-left: 0; 227 | margin-right: 0; 228 | margin-top: 0; 229 | padding-bottom: 0; 230 | padding-left: 0; 231 | padding-right: 0; 232 | padding-top: 0; 233 | margin-bottom: 1.45rem; 234 | color: inherit; 235 | font-weight: bold; 236 | text-rendering: optimizeLegibility; 237 | font-size: 1.62671rem; 238 | line-height: 1.1; 239 | } 240 | h3 { 241 | margin-left: 0; 242 | margin-right: 0; 243 | margin-top: 0; 244 | padding-bottom: 0; 245 | padding-left: 0; 246 | padding-right: 0; 247 | padding-top: 0; 248 | margin-bottom: 1.45rem; 249 | color: inherit; 250 | font-weight: bold; 251 | text-rendering: optimizeLegibility; 252 | font-size: 1.38316rem; 253 | line-height: 1.1; 254 | } 255 | h4 { 256 | margin-left: 0; 257 | margin-right: 0; 258 | margin-top: 0; 259 | padding-bottom: 0; 260 | padding-left: 0; 261 | padding-right: 0; 262 | padding-top: 0; 263 | margin-bottom: 1.45rem; 264 | color: inherit; 265 | font-weight: bold; 266 | text-rendering: optimizeLegibility; 267 | font-size: 1rem; 268 | line-height: 1.1; 269 | } 270 | h5 { 271 | margin-left: 0; 272 | margin-right: 0; 273 | margin-top: 0; 274 | padding-bottom: 0; 275 | padding-left: 0; 276 | padding-right: 0; 277 | padding-top: 0; 278 | margin-bottom: 1.45rem; 279 | color: inherit; 280 | font-weight: bold; 281 | text-rendering: optimizeLegibility; 282 | font-size: 0.85028rem; 283 | line-height: 1.1; 284 | } 285 | h6 { 286 | margin-left: 0; 287 | margin-right: 0; 288 | margin-top: 0; 289 | padding-bottom: 0; 290 | padding-left: 0; 291 | padding-right: 0; 292 | padding-top: 0; 293 | margin-bottom: 1.45rem; 294 | color: inherit; 295 | font-weight: bold; 296 | text-rendering: optimizeLegibility; 297 | font-size: 0.78405rem; 298 | line-height: 1.1; 299 | } 300 | hgroup { 301 | margin-left: 0; 302 | margin-right: 0; 303 | margin-top: 0; 304 | padding-bottom: 0; 305 | padding-left: 0; 306 | padding-right: 0; 307 | padding-top: 0; 308 | margin-bottom: 1.45rem; 309 | } 310 | ul { 311 | margin-left: 1.45rem; 312 | margin-right: 0; 313 | margin-top: 0; 314 | padding-bottom: 0; 315 | padding-left: 0; 316 | padding-right: 0; 317 | padding-top: 0; 318 | margin-bottom: 1.45rem; 319 | list-style-position: outside; 320 | list-style-image: none; 321 | } 322 | ol { 323 | margin-left: 1.45rem; 324 | margin-right: 0; 325 | margin-top: 0; 326 | padding-bottom: 0; 327 | padding-left: 0; 328 | padding-right: 0; 329 | padding-top: 0; 330 | margin-bottom: 1.45rem; 331 | list-style-position: outside; 332 | list-style-image: none; 333 | } 334 | dl { 335 | margin-left: 0; 336 | margin-right: 0; 337 | margin-top: 0; 338 | padding-bottom: 0; 339 | padding-left: 0; 340 | padding-right: 0; 341 | padding-top: 0; 342 | margin-bottom: 1.45rem; 343 | } 344 | dd { 345 | margin-left: 0; 346 | margin-right: 0; 347 | margin-top: 0; 348 | padding-bottom: 0; 349 | padding-left: 0; 350 | padding-right: 0; 351 | padding-top: 0; 352 | margin-bottom: 1.45rem; 353 | } 354 | p { 355 | margin-left: 0; 356 | margin-right: 0; 357 | margin-top: 0; 358 | padding-bottom: 0; 359 | padding-left: 0; 360 | padding-right: 0; 361 | padding-top: 0; 362 | margin-bottom: 1.45rem; 363 | } 364 | figure { 365 | margin-left: 0; 366 | margin-right: 0; 367 | margin-top: 0; 368 | padding-bottom: 0; 369 | padding-left: 0; 370 | padding-right: 0; 371 | padding-top: 0; 372 | margin-bottom: 1.45rem; 373 | } 374 | pre { 375 | margin-left: 0; 376 | margin-right: 0; 377 | margin-top: 0; 378 | padding-bottom: 0; 379 | padding-left: 0; 380 | padding-right: 0; 381 | padding-top: 0; 382 | margin-bottom: 1.45rem; 383 | font-size: 0.85rem; 384 | line-height: 1.42; 385 | background: hsla(0, 0%, 0%, 0.04); 386 | border-radius: 3px; 387 | overflow: auto; 388 | word-wrap: normal; 389 | padding: 1.45rem; 390 | } 391 | table { 392 | margin-left: 0; 393 | margin-right: 0; 394 | margin-top: 0; 395 | padding-bottom: 0; 396 | padding-left: 0; 397 | padding-right: 0; 398 | padding-top: 0; 399 | margin-bottom: 1.45rem; 400 | font-size: 1rem; 401 | line-height: 1.45rem; 402 | border-collapse: collapse; 403 | width: 100%; 404 | } 405 | fieldset { 406 | margin-left: 0; 407 | margin-right: 0; 408 | margin-top: 0; 409 | padding-bottom: 0; 410 | padding-left: 0; 411 | padding-right: 0; 412 | padding-top: 0; 413 | margin-bottom: 1.45rem; 414 | } 415 | blockquote { 416 | margin-left: 1.45rem; 417 | margin-right: 1.45rem; 418 | margin-top: 0; 419 | padding-bottom: 0; 420 | padding-left: 0; 421 | padding-right: 0; 422 | padding-top: 0; 423 | margin-bottom: 1.45rem; 424 | } 425 | form { 426 | margin-left: 0; 427 | margin-right: 0; 428 | margin-top: 0; 429 | padding-bottom: 0; 430 | padding-left: 0; 431 | padding-right: 0; 432 | padding-top: 0; 433 | margin-bottom: 1.45rem; 434 | } 435 | noscript { 436 | margin-left: 0; 437 | margin-right: 0; 438 | margin-top: 0; 439 | padding-bottom: 0; 440 | padding-left: 0; 441 | padding-right: 0; 442 | padding-top: 0; 443 | margin-bottom: 1.45rem; 444 | } 445 | iframe { 446 | margin-left: 0; 447 | margin-right: 0; 448 | margin-top: 0; 449 | padding-bottom: 0; 450 | padding-left: 0; 451 | padding-right: 0; 452 | padding-top: 0; 453 | margin-bottom: 1.45rem; 454 | } 455 | hr { 456 | margin-left: 0; 457 | margin-right: 0; 458 | margin-top: 0; 459 | padding-bottom: 0; 460 | padding-left: 0; 461 | padding-right: 0; 462 | padding-top: 0; 463 | margin-bottom: calc(1.45rem - 1px); 464 | background: hsla(0, 0%, 0%, 0.2); 465 | border: none; 466 | height: 1px; 467 | } 468 | address { 469 | margin-left: 0; 470 | margin-right: 0; 471 | margin-top: 0; 472 | padding-bottom: 0; 473 | padding-left: 0; 474 | padding-right: 0; 475 | padding-top: 0; 476 | margin-bottom: 1.45rem; 477 | } 478 | b { 479 | font-weight: bold; 480 | } 481 | strong { 482 | font-weight: bold; 483 | } 484 | dt { 485 | font-weight: bold; 486 | } 487 | th { 488 | font-weight: bold; 489 | } 490 | li { 491 | margin-bottom: calc(1.45rem / 2); 492 | } 493 | ol li { 494 | padding-left: 0; 495 | } 496 | ul li { 497 | padding-left: 0; 498 | } 499 | li > ol { 500 | margin-left: 1.45rem; 501 | margin-bottom: calc(1.45rem / 2); 502 | margin-top: calc(1.45rem / 2); 503 | } 504 | li > ul { 505 | margin-left: 1.45rem; 506 | margin-bottom: calc(1.45rem / 2); 507 | margin-top: calc(1.45rem / 2); 508 | } 509 | blockquote *:last-child { 510 | margin-bottom: 0; 511 | } 512 | li *:last-child { 513 | margin-bottom: 0; 514 | } 515 | p *:last-child { 516 | margin-bottom: 0; 517 | } 518 | li > p { 519 | margin-bottom: calc(1.45rem / 2); 520 | } 521 | code { 522 | font-size: 0.85rem; 523 | line-height: 1.45rem; 524 | } 525 | kbd { 526 | font-size: 0.85rem; 527 | line-height: 1.45rem; 528 | } 529 | samp { 530 | font-size: 0.85rem; 531 | line-height: 1.45rem; 532 | } 533 | abbr { 534 | border-bottom: 1px dotted hsla(0, 0%, 0%, 0.5); 535 | cursor: help; 536 | } 537 | acronym { 538 | border-bottom: 1px dotted hsla(0, 0%, 0%, 0.5); 539 | cursor: help; 540 | } 541 | abbr[title] { 542 | border-bottom: 1px dotted hsla(0, 0%, 0%, 0.5); 543 | cursor: help; 544 | text-decoration: none; 545 | } 546 | thead { 547 | text-align: left; 548 | } 549 | td, 550 | th { 551 | text-align: left; 552 | border-bottom: 1px solid hsla(0, 0%, 0%, 0.12); 553 | font-feature-settings: 'tnum'; 554 | -moz-font-feature-settings: 'tnum'; 555 | -ms-font-feature-settings: 'tnum'; 556 | -webkit-font-feature-settings: 'tnum'; 557 | padding-left: 0.96667rem; 558 | padding-right: 0.96667rem; 559 | padding-top: 0.725rem; 560 | padding-bottom: calc(0.725rem - 1px); 561 | } 562 | th:first-child, 563 | td:first-child { 564 | padding-left: 0; 565 | } 566 | th:last-child, 567 | td:last-child { 568 | padding-right: 0; 569 | } 570 | tt, 571 | code { 572 | background-color: hsla(0, 0%, 0%, 0.04); 573 | border-radius: 3px; 574 | font-family: 'SFMono-Regular', Consolas, 'Roboto Mono', 'Droid Sans Mono', 575 | 'Liberation Mono', Menlo, Courier, monospace; 576 | padding: 0; 577 | padding-top: 0.2em; 578 | padding-bottom: 0.2em; 579 | } 580 | pre code { 581 | background: none; 582 | line-height: 1.42; 583 | } 584 | code:before, 585 | code:after, 586 | tt:before, 587 | tt:after { 588 | letter-spacing: -0.2em; 589 | content: ' '; 590 | } 591 | pre code:before, 592 | pre code:after, 593 | pre tt:before, 594 | pre tt:after { 595 | content: ''; 596 | } 597 | @media only screen and (max-width: 480px) { 598 | html { 599 | font-size: 100%; 600 | } 601 | } 602 | body { 603 | margin: 0; 604 | padding: 0; 605 | font-family: 'Roboto', Helvetica, sans-serif; 606 | color: ${({ theme }) => (theme === 'light' ? 'hsla(0, 0%, 0%, 0.8)' : '#fff')}; 607 | background-color: ${({ theme }) => (theme === 'light' ? '#fff' : '#212121')}; 608 | transition: .3s all; 609 | font-weight: normal; 610 | word-wrap: break-word; 611 | font-kerning: normal; 612 | -moz-font-feature-settings: 'kern', 'liga', 'clig', 'calt'; 613 | -ms-font-feature-settings: 'kern', 'liga', 'clig', 'calt'; 614 | -webkit-font-feature-settings: 'kern', 'liga', 'clig', 'calt'; 615 | font-feature-settings: 'kern', 'liga', 'clig', 'calt'; 616 | } 617 | 618 | a { 619 | text-decoration: none; 620 | } 621 | 622 | input, select, textarea, button { 623 | &:focus { 624 | outline: none; 625 | } 626 | } 627 | } 628 | `; 629 | -------------------------------------------------------------------------------- /src/components/common/Seo/index.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import Helmet from "react-helmet"; 3 | import Thumbnail from "assets/thumbnail/thumbnail.png"; 4 | import { 5 | url, 6 | defaultDescription, 7 | social, 8 | defaultTitle, 9 | socialLinks, 10 | address, 11 | contact, 12 | legalName, 13 | foundingDate, 14 | logo, 15 | } from "data/config"; 16 | 17 | export const Seo = ({ 18 | title = defaultTitle, 19 | description = defaultDescription, 20 | location = "", 21 | }) => { 22 | const structuredDataOrganization = `{ 23 | "@context": "http://schema.org", 24 | "@type": "Organization", 25 | "legalName": "${legalName}", 26 | "url": "${url}", 27 | "logo": "${logo}", 28 | "foundingDate": "${foundingDate}", 29 | "founders": [{ 30 | "@type": "Person", 31 | "name": "${legalName}" 32 | }], 33 | "contactPoint": [{ 34 | "@type": "ContactPoint", 35 | "email": "${contact.email}", 36 | "telephone": "${contact.phone}", 37 | "contactType": "customer service" 38 | }], 39 | "address": { 40 | "@type": "PostalAddress", 41 | "addressLocality": "${address.city}", 42 | "addressRegion": "${address.region}", 43 | "addressCountry": "${address.country}", 44 | "postalCode": "${address.zipCode}" 45 | }, 46 | "sameAs": [ 47 | "${socialLinks.twitter}", 48 | "${socialLinks.google}", 49 | "${socialLinks.youtube}", 50 | "${socialLinks.linkedin}", 51 | "${socialLinks.instagram}", 52 | "${socialLinks.github}" 53 | ] 54 | }`; 55 | 56 | return ( 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | {title} 77 | 78 | 79 | ); 80 | }; 81 | -------------------------------------------------------------------------------- /src/components/common/index.js: -------------------------------------------------------------------------------- 1 | export * from "./Layout"; 2 | export * from "./Container"; 3 | export * from "./Button"; 4 | export * from "./Card"; 5 | export * from "./Input"; 6 | export * from "./Seo"; 7 | -------------------------------------------------------------------------------- /src/components/landing/Contact/ContactForm/index.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import axios from "axios"; 3 | import { Formik, Form, FastField, ErrorMessage } from "formik"; 4 | import Recaptcha from "react-google-recaptcha"; 5 | import * as Yup from "yup"; 6 | import { url } from "data/config"; 7 | import { Button, Input } from "components/common"; 8 | import { Error, Center, InputField } from "./styles"; 9 | 10 | const ContactForm = () => ( 11 | { 34 | try { 35 | await axios({ 36 | method: "POST", 37 | url: 38 | process.env.NODE_ENV !== "development" 39 | ? `${url}/api/contact` 40 | : "http://localhost:3000/api/contact", 41 | headers: { 42 | "Content-Type": "application/json", 43 | }, 44 | data: JSON.stringify({ 45 | name, 46 | email, 47 | message, 48 | }), 49 | }); 50 | setSubmitting(false); 51 | setFieldValue("success", true); 52 | setTimeout(() => resetForm(), 6000); 53 | } catch (err) { 54 | setSubmitting(false); 55 | setFieldValue("success", false); 56 | alert("Something went wrong, please try again!"); 57 | } 58 | }} 59 | > 60 | {({ values, touched, errors, setFieldValue, isSubmitting }) => ( 61 | 62 | 63 | 72 | 73 | 74 | 75 | 85 | 86 | 87 | 88 | 99 | 100 | 101 | {values.name && 102 | values.email && 103 | values.message && 104 | process.env.NODE_ENV !== "development" && ( 105 | 106 | setFieldValue("recaptcha", value)} 111 | /> 112 | 113 | 114 | )} 115 | {values.success && ( 116 | 117 | 118 | 119 | Your message has been successfully sent, I will get back to you 120 | ASAP! 121 | 122 | 123 | 124 | )} 125 | 126 | 127 | Submit 128 | 129 | 130 | 131 | )} 132 | 133 | ); 134 | 135 | export default ContactForm; 136 | -------------------------------------------------------------------------------- /src/components/landing/Contact/ContactForm/styles.js: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components'; 2 | 3 | export const Error = styled.span` 4 | color: #ff4136; 5 | `; 6 | 7 | export const Center = styled.div` 8 | text-align: left; 9 | 10 | h4 { 11 | font-weight: normal; 12 | } 13 | `; 14 | 15 | export const InputField = styled.div` 16 | position: relative; 17 | margin-bottom: 1rem; 18 | `; 19 | -------------------------------------------------------------------------------- /src/components/landing/Contact/index.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Container } from 'components/common'; 3 | import contact from 'assets/illustrations/contact.svg'; 4 | import { Wrapper, Details, Thumbnail } from './styles'; 5 | import ContactForm from './ContactForm'; 6 | 7 | export const Contact = () => ( 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | ); 17 | -------------------------------------------------------------------------------- /src/components/landing/Contact/styles.js: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components'; 2 | 3 | export const Wrapper = styled.div` 4 | padding: 4rem 0; 5 | display: flex; 6 | align-items: flex-start; 7 | justify-content: space-between; 8 | 9 | @media (max-width: 960px) { 10 | flex-direction: column; 11 | } 12 | `; 13 | 14 | export const Details = styled.div` 15 | flex: 1; 16 | padding-right: 2rem; 17 | 18 | @media (max-width: 960px) { 19 | padding-right: unset; 20 | width: 100%; 21 | order: 1; 22 | } 23 | 24 | h1 { 25 | margin-bottom: 2rem; 26 | font-size: 26pt; 27 | color: #212121; 28 | } 29 | 30 | p { 31 | margin-bottom: 2.5rem; 32 | font-size: 20pt; 33 | font-weight: normal; 34 | line-height: 1.3; 35 | color: #707070; 36 | } 37 | `; 38 | 39 | export const Thumbnail = styled.div` 40 | flex: 1; 41 | 42 | @media (max-width: 960px) { 43 | width: 100%; 44 | margin-bottom: 2rem; 45 | } 46 | 47 | img { 48 | width: 100%; 49 | } 50 | `; 51 | -------------------------------------------------------------------------------- /src/components/landing/Intro/index.jsx: -------------------------------------------------------------------------------- 1 | import React, { useContext } from 'react'; 2 | import AnchorLink from 'react-anchor-link-smooth-scroll'; 3 | import { ThemeContext } from 'providers/ThemeProvider'; 4 | import { Header } from 'components/theme'; 5 | import { Container, Button } from 'components/common'; 6 | import dev from 'assets/illustrations/dev.svg'; 7 | import { Wrapper, IntroWrapper, Details, Thumbnail } from './styles'; 8 | 9 | export const Intro = () => { 10 | const { theme } = useContext(ThemeContext); 11 | 12 | return ( 13 | 14 | 15 | 16 | 17 | Hi There! 18 | I’m John and I’m a JAMStack engineer! 19 | 20 | Hire me 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | ); 29 | }; 30 | -------------------------------------------------------------------------------- /src/components/landing/Intro/styles.js: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components'; 2 | import overlayIllustration from 'assets/illustrations/overlay.svg'; 3 | 4 | export const Wrapper = styled.div` 5 | padding-bottom: 4rem; 6 | background-image: url(${overlayIllustration}); 7 | background-size: contain; 8 | background-position: right top; 9 | background-repeat: no-repeat; 10 | `; 11 | 12 | export const IntroWrapper = styled.div` 13 | padding: 4rem 0; 14 | display: flex; 15 | align-items: center; 16 | justify-content: space-between; 17 | 18 | @media (max-width: 960px) { 19 | flex-direction: column; 20 | } 21 | `; 22 | 23 | export const Details = styled.div` 24 | flex: 1; 25 | 26 | @media (max-width: 960px) { 27 | width: 100%; 28 | margin-bottom: 2rem; 29 | } 30 | 31 | h1 { 32 | margin-bottom: 2rem; 33 | font-size: 36pt; 34 | color: ${({ theme }) => (theme === 'light' ? '#212121' : '#fff')}; 35 | 36 | @media (max-width: 960px) { 37 | mix-blend-mode: ${({ theme }) => (theme === 'light' ? 'unset' : 'difference')}; 38 | } 39 | 40 | @media (max-width: 680px) { 41 | font-size: 30pt; 42 | } 43 | } 44 | 45 | h4 { 46 | margin-bottom: 2.5rem; 47 | font-size: 32pt; 48 | font-weight: normal; 49 | color: ${({ theme }) => (theme === 'light' ? '#707070' : '#e6e6e6')}; 50 | 51 | @media (max-width: 960px) { 52 | mix-blend-mode: ${({ theme }) => (theme === 'light' ? 'unset' : 'difference')}; 53 | } 54 | 55 | @media (max-width: 680px) { 56 | font-size: 26pt; 57 | } 58 | } 59 | `; 60 | 61 | export const Thumbnail = styled.div` 62 | flex: 1; 63 | 64 | @media (max-width: 960px) { 65 | width: 100%; 66 | } 67 | 68 | img { 69 | width: 100%; 70 | } 71 | `; 72 | -------------------------------------------------------------------------------- /src/components/landing/Projects/index.jsx: -------------------------------------------------------------------------------- 1 | import React, { useContext } from 'react'; 2 | import { useStaticQuery, graphql } from 'gatsby'; 3 | import { ThemeContext } from 'providers/ThemeProvider'; 4 | import { Container, Card, TitleWrap } from 'components/common'; 5 | import Star from 'components/common/Icons/Star'; 6 | import Fork from 'components/common/Icons/Fork'; 7 | import { Wrapper, Grid, Item, Content, Stats, Languages } from './styles'; 8 | 9 | export const Projects = () => { 10 | const { theme } = useContext(ThemeContext); 11 | const { 12 | github: { 13 | viewer: { 14 | repositories: { edges }, 15 | }, 16 | }, 17 | } = useStaticQuery( 18 | graphql` 19 | { 20 | github { 21 | viewer { 22 | repositories(first: 8, orderBy: { field: STARGAZERS, direction: DESC }) { 23 | edges { 24 | node { 25 | id 26 | name 27 | url 28 | description 29 | stargazers { 30 | totalCount 31 | } 32 | forkCount 33 | languages(first: 3) { 34 | nodes { 35 | id, 36 | name 37 | } 38 | } 39 | } 40 | } 41 | } 42 | } 43 | } 44 | } 45 | ` 46 | ); 47 | return ( 48 | 49 | Projects 50 | 51 | {edges.map(({ node }) => ( 52 | 53 | 54 | 55 | {node.name} 56 | {node.description} 57 | 58 | 59 | 60 | 61 | 62 | {node.stargazers.totalCount} 63 | 64 | 65 | 66 | {node.forkCount} 67 | 68 | 69 | 70 | 71 | { 72 | node.languages.nodes.map(({ id, name }) => ( 73 | 74 | {name} 75 | 76 | )) 77 | } 78 | 79 | 80 | 81 | 82 | 83 | ))} 84 | 85 | 86 | ); 87 | }; 88 | -------------------------------------------------------------------------------- /src/components/landing/Projects/styles.js: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components'; 2 | 3 | export const Wrapper = styled.div` 4 | padding: 2rem 0; 5 | `; 6 | 7 | export const Grid = styled.div` 8 | display: grid; 9 | align-items: center; 10 | grid-template-columns: repeat(3, 1fr); 11 | grid-template-rows: 8fr; 12 | gap: 1.2rem 1.2rem; 13 | 14 | @media (max-width: 960px) { 15 | grid-template-columns: repeat(2, 1fr); 16 | } 17 | 18 | @media (max-width: 680px) { 19 | grid-template-columns: 1fr; 20 | } 21 | `; 22 | 23 | export const Item = styled.div` 24 | width: 100%; 25 | height: 100%; 26 | overflow: hidden; 27 | box-shadow: 0 1px 6px 0 rgba(0, 0, 0, 0.11); 28 | 29 | h4 { 30 | color: ${({ theme }) => (theme === 'light' ? '#212121' : '#fff')}; 31 | } 32 | 33 | p { 34 | color: ${({ theme }) => (theme === 'light' ? '#707070' : '#c7c7c7')}; 35 | } 36 | `; 37 | 38 | export const Content = styled.div` 39 | padding: 1rem 0; 40 | min-height: 160px; 41 | `; 42 | 43 | export const Stats = styled.div` 44 | display: flex; 45 | align-items: center; 46 | 47 | div { 48 | display: flex; 49 | &:first-child { 50 | margin-right: 0.5rem; 51 | } 52 | 53 | img { 54 | margin: 0; 55 | } 56 | 57 | svg path { 58 | fill: ${({ theme }) => (theme === 'light' ? '#000' : '#fff')}; 59 | } 60 | 61 | span { 62 | color: ${({ theme }) => (theme === 'light' ? '#000' : '#fff')}; 63 | margin-left: 0.5rem; 64 | } 65 | } 66 | `; 67 | 68 | export const Languages = styled.div` 69 | opacity: 0.5; 70 | font-size: 14px; 71 | `; 72 | -------------------------------------------------------------------------------- /src/components/landing/Skills/index.jsx: -------------------------------------------------------------------------------- 1 | import React, { useContext } from 'react'; 2 | import AnchorLink from 'react-anchor-link-smooth-scroll'; 3 | import { ThemeContext } from 'providers/ThemeProvider'; 4 | import { Container, Button } from 'components/common'; 5 | import dev from 'assets/illustrations/skills.svg'; 6 | import { Wrapper, SkillsWrapper, Details, Thumbnail } from './styles'; 7 | 8 | export const Skills = () => { 9 | const { theme } = useContext(ThemeContext); 10 | 11 | return ( 12 | 13 | 14 | 15 | 16 | 17 | 18 | More about me 19 | 20 | Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the 21 | industry’s standard dummy. 22 | 23 | 24 | Hire me 25 | 26 | 27 | 28 | 29 | ); 30 | }; 31 | -------------------------------------------------------------------------------- /src/components/landing/Skills/styles.js: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components'; 2 | import detailsIllustration from 'assets/illustrations/details.svg'; 3 | 4 | export const Wrapper = styled.div` 5 | background-image: url(${detailsIllustration}); 6 | background-size: contain; 7 | background-position: left top; 8 | background-repeat: no-repeat; 9 | `; 10 | 11 | export const SkillsWrapper = styled.div` 12 | padding: 4rem 0; 13 | display: flex; 14 | align-items: center; 15 | justify-content: space-between; 16 | 17 | @media (max-width: 960px) { 18 | flex-direction: column; 19 | } 20 | `; 21 | 22 | export const Details = styled.div` 23 | flex: 1; 24 | padding-left: 2rem; 25 | 26 | @media (max-width: 960px) { 27 | padding-left: unset; 28 | width: 100%; 29 | } 30 | 31 | h1 { 32 | margin-bottom: 2rem; 33 | font-size: 26pt; 34 | color: ${({ theme }) => (theme === 'dark' ? '#fff' : '#212121')}; 35 | 36 | @media (max-width: 960px) { 37 | mix-blend-mode: ${({ theme }) => (theme === 'light' ? 'unset' : 'difference')}; 38 | } 39 | } 40 | 41 | p { 42 | margin-bottom: 2.5rem; 43 | font-size: 20pt; 44 | font-weight: normal; 45 | line-height: 1.3; 46 | color: ${({ theme }) => (theme === 'dark' ? '#c7c7c7' : '#707070')}; 47 | 48 | @media (max-width: 960px) { 49 | mix-blend-mode: ${({ theme }) => (theme === 'light' ? 'unset' : 'difference')}; 50 | } 51 | } 52 | `; 53 | 54 | export const Thumbnail = styled.div` 55 | flex: 1; 56 | 57 | @media (max-width: 960px) { 58 | width: 100%; 59 | margin-bottom: 2rem; 60 | } 61 | 62 | img { 63 | width: 100%; 64 | } 65 | `; 66 | -------------------------------------------------------------------------------- /src/components/landing/index.js: -------------------------------------------------------------------------------- 1 | export * from './Intro'; 2 | export * from './Skills'; 3 | export * from './Contact'; 4 | export * from './Projects'; 5 | -------------------------------------------------------------------------------- /src/components/theme/Footer/index.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Container } from 'components/common'; 3 | import { Wrapper, Flex, Links, Details } from './styles'; 4 | import social from './social.json'; 5 | 6 | export const Footer = () => ( 7 | 8 | 9 | 10 | John Doe 11 | 12 | © All rights are reserved | {new Date().getFullYear()} | Made with{' '} 13 | 14 | 💖 15 | {' '} 16 | by{' '} 17 | 18 | shinevue 19 | 20 | 21 | 22 | 23 | {social.map(({ id, name, link, icon }) => ( 24 | 25 | 26 | 27 | ))} 28 | 29 | 30 | 31 | ); 32 | -------------------------------------------------------------------------------- /src/components/theme/Footer/social.json: -------------------------------------------------------------------------------- 1 | [ 2 | // { 3 | // "id": 0, 4 | // "name": "Telegram", 5 | // "link": "https://t.me/smakosh", 6 | // "icon": "/icons/telegram.svg" 7 | // }, 8 | { 9 | "id": 1, 10 | "name": "Github", 11 | "link": "https://github.com/shinevue", 12 | "icon": "/icons/github.svg" 13 | }, 14 | // { 15 | // "id": 2, 16 | // "name": "StackOverflow", 17 | // "link": "https://stackoverflow.com/users/story/7396786", 18 | // "icon": "/icons/stackoverflow.svg" 19 | // }, 20 | ] 21 | -------------------------------------------------------------------------------- /src/components/theme/Footer/styles.js: -------------------------------------------------------------------------------- 1 | import footerIllustration from 'assets/illustrations/footer.svg'; 2 | import styled from 'styled-components'; 3 | 4 | export const Wrapper = styled.div` 5 | padding: 28rem 0 4rem 0; 6 | background-image: url(${footerIllustration}); 7 | background-size: cover; 8 | background-position: top; 9 | background-repeat: no-repeat; 10 | 11 | @media (max-width: 1960px) { 12 | padding: 14rem 0 4rem; 13 | } 14 | `; 15 | 16 | export const Flex = styled.div` 17 | display: flex; 18 | align-items: flex-end; 19 | justify-content: space-between; 20 | 21 | @media (max-width: 680px) { 22 | flex-direction: column; 23 | text-align: center; 24 | align-items: center; 25 | } 26 | `; 27 | 28 | export const Links = styled.div` 29 | display: flex; 30 | align-items: center; 31 | 32 | a { 33 | margin: 0 0.5rem; 34 | 35 | img { 36 | margin: 0; 37 | } 38 | 39 | &:first-child, 40 | &:last-child { 41 | margin: 0; 42 | } 43 | } 44 | `; 45 | 46 | export const Details = styled.div` 47 | h2, 48 | a, 49 | span { 50 | color: #212121; 51 | } 52 | 53 | @media (max-width: 680px) { 54 | margin-bottom: 2rem; 55 | } 56 | `; 57 | -------------------------------------------------------------------------------- /src/components/theme/Header/Hamburger/index.jsx: -------------------------------------------------------------------------------- 1 | import React, {useContext} from 'react'; 2 | import { ThemeContext } from 'providers/ThemeProvider'; 3 | import { Wrapper, Bar } from './styles'; 4 | 5 | const Hamburger = ({ sidebar, toggle }) => { 6 | 7 | const { theme } = useContext(ThemeContext); 8 | 9 | return ( 10 | toggle(!sidebar)}> 11 | 12 | 13 | 14 | 15 | ) 16 | }; 17 | 18 | export default Hamburger; 19 | -------------------------------------------------------------------------------- /src/components/theme/Header/Hamburger/styles.js: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components'; 2 | 3 | export const Wrapper = styled.div` 4 | z-index: 5; 5 | top: 1.6rem; 6 | right: 1.8rem; 7 | display: none; 8 | cursor: pointer; 9 | transition: left 500ms cubic-bezier(0.6, 0.05, 0.28, 0.91); 10 | position: absolute; 11 | 12 | @media (max-width: 960px) { 13 | display: block; 14 | } 15 | 16 | ${({ sidebar }) => 17 | sidebar && 18 | ` 19 | right: 18%; 20 | top: 1.4rem; 21 | 22 | @media (max-width: 960px) { 23 | right: 35%; 24 | position: fixed; 25 | } 26 | 27 | @media (max-width: 600px) { 28 | right: 66%; 29 | } 30 | `} 31 | `; 32 | 33 | export const Bar = styled.div` 34 | width: 1.6rem; 35 | height: .15rem; 36 | margin-bottom: .3rem; 37 | background-color: #212121; 38 | transition: transform 500ms cubic-bezier(0.6, 0.05, 0.28, 0.91), 39 | opacity 500ms, 40 | box-shadow 250ms, 41 | background-color 500ms; 42 | 43 | @media (max-width: 600px){ 44 | width: 1.6rem; 45 | } 46 | 47 | ${({ top, sidebar, theme }) => 48 | top && 49 | sidebar && 50 | ` 51 | background-color: ${(theme === 'light' ? '#212121' : '#fff')}; 52 | transform: translateY(8px) rotate(-135deg); 53 | 54 | `} 55 | 56 | ${({ mid, sidebar }) => 57 | mid && 58 | sidebar && 59 | ` 60 | transform: scale(0); 61 | `} 62 | 63 | ${({ bottom, sidebar, theme }) => 64 | bottom && 65 | sidebar && 66 | ` 67 | background-color: ${(theme === 'light' ? '#212121' : '#fff')}; 68 | transform: translateY(-6px) rotate(-45deg); 69 | `} 70 | `; 71 | -------------------------------------------------------------------------------- /src/components/theme/Header/Navbar/index.jsx: -------------------------------------------------------------------------------- 1 | import React, { useContext } from 'react'; 2 | import { Link } from 'gatsby'; 3 | import { ThemeContext } from 'providers/ThemeProvider'; 4 | import { Container } from 'components/common'; 5 | import NavbarLinks from '../NavbarLinks'; 6 | import { Wrapper, Brand } from './styles'; 7 | 8 | const Navbar = () => { 9 | const { theme } = useContext(ThemeContext); 10 | 11 | return ( 12 | 13 | 14 | John Doe 15 | 16 | 17 | 18 | ); 19 | }; 20 | 21 | export default Navbar; 22 | -------------------------------------------------------------------------------- /src/components/theme/Header/Navbar/styles.js: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components'; 2 | 3 | export const Wrapper = styled.div` 4 | padding: 1.5rem 0; 5 | display: flex; 6 | align-items: center; 7 | justify-content: space-between; 8 | `; 9 | 10 | export const Brand = styled.a` 11 | color: ${({ theme }) => (theme === 'light' ? '#000' : '#fff')}; 12 | 13 | @media (max-width: 960px) { 14 | mix-blend-mode: ${({ theme }) => (theme === 'light' ? 'unset' : 'difference')}; 15 | } 16 | `; 17 | -------------------------------------------------------------------------------- /src/components/theme/Header/NavbarLinks/index.jsx: -------------------------------------------------------------------------------- 1 | import React, {useContext} from 'react'; 2 | import AnchorLink from 'react-anchor-link-smooth-scroll'; 3 | import { ThemeContext } from 'providers/ThemeProvider'; 4 | import ToggleTheme from 'components/theme/Header/ToggleTheme'; 5 | import { Wrapper } from './styles'; 6 | 7 | const NavbarLinks = ({ desktop }) => { 8 | const { theme } = useContext(ThemeContext); 9 | 10 | return ( 11 | 12 | About 13 | Projects 14 | Contact 15 | 16 | 17 | ) 18 | 19 | }; 20 | 21 | export default NavbarLinks; 22 | -------------------------------------------------------------------------------- /src/components/theme/Header/NavbarLinks/styles.js: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components'; 2 | 3 | export const Wrapper = styled.div` 4 | a { 5 | color: #000; 6 | text-decoration: none; 7 | 8 | @media (max-width: 960px) { 9 | color: ${({ theme }) => (theme === 'light' ? '#000' : '#fff')}; 10 | } 11 | } 12 | 13 | ${({ desktop }) => 14 | desktop 15 | ? ` 16 | align-items: center; 17 | display: flex; 18 | 19 | @media (max-width: 960px) { 20 | display: none; 21 | } 22 | 23 | a { 24 | margin-right: 1rem; 25 | 26 | &:last-child { 27 | margin-right: unset; 28 | } 29 | } 30 | ` 31 | : ` 32 | padding: 3rem; 33 | display: flex; 34 | flex-direction: column; 35 | 36 | a { 37 | margin-bottom: 1rem; 38 | 39 | &:last-child { 40 | margin-bottom: unset; 41 | } 42 | } 43 | `} 44 | `; 45 | -------------------------------------------------------------------------------- /src/components/theme/Header/Sidebar/index.jsx: -------------------------------------------------------------------------------- 1 | import React, { useContext } from 'react'; 2 | import { ThemeContext } from 'providers/ThemeProvider'; 3 | import NavbarLinks from '../NavbarLinks'; 4 | import { Wrapper } from './styles'; 5 | 6 | const Sidebar = ({ sidebar, toggle }) => { 7 | const { theme } = useContext(ThemeContext); 8 | 9 | return ( 10 | 11 | 12 | 13 | ) 14 | }; 15 | 16 | export default Sidebar; 17 | -------------------------------------------------------------------------------- /src/components/theme/Header/Sidebar/styles.js: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components'; 2 | 3 | export const Wrapper = styled.div` 4 | position: fixed; 5 | z-index: 4; 6 | overflow: auto; 7 | top: 0px; 8 | right: -275px; 9 | width: 0; 10 | opacity: 0; 11 | height: 100%; 12 | background-color: ${({ theme }) => (theme === 'light' ? '#fff' : '#212121')}; 13 | transition: all 350ms cubic-bezier(0.6, 0.05, 0.28, 0.91); 14 | 15 | ${({ active }) => 16 | active && 17 | ` 18 | width: 20%; 19 | right: 0px; 20 | opacity: 1; 21 | 22 | @media (max-width: 960px) { 23 | width: 40%; 24 | } 25 | 26 | @media (max-width: 600px) { 27 | width: 75%; 28 | } 29 | `} 30 | `; 31 | -------------------------------------------------------------------------------- /src/components/theme/Header/ToggleTheme/index.jsx: -------------------------------------------------------------------------------- 1 | import React, { useContext } from 'react'; 2 | import { ThemeContext } from 'providers/ThemeProvider'; 3 | import sunIcon from 'assets/icons/sun.svg'; 4 | import moonIcon from 'assets/icons/moon.svg'; 5 | import { Wrapper } from './styles'; 6 | 7 | const ToggleTheme = () => { 8 | const { theme, toggleTheme } = useContext(ThemeContext); 9 | 10 | return ( 11 | 12 | 13 | 14 | ); 15 | }; 16 | 17 | export default ToggleTheme; 18 | -------------------------------------------------------------------------------- /src/components/theme/Header/ToggleTheme/styles.js: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components'; 2 | 3 | export const Wrapper = styled.button` 4 | background: none; 5 | border: none; 6 | cursor: pointer; 7 | transition: 0.3s all; 8 | 9 | &:focus { 10 | outline: none; 11 | transition: 0.3s all; 12 | } 13 | 14 | @media (max-width: 960px) { 15 | text-align: left; 16 | } 17 | 18 | img { 19 | margin-bottom: unset; 20 | } 21 | `; 22 | -------------------------------------------------------------------------------- /src/components/theme/Header/index.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | import Navbar from './Navbar'; 3 | import Hamburger from './Hamburger'; 4 | import Sidebar from './Sidebar'; 5 | import { Wrapper, Overlay } from './styles'; 6 | 7 | export const Header = () => { 8 | const [sidebar, toggle] = useState(false); 9 | 10 | return ( 11 | 12 | toggle(!sidebar)} /> 13 | 14 | 15 | 16 | 17 | ); 18 | }; 19 | -------------------------------------------------------------------------------- /src/components/theme/Header/styles.js: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components'; 2 | 3 | export const Wrapper = styled.div` 4 | background: transparent; 5 | width: 100%; 6 | `; 7 | 8 | export const Overlay = styled.div` 9 | position: fixed; 10 | background: rgba(0, 0, 0, 0.7); 11 | width: 100%; 12 | height: 100%; 13 | display: none; 14 | transition: 0.4s; 15 | 16 | ${({ sidebar }) => 17 | sidebar && 18 | ` 19 | display: block; 20 | z-index: 4; 21 | `} 22 | `; 23 | -------------------------------------------------------------------------------- /src/components/theme/index.js: -------------------------------------------------------------------------------- 1 | export * from './Header'; 2 | export * from './Footer'; 3 | -------------------------------------------------------------------------------- /src/data/config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | defaultTitle: 'John Doe', 3 | logo: 'https://portfolio.smakosh.com/favicon/favicon-512.png', 4 | author: 'John Doe', 5 | url: 'https://portfolio.smakosh.com', 6 | legalName: 'John Doe', 7 | defaultDescription: 'I’m John and I’m a Backend & Devops engineer!', 8 | socialLinks: { 9 | // twitter: 'http://www.twitter.com/smakosh', 10 | github: 'https://github.com/shinevue', 11 | // linkedin: 'https://www.linkedin.com/in/ismail-ghallou-630149122/', 12 | // instagram: 'https://instagram.com/smakosh19', 13 | // youtube: 'https://www.youtube.com/user/smakoshthegamer', 14 | // google: 'https://plus.google.com/u/0/+IsmailSmakoshGhallou', 15 | }, 16 | googleAnalyticsID: 'UA-88875900-4', 17 | themeColor: '#6b63ff', 18 | backgroundColor: '#6b63ff', 19 | social: { 20 | // facebook: 'appId', 21 | // twitter: '@smakosh', 22 | }, 23 | address: { 24 | city: 'City', 25 | region: 'Region', 26 | country: 'Country', 27 | zipCode: 'ZipCode', 28 | }, 29 | contact: { 30 | email: 'email', 31 | phone: 'phone number', 32 | }, 33 | foundingDate: '2018', 34 | }; 35 | -------------------------------------------------------------------------------- /src/hooks/useDarkMode.js: -------------------------------------------------------------------------------- 1 | import { useState, useEffect } from 'react'; 2 | import useMedia from 'hooks/useMedia'; 3 | 4 | const useDarkMode = () => { 5 | const [theme, setTheme] = useState('light'); 6 | 7 | const toggleTheme = () => { 8 | if (theme === 'light') { 9 | window.localStorage.setItem('theme', 'dark'); 10 | setTheme('dark'); 11 | } else { 12 | window.localStorage.setItem('theme', 'light'); 13 | setTheme('light'); 14 | } 15 | }; 16 | 17 | const prefersDarkMode = useMedia(['(prefers-color-scheme: dark)'], [true], false); 18 | 19 | useEffect(() => { 20 | const localTheme = window.localStorage.getItem('theme'); 21 | if (localTheme) { 22 | window.localStorage.setItem('theme', localTheme); 23 | setTheme(localTheme); 24 | } else if (prefersDarkMode) { 25 | setTheme('dark'); 26 | } else { 27 | setTheme('light'); 28 | } 29 | }, [prefersDarkMode]); 30 | 31 | return [theme, toggleTheme]; 32 | }; 33 | 34 | export default useDarkMode -------------------------------------------------------------------------------- /src/hooks/useMedia.js: -------------------------------------------------------------------------------- 1 | import { useState, useEffect } from 'react'; 2 | 3 | const useMedia = (queries, values, defaultValue) => { 4 | const [value, setValue] = useState(null); 5 | 6 | useEffect(() => { 7 | const mediaQueryLists = queries.map(q => window.matchMedia(q)); 8 | 9 | const getValue = () => { 10 | const index = mediaQueryLists.findIndex(mql => mql.matches); 11 | return typeof values[index] !== 'undefined' ? values[index] : defaultValue; 12 | }; 13 | 14 | setValue(getValue); 15 | const handler = () => setValue(getValue); 16 | mediaQueryLists.forEach(mql => mql.addListener(handler)); 17 | return () => mediaQueryLists.forEach(mql => mql.removeListener(handler)); 18 | }, [defaultValue, queries, values]); 19 | 20 | return value; 21 | }; 22 | 23 | export default useMedia; 24 | -------------------------------------------------------------------------------- /src/pages/404.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Layout, Seo } from "components/common"; 3 | 4 | const NotFound = () => ( 5 | 6 | 7 | NOT FOUND 8 | You just hit a route that doesn't exist... the sadness. 9 | 10 | ); 11 | 12 | export default NotFound; 13 | -------------------------------------------------------------------------------- /src/pages/index.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Layout, Seo } from "components/common"; 3 | import { Intro, Skills, Contact, Projects } from "components/landing"; 4 | 5 | const Home = () => ( 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | ); 14 | 15 | export default Home; 16 | -------------------------------------------------------------------------------- /src/providers/ThemeProvider.jsx: -------------------------------------------------------------------------------- 1 | import React, { createContext } from "react"; 2 | import useDarkMode from "hooks/useDarkMode"; 3 | 4 | export const ThemeContext = createContext("light"); 5 | 6 | const ThemeProvider = ({ children }) => { 7 | const [theme, toggleTheme] = useDarkMode(); 8 | 9 | return ( 10 | 16 | {children} 17 | 18 | ); 19 | }; 20 | 21 | export default ThemeProvider; 22 | -------------------------------------------------------------------------------- /static/_redirects: -------------------------------------------------------------------------------- 1 | # Redirect default Netlify subdomain to primary domain 2 | https://gatsby-portfolio-dev.netlify.com/* https://portfolio.smakosh.com/:splat 301! -------------------------------------------------------------------------------- /static/favicon/favicon-512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shinevue/next-portfolio/005eb14a489ceb5260499e6927999dd7ee88bdda/static/favicon/favicon-512.png -------------------------------------------------------------------------------- /static/icons/github.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /static/icons/stackoverflow.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /static/icons/telegram.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /static/icons/twitter.svg: -------------------------------------------------------------------------------- 1 | --------------------------------------------------------------------------------
{node.description}
20 | Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the 21 | industry’s standard dummy. 22 |
You just hit a route that doesn't exist... the sadness.