├── .babelrc
├── .editorconfig
├── .eslintrc
├── .gitignore
├── .husky
├── .gitignore
└── pre-commit
├── .nvmrc
├── LICENSE
├── README.md
├── content
├── featured
│ ├── CarPricePredictor
│ │ ├── CarPricePrediction.png
│ │ ├── demo.png
│ │ └── index.md
│ ├── InstantMD
│ │ ├── InstantMD.png
│ │ ├── demo.png
│ │ └── index.md
│ └── StockPortfolio
│ │ ├── HomePage.png
│ │ └── index.md
├── jobs
│ ├── AUV
│ │ └── index.md
│ ├── Accenture
│ │ └── index.md
│ ├── Achievements
│ │ └── index.md
│ ├── HOPS
│ │ └── index.md
│ ├── Robocup
│ │ └── index.md
│ └── TacitPal
│ │ └── index.md
├── posts
│ ├── clickable-cards
│ │ └── index.md
│ ├── dark-mode-toggle
│ │ └── index.md
│ ├── docker-compose-error
│ │ └── index.md
│ ├── markdown-playground
│ │ ├── image.jpg
│ │ └── index.md
│ └── wordpress-publish-error
│ │ ├── console-errors.png
│ │ ├── draft-fail.png
│ │ ├── index.md
│ │ └── publish-error.png
└── projects
│ ├── CLIQuiz.md
│ ├── DjangoChat.md
│ ├── FlightPricePrediction.md
│ ├── PredictingShares.md
│ ├── SalaryPredictor.md
│ ├── SentimentAnalysis.md
│ ├── ToDoList.md
│ └── Xenith Space Shooter.md
├── gatsby-browser.js
├── gatsby-config.js
├── gatsby-node.js
├── gatsby-ssr.js
├── package.json
├── prettier.config.js
├── src
├── components
│ ├── email.js
│ ├── footer.js
│ ├── head.js
│ ├── icons
│ │ ├── appstore.js
│ │ ├── bookmark.js
│ │ ├── codepen.js
│ │ ├── external.js
│ │ ├── folder.js
│ │ ├── fork.js
│ │ ├── github.js
│ │ ├── icon.js
│ │ ├── index.js
│ │ ├── instagram.js
│ │ ├── linkedin.js
│ │ ├── loader.js
│ │ ├── logo.js
│ │ ├── playstore.js
│ │ ├── star.js
│ │ └── twitter.js
│ ├── index.js
│ ├── layout.js
│ ├── loader.js
│ ├── menu.js
│ ├── nav.js
│ ├── sections
│ │ ├── about.js
│ │ ├── contact.js
│ │ ├── featured.js
│ │ ├── hero.js
│ │ ├── jobs.js
│ │ └── projects.js
│ ├── side.js
│ └── social.js
├── config.js
├── fonts
│ ├── Calibre
│ │ ├── Calibre-Light.ttf
│ │ ├── Calibre-Light.woff
│ │ ├── Calibre-Light.woff2
│ │ ├── Calibre-LightItalic.ttf
│ │ ├── Calibre-LightItalic.woff
│ │ ├── Calibre-LightItalic.woff2
│ │ ├── Calibre-Medium.ttf
│ │ ├── Calibre-Medium.woff
│ │ ├── Calibre-Medium.woff2
│ │ ├── Calibre-MediumItalic.ttf
│ │ ├── Calibre-MediumItalic.woff
│ │ ├── Calibre-MediumItalic.woff2
│ │ ├── Calibre-Regular.ttf
│ │ ├── Calibre-Regular.woff
│ │ ├── Calibre-Regular.woff2
│ │ ├── Calibre-RegularItalic.ttf
│ │ ├── Calibre-RegularItalic.woff
│ │ ├── Calibre-RegularItalic.woff2
│ │ ├── Calibre-Semibold.ttf
│ │ ├── Calibre-Semibold.woff
│ │ ├── Calibre-Semibold.woff2
│ │ ├── Calibre-SemiboldItalic.ttf
│ │ ├── Calibre-SemiboldItalic.woff
│ │ └── Calibre-SemiboldItalic.woff2
│ └── SFMono
│ │ ├── SFMono-Medium.ttf
│ │ ├── SFMono-Medium.woff
│ │ ├── SFMono-Medium.woff2
│ │ ├── SFMono-MediumItalic.ttf
│ │ ├── SFMono-MediumItalic.woff
│ │ ├── SFMono-MediumItalic.woff2
│ │ ├── SFMono-Regular.ttf
│ │ ├── SFMono-Regular.woff
│ │ ├── SFMono-Regular.woff2
│ │ ├── SFMono-RegularItalic.ttf
│ │ ├── SFMono-RegularItalic.woff
│ │ ├── SFMono-RegularItalic.woff2
│ │ ├── SFMono-Semibold.ttf
│ │ ├── SFMono-Semibold.woff
│ │ ├── SFMono-Semibold.woff2
│ │ ├── SFMono-SemiboldItalic.ttf
│ │ ├── SFMono-SemiboldItalic.woff
│ │ └── SFMono-SemiboldItalic.woff2
├── hooks
│ ├── index.js
│ ├── useOnClickOutside.js
│ ├── usePrefersReducedMotion.js
│ └── useScrollDirection.js
├── images
│ ├── Google Lighthouse Performance Metrtics.png
│ ├── demo.png
│ ├── favicons
│ │ ├── android-icon-144x144.png
│ │ ├── android-icon-192x192.png
│ │ ├── android-icon-36x36.png
│ │ ├── android-icon-48x48.png
│ │ ├── android-icon-72x72.png
│ │ ├── android-icon-96x96.png
│ │ ├── apple-icon-114x114.png
│ │ ├── apple-icon-120x120.png
│ │ ├── apple-icon-144x144.png
│ │ ├── apple-icon-152x152.png
│ │ ├── apple-icon-180x180.png
│ │ ├── apple-icon-57x57.png
│ │ ├── apple-icon-60x60.png
│ │ ├── apple-icon-72x72.png
│ │ ├── apple-icon-76x76.png
│ │ ├── apple-icon-precomposed.png
│ │ ├── apple-icon.png
│ │ ├── favicon-16x16.png
│ │ ├── favicon-32x32.png
│ │ ├── favicon-96x96.png
│ │ ├── favicon.ico
│ │ ├── ms-icon-144x144.png
│ │ ├── ms-icon-150x150.png
│ │ ├── ms-icon-310x310.png
│ │ └── ms-icon-70x70.png
│ ├── logo.png
│ └── profile.jpeg
├── pages
│ ├── 404.js
│ ├── archive.js
│ ├── index.js
│ └── pensieve
│ │ ├── index.js
│ │ └── tags.js
├── styles
│ ├── GlobalStyle.js
│ ├── PrismStyles.js
│ ├── TransitionStyles.js
│ ├── fonts.js
│ ├── index.js
│ ├── mixins.js
│ ├── theme.js
│ └── variables.js
├── templates
│ ├── post.js
│ └── tag.js
└── utils
│ ├── index.js
│ └── sr.js
├── static
├── og.png
├── og@2x.png
└── resume.pdf
└── yarn.lock
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [
3 | "@babel/preset-react",
4 | "babel-preset-gatsby"
5 | ]
6 | }
7 |
--------------------------------------------------------------------------------
/.editorconfig:
--------------------------------------------------------------------------------
1 | [*]
2 | indent_style = space
3 | indent_size = 2
4 | end_of_line = lf
5 | charset = utf-8
6 | trim_trailing_whitespace = true
7 | insert_final_newline = true
8 |
--------------------------------------------------------------------------------
/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "root": true,
3 | "extends": "@upstatement/eslint-config/react"
4 | }
5 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Project dependencies
2 | .cache
3 | node_modules
4 | yarn-error.log
5 | package-lock.json
6 |
7 | # Build directory
8 | /public
9 | .DS_Store
10 |
11 | .vscode/
12 |
--------------------------------------------------------------------------------
/.husky/.gitignore:
--------------------------------------------------------------------------------
1 | _
2 |
--------------------------------------------------------------------------------
/.husky/pre-commit:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | . "$(dirname "$0")/_/husky.sh"
3 |
4 | npm run lint-staged
5 |
--------------------------------------------------------------------------------
/.nvmrc:
--------------------------------------------------------------------------------
1 | 14.16.0
2 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2022 Parth Desai
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 |
18 |
19 | 
20 | 
21 | 
22 | 
23 |
24 |
25 |
26 | 
27 |
28 | ## 🛠 Installation & Set Up
29 |
30 | 1. Install the Gatsby CLI
31 |
32 | ```sh
33 | npm install -g gatsby-cli
34 | ```
35 |
36 | 2. Install and use the correct version of Node using [NVM](https://github.com/nvm-sh/nvm)
37 |
38 | ```sh
39 | nvm install
40 | ```
41 |
42 | 3. Install dependencies
43 |
44 | ```sh
45 | yarn
46 | ```
47 |
48 | 4. Start the development server
49 |
50 | ```sh
51 | npm start
52 | ```
53 |
54 | ## 🚀 Building and Running for Production
55 |
56 | 1. Generate a full static production build
57 |
58 | ```sh
59 | npm run build
60 | ```
61 |
62 | 2. Preview the site as it will appear once deployed
63 |
64 | ```sh
65 | npm run serve
66 | ```
67 |
68 | ## 📊 Google Lighthouse Performance Metrics
69 |
70 | 
71 |
72 | ## 🚨 Forking this repo
73 |
74 | If you fork this website, **please attribute it** to the `). Links inside of the card are still clickable.
15 |
16 | ## CSS
17 |
18 | ```css
19 | .grid__item {
20 | &:hover,
21 | &:focus-within {
22 | background-color: #eee;
23 | }
24 |
25 | a {
26 | position: relative;
27 | z-index: 1;
28 | }
29 |
30 | h2 {
31 | a {
32 | position: static;
33 |
34 | &:hover,
35 | &:focus {
36 | color: blue;
37 | }
38 |
39 | &:before {
40 | content: '';
41 | display: block;
42 | position: absolute;
43 | z-index: 0;
44 | width: 100%;
45 | height: 100%;
46 | top: 0;
47 | left: 0;
48 | transition: background-color 0.1s ease-out;
49 | background-color: transparent;
50 | }
51 | }
52 | }
53 | }
54 | ```
55 |
--------------------------------------------------------------------------------
/content/posts/dark-mode-toggle/index.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: Dark Mode Toggle
3 | description: Dark mode without the flash of default theme
4 | date: 2021-04-21
5 | draft: false
6 | slug: /pensieve/dark-mode-toggle
7 | tags:
8 | - Theming
9 | - Dark Mode
10 | ---
11 |
12 | Dark mode toggle without the flash of default theme. Important bits:
13 |
14 | - CSS variables for color theming
15 | - Put `data-theme` attribute on ``, not ``, so we can run the JS before the DOM finishes rendering
16 | - Run local storage check in the ``
17 | - JS for toggle button click handler can come after render
18 |
19 | ## HTML
20 |
21 | ```html
22 |
23 |
24 |
25 |
26 |
27 | ...
28 |
35 |
36 |
37 |
38 |
43 |
51 |
52 |
53 |
54 |
55 |
56 |
57 | ```
58 |
59 | ## CSS Variables
60 |
61 | ```css
62 | :root {
63 | --bg: #ffffff;
64 | --text: #000000;
65 | }
66 |
67 | [data-theme='dark'] {
68 | --bg: #000000;
69 | --text: #ffffff;
70 | }
71 | ```
72 |
73 | ## JavaScript
74 |
75 | ```js:title=app.js
76 | const themeToggleBtn = document.querySelector('.js-theme-toggle');
77 |
78 | themeToggleBtn.addEventListener('click', () => onToggleClick());
79 |
80 | const onToggleClick = () => {
81 | const { theme } = document.documentElement.dataset;
82 | const themeTo = theme && theme === 'light' ? 'dark' : 'light';
83 | const label = `Activate ${theme} mode`;
84 |
85 | document.documentElement.setAttribute('data-theme', themeTo);
86 | localStorage.setItem('theme', themeTo);
87 |
88 | themeToggleBtn.setAttribute('aria-label', label);
89 | themeToggleBtn.setAttribute('title', label);
90 | };
91 | ```
92 |
93 | ## Resources
94 |
95 | -
96 | -
97 | -
98 | -
99 | -
100 |
--------------------------------------------------------------------------------
/content/posts/docker-compose-error/index.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: Docker Compose Error
3 | description: docker-compose version discrepancies
4 | date: '2019-12-13'
5 | draft: false
6 | slug: '/pensieve/docker-error'
7 | tags:
8 | - WordPress
9 | - Docker
10 | ---
11 |
12 | ## Problem
13 |
14 | Recently while updating with [Skela](https://github.com/Upstatement/skela-wp-theme) with webpack, I encountered a weird error where I wasn't able to run a simple script:
15 |
16 | ```shell:title=bin/composer
17 | #!/bin/bash
18 | docker-compose exec -w /var/www/html/wp-content/themes/skela wordpress composer "$@"
19 | ```
20 |
21 | When trying to run this script via `./bin/composer install`, I got this error in my terminal:
22 |
23 | ```shell
24 | ERROR: Setting workdir for exec is not supported in API < 1.35 (1.30)
25 | ```
26 |
27 | The error was coming from the `-w` flag in the `docker-compose exec` command in the `composer` script.
28 |
29 | ## Solution
30 |
31 | Turns The fix was to update the version in my `docker-compose.yml` file to from version `3.5` to `3.6`. It's strange because 3.5 isn't anywhere close to the API version `1.35` from the error message 🤷♀️
32 |
33 | ```yaml:title=docker-compose.yml
34 | version: '3.6' # highlight-line
35 | services:
36 | wordpress:
37 | build:
38 | ```
39 |
--------------------------------------------------------------------------------
/content/posts/markdown-playground/image.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pycoder2000/portfolio-v4/108af3f210879860f83bea6ab9e70b93e5286769/content/posts/markdown-playground/image.jpg
--------------------------------------------------------------------------------
/content/posts/markdown-playground/index.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: Markdown Test File
3 | description: abc234
4 | date: 2019-12-07
5 | draft: true
6 | slug: /pensieve/markdown-playground
7 | tags:
8 | - Testing
9 | ---
10 |
11 | 
12 |
13 | ```jsx
14 | class FlavorForm extends React.Component { // highlight-line
15 | constructor(props) {
16 | super(props);
17 | this.state = {value: 'coconut'};
18 |
19 | this.handleChange = this.handleChange.bind(this);
20 | this.handleSubmit = this.handleSubmit.bind(this);
21 | }
22 |
23 | handleChange(event) {
24 | // highlight-next-line
25 | this.setState({value: event.target.value});
26 | }
27 |
28 | // highlight-start
29 | handleSubmit(event) {
30 | alert('Your favorite flavor is: ' + this.state.value);
31 | event.preventDefault();
32 | }
33 | // highlight-end
34 |
35 | render() {
36 | return (
37 | { /* highlight-range{1,4-9,12} */ }
38 |
50 | );
51 | }
52 | }
53 | ```
54 |
55 | ```javascript:title=highlight.js
56 | // Here is a comment
57 | function $initHighlight(block, cls) {
58 | try {
59 | if (cls.search(/\bno\-highlight\b/) != -1)
60 | return process(block, true, 0x0F) +
61 | ` class="${cls}"`;
62 | } catch (e) {
63 | /* handle exception */
64 | }
65 | for (var i = 0 / 2; i < classes.length; i++) {
66 | if (checkCondition(classes[i]) === undefined) {
67 | console.log('undefined');
68 | }
69 | }
70 |
71 | return (
72 |
73 | {block}
74 |
75 | )
76 | }
77 |
78 | export $initHighlight;
79 | ```
80 |
81 | This is a paragraph.
82 |
83 | This is a paragraph.
84 |
85 | # Header 1
86 |
87 | ## Header 2
88 |
89 | Header 1
90 | ========
91 |
92 | Header 2
93 | --------
94 |
95 | ```css
96 | @import 'compass/reset';
97 |
98 | // variables
99 | $colorGreen: #008000;
100 | $colorGreenDark: darken($colorGreen, 10);
101 |
102 | @mixin container {
103 | max-width: 980px;
104 | }
105 |
106 | // mixins with parameters
107 | @mixin button($color: green) {
108 | @if ($color == green) {
109 | background-color: #008000;
110 | } @else if ($color == red) {
111 | background-color: #b22222;
112 | }
113 | }
114 |
115 | button {
116 | @include button(red);
117 | }
118 |
119 | div,
120 | .navbar,
121 | #header,
122 | input[type='input'] {
123 | font-family: 'Helvetica Neue', Arial, sans-serif;
124 | width: auto;
125 | margin: 0 auto;
126 | display: block;
127 | }
128 |
129 | .row-12 > [class*='spans'] {
130 | border-left: 1px solid #b5c583;
131 | }
132 |
133 | // nested definitions
134 | ul {
135 | width: 100%;
136 | padding: {
137 | left: 5px;
138 | right: 5px;
139 | }
140 | li {
141 | float: left;
142 | margin-right: 10px;
143 | .home {
144 | background: url('http://placehold.it/20') scroll no-repeat 0 0;
145 | }
146 | }
147 | }
148 |
149 | .banner {
150 | @extend .container;
151 | }
152 |
153 | a {
154 | color: $colorGreen;
155 | &:hover {
156 | color: $colorGreenDark;
157 | }
158 | &:visited {
159 | color: #c458cb;
160 | }
161 | }
162 |
163 | @for $i from 1 through 5 {
164 | .span#{$i} {
165 | width: 20px * $i;
166 | }
167 | }
168 |
169 | @mixin mobile {
170 | @media screen and (max-width: 600px) {
171 | @content;
172 | }
173 | }
174 | ```
175 |
176 | ```markdown
177 | # hello world
178 |
179 | you can write text [with links](http://example.com) inline or [link references][1].
180 |
181 | - one _thing_ has *em*phasis
182 | - two **things** are **bold**
183 |
184 | [1]: http://example.com
185 |
186 | ---
187 |
188 | # hello world
189 |
190 |
191 |
192 | > markdown is so cool
193 |
194 | so are code segments
195 |
196 | 1. one thing (yeah!)
197 | 2. two thing `i can write code`, and `more` wipee!
198 | ```
199 |
200 | # Header 1
201 |
202 | ## Header 2
203 |
204 | ### Header 3
205 |
206 | #### Header 4
207 |
208 | ##### Header 5
209 |
210 | ###### Header 6
211 |
212 | # Header 1
213 | ## Header 2
214 | ### Header 3
215 | #### Header 4
216 | ##### Header 5
217 | ###### Header 6
218 |
219 | # Header 1
220 |
221 | ## Header 2
222 |
223 | ### Header 3
224 |
225 | #### Header 4
226 |
227 | ##### Header 5
228 |
229 | ###### Header 6
230 |
231 | # Header 1 #
232 | ## Header 2 ##
233 | ### Header 3 ###
234 | #### Header 4 ####
235 | ##### Header 5 #####
236 | ###### Header 6 ######
237 |
238 | > Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aliquam hendrerit mi posuere lectus. Vestibulum enim wisi, viverra nec, fringilla in, laoreet vitae, risus.
239 |
240 | > Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aliquam hendrerit mi posuere lectus. Vestibulum enim wisi, viverra nec, fringilla in, laoreet vitae, risus.
241 |
242 | > ## This is a header
243 | >
244 | > 1. This is the first list item.
245 | > 2. This is the second list item.
246 | >
247 | > Here's some example code:
248 | >
249 | > Markdown.generate();
250 |
251 | > ## This is a header.
252 | > 1. This is the first list item.
253 | > 2. This is the second list item.
254 | >
255 | > Here's some example code:
256 | >
257 | > Markdown.generate();
258 |
259 | - Red
260 | - Green
261 | - Blue
262 |
263 | - Red
264 | - Green
265 | - Blue
266 |
267 | - Red
268 | - Green
269 | - Blue
270 |
271 | ```markdown
272 | - Red
273 | - Green
274 | - Blue
275 |
276 | * Red
277 | * Green
278 | * Blue
279 |
280 | - Red
281 | - Green
282 | - Blue
283 | ```
284 |
285 | 1. Buy flour and salt
286 | 2. Mix together with water
287 | 3. Bake
288 |
289 | ```markdown
290 | 1. Buy flour and salt
291 | 1. Mix together with water
292 | 1. Bake
293 | ```
294 |
295 | Paragraph:
296 |
297 | Code
298 |
299 |
300 |
301 | Paragraph:
302 |
303 | Code
304 |
305 | ---
306 |
307 | ---
308 |
309 | ---
310 |
311 | ---
312 |
313 | ---
314 |
315 | * * *
316 |
317 | ***
318 |
319 | *****
320 |
321 | - - -
322 |
323 | ---------------------------------------
324 |
325 | This is [an example](http://example.com 'Example') link.
326 |
327 | [This link](http://example.com) has no title attr.
328 |
329 | This is [an example][id] reference-style link.
330 |
331 | [id]: http://example.com 'Optional Title'
332 |
333 | This is [an example](http://example.com "Example") link.
334 |
335 | [This link](http://example.com) has no title attr.
336 |
337 | This is [an example] [id] reference-style link.
338 |
339 | [id]: http://example.com "Optional Title"
340 |
341 | _single asterisks_
342 |
343 | _single underscores_
344 |
345 | **double asterisks**
346 |
347 | **double underscores**
348 |
349 | *single asterisks*
350 |
351 | _single underscores_
352 |
353 | **double asterisks**
354 |
355 | __double underscores__
356 |
357 | This paragraph has some `code` in it.
358 |
359 | This paragraph has some `code` in it.
360 |
--------------------------------------------------------------------------------
/content/posts/wordpress-publish-error/console-errors.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pycoder2000/portfolio-v4/108af3f210879860f83bea6ab9e70b93e5286769/content/posts/wordpress-publish-error/console-errors.png
--------------------------------------------------------------------------------
/content/posts/wordpress-publish-error/draft-fail.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pycoder2000/portfolio-v4/108af3f210879860f83bea6ab9e70b93e5286769/content/posts/wordpress-publish-error/draft-fail.png
--------------------------------------------------------------------------------
/content/posts/wordpress-publish-error/index.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: WordPress Publishing Error
3 | description: Trying to create a simple post in WordPress
4 | date: 2019-12-03
5 | draft: false
6 | slug: /pensieve/wordpress-publish-error
7 | tags:
8 | - WordPress
9 | ---
10 |
11 | ## Problem
12 |
13 | Recently while working on a WordPress project with [Ups Dock](https://github.com/Upstatement/ups-dock), I encountered a weird error where I wasn't able to update or publish a simple post in my local WP admin.
14 |
15 | It looked something like this:
16 |
17 | 
18 |
19 | Sometimes the error message would be slightly more helpful: `Publishing failed. Error message: The response is not a valid JSON response.`
20 |
21 | 
22 |
23 | And if I popped open the console, I saw these errors:
24 |
25 | 
26 |
27 | ## Solution
28 |
29 | Since the error message had to do with a JSON response, I initially thought it was a Gutenberg or ACF issue. But it turned out this was happening because I was on the https WP admin (i.e. [https://project.ups.dock/wp-admin](https://project.ups.dock/wp-admin)), not the unsecure WP admin ([http://project.ups.dock/wp-admin](http://project.ups.dock/wp-admin)).
30 |
31 | It was a CORS error!! I was trying to modify a non-https domain from a https domain. Switching to a non-https WP admin allowed me to publish posts with no problem.
32 |
--------------------------------------------------------------------------------
/content/posts/wordpress-publish-error/publish-error.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pycoder2000/portfolio-v4/108af3f210879860f83bea6ab9e70b93e5286769/content/posts/wordpress-publish-error/publish-error.png
--------------------------------------------------------------------------------
/content/projects/CLIQuiz.md:
--------------------------------------------------------------------------------
1 | ---
2 | date: '2022-01-05'
3 | title: 'CLI Quiz'
4 | github: 'https://github.com/pycoder2000/CLI-Quiz'
5 | external: 'https://libraries.io/npm/cliquiz-millionaire'
6 | tech:
7 | - Node
8 | - Javascript
9 | company: ''
10 | showInProjects: true
11 | ios: ''
12 | android: ''
13 | ---
14 |
15 | "Who wants to be a Millionaire? (JavaScript Edition)" Terminal Game that I made to practice my JavaScript skills.
16 |
--------------------------------------------------------------------------------
/content/projects/DjangoChat.md:
--------------------------------------------------------------------------------
1 | ---
2 | date: '2022-03-12'
3 | title: 'Django Chat App'
4 | github: 'https://github.com/pycoder2000/djangochat'
5 | external: ''
6 | tech:
7 | - Django
8 | - HTML
9 | - CSS
10 | - Channels
11 | - Vanilla Javascript
12 | - Web Sockets
13 | company: ''
14 | showInProjects: true
15 | ios: ''
16 | android: ''
17 | ---
18 |
19 | A small real time chat application built using Django. It also uses Channels and Vanilla Javascript with Web Sockets.
20 |
--------------------------------------------------------------------------------
/content/projects/FlightPricePrediction.md:
--------------------------------------------------------------------------------
1 | ---
2 | date: '2019-03-25'
3 | title: 'Flight Price Prediction'
4 | github: 'https://github.com/pycoder2000/Flight-Price-Prediction'
5 | external: ''
6 | tech:
7 | - Tensorflow
8 | - Python
9 | - Flask
10 | - Scipy
11 | company: ''
12 | showInProjects: true
13 | ios: ''
14 | android: ''
15 | ---
16 |
17 | Flight Price Prediction with Tensorflow. Here you will be provided with prices of flight tickets for various airlines between the months of March and June of 2019 and between various cities.
18 |
--------------------------------------------------------------------------------
/content/projects/PredictingShares.md:
--------------------------------------------------------------------------------
1 | ---
2 | date: '2018-10-01'
3 | title: 'Predicting number of Shares'
4 | github: 'https://github.com/pycoder2000/Predicting-Number-of-Shares'
5 | external: ''
6 | tech:
7 | - Scipy
8 | - Machine Learning
9 | - LinearRegression
10 | company: ''
11 | showInProjects: true
12 | ios: ''
13 | android: ''
14 | ---
15 |
16 | Predicting the number of shares based on how popular the article is.
17 |
--------------------------------------------------------------------------------
/content/projects/SalaryPredictor.md:
--------------------------------------------------------------------------------
1 | ---
2 | date: '2022-03-25'
3 | title: 'Salary Predictor'
4 | github: 'https://github.com/pycoder2000/Salary'
5 | external: 'https://salary-predictor-stream.herokuapp.com/'
6 | tech:
7 | - Python
8 | - Streamlit
9 | - Javascript
10 | company: ''
11 | showInProjects: true
12 | ios: ''
13 | android: ''
14 | ---
15 |
16 | Salary Prediction App made with StreamLit just to practice the Streamlit framework
17 |
18 | A [Streamlit](https://streamlit.io/) demo [written in pure Python](https://github.com/pycoder2000/Salary/blob/main/app.py) to predict salary based on your years of experience.
19 |
--------------------------------------------------------------------------------
/content/projects/SentimentAnalysis.md:
--------------------------------------------------------------------------------
1 | ---
2 | date: '2021-12-03'
3 | title: 'Sentiment Analysis'
4 | github: 'https://github.com/pycoder2000/sentiment-analysis'
5 | external: ''
6 | tech:
7 | - Flask
8 | - HTML
9 | - NLTK
10 | - TfidfVectorizer
11 | - Python
12 | company: 'Nirma University'
13 | showInProjects: true
14 | ios: ''
15 | android: ''
16 | ---
17 |
18 | This is a web app which can be used to analyze users' sentiments across different platforms using REST Apis. Made with Python, Flask, HTML, Javascript and deployed using Vercel. The model was trained using tweets from Sentiment140 dataset with 1.6 million tweets.
19 |
--------------------------------------------------------------------------------
/content/projects/ToDoList.md:
--------------------------------------------------------------------------------
1 | ---
2 | date: '2022-02-23'
3 | title: 'To Do List'
4 | github: 'https://github.com/pycoder2000/Todo-List'
5 | external: ''
6 | tech:
7 | - Django
8 | - HTML
9 | - CSS
10 | - SQL
11 | company: ''
12 | showInProjects: true
13 | ios: ''
14 | android: ''
15 | ---
16 |
17 | Clean and simple to-do list application made with Django framework.
18 |
19 | Features:
20 |
21 | - Easy add, delete and edit tasks
22 | - Reorder items
23 | - Login and register
24 | - Mark tasks as completed
25 | - Clean UI
26 |
--------------------------------------------------------------------------------
/content/projects/Xenith Space Shooter.md:
--------------------------------------------------------------------------------
1 | ---
2 | date: '2020-10-23'
3 | title: 'Xenith Space Shooter'
4 | github: 'https://github.com/pycoder2000/Xenith-Space_Shooter'
5 | external: ''
6 | tech:
7 | - Pygame
8 | - Python
9 | - tkinter
10 | company: ''
11 | showInProjects: true
12 | ios: ''
13 | android: ''
14 | ---
15 |
16 | A Knock-off of a retro space shooter game that I made with Pygame.
17 |
18 | Xenith is a knock-off of a popular game called Space Shooters that I made for my Girlfriend on our 1st Anniversary using Pygame.
19 |
--------------------------------------------------------------------------------
/gatsby-browser.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Implement Gatsby's Browser APIs in this file.
3 | *
4 | * See: https://www.gatsbyjs.org/docs/browser-apis/
5 | */
6 |
--------------------------------------------------------------------------------
/gatsby-config.js:
--------------------------------------------------------------------------------
1 | const config = require("./src/config");
2 |
3 | module.exports = {
4 | siteMetadata: {
5 | title: "Portfolio | Parth Desai",
6 | description:
7 | "I am a Data Engineer who is passionate about Data Science and Automation. In my free time, I like developing.",
8 | siteUrl: "https://parthdesai.vercel.app", // No trailing slash allowed!
9 | image: "/og.png", // Path to your image you placed in the 'static' folder
10 | twitterUsername: "@lone_Musk",
11 | },
12 | plugins: [
13 | `gatsby-plugin-react-helmet`,
14 | `gatsby-plugin-styled-components`,
15 | `gatsby-plugin-image`,
16 | `gatsby-plugin-sharp`,
17 | `gatsby-transformer-sharp`,
18 | `gatsby-plugin-sitemap`,
19 | `gatsby-plugin-robots-txt`,
20 | {
21 | resolve: `gatsby-plugin-manifest`,
22 | options: {
23 | name: "ParthDesai",
24 | short_name: "ParthDesai",
25 | start_url: "/",
26 | background_color: config.colors.darkNavy,
27 | theme_color: config.colors.navy,
28 | display: "minimal-ui",
29 | icon: "src/images/logo.png",
30 | },
31 | },
32 | `gatsby-plugin-offline`,
33 | {
34 | resolve: `gatsby-source-filesystem`,
35 | options: {
36 | name: `images`,
37 | path: `${__dirname}/src/images`,
38 | },
39 | },
40 | {
41 | resolve: "gatsby-source-filesystem",
42 | options: {
43 | name: "content",
44 | path: `${__dirname}/content/`,
45 | },
46 | },
47 | {
48 | resolve: `gatsby-source-filesystem`,
49 | options: {
50 | name: `posts`,
51 | path: `${__dirname}/content/posts`,
52 | },
53 | },
54 | {
55 | resolve: `gatsby-source-filesystem`,
56 | options: {
57 | name: `projects`,
58 | path: `${__dirname}/content/projects`,
59 | },
60 | },
61 | {
62 | resolve: `gatsby-transformer-remark`,
63 | options: {
64 | plugins: [
65 | {
66 | // https://www.gatsbyjs.org/packages/gatsby-remark-external-links
67 | resolve: "gatsby-remark-external-links",
68 | options: {
69 | target: "_blank",
70 | rel: "nofollow noopener noreferrer",
71 | },
72 | },
73 | {
74 | // https://www.gatsbyjs.org/packages/gatsby-remark-images
75 | resolve: "gatsby-remark-images",
76 | options: {
77 | maxWidth: 700,
78 | linkImagesToOriginal: true,
79 | quality: 90,
80 | tracedSVG: { color: config.colors.green },
81 | },
82 | },
83 | {
84 | // https://www.gatsbyjs.org/packages/gatsby-remark-code-titles/
85 | resolve: "gatsby-remark-code-titles",
86 | }, // IMPORTANT: this must be ahead of other plugins that use code blocks
87 | {
88 | // https://www.gatsbyjs.org/packages/gatsby-remark-prismjs
89 | resolve: `gatsby-remark-prismjs`,
90 | options: {
91 | // Class prefix for tags containing syntax highlighting;
92 | // defaults to 'language-' (e.g. ).
93 | // If your site loads Prism into the browser at runtime,
94 | // (e.g. for use with libraries like react-live),
95 | // you may use this to prevent Prism from re-processing syntax.
96 | // This is an uncommon use-case though;
97 | // If you're unsure, it's best to use the default value.
98 | classPrefix: "language-",
99 | // This is used to allow setting a language for inline code
100 | // (i.e. single backticks) by creating a separator.
101 | // This separator is a string and will do no white-space
102 | // stripping.
103 | // A suggested value for English speakers is the non-ascii
104 | // character '›'.
105 | inlineCodeMarker: null,
106 | // This lets you set up language aliases. For example,
107 | // setting this to '{ sh: "bash" }' will let you use
108 | // the language "sh" which will highlight using the
109 | // bash highlighter.
110 | aliases: {},
111 | // This toggles the display of line numbers globally alongside the code.
112 | // To use it, add the following line in gatsby-browser.js
113 | // right after importing the prism color scheme:
114 | // require("prismjs/plugins/line-numbers/prism-line-numbers.css")
115 | // Defaults to false.
116 | // If you wish to only show line numbers on certain code blocks,
117 | // leave false and use the {numberLines: true} syntax below
118 | showLineNumbers: false,
119 | // If setting this to true, the parser won't handle and highlight inline
120 | // code used in markdown i.e. single backtick code like `this`.
121 | noInlineHighlight: false,
122 | // This adds a new language definition to Prism or extend an already
123 | // existing language definition. More details on this option can be
124 | // found under the header "Add new language definition or extend an
125 | // existing language" below.
126 | languageExtensions: [
127 | {
128 | language: "superscript",
129 | extend: "javascript",
130 | definition: {
131 | superscript_types: /(SuperType)/,
132 | },
133 | insertBefore: {
134 | function: {
135 | superscript_keywords: /(superif|superelse)/,
136 | },
137 | },
138 | },
139 | ],
140 | // Customize the prompt used in shell output
141 | // Values below are default
142 | prompt: {
143 | user: "root",
144 | host: "localhost",
145 | global: false,
146 | },
147 | },
148 | },
149 | ],
150 | },
151 | },
152 | {
153 | resolve: `gatsby-plugin-google-analytics`,
154 | options: {
155 | trackingId: "G-X3DVJHL358",
156 | },
157 | },
158 | ],
159 | };
160 |
--------------------------------------------------------------------------------
/gatsby-node.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Implement Gatsby's Node APIs in this file.
3 | *
4 | * See: https://www.gatsbyjs.org/docs/node-apis/
5 | */
6 |
7 | const path = require('path');
8 | const _ = require('lodash');
9 |
10 | exports.createPages = async ({ actions, graphql, reporter }) => {
11 | const { createPage } = actions;
12 | const postTemplate = path.resolve(`src/templates/post.js`);
13 | const tagTemplate = path.resolve('src/templates/tag.js');
14 |
15 | const result = await graphql(`
16 | {
17 | postsRemark: allMarkdownRemark(
18 | filter: { fileAbsolutePath: { regex: "/content/posts/" } }
19 | sort: { order: DESC, fields: [frontmatter___date] }
20 | limit: 1000
21 | ) {
22 | edges {
23 | node {
24 | frontmatter {
25 | slug
26 | }
27 | }
28 | }
29 | }
30 | tagsGroup: allMarkdownRemark(limit: 2000) {
31 | group(field: frontmatter___tags) {
32 | fieldValue
33 | }
34 | }
35 | }
36 | `);
37 |
38 | // Handle errors
39 | if (result.errors) {
40 | reporter.panicOnBuild(`Error while running GraphQL query.`);
41 | return;
42 | }
43 |
44 | // Create post detail pages
45 | const posts = result.data.postsRemark.edges;
46 |
47 | posts.forEach(({ node }) => {
48 | createPage({
49 | path: node.frontmatter.slug,
50 | component: postTemplate,
51 | context: {},
52 | });
53 | });
54 |
55 | // Extract tag data from query
56 | const tags = result.data.tagsGroup.group;
57 | // Make tag pages
58 | tags.forEach(tag => {
59 | createPage({
60 | path: `/pensieve/tags/${_.kebabCase(tag.fieldValue)}/`,
61 | component: tagTemplate,
62 | context: {
63 | tag: tag.fieldValue,
64 | },
65 | });
66 | });
67 | };
68 |
69 | // https://www.gatsbyjs.org/docs/node-apis/#onCreateWebpackConfig
70 | exports.onCreateWebpackConfig = ({ stage, loaders, actions }) => {
71 | // https://www.gatsbyjs.org/docs/debugging-html-builds/#fixing-third-party-modules
72 | if (stage === 'build-html' || stage === 'develop-html') {
73 | actions.setWebpackConfig({
74 | module: {
75 | rules: [
76 | {
77 | test: /scrollreveal/,
78 | use: loaders.null(),
79 | },
80 | {
81 | test: /animejs/,
82 | use: loaders.null(),
83 | },
84 | {
85 | test: /miniraf/,
86 | use: loaders.null(),
87 | },
88 | ],
89 | },
90 | });
91 | }
92 |
93 | actions.setWebpackConfig({
94 | resolve: {
95 | alias: {
96 | '@components': path.resolve(__dirname, 'src/components'),
97 | '@config': path.resolve(__dirname, 'src/config'),
98 | '@fonts': path.resolve(__dirname, 'src/fonts'),
99 | '@hooks': path.resolve(__dirname, 'src/hooks'),
100 | '@images': path.resolve(__dirname, 'src/images'),
101 | '@pages': path.resolve(__dirname, 'src/pages'),
102 | '@styles': path.resolve(__dirname, 'src/styles'),
103 | '@utils': path.resolve(__dirname, 'src/utils'),
104 | },
105 | },
106 | });
107 | };
108 |
--------------------------------------------------------------------------------
/gatsby-ssr.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Implement Gatsby's SSR (Server Side Rendering) APIs in this file.
3 | *
4 | * See: https://www.gatsbyjs.org/docs/ssr-apis/
5 | */
6 |
7 | // You can delete this file if you're not using it
8 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "v4",
3 | "description": "Personal Website",
4 | "version": "1.0.0",
5 | "author": "Parth Desai ",
6 | "repository": {
7 | "type": "git",
8 | "url": "https://github.com/pycoder2000/portfolio-v4"
9 | },
10 | "keywords": [
11 | "gatsby"
12 | ],
13 | "license": "MIT",
14 | "browserslist": "> 0.25%, not dead",
15 | "scripts": {
16 | "build": "gatsby build",
17 | "develop": "gatsby develop",
18 | "format": "prettier --write \"**/*.{js,jsx,json,md}\"",
19 | "start": "npm run develop",
20 | "serve": "gatsby serve",
21 | "clean": "gatsby clean",
22 | "prepare": "husky install",
23 | "lint-staged": "lint-staged"
24 | },
25 | "lint-staged": {
26 | "*.{js,css,json,md}": [
27 | "prettier --write"
28 | ],
29 | "*.js": [
30 | "eslint --fix"
31 | ]
32 | },
33 | "dependencies": {
34 | "animejs": "^3.1.0",
35 | "babel-plugin-styled-components": "^1.12.0",
36 | "gatsby": "^3.4.1",
37 | "gatsby-plugin-google-analytics": "^3.4.0",
38 | "gatsby-plugin-image": "^1.4.0",
39 | "gatsby-plugin-manifest": "^3.4.0",
40 | "gatsby-plugin-netlify": "^3.4.0",
41 | "gatsby-plugin-offline": "^4.4.0",
42 | "gatsby-plugin-react-helmet": "^4.4.0",
43 | "gatsby-plugin-robots-txt": "^1.5.6",
44 | "gatsby-plugin-sharp": "^3.4.1",
45 | "gatsby-plugin-sitemap": "^4.0.0",
46 | "gatsby-plugin-styled-components": "^4.4.0",
47 | "gatsby-remark-external-links": "0.0.4",
48 | "gatsby-remark-images": "^5.1.0",
49 | "gatsby-remark-prismjs": "^5.1.0",
50 | "gatsby-source-filesystem": "^3.4.0",
51 | "gatsby-transformer-remark": "^4.1.0",
52 | "gatsby-transformer-sharp": "^3.4.0",
53 | "lodash": "^4.17.19",
54 | "prismjs": "^1.25.0",
55 | "prop-types": "^15.7.2",
56 | "react": "^17.0.2",
57 | "react-dom": "^17.0.2",
58 | "react-helmet": "^6.1.0",
59 | "react-scroll-progress-bar": "^1.1.13",
60 | "react-transition-group": "^4.3.0",
61 | "scrollreveal": "^4.0.5",
62 | "styled-components": "^5.3.0"
63 | },
64 | "devDependencies": {
65 | "@babel/core": "^7.14.0",
66 | "@babel/eslint-parser": "^7.13.14",
67 | "@babel/preset-react": "^7.13.13",
68 | "@upstatement/eslint-config": "^1.0.0",
69 | "@upstatement/prettier-config": "^1.0.0",
70 | "babel-preset-gatsby": "^1.4.0",
71 | "eslint": "^7.25.0",
72 | "eslint-config-prettier": "^8.3.0",
73 | "eslint-plugin-jsx-a11y": "^6.4.1",
74 | "eslint-plugin-react": "^7.23.2",
75 | "gatsby-remark-code-titles": "^1.1.0",
76 | "husky": "^6.0.0",
77 | "lint-staged": "^10.1.2",
78 | "prettier": "^2.2.1"
79 | }
80 | }
81 |
--------------------------------------------------------------------------------
/prettier.config.js:
--------------------------------------------------------------------------------
1 | module.exports = require('@upstatement/prettier-config');
2 |
--------------------------------------------------------------------------------
/src/components/email.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import styled from 'styled-components';
4 | import { email } from '@config';
5 | import { Side } from '@components';
6 |
7 | const StyledLinkWrapper = styled.div`
8 | display: flex;
9 | flex-direction: column;
10 | align-items: center;
11 | position: relative;
12 |
13 | &:after {
14 | content: '';
15 | display: block;
16 | width: 1px;
17 | height: 90px;
18 | margin: 0 auto;
19 | background-color: var(--light-slate);
20 | }
21 |
22 | a {
23 | margin: 20px auto;
24 | padding: 10px;
25 | font-family: var(--font-mono);
26 | font-size: var(--fz-xxs);
27 | line-height: var(--fz-lg);
28 | letter-spacing: 0.1em;
29 | writing-mode: vertical-rl;
30 |
31 | &:hover,
32 | &:focus {
33 | transform: translateY(-3px);
34 | text-decoration: underline;
35 | text-underline-offset: 50%;
36 | }
37 | }
38 | `;
39 |
40 | const Email = ({ isHome }) => (
41 |
42 |
43 | {email}
44 |
45 |
46 | );
47 |
48 | Email.propTypes = {
49 | isHome: PropTypes.bool,
50 | };
51 |
52 | export default Email;
53 |
--------------------------------------------------------------------------------
/src/components/footer.js:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect } from 'react';
2 | import PropTypes from 'prop-types';
3 | import styled from 'styled-components';
4 | import { Icon } from '@components/icons';
5 | import { socialMedia } from '@config';
6 |
7 | const StyledFooter = styled.footer`
8 | ${({ theme }) => theme.mixins.flexCenter};
9 | flex-direction: column;
10 | height: auto;
11 | min-height: 70px;
12 | padding: 15px;
13 | text-align: center;
14 | `;
15 |
16 | const StyledSocialLinks = styled.div`
17 | display: none;
18 |
19 | @media (max-width: 768px) {
20 | display: block;
21 | width: 100%;
22 | max-width: 270px;
23 | margin: 0 auto 10px;
24 | color: var(--light-slate);
25 | }
26 |
27 | ul {
28 | ${({ theme }) => theme.mixins.flexBetween};
29 | padding: 0;
30 | margin: 0;
31 | list-style: none;
32 |
33 | a {
34 | padding: 10px;
35 | svg {
36 | width: 20px;
37 | height: 20px;
38 | }
39 | }
40 | }
41 | `;
42 |
43 | const StyledCredit = styled.div`
44 | color: var(--light-slate);
45 | font-family: var(--font-mono);
46 | font-size: var(--fz-xxs);
47 | line-height: 1;
48 |
49 | a {
50 | padding: 10px;
51 | }
52 |
53 | .github-stats {
54 | margin-top: 10px;
55 |
56 | & > span {
57 | display: inline-flex;
58 | align-items: center;
59 | margin: 0 7px;
60 | }
61 | svg {
62 | display: inline-block;
63 | margin-right: 5px;
64 | width: 14px;
65 | height: 14px;
66 | }
67 | }
68 | `;
69 |
70 | const Footer = () => {
71 | const [githubInfo, setGitHubInfo] = useState({
72 | stars: null,
73 | forks: null,
74 | });
75 |
76 | useEffect(() => {
77 | if (process.env.NODE_ENV !== 'production') {
78 | return;
79 | }
80 | fetch('https://api.github.com/repos/bchiang7/v4')
81 | .then(response => response.json())
82 | .then(json => {
83 | const { stargazers_count, forks_count } = json;
84 | setGitHubInfo({
85 | stars: stargazers_count,
86 | forks: forks_count,
87 | });
88 | })
89 | .catch(e => console.error(e));
90 | }, []);
91 |
92 | return (
93 |
94 |
95 |
96 | {socialMedia &&
97 | socialMedia.map(({ name, url }, i) => (
98 |
99 |
100 |
101 |
102 |
103 | ))}
104 |
105 |
106 |
107 |
108 |
109 | Designed & Built by Brittany Chiang
110 |
111 | Revised by Parth Desai
112 | {githubInfo.stars && githubInfo.forks && (
113 |
114 |
115 |
116 | {githubInfo.stars.toLocaleString()}
117 |
118 |
119 |
120 | {githubInfo.forks.toLocaleString()}
121 |
122 |
123 | )}
124 |
125 |
126 |
127 | );
128 | };
129 |
130 | Footer.propTypes = {
131 | githubInfo: PropTypes.object,
132 | };
133 |
134 | export default Footer;
135 |
--------------------------------------------------------------------------------
/src/components/head.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import { Helmet } from 'react-helmet';
4 | import { useLocation } from '@reach/router';
5 | import { useStaticQuery, graphql } from 'gatsby';
6 |
7 | // https://www.gatsbyjs.com/docs/add-seo-component/
8 |
9 | const Head = ({ title, description, image }) => {
10 | const { pathname } = useLocation();
11 |
12 | const { site } = useStaticQuery(
13 | graphql`
14 | query {
15 | site {
16 | siteMetadata {
17 | defaultTitle: title
18 | defaultDescription: description
19 | siteUrl
20 | defaultImage: image
21 | twitterUsername
22 | }
23 | }
24 | }
25 | `,
26 | );
27 |
28 | const {
29 | defaultTitle,
30 | defaultDescription,
31 | siteUrl,
32 | defaultImage,
33 | twitterUsername,
34 | } = site.siteMetadata;
35 |
36 | const seo = {
37 | title: title || defaultTitle,
38 | description: description || defaultDescription,
39 | image: `${siteUrl}${image || defaultImage}`,
40 | url: `${siteUrl}${pathname}`,
41 | };
42 |
43 | return (
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 | );
64 | };
65 |
66 | export default Head;
67 |
68 | Head.propTypes = {
69 | title: PropTypes.string,
70 | description: PropTypes.string,
71 | image: PropTypes.string,
72 | };
73 |
74 | Head.defaultProps = {
75 | title: null,
76 | description: null,
77 | image: null,
78 | };
79 |
--------------------------------------------------------------------------------
/src/components/icons/appstore.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | const IconAppStore = () => (
4 |
11 | Apple App Store
12 |
13 |
14 |
19 |
20 |
21 |
22 |
23 |
29 |
30 |
31 |
32 |
33 |
38 |
39 |
40 |
41 |
42 |
46 |
47 |
48 |
49 | );
50 |
51 | export default IconAppStore;
52 |
--------------------------------------------------------------------------------
/src/components/icons/bookmark.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | const IconBookmark = () => (
4 |
13 | Bookmark
14 |
15 |
16 | );
17 |
18 | export default IconBookmark;
19 |
--------------------------------------------------------------------------------
/src/components/icons/codepen.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | const IconCodepen = () => (
4 |
14 | CodePen
15 |
16 |
17 |
18 |
19 |
20 |
21 | );
22 |
23 | export default IconCodepen;
24 |
--------------------------------------------------------------------------------
/src/components/icons/external.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | const IconExternal = () => (
4 |
14 | External Link
15 |
16 |
17 |
18 |
19 | );
20 |
21 | export default IconExternal;
22 |
--------------------------------------------------------------------------------
/src/components/icons/folder.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | const IconFolder = () => (
4 |
14 | Folder
15 |
16 |
17 | );
18 |
19 | export default IconFolder;
20 |
--------------------------------------------------------------------------------
/src/components/icons/fork.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | const IconFork = () => (
4 |
12 | Git Fork
13 |
14 |
15 |
16 |
17 |
18 | );
19 |
20 | export default IconFork;
21 |
--------------------------------------------------------------------------------
/src/components/icons/github.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | const IconGitHub = () => (
4 |
14 | GitHub
15 |
16 |
17 | );
18 |
19 | export default IconGitHub;
20 |
--------------------------------------------------------------------------------
/src/components/icons/icon.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import {
4 | IconAppStore,
5 | IconBookmark,
6 | IconCodepen,
7 | IconExternal,
8 | IconFolder,
9 | IconFork,
10 | IconGitHub,
11 | IconInstagram,
12 | IconLinkedin,
13 | IconLoader,
14 | IconLogo,
15 | IconPlayStore,
16 | IconStar,
17 | IconTwitter,
18 | } from '@components/icons';
19 |
20 | const Icon = ({ name }) => {
21 | switch (name) {
22 | case 'AppStore':
23 | return ;
24 | case 'Bookmark':
25 | return ;
26 | case 'Codepen':
27 | return ;
28 | case 'External':
29 | return ;
30 | case 'Folder':
31 | return ;
32 | case 'Fork':
33 | return ;
34 | case 'GitHub':
35 | return ;
36 | case 'Instagram':
37 | return ;
38 | case 'Linkedin':
39 | return ;
40 | case 'Loader':
41 | return ;
42 | case 'Logo':
43 | return ;
44 | case 'PlayStore':
45 | return ;
46 | case 'Star':
47 | return ;
48 | case 'Twitter':
49 | return ;
50 | default:
51 | return ;
52 | }
53 | };
54 |
55 | Icon.propTypes = {
56 | name: PropTypes.string.isRequired,
57 | };
58 |
59 | export default Icon;
60 |
--------------------------------------------------------------------------------
/src/components/icons/index.js:
--------------------------------------------------------------------------------
1 | export { default as IconAppStore } from './appstore';
2 | export { default as IconBookmark } from './bookmark';
3 | export { default as IconCodepen } from './codepen';
4 | export { default as IconExternal } from './external';
5 | export { default as IconFolder } from './folder';
6 | export { default as IconFork } from './fork';
7 | export { default as Icon } from './icon';
8 | export { default as IconGitHub } from './github';
9 | export { default as IconInstagram } from './instagram';
10 | export { default as IconLinkedin } from './linkedin';
11 | export { default as IconLoader } from './loader';
12 | export { default as IconLogo } from './logo';
13 | export { default as IconPlayStore } from './playstore';
14 | export { default as IconStar } from './star';
15 | export { default as IconTwitter } from './twitter';
16 |
--------------------------------------------------------------------------------
/src/components/icons/instagram.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | const IconInstagram = () => (
4 |
14 | Instagram
15 |
16 |
17 |
18 |
19 | );
20 |
21 | export default IconInstagram;
22 |
--------------------------------------------------------------------------------
/src/components/icons/linkedin.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | const IconLinkedin = () => (
4 |
14 | LinkedIn
15 |
16 |
17 |
18 |
19 | );
20 |
21 | export default IconLinkedin;
22 |
--------------------------------------------------------------------------------
/src/components/icons/loader.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | const IconLoader = () => (
4 |
5 | Loader Logo
6 |
7 |
8 |
20 |
21 |
22 | P
23 |
24 |
25 | );
26 |
27 | export default IconLoader;
28 |
--------------------------------------------------------------------------------
/src/components/icons/logo.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | const IconLogo = () => (
4 |
5 | Logo
6 |
7 |
8 |
16 |
17 |
18 |
19 | P
20 |
21 |
22 | );
23 |
24 | export default IconLogo;
25 |
--------------------------------------------------------------------------------
/src/components/icons/playstore.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | const IconPlayStore = () => (
4 |
5 | Google Play Store
6 |
14 |
15 | );
16 |
17 | export default IconPlayStore;
18 |
--------------------------------------------------------------------------------
/src/components/icons/star.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | const IconStar = () => (
4 |
12 | Star
13 |
14 |
15 | );
16 |
17 | export default IconStar;
18 |
--------------------------------------------------------------------------------
/src/components/icons/twitter.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | const IconTwitter = () => (
4 |
14 | Twitter
15 |
16 |
17 | );
18 |
19 | export default IconTwitter;
20 |
--------------------------------------------------------------------------------
/src/components/index.js:
--------------------------------------------------------------------------------
1 | export { default as Head } from './head';
2 | export { default as Layout } from './layout';
3 | export { default as Loader } from './loader';
4 | export { default as Nav } from './nav';
5 | export { default as Menu } from './menu';
6 | export { default as Side } from './side';
7 | export { default as Social } from './social';
8 | export { default as Email } from './email';
9 | export { default as Footer } from './footer';
10 | export { default as Hero } from './sections/hero';
11 | export { default as About } from './sections/about';
12 | export { default as Jobs } from './sections/jobs';
13 | export { default as Featured } from './sections/featured';
14 | export { default as Projects } from './sections/projects';
15 | export { default as Contact } from './sections/contact';
16 |
--------------------------------------------------------------------------------
/src/components/layout.js:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect } from 'react';
2 | import PropTypes from 'prop-types';
3 | import styled, { ThemeProvider } from 'styled-components';
4 | import { Head, Loader, Nav, Social, Email, Footer } from '@components';
5 | import { GlobalStyle, theme } from '@styles';
6 |
7 | const StyledContent = styled.div`
8 | display: flex;
9 | flex-direction: column;
10 | min-height: 100vh;
11 | `;
12 |
13 | const Layout = ({ children, location }) => {
14 | const isHome = location.pathname === '/';
15 | const [isLoading, setIsLoading] = useState(isHome);
16 |
17 | // Sets target="_blank" rel="noopener noreferrer" on external links
18 | const handleExternalLinks = () => {
19 | const allLinks = Array.from(document.querySelectorAll('a'));
20 | if (allLinks.length > 0) {
21 | allLinks.forEach(link => {
22 | if (link.host !== window.location.host) {
23 | link.setAttribute('rel', 'noopener noreferrer');
24 | link.setAttribute('target', '_blank');
25 | }
26 | });
27 | }
28 | };
29 |
30 | useEffect(() => {
31 | if (isLoading) {
32 | return;
33 | }
34 |
35 | if (location.hash) {
36 | const id = location.hash.substring(1); // location.hash without the '#'
37 | setTimeout(() => {
38 | const el = document.getElementById(id);
39 | if (el) {
40 | el.scrollIntoView();
41 | el.focus();
42 | }
43 | }, 0);
44 | }
45 |
46 | handleExternalLinks();
47 | }, [isLoading]);
48 |
49 | return (
50 | <>
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 | Skip to Content
59 |
60 |
61 | {isLoading && isHome ? (
62 | setIsLoading(false)} />
63 | ) : (
64 |
65 |
66 |
67 |
68 |
69 |
70 | {children}
71 |
72 |
73 |
74 | )}
75 |
76 |
77 | >
78 | );
79 | };
80 |
81 | Layout.propTypes = {
82 | children: PropTypes.node.isRequired,
83 | location: PropTypes.object.isRequired,
84 | };
85 |
86 | export default Layout;
87 |
--------------------------------------------------------------------------------
/src/components/loader.js:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect } from 'react';
2 | import { Helmet } from 'react-helmet';
3 | import PropTypes from 'prop-types';
4 | import anime from 'animejs';
5 | import styled from 'styled-components';
6 | import { IconLoader } from '@components/icons';
7 |
8 | const StyledLoader = styled.div`
9 | ${({ theme }) => theme.mixins.flexCenter};
10 | position: fixed;
11 | top: 0;
12 | bottom: 0;
13 | left: 0;
14 | right: 0;
15 | width: 100%;
16 | height: 100%;
17 | background-color: var(--dark-navy);
18 | z-index: 99;
19 |
20 | .logo-wrapper {
21 | width: max-content;
22 | max-width: 100px;
23 | transition: var(--transition);
24 | opacity: ${props => (props.isMounted ? 1 : 0)};
25 | svg {
26 | display: block;
27 | width: 100%;
28 | height: 100%;
29 | margin: 0 auto;
30 | fill: none;
31 | user-select: none;
32 | #B {
33 | opacity: 0;
34 | }
35 | }
36 | }
37 | `;
38 |
39 | const Loader = ({ finishLoading }) => {
40 | const [isMounted, setIsMounted] = useState(false);
41 |
42 | const animate = () => {
43 | const loader = anime.timeline({
44 | complete: () => finishLoading(),
45 | });
46 |
47 | loader
48 | .add({
49 | targets: '#logo path',
50 | delay: 300,
51 | duration: 1500,
52 | easing: 'easeInOutQuart',
53 | strokeDashoffset: [anime.setDashoffset, 0],
54 | })
55 | .add({
56 | targets: '#logo #B',
57 | duration: 700,
58 | easing: 'easeInOutQuart',
59 | opacity: 1,
60 | })
61 | .add({
62 | targets: '#logo',
63 | delay: 500,
64 | duration: 300,
65 | easing: 'easeInOutQuart',
66 | opacity: 0,
67 | scale: 0.1,
68 | })
69 | .add({
70 | targets: '.loader',
71 | duration: 200,
72 | easing: 'easeInOutQuart',
73 | opacity: 0,
74 | zIndex: -1,
75 | });
76 | };
77 |
78 | useEffect(() => {
79 | const timeout = setTimeout(() => setIsMounted(true), 10);
80 | animate();
81 | return () => clearTimeout(timeout);
82 | }, []);
83 |
84 | return (
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 | );
93 | };
94 |
95 | Loader.propTypes = {
96 | finishLoading: PropTypes.func.isRequired,
97 | };
98 |
99 | export default Loader;
100 |
--------------------------------------------------------------------------------
/src/components/menu.js:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect, useRef } from 'react';
2 | import { Helmet } from 'react-helmet';
3 | import { Link } from 'gatsby';
4 | import styled from 'styled-components';
5 | import { navLinks } from '@config';
6 | import { KEY_CODES } from '@utils';
7 | import { useOnClickOutside } from '@hooks';
8 |
9 | const StyledMenu = styled.div`
10 | display: none;
11 |
12 | @media (max-width: 768px) {
13 | display: block;
14 | }
15 | `;
16 |
17 | const StyledHamburgerButton = styled.button`
18 | display: none;
19 |
20 | @media (max-width: 768px) {
21 | ${({ theme }) => theme.mixins.flexCenter};
22 | position: relative;
23 | z-index: 10;
24 | margin-right: -15px;
25 | padding: 15px;
26 | border: 0;
27 | background-color: transparent;
28 | color: inherit;
29 | text-transform: none;
30 | transition-timing-function: linear;
31 | transition-duration: 0.15s;
32 | transition-property: opacity, filter;
33 | }
34 |
35 | .ham-box {
36 | display: inline-block;
37 | position: relative;
38 | width: var(--hamburger-width);
39 | height: 24px;
40 | }
41 |
42 | .ham-box-inner {
43 | position: absolute;
44 | top: 50%;
45 | right: 0;
46 | width: var(--hamburger-width);
47 | height: 2px;
48 | border-radius: var(--border-radius);
49 | background-color: var(--green);
50 | transition-duration: 0.22s;
51 | transition-property: transform;
52 | transition-delay: ${props => (props.menuOpen ? `0.12s` : `0s`)};
53 | transform: rotate(${props => (props.menuOpen ? `225deg` : `0deg`)});
54 | transition-timing-function: cubic-bezier(
55 | ${props => (props.menuOpen ? `0.215, 0.61, 0.355, 1` : `0.55, 0.055, 0.675, 0.19`)}
56 | );
57 | &:before,
58 | &:after {
59 | content: '';
60 | display: block;
61 | position: absolute;
62 | left: auto;
63 | right: 0;
64 | width: var(--hamburger-width);
65 | height: 2px;
66 | border-radius: 4px;
67 | background-color: var(--green);
68 | transition-timing-function: ease;
69 | transition-duration: 0.15s;
70 | transition-property: transform;
71 | }
72 | &:before {
73 | width: ${props => (props.menuOpen ? `100%` : `120%`)};
74 | top: ${props => (props.menuOpen ? `0` : `-10px`)};
75 | opacity: ${props => (props.menuOpen ? 0 : 1)};
76 | transition: ${({ menuOpen }) =>
77 | menuOpen ? 'var(--ham-before-active)' : 'var(--ham-before)'};
78 | }
79 | &:after {
80 | width: ${props => (props.menuOpen ? `100%` : `80%`)};
81 | bottom: ${props => (props.menuOpen ? `0` : `-10px`)};
82 | transform: rotate(${props => (props.menuOpen ? `-90deg` : `0`)});
83 | transition: ${({ menuOpen }) => (menuOpen ? 'var(--ham-after-active)' : 'var(--ham-after)')};
84 | }
85 | }
86 | `;
87 |
88 | const StyledSidebar = styled.aside`
89 | display: none;
90 |
91 | @media (max-width: 768px) {
92 | ${({ theme }) => theme.mixins.flexCenter};
93 | position: fixed;
94 | top: 0;
95 | bottom: 0;
96 | right: 0;
97 | padding: 50px 10px;
98 | width: min(75vw, 400px);
99 | height: 100vh;
100 | outline: 0;
101 | background-color: var(--light-navy);
102 | box-shadow: -10px 0px 30px -15px var(--navy-shadow);
103 | z-index: 9;
104 | transform: translateX(${props => (props.menuOpen ? 0 : 100)}vw);
105 | visibility: ${props => (props.menuOpen ? 'visible' : 'hidden')};
106 | transition: var(--transition);
107 | }
108 |
109 | nav {
110 | ${({ theme }) => theme.mixins.flexBetween};
111 | width: 100%;
112 | flex-direction: column;
113 | color: var(--lightest-slate);
114 | font-family: var(--font-mono);
115 | text-align: center;
116 | }
117 |
118 | ol {
119 | padding: 0;
120 | margin: 0;
121 | list-style: none;
122 | width: 100%;
123 |
124 | li {
125 | position: relative;
126 | margin: 0 auto 20px;
127 | counter-increment: item 1;
128 | font-size: clamp(var(--fz-sm), 4vw, var(--fz-lg));
129 |
130 | @media (max-width: 600px) {
131 | margin: 0 auto 10px;
132 | }
133 |
134 | &:before {
135 | content: '0' counter(item) '.';
136 | display: block;
137 | margin-bottom: 5px;
138 | color: var(--green);
139 | font-size: var(--fz-sm);
140 | }
141 | }
142 |
143 | a {
144 | ${({ theme }) => theme.mixins.link};
145 | width: 100%;
146 | padding: 3px 20px 20px;
147 | }
148 | }
149 |
150 | .resume-link {
151 | ${({ theme }) => theme.mixins.bigButton};
152 | padding: 18px 50px;
153 | margin: 10% auto 0;
154 | width: max-content;
155 | }
156 | `;
157 |
158 | const Menu = () => {
159 | const [menuOpen, setMenuOpen] = useState(false);
160 |
161 | const toggleMenu = () => setMenuOpen(!menuOpen);
162 |
163 | const buttonRef = useRef(null);
164 | const navRef = useRef(null);
165 |
166 | let menuFocusables;
167 | let firstFocusableEl;
168 | let lastFocusableEl;
169 |
170 | const setFocusables = () => {
171 | menuFocusables = [buttonRef.current, ...Array.from(navRef.current.querySelectorAll('a'))];
172 | firstFocusableEl = menuFocusables[0];
173 | lastFocusableEl = menuFocusables[menuFocusables.length - 1];
174 | };
175 |
176 | const handleBackwardTab = e => {
177 | if (document.activeElement === firstFocusableEl) {
178 | e.preventDefault();
179 | lastFocusableEl.focus();
180 | }
181 | };
182 |
183 | const handleForwardTab = e => {
184 | if (document.activeElement === lastFocusableEl) {
185 | e.preventDefault();
186 | firstFocusableEl.focus();
187 | }
188 | };
189 |
190 | const onKeyDown = e => {
191 | switch (e.key) {
192 | case KEY_CODES.ESCAPE:
193 | case KEY_CODES.ESCAPE_IE11: {
194 | setMenuOpen(false);
195 | break;
196 | }
197 |
198 | case KEY_CODES.TAB: {
199 | if (menuFocusables && menuFocusables.length === 1) {
200 | e.preventDefault();
201 | break;
202 | }
203 | if (e.shiftKey) {
204 | handleBackwardTab(e);
205 | } else {
206 | handleForwardTab(e);
207 | }
208 | break;
209 | }
210 |
211 | default: {
212 | break;
213 | }
214 | }
215 | };
216 |
217 | const onResize = e => {
218 | if (e.currentTarget.innerWidth > 768) {
219 | setMenuOpen(false);
220 | }
221 | };
222 |
223 | useEffect(() => {
224 | document.addEventListener('keydown', onKeyDown);
225 | window.addEventListener('resize', onResize);
226 |
227 | setFocusables();
228 |
229 | return () => {
230 | document.removeEventListener('keydown', onKeyDown);
231 | window.removeEventListener('resize', onResize);
232 | };
233 | }, []);
234 |
235 | const wrapperRef = useRef();
236 | useOnClickOutside(wrapperRef, () => setMenuOpen(false));
237 |
238 | return (
239 |
240 |
241 |
242 |
243 |
244 |
245 |
250 |
253 |
254 |
255 |
256 |
257 | {navLinks && (
258 |
259 | {navLinks.map(({ url, name }, i) => (
260 |
261 | setMenuOpen(false)}>
262 | {name}
263 |
264 |
265 | ))}
266 |
267 | )}
268 |
269 |
270 | Resume
271 |
272 |
273 |
274 |
275 |
276 | );
277 | };
278 |
279 | export default Menu;
280 |
--------------------------------------------------------------------------------
/src/components/nav.js:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect } from 'react';
2 | import { Link } from 'gatsby';
3 | import PropTypes from 'prop-types';
4 | import { CSSTransition, TransitionGroup } from 'react-transition-group';
5 | import styled, { css } from 'styled-components';
6 | import { navLinks } from '@config';
7 | import { loaderDelay } from '@utils';
8 | import { useScrollDirection, usePrefersReducedMotion } from '@hooks';
9 | import { Menu } from '@components';
10 | import { IconLogo } from '@components/icons';
11 |
12 | const StyledHeader = styled.header`
13 | ${({ theme }) => theme.mixins.flexBetween};
14 | position: fixed;
15 | top: 0;
16 | z-index: 11;
17 | padding: 0px 50px;
18 | width: 100%;
19 | height: var(--nav-height);
20 | background-color: rgba(10, 25, 47, 0.85);
21 | filter: none !important;
22 | pointer-events: auto !important;
23 | user-select: auto !important;
24 | backdrop-filter: blur(10px);
25 | transition: var(--transition);
26 |
27 | @media (max-width: 1080px) {
28 | padding: 0 40px;
29 | }
30 | @media (max-width: 768px) {
31 | padding: 0 25px;
32 | }
33 |
34 | @media (prefers-reduced-motion: no-preference) {
35 | ${props =>
36 | props.scrollDirection === 'up' &&
37 | !props.scrolledToTop &&
38 | css`
39 | height: var(--nav-scroll-height);
40 | transform: translateY(0px);
41 | background-color: rgba(10, 25, 47, 0.85);
42 | box-shadow: 0 10px 30px -10px var(--navy-shadow);
43 | `};
44 |
45 | ${props =>
46 | props.scrollDirection === 'down' &&
47 | !props.scrolledToTop &&
48 | css`
49 | box-shadow: 0 10px 30px -10px var(--navy-shadow);
50 | `};
51 | }
52 | `;
53 |
54 | const StyledNav = styled.nav`
55 | ${({ theme }) => theme.mixins.flexBetween};
56 | position: relative;
57 | width: 100%;
58 | color: var(--lightest-slate);
59 | font-family: var(--font-mono);
60 | counter-reset: item 0;
61 | z-index: 12;
62 |
63 | .logo {
64 | ${({ theme }) => theme.mixins.flexCenter};
65 |
66 | a {
67 | color: var(--green);
68 | width: 42px;
69 | height: 42px;
70 |
71 | &:hover,
72 | &:focus {
73 | svg {
74 | fill: var(--green-tint);
75 | }
76 | }
77 |
78 | svg {
79 | fill: none;
80 | transition: var(--transition);
81 | user-select: none;
82 | }
83 | }
84 | }
85 | `;
86 |
87 | const StyledLinks = styled.div`
88 | display: flex;
89 | align-items: center;
90 |
91 | @media (max-width: 768px) {
92 | display: none;
93 | }
94 |
95 | ol {
96 | ${({ theme }) => theme.mixins.flexBetween};
97 | padding: 0;
98 | margin: 0;
99 | list-style: none;
100 |
101 | li {
102 | margin: 0 5px;
103 | position: relative;
104 | counter-increment: item 1;
105 | font-size: var(--fz-xs);
106 |
107 | a {
108 | padding: 10px;
109 |
110 | &:before {
111 | content: '0' counter(item) '.';
112 | margin-right: 5px;
113 | color: var(--green);
114 | font-size: var(--fz-xxs);
115 | text-align: right;
116 | }
117 | }
118 | }
119 | }
120 |
121 | .resume-button {
122 | ${({ theme }) => theme.mixins.smallButton};
123 | margin-left: 15px;
124 | font-size: var(--fz-xs);
125 | }
126 | `;
127 |
128 | const Nav = ({ isHome }) => {
129 | const [isMounted, setIsMounted] = useState(!isHome);
130 | const scrollDirection = useScrollDirection('down');
131 | const [scrolledToTop, setScrolledToTop] = useState(true);
132 | const prefersReducedMotion = usePrefersReducedMotion();
133 |
134 | const handleScroll = () => {
135 | setScrolledToTop(window.pageYOffset < 50);
136 | };
137 |
138 | useEffect(() => {
139 | if (prefersReducedMotion) {
140 | return;
141 | }
142 |
143 | const timeout = setTimeout(() => {
144 | setIsMounted(true);
145 | }, 100);
146 |
147 | window.addEventListener('scroll', handleScroll);
148 |
149 | return () => {
150 | clearTimeout(timeout);
151 | window.removeEventListener('scroll', handleScroll);
152 | };
153 | }, []);
154 |
155 | const timeout = isHome ? loaderDelay : 0;
156 | const fadeClass = isHome ? 'fade' : '';
157 | const fadeDownClass = isHome ? 'fadedown' : '';
158 |
159 | const Logo = (
160 |
161 | {isHome ? (
162 |
163 |
164 |
165 | ) : (
166 |
167 |
168 |
169 | )}
170 |
171 | );
172 |
173 | const ResumeLink = (
174 |
175 | Resume
176 |
177 | );
178 |
179 | return (
180 |
181 |
182 | {prefersReducedMotion ? (
183 | <>
184 | {Logo}
185 |
186 |
187 |
188 | {navLinks &&
189 | navLinks.map(({ url, name }, i) => (
190 |
191 | {name}
192 |
193 | ))}
194 |
195 | {ResumeLink}
196 |
197 |
198 |
199 | >
200 | ) : (
201 | <>
202 |
203 | {isMounted && (
204 |
205 | <>{Logo}>
206 |
207 | )}
208 |
209 |
210 |
211 |
212 |
213 | {isMounted &&
214 | navLinks &&
215 | navLinks.map(({ url, name }, i) => (
216 |
217 |
218 | {name}
219 |
220 |
221 | ))}
222 |
223 |
224 |
225 |
226 | {isMounted && (
227 |
228 |
229 | {ResumeLink}
230 |
231 |
232 | )}
233 |
234 |
235 |
236 |
237 | {isMounted && (
238 |
239 |
240 |
241 | )}
242 |
243 | >
244 | )}
245 |
246 |
247 | );
248 | };
249 |
250 | Nav.propTypes = {
251 | isHome: PropTypes.bool,
252 | };
253 |
254 | export default Nav;
255 |
--------------------------------------------------------------------------------
/src/components/sections/about.js:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useRef } from 'react';
2 | import { StaticImage } from 'gatsby-plugin-image';
3 | import styled from 'styled-components';
4 | import { srConfig } from '@config';
5 | import sr from '@utils/sr';
6 | import { usePrefersReducedMotion } from '@hooks';
7 |
8 | const StyledAboutSection = styled.section`
9 | max-width: 900px;
10 |
11 | .inner {
12 | display: grid;
13 | grid-template-columns: 3fr 2fr;
14 | grid-gap: 50px;
15 |
16 | @media (max-width: 768px) {
17 | display: block;
18 | }
19 | }
20 | `;
21 | const StyledText = styled.div`
22 | ul.skills-list {
23 | display: grid;
24 | grid-template-columns: repeat(2, minmax(140px, 200px));
25 | grid-gap: 0 10px;
26 | padding: 0;
27 | margin: 20px 0 0 0;
28 | overflow: hidden;
29 | list-style: none;
30 |
31 | li {
32 | position: relative;
33 | margin-bottom: 10px;
34 | padding-left: 20px;
35 | font-family: var(--font-mono);
36 | font-size: var(--fz-xs);
37 |
38 | &:before {
39 | content: '▹';
40 | position: absolute;
41 | left: 0;
42 | color: var(--green);
43 | font-size: var(--fz-sm);
44 | line-height: 12px;
45 | }
46 | }
47 | }
48 | `;
49 | const StyledPic = styled.div`
50 | position: relative;
51 | max-width: 300px;
52 |
53 | @media (max-width: 768px) {
54 | margin: 50px auto 0;
55 | width: 70%;
56 | }
57 |
58 | .wrapper {
59 | ${({ theme }) => theme.mixins.boxShadow};
60 | display: block;
61 | position: relative;
62 | width: 100%;
63 | border-radius: var(--border-radius);
64 | background-color: var(--green);
65 |
66 | &:hover,
67 | &:focus {
68 | outline: 0;
69 |
70 | &:after {
71 | top: 15px;
72 | left: 15px;
73 | }
74 |
75 | .img {
76 | filter: none;
77 | mix-blend-mode: normal;
78 | }
79 | }
80 |
81 | .img {
82 | position: relative;
83 | border-radius: var(--border-radius);
84 | mix-blend-mode: multiply;
85 | filter: grayscale(100%) contrast(1);
86 | transition: var(--transition);
87 | }
88 |
89 | &:before,
90 | &:after {
91 | content: '';
92 | display: block;
93 | position: absolute;
94 | width: 100%;
95 | height: 100%;
96 | border-radius: var(--border-radius);
97 | transition: var(--transition);
98 | }
99 |
100 | &:before {
101 | top: 0;
102 | left: 0;
103 | background-color: var(--navy);
104 | mix-blend-mode: screen;
105 | }
106 |
107 | &:after {
108 | border: 2px solid var(--green);
109 | top: 20px;
110 | left: 20px;
111 | z-index: -1;
112 | }
113 | }
114 | `;
115 |
116 | const About = () => {
117 | const revealContainer = useRef(null);
118 | const prefersReducedMotion = usePrefersReducedMotion();
119 |
120 | useEffect(() => {
121 | if (prefersReducedMotion) {
122 | return;
123 | }
124 |
125 | sr.reveal(revealContainer.current, srConfig());
126 | }, []);
127 |
128 | const skills = ['Python', 'Scala', 'Django', 'Flutter', 'C', 'Keras', 'Spark', 'SQL'];
129 |
130 | return (
131 |
132 | About Me
133 |
134 |
135 |
136 |
137 |
138 | I finished my bachelors from{' '}
139 |
140 | Nirma University
141 | {' '}
142 | in 2022. I was introduced to Data Science in my 5th semester and have been
143 | interested ever since. Besides studying and programming, I love participating in
144 | debates, extempores or general discussions. On an off day you'll find me bundled up in
145 | a corner reading something.
146 |
147 |
148 |
149 | I am always looking to learn new things. I am currently working on a few projects
150 | related to Natural Language Processing and Machine Learning . At the same
151 | time I am actively on the lookout for remote internships which I can pursue in field
152 | of Data Science.
153 |
154 |
155 |
156 | I am a strong advocate for open source and I am always interested in working on new
157 | projects with new people. Do check out my{' '}
158 |
163 | repositories
164 | {' '}
165 | and feel free to reach out on{' '}
166 |
171 | Whatsapp
172 | {' '}
173 | or{' '}
174 |
175 | email
176 | {' '}
177 | if you would like to collaborate on any project.
178 |
179 |
180 |
Here are a few technologies I’ve been working with recently:
181 |
182 |
183 |
184 | {skills && skills.map((skill, i) => {skill} )}
185 |
186 |
187 |
188 |
189 |
190 |
198 |
199 |
200 |
201 |
202 | );
203 | };
204 |
205 | export default About;
206 |
--------------------------------------------------------------------------------
/src/components/sections/contact.js:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useRef } from 'react';
2 | import styled from 'styled-components';
3 | import { srConfig, email } from '@config';
4 | import sr from '@utils/sr';
5 | import { usePrefersReducedMotion } from '@hooks';
6 |
7 | const StyledContactSection = styled.section`
8 | max-width: 600px;
9 | margin: 0 auto 100px;
10 | text-align: center;
11 |
12 | @media (max-width: 768px) {
13 | margin: 0 auto 50px;
14 | }
15 |
16 | .overline {
17 | display: block;
18 | margin-bottom: 20px;
19 | color: var(--green);
20 | font-family: var(--font-mono);
21 | font-size: var(--fz-md);
22 | font-weight: 400;
23 |
24 | &:before {
25 | bottom: 0;
26 | font-size: var(--fz-sm);
27 | }
28 |
29 | &:after {
30 | display: none;
31 | }
32 | }
33 |
34 | .title {
35 | font-size: clamp(40px, 5vw, 60px);
36 | }
37 |
38 | .email-link {
39 | ${({ theme }) => theme.mixins.bigButton};
40 | margin-top: 50px;
41 | }
42 | `;
43 |
44 | const Contact = () => {
45 | const revealContainer = useRef(null);
46 | const prefersReducedMotion = usePrefersReducedMotion();
47 |
48 | useEffect(() => {
49 | if (prefersReducedMotion) {
50 | return;
51 | }
52 |
53 | sr.reveal(revealContainer.current, srConfig());
54 | }, []);
55 |
56 | return (
57 |
58 | What’s Next?
59 |
60 | Get In Touch
61 |
62 |
63 | My inbox is always open. Whether you have a question or just want to say hello, I'll try my
64 | best to get back to you! Feel free to mail me about any relevant job oppurtunities.
65 |
66 |
67 |
68 | Say Hello
69 |
70 |
71 | );
72 | };
73 |
74 | export default Contact;
75 |
--------------------------------------------------------------------------------
/src/components/sections/featured.js:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useRef } from 'react';
2 | import { useStaticQuery, graphql } from 'gatsby';
3 | import { GatsbyImage, getImage } from 'gatsby-plugin-image';
4 | import styled from 'styled-components';
5 | import sr from '@utils/sr';
6 | import { srConfig } from '@config';
7 | import { Icon } from '@components/icons';
8 | import { usePrefersReducedMotion } from '@hooks';
9 |
10 | const StyledProjectsGrid = styled.ul`
11 | ${({ theme }) => theme.mixins.resetList};
12 |
13 | a {
14 | position: relative;
15 | z-index: 1;
16 | }
17 | `;
18 |
19 | const StyledProject = styled.li`
20 | position: relative;
21 | display: grid;
22 | grid-gap: 10px;
23 | grid-template-columns: repeat(12, 1fr);
24 | align-items: center;
25 |
26 | @media (max-width: 768px) {
27 | ${({ theme }) => theme.mixins.boxShadow};
28 | }
29 |
30 | &:not(:last-of-type) {
31 | margin-bottom: 100px;
32 |
33 | @media (max-width: 768px) {
34 | margin-bottom: 70px;
35 | }
36 |
37 | @media (max-width: 480px) {
38 | margin-bottom: 30px;
39 | }
40 | }
41 |
42 | &:nth-of-type(odd) {
43 | .project-content {
44 | grid-column: 7 / -1;
45 | text-align: right;
46 |
47 | @media (max-width: 1080px) {
48 | grid-column: 5 / -1;
49 | }
50 | @media (max-width: 768px) {
51 | grid-column: 1 / -1;
52 | padding: 40px 40px 30px;
53 | text-align: left;
54 | }
55 | @media (max-width: 480px) {
56 | padding: 25px 25px 20px;
57 | }
58 | }
59 | .project-tech-list {
60 | justify-content: flex-end;
61 |
62 | @media (max-width: 768px) {
63 | justify-content: flex-start;
64 | }
65 |
66 | li {
67 | margin: 0 0 5px 20px;
68 |
69 | @media (max-width: 768px) {
70 | margin: 0 10px 5px 0;
71 | }
72 | }
73 | }
74 | .project-links {
75 | justify-content: flex-end;
76 | margin-left: 0;
77 | margin-right: -10px;
78 |
79 | @media (max-width: 768px) {
80 | justify-content: flex-start;
81 | margin-left: -10px;
82 | margin-right: 0;
83 | }
84 | }
85 | .project-image {
86 | grid-column: 1 / 8;
87 |
88 | @media (max-width: 768px) {
89 | grid-column: 1 / -1;
90 | }
91 | }
92 | }
93 |
94 | .project-content {
95 | position: relative;
96 | grid-column: 1 / 7;
97 | grid-row: 1 / -1;
98 |
99 | @media (max-width: 1080px) {
100 | grid-column: 1 / 9;
101 | }
102 |
103 | @media (max-width: 768px) {
104 | display: flex;
105 | flex-direction: column;
106 | justify-content: center;
107 | height: 100%;
108 | grid-column: 1 / -1;
109 | padding: 40px 40px 30px;
110 | z-index: 5;
111 | }
112 |
113 | @media (max-width: 480px) {
114 | padding: 30px 25px 20px;
115 | }
116 | }
117 |
118 | .project-overline {
119 | margin: 10px 0;
120 | color: var(--green);
121 | font-family: var(--font-mono);
122 | font-size: var(--fz-xs);
123 | font-weight: 400;
124 | }
125 |
126 | .project-title {
127 | color: var(--lightest-slate);
128 | font-size: clamp(24px, 5vw, 28px);
129 |
130 | @media (min-width: 768px) {
131 | margin: 0 0 20px;
132 | }
133 |
134 | @media (max-width: 768px) {
135 | color: var(--white);
136 |
137 | a {
138 | position: static;
139 |
140 | &:before {
141 | content: '';
142 | display: block;
143 | position: absolute;
144 | z-index: 0;
145 | width: 100%;
146 | height: 100%;
147 | top: 0;
148 | left: 0;
149 | }
150 | }
151 | }
152 | }
153 |
154 | .project-description {
155 | ${({ theme }) => theme.mixins.boxShadow};
156 | position: relative;
157 | z-index: 2;
158 | padding: 25px;
159 | border-radius: var(--border-radius);
160 | background-color: var(--light-navy);
161 | color: var(--light-slate);
162 | font-size: var(--fz-lg);
163 |
164 | @media (max-width: 768px) {
165 | padding: 20px 0;
166 | background-color: transparent;
167 | box-shadow: none;
168 |
169 | &:hover {
170 | box-shadow: none;
171 | }
172 | }
173 |
174 | a {
175 | ${({ theme }) => theme.mixins.inlineLink};
176 | }
177 |
178 | strong {
179 | color: var(--white);
180 | font-weight: normal;
181 | }
182 | }
183 |
184 | .project-tech-list {
185 | display: flex;
186 | flex-wrap: wrap;
187 | position: relative;
188 | z-index: 2;
189 | margin: 25px 0 10px;
190 | padding: 0;
191 | list-style: none;
192 |
193 | li {
194 | margin: 0 20px 5px 0;
195 | color: var(--light-slate);
196 | font-family: var(--font-mono);
197 | font-size: var(--fz-xs);
198 | white-space: nowrap;
199 | }
200 |
201 | @media (max-width: 768px) {
202 | margin: 10px 0;
203 |
204 | li {
205 | margin: 0 10px 5px 0;
206 | color: var(--lightest-slate);
207 | }
208 | }
209 | }
210 |
211 | .project-links {
212 | display: flex;
213 | align-items: center;
214 | position: relative;
215 | margin-top: 10px;
216 | margin-left: -10px;
217 | color: var(--lightest-slate);
218 |
219 | a {
220 | ${({ theme }) => theme.mixins.flexCenter};
221 | padding: 10px;
222 |
223 | &.external {
224 | svg {
225 | width: 22px;
226 | height: 22px;
227 | margin-top: -4px;
228 | }
229 | }
230 |
231 | svg {
232 | width: 20px;
233 | height: 20px;
234 | }
235 | }
236 |
237 | .cta {
238 | ${({ theme }) => theme.mixins.smallButton};
239 | margin: 10px;
240 | }
241 | }
242 |
243 | .project-image {
244 | ${({ theme }) => theme.mixins.boxShadow};
245 | grid-column: 6 / -1;
246 | grid-row: 1 / -1;
247 | position: relative;
248 | z-index: 1;
249 |
250 | @media (max-width: 768px) {
251 | grid-column: 1 / -1;
252 | height: 100%;
253 | opacity: 0.25;
254 | }
255 |
256 | a {
257 | width: 100%;
258 | height: 100%;
259 | background-color: var(--green);
260 | border-radius: var(--border-radius);
261 | vertical-align: middle;
262 |
263 | &:hover,
264 | &:focus {
265 | background: transparent;
266 | outline: 0;
267 |
268 | &:before,
269 | .img {
270 | background: transparent;
271 | filter: none;
272 | }
273 | }
274 |
275 | &:before {
276 | content: '';
277 | position: absolute;
278 | width: 100%;
279 | height: 100%;
280 | top: 0;
281 | left: 0;
282 | right: 0;
283 | bottom: 0;
284 | z-index: 3;
285 | transition: var(--transition);
286 | background-color: var(--navy);
287 | mix-blend-mode: screen;
288 | }
289 | }
290 |
291 | .img {
292 | border-radius: var(--border-radius);
293 | mix-blend-mode: multiply;
294 | filter: grayscale(100%) contrast(1) brightness(90%);
295 |
296 | @media (max-width: 768px) {
297 | object-fit: cover;
298 | width: auto;
299 | height: 100%;
300 | filter: grayscale(100%) contrast(1) brightness(50%);
301 | }
302 | }
303 | }
304 | `;
305 |
306 | const Featured = () => {
307 | const data = useStaticQuery(graphql`
308 | {
309 | featured: allMarkdownRemark(
310 | filter: { fileAbsolutePath: { regex: "/content/featured/" } }
311 | sort: { fields: [frontmatter___date], order: ASC }
312 | ) {
313 | edges {
314 | node {
315 | frontmatter {
316 | title
317 | cover {
318 | childImageSharp {
319 | gatsbyImageData(width: 700, placeholder: BLURRED, formats: [AUTO, WEBP, AVIF])
320 | }
321 | }
322 | tech
323 | github
324 | external
325 | cta
326 | }
327 | html
328 | }
329 | }
330 | }
331 | }
332 | `);
333 |
334 | const featuredProjects = data.featured.edges.filter(({ node }) => node);
335 | const revealTitle = useRef(null);
336 | const revealProjects = useRef([]);
337 | const prefersReducedMotion = usePrefersReducedMotion();
338 |
339 | useEffect(() => {
340 | if (prefersReducedMotion) {
341 | return;
342 | }
343 |
344 | sr.reveal(revealTitle.current, srConfig());
345 | revealProjects.current.forEach((ref, i) => sr.reveal(ref, srConfig(i * 100)));
346 | }, []);
347 |
348 | return (
349 |
350 |
351 | Some Things I’ve Built
352 |
353 |
354 |
355 | {featuredProjects &&
356 | featuredProjects.map(({ node }, i) => {
357 | const { frontmatter, html } = node;
358 | const { external, title, tech, github, cover, cta } = frontmatter;
359 | const image = getImage(cover);
360 |
361 | return (
362 | (revealProjects.current[i] = el)}>
363 |
364 |
365 |
Featured Project
366 |
367 |
370 |
371 |
375 |
376 | {tech.length && (
377 |
378 | {tech.map((tech, i) => (
379 | {tech}
380 | ))}
381 |
382 | )}
383 |
384 |
401 |
402 |
403 |
404 |
409 |
410 | );
411 | })}
412 |
413 |
414 | );
415 | };
416 |
417 | export default Featured;
418 |
--------------------------------------------------------------------------------
/src/components/sections/hero.js:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect } from 'react';
2 | import { CSSTransition, TransitionGroup } from 'react-transition-group';
3 | import styled from 'styled-components';
4 | import { navDelay, loaderDelay } from '@utils';
5 | import { usePrefersReducedMotion } from '@hooks';
6 | // import { email } from '@config';
7 |
8 | const StyledHeroSection = styled.section`
9 | ${({ theme }) => theme.mixins.flexCenter};
10 | flex-direction: column;
11 | align-items: flex-start;
12 | min-height: 100vh;
13 | padding: 0;
14 |
15 | @media (max-width: 480px) and (min-height: 700px) {
16 | padding-bottom: 10vh;
17 | }
18 |
19 | h1 {
20 | margin: 0 0 30px 4px;
21 | color: var(--green);
22 | font-family: var(--font-mono);
23 | font-size: clamp(var(--fz-sm), 5vw, var(--fz-md));
24 | font-weight: 400;
25 |
26 | @media (max-width: 480px) {
27 | margin: 0 0 20px 2px;
28 | }
29 | }
30 |
31 | h3 {
32 | margin-top: 10px;
33 | color: var(--slate);
34 | line-height: 0.9;
35 | }
36 |
37 | p {
38 | margin: 20px 0 0;
39 | max-width: 540px;
40 | }
41 |
42 | .email-link {
43 | ${({ theme }) => theme.mixins.bigButton};
44 | margin-top: 50px;
45 | }
46 | `;
47 |
48 | const Hero = () => {
49 | const [isMounted, setIsMounted] = useState(false);
50 | const prefersReducedMotion = usePrefersReducedMotion();
51 |
52 | useEffect(() => {
53 | if (prefersReducedMotion) {
54 | return;
55 | }
56 |
57 | const timeout = setTimeout(() => setIsMounted(true), navDelay);
58 | return () => clearTimeout(timeout);
59 | }, []);
60 |
61 | const one = Hi, my name is ;
62 | const two = Parth Desai. ;
63 | const three = I design and code simple things. ;
64 | const four = (
65 | <>
66 |
67 | I am an India based Data Engineer with a bachelors in Computer Science. I am passionate
68 | about Data Science and Automation. I am also fascinated with Mathematics and wish to make a
69 | career out of it someday. Currently, I’m focused on building data pipelines and automating
70 | them at{' '}
71 |
76 | Accenture
77 |
78 | .
79 |
80 | >
81 | );
82 | const five = (
83 |
89 | Contact me
90 |
91 | );
92 |
93 | const items = [one, two, three, four, five];
94 |
95 | return (
96 |
97 | {prefersReducedMotion ? (
98 | <>
99 | {items.map((item, i) => (
100 | {item}
101 | ))}
102 | >
103 | ) : (
104 |
105 | {isMounted &&
106 | items.map((item, i) => (
107 |
108 | {item}
109 |
110 | ))}
111 |
112 | )}
113 |
114 | );
115 | };
116 |
117 | export default Hero;
118 |
--------------------------------------------------------------------------------
/src/components/sections/jobs.js:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect, useRef } from 'react';
2 | import { useStaticQuery, graphql } from 'gatsby';
3 | import { CSSTransition } from 'react-transition-group';
4 | import styled from 'styled-components';
5 | import { srConfig } from '@config';
6 | import { KEY_CODES } from '@utils';
7 | import sr from '@utils/sr';
8 | import { usePrefersReducedMotion } from '@hooks';
9 |
10 | const StyledJobsSection = styled.section`
11 | max-width: 700px;
12 |
13 | .inner {
14 | display: flex;
15 |
16 | @media (max-width: 600px) {
17 | display: block;
18 | }
19 |
20 | // Prevent container from jumping
21 | @media (min-width: 700px) {
22 | min-height: 340px;
23 | }
24 | }
25 | `;
26 |
27 | const StyledTabList = styled.div`
28 | position: relative;
29 | z-index: 3;
30 | width: max-content;
31 | padding: 0;
32 | margin: 0;
33 | list-style: none;
34 |
35 | @media (max-width: 600px) {
36 | display: flex;
37 | overflow-x: auto;
38 | width: calc(100% + 100px);
39 | padding-left: 50px;
40 | margin-left: -50px;
41 | margin-bottom: 30px;
42 | }
43 | @media (max-width: 480px) {
44 | width: calc(100% + 50px);
45 | padding-left: 25px;
46 | margin-left: -25px;
47 | }
48 |
49 | li {
50 | &:first-of-type {
51 | @media (max-width: 600px) {
52 | margin-left: 50px;
53 | }
54 | @media (max-width: 480px) {
55 | margin-left: 25px;
56 | }
57 | }
58 | &:last-of-type {
59 | @media (max-width: 600px) {
60 | padding-right: 50px;
61 | }
62 | @media (max-width: 480px) {
63 | padding-right: 25px;
64 | }
65 | }
66 | }
67 | `;
68 |
69 | const StyledTabButton = styled.button`
70 | ${({ theme }) => theme.mixins.link};
71 | display: flex;
72 | align-items: center;
73 | width: 100%;
74 | height: var(--tab-height);
75 | padding: 0 20px 2px;
76 | border-left: 2px solid var(--lightest-navy);
77 | background-color: transparent;
78 | color: ${({ isActive }) => (isActive ? 'var(--green)' : 'var(--slate)')};
79 | font-family: var(--font-mono);
80 | font-size: var(--fz-xs);
81 | text-align: left;
82 | white-space: nowrap;
83 |
84 | @media (max-width: 768px) {
85 | padding: 0 15px 2px;
86 | }
87 | @media (max-width: 600px) {
88 | ${({ theme }) => theme.mixins.flexCenter};
89 | min-width: 120px;
90 | padding: 0 15px;
91 | border-left: 0;
92 | border-bottom: 2px solid var(--lightest-navy);
93 | text-align: center;
94 | }
95 |
96 | &:hover,
97 | &:focus {
98 | background-color: var(--light-navy);
99 | }
100 | `;
101 |
102 | const StyledHighlight = styled.div`
103 | position: absolute;
104 | top: 0;
105 | left: 0;
106 | z-index: 10;
107 | width: 2px;
108 | height: var(--tab-height);
109 | border-radius: var(--border-radius);
110 | background: var(--green);
111 | transform: translateY(calc(${({ activeTabId }) => activeTabId} * var(--tab-height)));
112 | transition: transform 0.25s cubic-bezier(0.645, 0.045, 0.355, 1);
113 | transition-delay: 0.1s;
114 |
115 | @media (max-width: 600px) {
116 | top: auto;
117 | bottom: 0;
118 | width: 100%;
119 | max-width: var(--tab-width);
120 | height: 2px;
121 | margin-left: 50px;
122 | transform: translateX(calc(${({ activeTabId }) => activeTabId} * var(--tab-width)));
123 | }
124 | @media (max-width: 480px) {
125 | margin-left: 25px;
126 | }
127 | `;
128 |
129 | const StyledTabPanels = styled.div`
130 | position: relative;
131 | width: 100%;
132 | margin-left: 20px;
133 |
134 | @media (max-width: 600px) {
135 | margin-left: 0;
136 | }
137 | `;
138 |
139 | const StyledTabPanel = styled.div`
140 | width: 100%;
141 | height: auto;
142 | padding: 10px 5px;
143 |
144 | ul {
145 | ${({ theme }) => theme.mixins.fancyList};
146 | }
147 |
148 | h3 {
149 | margin-bottom: 2px;
150 | font-size: var(--fz-xxl);
151 | font-weight: 500;
152 | line-height: 1.3;
153 |
154 | .company {
155 | color: var(--green);
156 | }
157 | }
158 |
159 | .range {
160 | margin-bottom: 25px;
161 | color: var(--light-slate);
162 | font-family: var(--font-mono);
163 | font-size: var(--fz-xs);
164 | }
165 | `;
166 |
167 | const Jobs = () => {
168 | const data = useStaticQuery(graphql`
169 | query {
170 | jobs: allMarkdownRemark(
171 | filter: { fileAbsolutePath: { regex: "/content/jobs/" } }
172 | sort: { fields: [frontmatter___date], order: DESC }
173 | ) {
174 | edges {
175 | node {
176 | frontmatter {
177 | title
178 | company
179 | location
180 | range
181 | url
182 | }
183 | html
184 | }
185 | }
186 | }
187 | }
188 | `);
189 |
190 | const jobsData = data.jobs.edges;
191 |
192 | const [activeTabId, setActiveTabId] = useState(0);
193 | const [tabFocus, setTabFocus] = useState(null);
194 | const tabs = useRef([]);
195 | const revealContainer = useRef(null);
196 | const prefersReducedMotion = usePrefersReducedMotion();
197 |
198 | useEffect(() => {
199 | if (prefersReducedMotion) {
200 | return;
201 | }
202 |
203 | sr.reveal(revealContainer.current, srConfig());
204 | }, []);
205 |
206 | const focusTab = () => {
207 | if (tabs.current[tabFocus]) {
208 | tabs.current[tabFocus].focus();
209 | return;
210 | }
211 | // If we're at the end, go to the start
212 | if (tabFocus >= tabs.current.length) {
213 | setTabFocus(0);
214 | }
215 | // If we're at the start, move to the end
216 | if (tabFocus < 0) {
217 | setTabFocus(tabs.current.length - 1);
218 | }
219 | };
220 |
221 | // Only re-run the effect if tabFocus changes
222 | useEffect(() => focusTab(), [tabFocus]);
223 |
224 | // Focus on tabs when using up & down arrow keys
225 | const onKeyDown = e => {
226 | switch (e.key) {
227 | case KEY_CODES.ARROW_UP: {
228 | e.preventDefault();
229 | setTabFocus(tabFocus - 1);
230 | break;
231 | }
232 |
233 | case KEY_CODES.ARROW_DOWN: {
234 | e.preventDefault();
235 | setTabFocus(tabFocus + 1);
236 | break;
237 | }
238 |
239 | default: {
240 | break;
241 | }
242 | }
243 | };
244 |
245 | return (
246 |
247 | Where I’ve Worked
248 |
249 |
250 |
onKeyDown(e)}>
251 | {jobsData &&
252 | jobsData.map(({ node }, i) => {
253 | const { company } = node.frontmatter;
254 | return (
255 | setActiveTabId(i)}
259 | ref={el => (tabs.current[i] = el)}
260 | id={`tab-${i}`}
261 | role="tab"
262 | tabIndex={activeTabId === i ? '0' : '-1'}
263 | aria-selected={activeTabId === i ? true : false}
264 | aria-controls={`panel-${i}`}>
265 | {company}
266 |
267 | );
268 | })}
269 |
270 |
271 |
272 |
273 | {jobsData &&
274 | jobsData.map(({ node }, i) => {
275 | const { frontmatter, html } = node;
276 | const { title, url, company, range } = frontmatter;
277 |
278 | return (
279 |
280 |
287 |
288 | {title}
289 |
290 | @
291 |
292 | {company}
293 |
294 |
295 |
296 |
297 | {range}
298 |
299 |
300 |
301 |
302 | );
303 | })}
304 |
305 |
306 |
307 | );
308 | };
309 |
310 | export default Jobs;
311 |
--------------------------------------------------------------------------------
/src/components/sections/projects.js:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect, useRef } from 'react';
2 | import { Link, useStaticQuery, graphql } from 'gatsby';
3 | import { CSSTransition, TransitionGroup } from 'react-transition-group';
4 | import styled from 'styled-components';
5 | import { srConfig } from '@config';
6 | import sr from '@utils/sr';
7 | import { Icon } from '@components/icons';
8 | import { usePrefersReducedMotion } from '@hooks';
9 |
10 | const StyledProjectsSection = styled.section`
11 | display: flex;
12 | flex-direction: column;
13 | align-items: center;
14 |
15 | h2 {
16 | font-size: clamp(24px, 5vw, var(--fz-heading));
17 | }
18 |
19 | .archive-link {
20 | font-family: var(--font-mono);
21 | font-size: var(--fz-sm);
22 | &:after {
23 | bottom: 0.1em;
24 | }
25 | }
26 |
27 | .projects-grid {
28 | ${({ theme }) => theme.mixins.resetList};
29 | display: grid;
30 | grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
31 | grid-gap: 15px;
32 | position: relative;
33 | margin-top: 50px;
34 |
35 | @media (max-width: 1080px) {
36 | grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
37 | }
38 | }
39 |
40 | .more-button {
41 | ${({ theme }) => theme.mixins.button};
42 | margin: 80px auto 0;
43 | }
44 | `;
45 |
46 | const StyledProject = styled.li`
47 | position: relative;
48 | cursor: default;
49 | transition: var(--transition);
50 |
51 | @media (prefers-reduced-motion: no-preference) {
52 | &:hover,
53 | &:focus-within {
54 | .project-inner {
55 | transform: translateY(-7px);
56 | }
57 | }
58 | }
59 |
60 | a {
61 | position: relative;
62 | z-index: 1;
63 | }
64 |
65 | .project-inner {
66 | ${({ theme }) => theme.mixins.boxShadow};
67 | ${({ theme }) => theme.mixins.flexBetween};
68 | flex-direction: column;
69 | align-items: flex-start;
70 | position: relative;
71 | height: 100%;
72 | padding: 2rem 1.75rem;
73 | border-radius: var(--border-radius);
74 | background-color: var(--light-navy);
75 | transition: var(--transition);
76 | overflow: auto;
77 | }
78 |
79 | .project-top {
80 | ${({ theme }) => theme.mixins.flexBetween};
81 | margin-bottom: 35px;
82 |
83 | .folder {
84 | color: var(--green);
85 | svg {
86 | width: 40px;
87 | height: 40px;
88 | }
89 | }
90 |
91 | .project-links {
92 | display: flex;
93 | align-items: center;
94 | margin-right: -10px;
95 | color: var(--light-slate);
96 |
97 | a {
98 | ${({ theme }) => theme.mixins.flexCenter};
99 | padding: 5px 7px;
100 |
101 | &.external {
102 | svg {
103 | width: 22px;
104 | height: 22px;
105 | margin-top: -4px;
106 | }
107 | }
108 |
109 | svg {
110 | width: 20px;
111 | height: 20px;
112 | }
113 | }
114 | }
115 | }
116 |
117 | .project-title {
118 | margin: 0 0 10px;
119 | color: var(--lightest-slate);
120 | font-size: var(--fz-xxl);
121 |
122 | a {
123 | position: static;
124 |
125 | &:before {
126 | content: '';
127 | display: block;
128 | position: absolute;
129 | z-index: 0;
130 | width: 100%;
131 | height: 100%;
132 | top: 0;
133 | left: 0;
134 | }
135 | }
136 | }
137 |
138 | .project-description {
139 | color: var(--light-slate);
140 | font-size: 17px;
141 |
142 | a {
143 | ${({ theme }) => theme.mixins.inlineLink};
144 | }
145 | }
146 |
147 | .project-tech-list {
148 | display: flex;
149 | align-items: flex-end;
150 | flex-grow: 1;
151 | flex-wrap: wrap;
152 | padding: 0;
153 | margin: 20px 0 0 0;
154 | list-style: none;
155 |
156 | li {
157 | font-family: var(--font-mono);
158 | font-size: var(--fz-xxs);
159 | line-height: 1.75;
160 |
161 | &:not(:last-of-type) {
162 | margin-right: 15px;
163 | }
164 | }
165 | }
166 | `;
167 |
168 | const Projects = () => {
169 | const data = useStaticQuery(graphql`
170 | query {
171 | projects: allMarkdownRemark(
172 | filter: {
173 | fileAbsolutePath: { regex: "/content/projects/" }
174 | frontmatter: { showInProjects: { ne: false } }
175 | }
176 | sort: { fields: [frontmatter___date], order: DESC }
177 | ) {
178 | edges {
179 | node {
180 | frontmatter {
181 | title
182 | tech
183 | github
184 | external
185 | }
186 | html
187 | }
188 | }
189 | }
190 | }
191 | `);
192 |
193 | const [showMore, setShowMore] = useState(false);
194 | const revealTitle = useRef(null);
195 | const revealArchiveLink = useRef(null);
196 | const revealProjects = useRef([]);
197 | const prefersReducedMotion = usePrefersReducedMotion();
198 |
199 | useEffect(() => {
200 | if (prefersReducedMotion) {
201 | return;
202 | }
203 |
204 | sr.reveal(revealTitle.current, srConfig());
205 | sr.reveal(revealArchiveLink.current, srConfig());
206 | revealProjects.current.forEach((ref, i) => sr.reveal(ref, srConfig(i * 100)));
207 | }, []);
208 |
209 | const GRID_LIMIT = 6;
210 | const projects = data.projects.edges.filter(({ node }) => node);
211 | const firstSix = projects.slice(0, GRID_LIMIT);
212 | const projectsToShow = showMore ? projects : firstSix;
213 |
214 | const projectInner = node => {
215 | const { frontmatter, html } = node;
216 | const { github, external, title, tech } = frontmatter;
217 |
218 | return (
219 |
220 |
221 |
222 |
223 |
224 |
225 |
242 |
243 |
244 |
249 |
250 |
251 |
252 |
253 |
254 | {tech && (
255 |
256 | {tech.map((tech, i) => (
257 | {tech}
258 | ))}
259 |
260 | )}
261 |
262 |
263 | );
264 | };
265 |
266 | return (
267 |
268 | Other Noteworthy Projects
269 |
270 |
271 | view the archive
272 |
273 |
274 |
275 | {prefersReducedMotion ? (
276 | <>
277 | {projectsToShow &&
278 | projectsToShow.map(({ node }, i) => (
279 | {projectInner(node)}
280 | ))}
281 | >
282 | ) : (
283 |
284 | {projectsToShow &&
285 | projectsToShow.map(({ node }, i) => (
286 | = GRID_LIMIT ? (i - GRID_LIMIT) * 300 : 300}
290 | exit={false}>
291 | (revealProjects.current[i] = el)}
294 | style={{
295 | transitionDelay: `${i >= GRID_LIMIT ? (i - GRID_LIMIT) * 100 : 0}ms`,
296 | }}>
297 | {projectInner(node)}
298 |
299 |
300 | ))}
301 |
302 | )}
303 |
304 |
305 | setShowMore(!showMore)}>
306 | Show {showMore ? 'Less' : 'More'}
307 |
308 |
309 | );
310 | };
311 |
312 | export default Projects;
313 |
--------------------------------------------------------------------------------
/src/components/side.js:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect } from 'react';
2 | import PropTypes from 'prop-types';
3 | import { CSSTransition, TransitionGroup } from 'react-transition-group';
4 | import styled from 'styled-components';
5 | import { loaderDelay } from '@utils';
6 | import { usePrefersReducedMotion } from '@hooks';
7 |
8 | const StyledSideElement = styled.div`
9 | width: 40px;
10 | position: fixed;
11 | bottom: 0;
12 | left: ${props => (props.orientation === 'left' ? '40px' : 'auto')};
13 | right: ${props => (props.orientation === 'left' ? 'auto' : '40px')};
14 | z-index: 10;
15 | color: var(--light-slate);
16 |
17 | @media (max-width: 1080px) {
18 | left: ${props => (props.orientation === 'left' ? '20px' : 'auto')};
19 | right: ${props => (props.orientation === 'left' ? 'auto' : '20px')};
20 | }
21 |
22 | @media (max-width: 768px) {
23 | display: none;
24 | }
25 | `;
26 |
27 | const Side = ({ children, isHome, orientation }) => {
28 | const [isMounted, setIsMounted] = useState(!isHome);
29 | const prefersReducedMotion = usePrefersReducedMotion();
30 |
31 | useEffect(() => {
32 | if (!isHome || prefersReducedMotion) {
33 | return;
34 | }
35 | const timeout = setTimeout(() => setIsMounted(true), loaderDelay);
36 | return () => clearTimeout(timeout);
37 | }, []);
38 |
39 | return (
40 |
41 | {prefersReducedMotion ? (
42 | <>{children}>
43 | ) : (
44 |
45 | {isMounted && (
46 |
47 | {children}
48 |
49 | )}
50 |
51 | )}
52 |
53 | );
54 | };
55 |
56 | Side.propTypes = {
57 | children: PropTypes.node.isRequired,
58 | isHome: PropTypes.bool,
59 | orientation: PropTypes.string,
60 | };
61 |
62 | export default Side;
63 |
--------------------------------------------------------------------------------
/src/components/social.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import styled from 'styled-components';
4 | import { socialMedia } from '@config';
5 | import { Side } from '@components';
6 | import { Icon } from '@components/icons';
7 |
8 | const StyledSocialList = styled.ul`
9 | display: flex;
10 | flex-direction: column;
11 | align-items: center;
12 | margin: 0;
13 | padding: 0;
14 | list-style: none;
15 |
16 | &:after {
17 | content: '';
18 | display: block;
19 | width: 1px;
20 | height: 90px;
21 | margin: 0 auto;
22 | background-color: var(--light-slate);
23 | }
24 |
25 | li {
26 | &:last-of-type {
27 | margin-bottom: 20px;
28 | }
29 |
30 | a {
31 | padding: 10px;
32 |
33 | &:hover,
34 | &:focus {
35 | transform: translateY(-3px);
36 | }
37 |
38 | svg {
39 | width: 20px;
40 | height: 20px;
41 | }
42 | }
43 | }
44 | `;
45 |
46 | const Social = ({ isHome }) => (
47 |
48 |
49 | {socialMedia &&
50 | socialMedia.map(({ url, name }, i) => (
51 |
52 |
53 |
54 |
55 |
56 | ))}
57 |
58 |
59 | );
60 |
61 | Social.propTypes = {
62 | isHome: PropTypes.bool,
63 | };
64 |
65 | export default Social;
66 |
--------------------------------------------------------------------------------
/src/config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | email: 'desaiparth2000@gmail.com',
3 |
4 | socialMedia: [
5 | {
6 | name: 'GitHub',
7 | url: 'https://github.com/pycoder2000',
8 | },
9 | {
10 | name: 'Twitter',
11 | url: 'https://twitter.com/lone_Musk',
12 | },
13 | {
14 | name: 'Linkedin',
15 | url: 'https://www.linkedin.com/in/parth-desai-2bb1b0160/',
16 | },
17 | ],
18 |
19 | navLinks: [
20 | {
21 | name: 'About',
22 | url: '/#about',
23 | },
24 | {
25 | name: 'Experience',
26 | url: '/#jobs',
27 | },
28 | {
29 | name: 'Work',
30 | url: '/#projects',
31 | },
32 | {
33 | name: 'Contact',
34 | url: '/#contact',
35 | },
36 | {
37 | name: 'Blog',
38 | url: 'https://musing.vercel.app/',
39 | },
40 | ],
41 |
42 | colors: {
43 | green: '#64ffda',
44 | navy: '#0a192f',
45 | darkNavy: '#020c1b',
46 | },
47 |
48 | srConfig: (delay = 200, viewFactor = 0.25) => ({
49 | origin: 'bottom',
50 | distance: '20px',
51 | duration: 500,
52 | delay,
53 | rotate: { x: 0, y: 0, z: 0 },
54 | opacity: 0,
55 | scale: 1,
56 | easing: 'cubic-bezier(0.645, 0.045, 0.355, 1)',
57 | mobile: true,
58 | reset: false,
59 | useDelay: 'always',
60 | viewFactor,
61 | viewOffset: { top: 0, right: 0, bottom: 0, left: 0 },
62 | }),
63 | };
64 |
--------------------------------------------------------------------------------
/src/fonts/Calibre/Calibre-Light.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pycoder2000/portfolio-v4/108af3f210879860f83bea6ab9e70b93e5286769/src/fonts/Calibre/Calibre-Light.ttf
--------------------------------------------------------------------------------
/src/fonts/Calibre/Calibre-Light.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pycoder2000/portfolio-v4/108af3f210879860f83bea6ab9e70b93e5286769/src/fonts/Calibre/Calibre-Light.woff
--------------------------------------------------------------------------------
/src/fonts/Calibre/Calibre-Light.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pycoder2000/portfolio-v4/108af3f210879860f83bea6ab9e70b93e5286769/src/fonts/Calibre/Calibre-Light.woff2
--------------------------------------------------------------------------------
/src/fonts/Calibre/Calibre-LightItalic.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pycoder2000/portfolio-v4/108af3f210879860f83bea6ab9e70b93e5286769/src/fonts/Calibre/Calibre-LightItalic.ttf
--------------------------------------------------------------------------------
/src/fonts/Calibre/Calibre-LightItalic.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pycoder2000/portfolio-v4/108af3f210879860f83bea6ab9e70b93e5286769/src/fonts/Calibre/Calibre-LightItalic.woff
--------------------------------------------------------------------------------
/src/fonts/Calibre/Calibre-LightItalic.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pycoder2000/portfolio-v4/108af3f210879860f83bea6ab9e70b93e5286769/src/fonts/Calibre/Calibre-LightItalic.woff2
--------------------------------------------------------------------------------
/src/fonts/Calibre/Calibre-Medium.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pycoder2000/portfolio-v4/108af3f210879860f83bea6ab9e70b93e5286769/src/fonts/Calibre/Calibre-Medium.ttf
--------------------------------------------------------------------------------
/src/fonts/Calibre/Calibre-Medium.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pycoder2000/portfolio-v4/108af3f210879860f83bea6ab9e70b93e5286769/src/fonts/Calibre/Calibre-Medium.woff
--------------------------------------------------------------------------------
/src/fonts/Calibre/Calibre-Medium.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pycoder2000/portfolio-v4/108af3f210879860f83bea6ab9e70b93e5286769/src/fonts/Calibre/Calibre-Medium.woff2
--------------------------------------------------------------------------------
/src/fonts/Calibre/Calibre-MediumItalic.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pycoder2000/portfolio-v4/108af3f210879860f83bea6ab9e70b93e5286769/src/fonts/Calibre/Calibre-MediumItalic.ttf
--------------------------------------------------------------------------------
/src/fonts/Calibre/Calibre-MediumItalic.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pycoder2000/portfolio-v4/108af3f210879860f83bea6ab9e70b93e5286769/src/fonts/Calibre/Calibre-MediumItalic.woff
--------------------------------------------------------------------------------
/src/fonts/Calibre/Calibre-MediumItalic.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pycoder2000/portfolio-v4/108af3f210879860f83bea6ab9e70b93e5286769/src/fonts/Calibre/Calibre-MediumItalic.woff2
--------------------------------------------------------------------------------
/src/fonts/Calibre/Calibre-Regular.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pycoder2000/portfolio-v4/108af3f210879860f83bea6ab9e70b93e5286769/src/fonts/Calibre/Calibre-Regular.ttf
--------------------------------------------------------------------------------
/src/fonts/Calibre/Calibre-Regular.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pycoder2000/portfolio-v4/108af3f210879860f83bea6ab9e70b93e5286769/src/fonts/Calibre/Calibre-Regular.woff
--------------------------------------------------------------------------------
/src/fonts/Calibre/Calibre-Regular.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pycoder2000/portfolio-v4/108af3f210879860f83bea6ab9e70b93e5286769/src/fonts/Calibre/Calibre-Regular.woff2
--------------------------------------------------------------------------------
/src/fonts/Calibre/Calibre-RegularItalic.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pycoder2000/portfolio-v4/108af3f210879860f83bea6ab9e70b93e5286769/src/fonts/Calibre/Calibre-RegularItalic.ttf
--------------------------------------------------------------------------------
/src/fonts/Calibre/Calibre-RegularItalic.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pycoder2000/portfolio-v4/108af3f210879860f83bea6ab9e70b93e5286769/src/fonts/Calibre/Calibre-RegularItalic.woff
--------------------------------------------------------------------------------
/src/fonts/Calibre/Calibre-RegularItalic.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pycoder2000/portfolio-v4/108af3f210879860f83bea6ab9e70b93e5286769/src/fonts/Calibre/Calibre-RegularItalic.woff2
--------------------------------------------------------------------------------
/src/fonts/Calibre/Calibre-Semibold.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pycoder2000/portfolio-v4/108af3f210879860f83bea6ab9e70b93e5286769/src/fonts/Calibre/Calibre-Semibold.ttf
--------------------------------------------------------------------------------
/src/fonts/Calibre/Calibre-Semibold.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pycoder2000/portfolio-v4/108af3f210879860f83bea6ab9e70b93e5286769/src/fonts/Calibre/Calibre-Semibold.woff
--------------------------------------------------------------------------------
/src/fonts/Calibre/Calibre-Semibold.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pycoder2000/portfolio-v4/108af3f210879860f83bea6ab9e70b93e5286769/src/fonts/Calibre/Calibre-Semibold.woff2
--------------------------------------------------------------------------------
/src/fonts/Calibre/Calibre-SemiboldItalic.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pycoder2000/portfolio-v4/108af3f210879860f83bea6ab9e70b93e5286769/src/fonts/Calibre/Calibre-SemiboldItalic.ttf
--------------------------------------------------------------------------------
/src/fonts/Calibre/Calibre-SemiboldItalic.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pycoder2000/portfolio-v4/108af3f210879860f83bea6ab9e70b93e5286769/src/fonts/Calibre/Calibre-SemiboldItalic.woff
--------------------------------------------------------------------------------
/src/fonts/Calibre/Calibre-SemiboldItalic.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pycoder2000/portfolio-v4/108af3f210879860f83bea6ab9e70b93e5286769/src/fonts/Calibre/Calibre-SemiboldItalic.woff2
--------------------------------------------------------------------------------
/src/fonts/SFMono/SFMono-Medium.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pycoder2000/portfolio-v4/108af3f210879860f83bea6ab9e70b93e5286769/src/fonts/SFMono/SFMono-Medium.ttf
--------------------------------------------------------------------------------
/src/fonts/SFMono/SFMono-Medium.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pycoder2000/portfolio-v4/108af3f210879860f83bea6ab9e70b93e5286769/src/fonts/SFMono/SFMono-Medium.woff
--------------------------------------------------------------------------------
/src/fonts/SFMono/SFMono-Medium.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pycoder2000/portfolio-v4/108af3f210879860f83bea6ab9e70b93e5286769/src/fonts/SFMono/SFMono-Medium.woff2
--------------------------------------------------------------------------------
/src/fonts/SFMono/SFMono-MediumItalic.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pycoder2000/portfolio-v4/108af3f210879860f83bea6ab9e70b93e5286769/src/fonts/SFMono/SFMono-MediumItalic.ttf
--------------------------------------------------------------------------------
/src/fonts/SFMono/SFMono-MediumItalic.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pycoder2000/portfolio-v4/108af3f210879860f83bea6ab9e70b93e5286769/src/fonts/SFMono/SFMono-MediumItalic.woff
--------------------------------------------------------------------------------
/src/fonts/SFMono/SFMono-MediumItalic.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pycoder2000/portfolio-v4/108af3f210879860f83bea6ab9e70b93e5286769/src/fonts/SFMono/SFMono-MediumItalic.woff2
--------------------------------------------------------------------------------
/src/fonts/SFMono/SFMono-Regular.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pycoder2000/portfolio-v4/108af3f210879860f83bea6ab9e70b93e5286769/src/fonts/SFMono/SFMono-Regular.ttf
--------------------------------------------------------------------------------
/src/fonts/SFMono/SFMono-Regular.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pycoder2000/portfolio-v4/108af3f210879860f83bea6ab9e70b93e5286769/src/fonts/SFMono/SFMono-Regular.woff
--------------------------------------------------------------------------------
/src/fonts/SFMono/SFMono-Regular.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pycoder2000/portfolio-v4/108af3f210879860f83bea6ab9e70b93e5286769/src/fonts/SFMono/SFMono-Regular.woff2
--------------------------------------------------------------------------------
/src/fonts/SFMono/SFMono-RegularItalic.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pycoder2000/portfolio-v4/108af3f210879860f83bea6ab9e70b93e5286769/src/fonts/SFMono/SFMono-RegularItalic.ttf
--------------------------------------------------------------------------------
/src/fonts/SFMono/SFMono-RegularItalic.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pycoder2000/portfolio-v4/108af3f210879860f83bea6ab9e70b93e5286769/src/fonts/SFMono/SFMono-RegularItalic.woff
--------------------------------------------------------------------------------
/src/fonts/SFMono/SFMono-RegularItalic.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pycoder2000/portfolio-v4/108af3f210879860f83bea6ab9e70b93e5286769/src/fonts/SFMono/SFMono-RegularItalic.woff2
--------------------------------------------------------------------------------
/src/fonts/SFMono/SFMono-Semibold.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pycoder2000/portfolio-v4/108af3f210879860f83bea6ab9e70b93e5286769/src/fonts/SFMono/SFMono-Semibold.ttf
--------------------------------------------------------------------------------
/src/fonts/SFMono/SFMono-Semibold.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pycoder2000/portfolio-v4/108af3f210879860f83bea6ab9e70b93e5286769/src/fonts/SFMono/SFMono-Semibold.woff
--------------------------------------------------------------------------------
/src/fonts/SFMono/SFMono-Semibold.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pycoder2000/portfolio-v4/108af3f210879860f83bea6ab9e70b93e5286769/src/fonts/SFMono/SFMono-Semibold.woff2
--------------------------------------------------------------------------------
/src/fonts/SFMono/SFMono-SemiboldItalic.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pycoder2000/portfolio-v4/108af3f210879860f83bea6ab9e70b93e5286769/src/fonts/SFMono/SFMono-SemiboldItalic.ttf
--------------------------------------------------------------------------------
/src/fonts/SFMono/SFMono-SemiboldItalic.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pycoder2000/portfolio-v4/108af3f210879860f83bea6ab9e70b93e5286769/src/fonts/SFMono/SFMono-SemiboldItalic.woff
--------------------------------------------------------------------------------
/src/fonts/SFMono/SFMono-SemiboldItalic.woff2:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pycoder2000/portfolio-v4/108af3f210879860f83bea6ab9e70b93e5286769/src/fonts/SFMono/SFMono-SemiboldItalic.woff2
--------------------------------------------------------------------------------
/src/hooks/index.js:
--------------------------------------------------------------------------------
1 | export { default as useOnClickOutside } from './useOnClickOutside';
2 | export { default as usePrefersReducedMotion } from './usePrefersReducedMotion';
3 | export { default as useScrollDirection } from './useScrollDirection';
4 |
--------------------------------------------------------------------------------
/src/hooks/useOnClickOutside.js:
--------------------------------------------------------------------------------
1 | import { useEffect } from 'react';
2 |
3 | // https://usehooks.com/useOnClickOutside/
4 |
5 | const useOnClickOutside = (ref, handler) => {
6 | useEffect(
7 | () => {
8 | const listener = event => {
9 | // Do nothing if clicking ref's element or descendent elements
10 | if (!ref.current || ref.current.contains(event.target)) {
11 | return;
12 | }
13 |
14 | handler(event);
15 | };
16 |
17 | document.addEventListener('mousedown', listener);
18 | document.addEventListener('touchstart', listener);
19 |
20 | return () => {
21 | document.removeEventListener('mousedown', listener);
22 | document.removeEventListener('touchstart', listener);
23 | };
24 | },
25 | // Add ref and handler to effect dependencies
26 | // It's worth noting that because passed in handler is a new ...
27 | // ... function on every render that will cause this effect ...
28 | // ... callback/cleanup to run every render. It's not a big deal ...
29 | // ... but to optimize you can wrap handler in useCallback before ...
30 | // ... passing it into this hook.
31 | [ref, handler],
32 | );
33 | };
34 |
35 | export default useOnClickOutside;
36 |
--------------------------------------------------------------------------------
/src/hooks/usePrefersReducedMotion.js:
--------------------------------------------------------------------------------
1 | /**
2 | * https://www.joshwcomeau.com/snippets/react-hooks/use-prefers-reduced-motion/
3 | */
4 |
5 | import { useState, useEffect } from 'react';
6 | const QUERY = '(prefers-reduced-motion: no-preference)';
7 | const isRenderingOnServer = typeof window === 'undefined';
8 |
9 | const getInitialState = () =>
10 | // For our initial server render, we won't know if the user
11 | // prefers reduced motion, but it doesn't matter. This value
12 | // will be overwritten on the client, before any animations
13 | // occur.
14 | isRenderingOnServer ? true : !window.matchMedia(QUERY).matches;
15 | function usePrefersReducedMotion() {
16 | const [prefersReducedMotion, setPrefersReducedMotion] = useState(getInitialState);
17 | useEffect(() => {
18 | const mediaQueryList = window.matchMedia(QUERY);
19 | const listener = event => {
20 | setPrefersReducedMotion(!event.matches);
21 | };
22 | mediaQueryList.addListener(listener);
23 | return () => {
24 | mediaQueryList.removeListener(listener);
25 | };
26 | }, []);
27 | return prefersReducedMotion;
28 | }
29 |
30 | export default usePrefersReducedMotion;
31 |
--------------------------------------------------------------------------------
/src/hooks/useScrollDirection.js:
--------------------------------------------------------------------------------
1 | const SCROLL_UP = 'up';
2 | const SCROLL_DOWN = 'down';
3 |
4 | import { useState, useEffect } from 'react';
5 |
6 | const useScrollDirection = ({ initialDirection, thresholdPixels, off } = {}) => {
7 | const [scrollDir, setScrollDir] = useState(initialDirection);
8 |
9 | useEffect(() => {
10 | const threshold = thresholdPixels || 0;
11 | let lastScrollY = window.pageYOffset;
12 | let ticking = false;
13 |
14 | const updateScrollDir = () => {
15 | const scrollY = window.pageYOffset;
16 |
17 | if (Math.abs(scrollY - lastScrollY) < threshold) {
18 | // We haven't exceeded the threshold
19 | ticking = false;
20 | return;
21 | }
22 |
23 | setScrollDir(scrollY > lastScrollY ? SCROLL_DOWN : SCROLL_UP);
24 | lastScrollY = scrollY > 0 ? scrollY : 0;
25 | ticking = false;
26 | };
27 |
28 | const onScroll = () => {
29 | if (!ticking) {
30 | window.requestAnimationFrame(updateScrollDir);
31 | ticking = true;
32 | }
33 | };
34 |
35 | /**
36 | * Bind the scroll handler if `off` is set to false.
37 | * If `off` is set to true reset the scroll direction.
38 | */
39 | !off ? window.addEventListener('scroll', onScroll) : setScrollDir(initialDirection);
40 |
41 | return () => window.removeEventListener('scroll', onScroll);
42 | }, [initialDirection, thresholdPixels, off]);
43 |
44 | return scrollDir;
45 | };
46 |
47 | export default useScrollDirection;
48 |
--------------------------------------------------------------------------------
/src/images/Google Lighthouse Performance Metrtics.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pycoder2000/portfolio-v4/108af3f210879860f83bea6ab9e70b93e5286769/src/images/Google Lighthouse Performance Metrtics.png
--------------------------------------------------------------------------------
/src/images/demo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pycoder2000/portfolio-v4/108af3f210879860f83bea6ab9e70b93e5286769/src/images/demo.png
--------------------------------------------------------------------------------
/src/images/favicons/android-icon-144x144.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pycoder2000/portfolio-v4/108af3f210879860f83bea6ab9e70b93e5286769/src/images/favicons/android-icon-144x144.png
--------------------------------------------------------------------------------
/src/images/favicons/android-icon-192x192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pycoder2000/portfolio-v4/108af3f210879860f83bea6ab9e70b93e5286769/src/images/favicons/android-icon-192x192.png
--------------------------------------------------------------------------------
/src/images/favicons/android-icon-36x36.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pycoder2000/portfolio-v4/108af3f210879860f83bea6ab9e70b93e5286769/src/images/favicons/android-icon-36x36.png
--------------------------------------------------------------------------------
/src/images/favicons/android-icon-48x48.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pycoder2000/portfolio-v4/108af3f210879860f83bea6ab9e70b93e5286769/src/images/favicons/android-icon-48x48.png
--------------------------------------------------------------------------------
/src/images/favicons/android-icon-72x72.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pycoder2000/portfolio-v4/108af3f210879860f83bea6ab9e70b93e5286769/src/images/favicons/android-icon-72x72.png
--------------------------------------------------------------------------------
/src/images/favicons/android-icon-96x96.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pycoder2000/portfolio-v4/108af3f210879860f83bea6ab9e70b93e5286769/src/images/favicons/android-icon-96x96.png
--------------------------------------------------------------------------------
/src/images/favicons/apple-icon-114x114.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pycoder2000/portfolio-v4/108af3f210879860f83bea6ab9e70b93e5286769/src/images/favicons/apple-icon-114x114.png
--------------------------------------------------------------------------------
/src/images/favicons/apple-icon-120x120.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pycoder2000/portfolio-v4/108af3f210879860f83bea6ab9e70b93e5286769/src/images/favicons/apple-icon-120x120.png
--------------------------------------------------------------------------------
/src/images/favicons/apple-icon-144x144.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pycoder2000/portfolio-v4/108af3f210879860f83bea6ab9e70b93e5286769/src/images/favicons/apple-icon-144x144.png
--------------------------------------------------------------------------------
/src/images/favicons/apple-icon-152x152.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pycoder2000/portfolio-v4/108af3f210879860f83bea6ab9e70b93e5286769/src/images/favicons/apple-icon-152x152.png
--------------------------------------------------------------------------------
/src/images/favicons/apple-icon-180x180.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pycoder2000/portfolio-v4/108af3f210879860f83bea6ab9e70b93e5286769/src/images/favicons/apple-icon-180x180.png
--------------------------------------------------------------------------------
/src/images/favicons/apple-icon-57x57.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pycoder2000/portfolio-v4/108af3f210879860f83bea6ab9e70b93e5286769/src/images/favicons/apple-icon-57x57.png
--------------------------------------------------------------------------------
/src/images/favicons/apple-icon-60x60.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pycoder2000/portfolio-v4/108af3f210879860f83bea6ab9e70b93e5286769/src/images/favicons/apple-icon-60x60.png
--------------------------------------------------------------------------------
/src/images/favicons/apple-icon-72x72.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pycoder2000/portfolio-v4/108af3f210879860f83bea6ab9e70b93e5286769/src/images/favicons/apple-icon-72x72.png
--------------------------------------------------------------------------------
/src/images/favicons/apple-icon-76x76.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pycoder2000/portfolio-v4/108af3f210879860f83bea6ab9e70b93e5286769/src/images/favicons/apple-icon-76x76.png
--------------------------------------------------------------------------------
/src/images/favicons/apple-icon-precomposed.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pycoder2000/portfolio-v4/108af3f210879860f83bea6ab9e70b93e5286769/src/images/favicons/apple-icon-precomposed.png
--------------------------------------------------------------------------------
/src/images/favicons/apple-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pycoder2000/portfolio-v4/108af3f210879860f83bea6ab9e70b93e5286769/src/images/favicons/apple-icon.png
--------------------------------------------------------------------------------
/src/images/favicons/favicon-16x16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pycoder2000/portfolio-v4/108af3f210879860f83bea6ab9e70b93e5286769/src/images/favicons/favicon-16x16.png
--------------------------------------------------------------------------------
/src/images/favicons/favicon-32x32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pycoder2000/portfolio-v4/108af3f210879860f83bea6ab9e70b93e5286769/src/images/favicons/favicon-32x32.png
--------------------------------------------------------------------------------
/src/images/favicons/favicon-96x96.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pycoder2000/portfolio-v4/108af3f210879860f83bea6ab9e70b93e5286769/src/images/favicons/favicon-96x96.png
--------------------------------------------------------------------------------
/src/images/favicons/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pycoder2000/portfolio-v4/108af3f210879860f83bea6ab9e70b93e5286769/src/images/favicons/favicon.ico
--------------------------------------------------------------------------------
/src/images/favicons/ms-icon-144x144.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pycoder2000/portfolio-v4/108af3f210879860f83bea6ab9e70b93e5286769/src/images/favicons/ms-icon-144x144.png
--------------------------------------------------------------------------------
/src/images/favicons/ms-icon-150x150.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pycoder2000/portfolio-v4/108af3f210879860f83bea6ab9e70b93e5286769/src/images/favicons/ms-icon-150x150.png
--------------------------------------------------------------------------------
/src/images/favicons/ms-icon-310x310.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pycoder2000/portfolio-v4/108af3f210879860f83bea6ab9e70b93e5286769/src/images/favicons/ms-icon-310x310.png
--------------------------------------------------------------------------------
/src/images/favicons/ms-icon-70x70.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pycoder2000/portfolio-v4/108af3f210879860f83bea6ab9e70b93e5286769/src/images/favicons/ms-icon-70x70.png
--------------------------------------------------------------------------------
/src/images/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pycoder2000/portfolio-v4/108af3f210879860f83bea6ab9e70b93e5286769/src/images/logo.png
--------------------------------------------------------------------------------
/src/images/profile.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pycoder2000/portfolio-v4/108af3f210879860f83bea6ab9e70b93e5286769/src/images/profile.jpeg
--------------------------------------------------------------------------------
/src/pages/404.js:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect } from 'react';
2 | import { Link } from 'gatsby';
3 | import { Helmet } from 'react-helmet';
4 | import { CSSTransition, TransitionGroup } from 'react-transition-group';
5 | import PropTypes from 'prop-types';
6 | import styled from 'styled-components';
7 | import { navDelay } from '@utils';
8 | import { Layout } from '@components';
9 | import { usePrefersReducedMotion } from '@hooks';
10 |
11 | const StyledMainContainer = styled.main`
12 | ${({ theme }) => theme.mixins.flexCenter};
13 | flex-direction: column;
14 | `;
15 | const StyledTitle = styled.h1`
16 | color: var(--green);
17 | font-family: var(--font-mono);
18 | font-size: clamp(100px, 25vw, 200px);
19 | line-height: 1;
20 | `;
21 | const StyledSubtitle = styled.h2`
22 | font-size: clamp(30px, 5vw, 50px);
23 | font-weight: 400;
24 | `;
25 | const StyledHomeButton = styled(Link)`
26 | ${({ theme }) => theme.mixins.bigButton};
27 | margin-top: 40px;
28 | `;
29 |
30 | const NotFoundPage = ({ location }) => {
31 | const [isMounted, setIsMounted] = useState(false);
32 | const prefersReducedMotion = usePrefersReducedMotion();
33 |
34 | useEffect(() => {
35 | if (prefersReducedMotion) {
36 | return;
37 | }
38 |
39 | const timeout = setTimeout(() => setIsMounted(true), navDelay);
40 | return () => clearTimeout(timeout);
41 | }, []);
42 |
43 | const content = (
44 |
45 | 404
46 | Page Not Found
47 | Go Home
48 |
49 | );
50 |
51 | return (
52 |
53 |
54 |
55 | {prefersReducedMotion ? (
56 | <>{content}>
57 | ) : (
58 |
59 | {isMounted && (
60 |
61 | {content}
62 |
63 | )}
64 |
65 | )}
66 |
67 | );
68 | };
69 |
70 | NotFoundPage.propTypes = {
71 | location: PropTypes.object.isRequired,
72 | };
73 |
74 | export default NotFoundPage;
75 |
--------------------------------------------------------------------------------
/src/pages/archive.js:
--------------------------------------------------------------------------------
1 | import React, { useRef, useEffect } from 'react';
2 | import { graphql } from 'gatsby';
3 | import PropTypes from 'prop-types';
4 | import { Helmet } from 'react-helmet';
5 | import styled from 'styled-components';
6 | import { srConfig } from '@config';
7 | import sr from '@utils/sr';
8 | import { Layout } from '@components';
9 | import { Icon } from '@components/icons';
10 | import { usePrefersReducedMotion } from '@hooks';
11 |
12 | const StyledTableContainer = styled.div`
13 | margin: 100px -20px;
14 |
15 | @media (max-width: 768px) {
16 | margin: 50px -10px;
17 | }
18 |
19 | table {
20 | width: 100%;
21 | border-collapse: collapse;
22 |
23 | .hide-on-mobile {
24 | @media (max-width: 768px) {
25 | display: none;
26 | }
27 | }
28 |
29 | tbody tr {
30 | &:hover,
31 | &:focus {
32 | background-color: var(--light-navy);
33 | }
34 | }
35 |
36 | th,
37 | td {
38 | padding: 10px;
39 | text-align: left;
40 |
41 | &:first-child {
42 | padding-left: 20px;
43 |
44 | @media (max-width: 768px) {
45 | padding-left: 10px;
46 | }
47 | }
48 | &:last-child {
49 | padding-right: 20px;
50 |
51 | @media (max-width: 768px) {
52 | padding-right: 10px;
53 | }
54 | }
55 |
56 | svg {
57 | width: 20px;
58 | height: 20px;
59 | }
60 | }
61 |
62 | tr {
63 | cursor: default;
64 |
65 | td:first-child {
66 | border-top-left-radius: var(--border-radius);
67 | border-bottom-left-radius: var(--border-radius);
68 | }
69 | td:last-child {
70 | border-top-right-radius: var(--border-radius);
71 | border-bottom-right-radius: var(--border-radius);
72 | }
73 | }
74 |
75 | td {
76 | &.year {
77 | padding-right: 20px;
78 |
79 | @media (max-width: 768px) {
80 | padding-right: 10px;
81 | font-size: var(--fz-sm);
82 | }
83 | }
84 |
85 | &.title {
86 | padding-top: 15px;
87 | padding-right: 20px;
88 | color: var(--lightest-slate);
89 | font-size: var(--fz-xl);
90 | font-weight: 600;
91 | line-height: 1.25;
92 | }
93 |
94 | &.company {
95 | font-size: var(--fz-lg);
96 | white-space: nowrap;
97 | }
98 |
99 | &.tech {
100 | font-size: var(--fz-xxs);
101 | font-family: var(--font-mono);
102 | line-height: 1.5;
103 | .separator {
104 | margin: 0 5px;
105 | }
106 | span {
107 | display: inline-block;
108 | }
109 | }
110 |
111 | &.links {
112 | min-width: 100px;
113 |
114 | div {
115 | display: flex;
116 | align-items: center;
117 |
118 | a {
119 | ${({ theme }) => theme.mixins.flexCenter};
120 | flex-shrink: 0;
121 | }
122 |
123 | a + a {
124 | margin-left: 10px;
125 | }
126 | }
127 | }
128 | }
129 | }
130 | `;
131 |
132 | const ArchivePage = ({ location, data }) => {
133 | const projects = data.allMarkdownRemark.edges;
134 | const revealTitle = useRef(null);
135 | const revealTable = useRef(null);
136 | const revealProjects = useRef([]);
137 | const prefersReducedMotion = usePrefersReducedMotion();
138 |
139 | useEffect(() => {
140 | if (prefersReducedMotion) {
141 | return;
142 | }
143 |
144 | sr.reveal(revealTitle.current, srConfig());
145 | sr.reveal(revealTable.current, srConfig(200, 0));
146 | revealProjects.current.forEach((ref, i) => sr.reveal(ref, srConfig(i * 10)));
147 | }, []);
148 |
149 | return (
150 |
151 |
152 |
153 |
154 |
158 |
159 |
160 |
161 |
162 |
163 | Year
164 | Title
165 | Made at
166 | Built with
167 | Link
168 |
169 |
170 |
171 | {projects.length > 0 &&
172 | projects.map(({ node }, i) => {
173 | const {
174 | date,
175 | github,
176 | external,
177 | ios,
178 | android,
179 | title,
180 | tech,
181 | company,
182 | } = node.frontmatter;
183 | return (
184 | (revealProjects.current[i] = el)}>
185 | {`${new Date(date).getFullYear()}`}
186 |
187 | {title}
188 |
189 |
190 | {company ? {company} : — }
191 |
192 |
193 |
194 | {tech?.length > 0 &&
195 | tech.map((item, i) => (
196 |
197 | {item}
198 | {''}
199 | {i !== tech.length - 1 && · }
200 |
201 | ))}
202 |
203 |
204 |
205 |
227 |
228 |
229 | );
230 | })}
231 |
232 |
233 |
234 |
235 |
236 | );
237 | };
238 | ArchivePage.propTypes = {
239 | location: PropTypes.object.isRequired,
240 | data: PropTypes.object.isRequired,
241 | };
242 |
243 | export default ArchivePage;
244 |
245 | export const pageQuery = graphql`
246 | {
247 | allMarkdownRemark(
248 | filter: { fileAbsolutePath: { regex: "/content/projects/" } }
249 | sort: { fields: [frontmatter___date], order: DESC }
250 | ) {
251 | edges {
252 | node {
253 | frontmatter {
254 | date
255 | title
256 | tech
257 | github
258 | external
259 | ios
260 | android
261 | company
262 | }
263 | html
264 | }
265 | }
266 | }
267 | }
268 | `;
269 |
--------------------------------------------------------------------------------
/src/pages/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 | import styled from 'styled-components';
4 | import { Layout, Hero, About, Jobs, Featured, Projects, Contact } from '@components';
5 | import ProgressBar from 'react-scroll-progress-bar';
6 | const StyledMainContainer = styled.main`
7 | counter-reset: section;
8 | `;
9 |
10 | const IndexPage = ({ location }) => (
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 | );
23 |
24 | IndexPage.propTypes = {
25 | location: PropTypes.object.isRequired,
26 | };
27 |
28 | export default IndexPage;
29 |
--------------------------------------------------------------------------------
/src/pages/pensieve/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { graphql, Link } from 'gatsby';
3 | import kebabCase from 'lodash/kebabCase';
4 | import PropTypes from 'prop-types';
5 | import { Helmet } from 'react-helmet';
6 | import styled from 'styled-components';
7 | import { Layout } from '@components';
8 | import { IconBookmark } from '@components/icons';
9 |
10 | const StyledMainContainer = styled.main`
11 | & > header {
12 | margin-bottom: 100px;
13 | text-align: center;
14 |
15 | a {
16 | &:hover,
17 | &:focus {
18 | cursor: url("data:image/svg+xml;utf8,⚡ ")
19 | 20 0,
20 | auto;
21 | }
22 | }
23 | }
24 |
25 | footer {
26 | ${({ theme }) => theme.mixins.flexBetween};
27 | width: 100%;
28 | margin-top: 20px;
29 | }
30 | `;
31 | const StyledGrid = styled.ul`
32 | ${({ theme }) => theme.mixins.resetList};
33 | display: grid;
34 | grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
35 | grid-gap: 15px;
36 | margin-top: 50px;
37 | position: relative;
38 |
39 | @media (max-width: 1080px) {
40 | grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
41 | }
42 | `;
43 | const StyledPost = styled.li`
44 | transition: var(--transition);
45 | cursor: default;
46 |
47 | @media (prefers-reduced-motion: no-preference) {
48 | &:hover,
49 | &:focus-within {
50 | .post__inner {
51 | transform: translateY(-7px);
52 | }
53 | }
54 | }
55 |
56 | a {
57 | position: relative;
58 | z-index: 1;
59 | }
60 |
61 | .post__inner {
62 | ${({ theme }) => theme.mixins.boxShadow};
63 | ${({ theme }) => theme.mixins.flexBetween};
64 | flex-direction: column;
65 | align-items: flex-start;
66 | position: relative;
67 | height: 100%;
68 | padding: 2rem 1.75rem;
69 | border-radius: var(--border-radius);
70 | transition: var(--transition);
71 | background-color: var(--light-navy);
72 |
73 | header,
74 | a {
75 | width: 100%;
76 | }
77 | }
78 |
79 | .post__icon {
80 | ${({ theme }) => theme.mixins.flexBetween};
81 | color: var(--green);
82 | margin-bottom: 30px;
83 | margin-left: -5px;
84 |
85 | svg {
86 | width: 40px;
87 | height: 40px;
88 | }
89 | }
90 |
91 | .post__title {
92 | margin: 0 0 10px;
93 | color: var(--lightest-slate);
94 | font-size: var(--fz-xxl);
95 |
96 | a {
97 | position: static;
98 |
99 | &:before {
100 | content: '';
101 | display: block;
102 | position: absolute;
103 | z-index: 0;
104 | width: 100%;
105 | height: 100%;
106 | top: 0;
107 | left: 0;
108 | }
109 | }
110 | }
111 |
112 | .post__desc {
113 | color: var(--light-slate);
114 | font-size: 17px;
115 | }
116 |
117 | .post__date {
118 | color: var(--light-slate);
119 | font-family: var(--font-mono);
120 | font-size: var(--fz-xxs);
121 | text-transform: uppercase;
122 | }
123 |
124 | ul.post__tags {
125 | display: flex;
126 | align-items: flex-end;
127 | flex-wrap: wrap;
128 | padding: 0;
129 | margin: 0;
130 | list-style: none;
131 |
132 | li {
133 | color: var(--green);
134 | font-family: var(--font-mono);
135 | font-size: var(--fz-xxs);
136 | line-height: 1.75;
137 |
138 | &:not(:last-of-type) {
139 | margin-right: 15px;
140 | }
141 | }
142 | }
143 | `;
144 |
145 | const PensievePage = ({ location, data }) => {
146 | const posts = data.allMarkdownRemark.edges;
147 |
148 | return (
149 |
150 |
151 |
152 |
153 |
161 |
162 |
163 | {posts.length > 0 &&
164 | posts.map(({ node }, i) => {
165 | const { frontmatter } = node;
166 | const { title, description, slug, date, tags } = frontmatter;
167 | const formattedDate = new Date(date).toLocaleDateString();
168 |
169 | return (
170 |
171 |
172 |
173 |
174 |
175 |
176 |
177 | {title}
178 |
179 | {description}
180 |
181 |
182 |
183 | {formattedDate}
184 |
185 | {tags.map((tag, i) => (
186 |
187 |
188 | #{tag}
189 |
190 |
191 | ))}
192 |
193 |
194 |
195 |
196 | );
197 | })}
198 |
199 |
200 |
201 | );
202 | };
203 |
204 | PensievePage.propTypes = {
205 | location: PropTypes.object.isRequired,
206 | data: PropTypes.object.isRequired,
207 | };
208 |
209 | export default PensievePage;
210 |
211 | export const pageQuery = graphql`
212 | {
213 | allMarkdownRemark(
214 | filter: { fileAbsolutePath: { regex: "/content/posts/" }, frontmatter: { draft: { ne: true } } }
215 | sort: { fields: [frontmatter___date], order: DESC }
216 | ) {
217 | edges {
218 | node {
219 | frontmatter {
220 | title
221 | description
222 | slug
223 | date
224 | tags
225 | draft
226 | }
227 | html
228 | }
229 | }
230 | }
231 | }
232 | `;
233 |
--------------------------------------------------------------------------------
/src/pages/pensieve/tags.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Link, graphql } from 'gatsby';
3 | import kebabCase from 'lodash/kebabCase';
4 | import PropTypes from 'prop-types';
5 | import { Helmet } from 'react-helmet';
6 | import styled from 'styled-components';
7 | import { Layout } from '@components';
8 |
9 | const StyledTagsContainer = styled.main`
10 | max-width: 1000px;
11 |
12 | h1 {
13 | margin-bottom: 50px;
14 | }
15 | ul {
16 | color: var(--light-slate);
17 |
18 | li {
19 | font-size: var(--fz-xxl);
20 |
21 | a {
22 | color: var(--light-slate);
23 |
24 | .count {
25 | color: var(--slate);
26 | font-family: var(--font-mono);
27 | font-size: var(--fz-md);
28 | }
29 | }
30 | }
31 | }
32 | `;
33 |
34 | const TagsPage = ({
35 | data: {
36 | allMarkdownRemark: { group },
37 | },
38 | location,
39 | }) => (
40 |
41 |
42 |
43 |
44 |
45 | ←
46 | All memories
47 |
48 |
49 | Tags
50 |
51 | {group.map(tag => (
52 |
53 |
54 | {tag.fieldValue} ({tag.totalCount})
55 |
56 |
57 | ))}
58 |
59 |
60 |
61 | );
62 |
63 | TagsPage.propTypes = {
64 | data: PropTypes.shape({
65 | allMarkdownRemark: PropTypes.shape({
66 | group: PropTypes.arrayOf(
67 | PropTypes.shape({
68 | fieldValue: PropTypes.string.isRequired,
69 | totalCount: PropTypes.number.isRequired,
70 | }).isRequired,
71 | ),
72 | }),
73 | site: PropTypes.shape({
74 | siteMetadata: PropTypes.shape({
75 | title: PropTypes.string.isRequired,
76 | }),
77 | }),
78 | }),
79 | location: PropTypes.object,
80 | };
81 |
82 | export default TagsPage;
83 |
84 | export const pageQuery = graphql`
85 | query {
86 | allMarkdownRemark(limit: 2000, filter: { frontmatter: { draft: { ne: true } } }) {
87 | group(field: frontmatter___tags) {
88 | fieldValue
89 | totalCount
90 | }
91 | }
92 | }
93 | `;
94 |
--------------------------------------------------------------------------------
/src/styles/GlobalStyle.js:
--------------------------------------------------------------------------------
1 | import { createGlobalStyle } from 'styled-components';
2 | import fonts from './fonts';
3 | import variables from './variables';
4 | import TransitionStyles from './TransitionStyles';
5 | import PrismStyles from './PrismStyles';
6 |
7 | const GlobalStyle = createGlobalStyle`
8 | ${fonts};
9 | ${variables};
10 |
11 | html {
12 | box-sizing: border-box;
13 | width: 100%;
14 | scroll-behavior: smooth;
15 | }
16 |
17 | *,
18 | *:before,
19 | *:after {
20 | box-sizing: inherit;
21 | }
22 |
23 | ::selection {
24 | background-color: var(--lightest-navy);
25 | color: var(--lightest-slate);
26 | }
27 |
28 | /* Provide basic, default focus styles.*/
29 | :focus {
30 | outline: 2px dashed var(--green);
31 | outline-offset: 3px;
32 | }
33 |
34 | /*
35 | Remove default focus styles for mouse users ONLY if
36 | :focus-visible is supported on this platform.
37 | */
38 | :focus:not(:focus-visible) {
39 | outline: none;
40 | outline-offset: 0px;
41 | }
42 |
43 | /*
44 | Optionally: If :focus-visible is supported on this
45 | platform, provide enhanced focus styles for keyboard
46 | focus.
47 | */
48 | :focus-visible {
49 | outline: 2px dashed var(--green);
50 | outline-offset: 3px;
51 | }
52 |
53 | /* Scrollbar Styles */
54 | html {
55 | scrollbar-width: thin;
56 | scrollbar-color: var(--dark-slate) var(--navy);
57 | }
58 | ::-webkit-scrollbar {
59 | width: 12px;
60 | }
61 | ::-webkit-scrollbar-track {
62 | background: var(--navy);
63 | }
64 | ::-webkit-scrollbar-thumb {
65 | background-color: var(--dark-slate);
66 | border: 3px solid var(--navy);
67 | border-radius: 10px;
68 | }
69 |
70 | body {
71 | margin: 0;
72 | width: 100%;
73 | min-height: 100%;
74 | overflow-x: hidden;
75 | -moz-osx-font-smoothing: grayscale;
76 | -webkit-font-smoothing: antialiased;
77 | background-color: var(--navy);
78 | color: var(--slate);
79 | font-family: var(--font-sans);
80 | font-size: var(--fz-xl);
81 | line-height: 1.3;
82 |
83 | @media (max-width: 480px) {
84 | font-size: var(--fz-lg);
85 | }
86 |
87 | &.hidden {
88 | overflow: hidden;
89 | }
90 |
91 | &.blur {
92 | overflow: hidden;
93 |
94 | header {
95 | background-color: transparent;
96 | }
97 |
98 | #content > * {
99 | filter: blur(5px) brightness(0.7);
100 | transition: var(--transition);
101 | pointer-events: none;
102 | user-select: none;
103 | }
104 | }
105 | }
106 |
107 | #root {
108 | min-height: 100vh;
109 | display: grid;
110 | grid-template-rows: 1fr auto;
111 | grid-template-columns: 100%;
112 | }
113 |
114 | main {
115 | margin: 0 auto;
116 | width: 100%;
117 | max-width: 1600px;
118 | min-height: 100vh;
119 | padding: 200px 150px;
120 |
121 | @media (max-width: 1080px) {
122 | padding: 200px 100px;
123 | }
124 | @media (max-width: 768px) {
125 | padding: 150px 50px;
126 | }
127 | @media (max-width: 480px) {
128 | padding: 125px 25px;
129 | }
130 |
131 | &.fillHeight {
132 | padding: 0 150px;
133 |
134 | @media (max-width: 1080px) {
135 | padding: 0 100px;
136 | }
137 | @media (max-width: 768px) {
138 | padding: 0 50px;
139 | }
140 | @media (max-width: 480px) {
141 | padding: 0 25px;
142 | }
143 | }
144 | }
145 |
146 | section {
147 | margin: 0 auto;
148 | padding: 100px 0;
149 | max-width: 1000px;
150 |
151 | @media (max-width: 768px) {
152 | padding: 80px 0;
153 | }
154 |
155 | @media (max-width: 480px) {
156 | padding: 60px 0;
157 | }
158 | }
159 |
160 | h1,
161 | h2,
162 | h3,
163 | h4,
164 | h5,
165 | h6 {
166 | margin: 0 0 10px 0;
167 | font-weight: 600;
168 | color: var(--lightest-slate);
169 | line-height: 1.1;
170 | }
171 |
172 | .big-heading {
173 | margin: 0;
174 | font-size: clamp(40px, 8vw, 80px);
175 | }
176 |
177 | .medium-heading {
178 | margin: 0;
179 | font-size: clamp(40px, 8vw, 60px);
180 | }
181 |
182 | .numbered-heading {
183 | display: flex;
184 | align-items: center;
185 | position: relative;
186 | margin: 10px 0 40px;
187 | width: 100%;
188 | font-size: clamp(26px, 5vw, var(--fz-heading));
189 | white-space: nowrap;
190 |
191 | &:before {
192 | position: relative;
193 | bottom: 4px;
194 | counter-increment: section;
195 | content: '0' counter(section) '.';
196 | margin-right: 10px;
197 | color: var(--green);
198 | font-family: var(--font-mono);
199 | font-size: clamp(var(--fz-md), 3vw, var(--fz-xl));
200 | font-weight: 400;
201 |
202 | @media (max-width: 480px) {
203 | margin-bottom: -3px;
204 | margin-right: 5px;
205 | }
206 | }
207 |
208 | &:after {
209 | content: '';
210 | display: block;
211 | position: relative;
212 | top: -5px;
213 | width: 300px;
214 | height: 1px;
215 | margin-left: 20px;
216 | background-color: var(--lightest-navy);
217 |
218 | @media (max-width: 1080px) {
219 | width: 200px;
220 | }
221 | @media (max-width: 768px) {
222 | width: 100%;
223 | }
224 | @media (max-width: 600px) {
225 | margin-left: 10px;
226 | }
227 | }
228 | }
229 |
230 | img,
231 | svg,
232 | .gatsby-image-wrapper {
233 | width: 100%;
234 | max-width: 100%;
235 | vertical-align: middle;
236 | }
237 |
238 | img[alt=""],
239 | img:not([alt]) {
240 | filter: blur(5px);
241 | }
242 |
243 | svg {
244 | width: 100%;
245 | height: 100%;
246 | fill: currentColor;
247 | vertical-align: middle;
248 |
249 | &.feather {
250 | fill: none;
251 | }
252 | }
253 |
254 | a {
255 | display: inline-block;
256 | text-decoration: none;
257 | text-decoration-skip-ink: auto;
258 | color: inherit;
259 | position: relative;
260 | transition: var(--transition);
261 |
262 | &:hover,
263 | &:focus {
264 | color: var(--green);
265 | }
266 |
267 | &.inline-link {
268 | ${({ theme }) => theme.mixins.inlineLink};
269 | }
270 | }
271 |
272 | button {
273 | cursor: pointer;
274 | border: 0;
275 | border-radius: 0;
276 | }
277 |
278 | input, textarea {
279 | border-radius: 0;
280 | outline: 0;
281 |
282 | &:focus {
283 | outline: 0;
284 | }
285 | &:focus,
286 | &:active {
287 | &::placeholder {
288 | opacity: 0.5;
289 | }
290 | }
291 | }
292 |
293 | p {
294 | margin: 0 0 15px 0;
295 |
296 | &:last-child,
297 | &:last-of-type {
298 | margin: 0;
299 | }
300 |
301 | & > a {
302 | ${({ theme }) => theme.mixins.inlineLink};
303 | }
304 |
305 | & > code {
306 | background-color: var(--light-navy);
307 | color: var(--white);
308 | font-size: var(--fz-sm);
309 | border-radius: var(--border-radius);
310 | padding: 0.3em 0.5em;
311 | }
312 | }
313 |
314 | ul {
315 | &.fancy-list {
316 | padding: 0;
317 | margin: 0;
318 | list-style: none;
319 | font-size: var(--fz-lg);
320 | li {
321 | position: relative;
322 | padding-left: 30px;
323 | margin-bottom: 10px;
324 | &:before {
325 | content: '▹';
326 | position: absolute;
327 | left: 0;
328 | color: var(--green);
329 | }
330 | }
331 | }
332 | }
333 |
334 | blockquote {
335 | border-left-color: var(--green);
336 | border-left-style: solid;
337 | border-left-width: 1px;
338 | margin-left: 0px;
339 | margin-right: 0px;
340 | padding-left: 1.5rem;
341 |
342 | p {
343 | font-style: italic;
344 | font-size: 24px;
345 | }
346 | }
347 |
348 | hr {
349 | background-color: var(--lightest-navy);
350 | height: 1px;
351 | border-width: 0px;
352 | border-style: initial;
353 | border-color: initial;
354 | border-image: initial;
355 | margin: 1rem;
356 | }
357 |
358 | code {
359 | font-family: var(--font-mono);
360 | font-size: var(--fz-md);
361 | }
362 |
363 | .skip-to-content {
364 | ${({ theme }) => theme.mixins.button};
365 | position: absolute;
366 | top: auto;
367 | left: -999px;
368 | width: 1px;
369 | height: 1px;
370 | overflow: hidden;
371 | z-index: -99;
372 |
373 | &:focus,
374 | &:active {
375 | background-color: var(--green);
376 | color: var(--navy);
377 | top: 0;
378 | left: 0;
379 | width: auto;
380 | height: auto;
381 | overflow: auto;
382 | z-index: 99;
383 | }
384 | }
385 |
386 | #logo {
387 | color: var(--green);
388 | }
389 |
390 | .overline {
391 | color: var(--green);
392 | font-family: var(--font-mono);
393 | font-size: var(--fz-md);
394 | font-weight: 400;
395 | }
396 |
397 | .subtitle {
398 | color: var(--green);
399 | margin: 0 0 20px 0;
400 | font-size: var(--fz-md);
401 | font-family: var(--font-mono);
402 | font-weight: 400;
403 | line-height: 1.5;
404 | @media (max-width: 1080px) {
405 | font-size: var(--fz-sm);
406 | }
407 | @media (max-width: 768px) {
408 | font-size: var(--fz-xs);
409 | }
410 |
411 | a {
412 | ${({ theme }) => theme.mixins.inlineLink};
413 | line-height: 1.5;
414 | }
415 | }
416 |
417 | .breadcrumb {
418 | display: flex;
419 | align-items: center;
420 | margin-bottom: 50px;
421 | color: var(--green);
422 |
423 | .arrow {
424 | display: block;
425 | margin-right: 10px;
426 | padding-top: 4px;
427 | }
428 |
429 | a {
430 | ${({ theme }) => theme.mixins.inlineLink};
431 | font-family: var(--font-mono);
432 | font-size: var(--fz-sm);
433 | font-weight: 600;
434 | line-height: 1.5;
435 | text-transform: uppercase;
436 | letter-spacing: 0.1em;
437 | }
438 | }
439 |
440 | .gatsby-image-outer-wrapper {
441 | height: 100%;
442 | }
443 |
444 | ${TransitionStyles};
445 |
446 | ${PrismStyles};
447 | `;
448 |
449 | export default GlobalStyle;
450 |
--------------------------------------------------------------------------------
/src/styles/PrismStyles.js:
--------------------------------------------------------------------------------
1 | import { css } from 'styled-components';
2 |
3 | const prismColors = {
4 | bg: `#112340`,
5 | lineHighlight: `#1d2d50`,
6 | blue: `#5ccfe6`,
7 | purple: `#c3a6ff`,
8 | green: `#bae67e`,
9 | yellow: `#ffd580`,
10 | orange: `#ffae57`,
11 | red: `#ef6b73`,
12 | grey: `#a2aabc`,
13 | comment: `#8695b799`,
14 | };
15 |
16 | // https://www.gatsbyjs.org/packages/gatsby-remark-prismjs
17 |
18 | const PrismStyles = css`
19 | /**
20 | * Add back the container background-color, border-radius, padding, margin
21 | * and overflow that we removed from .
22 | */
23 | .gatsby-highlight {
24 | background-color: ${prismColors.bg};
25 | color: ${prismColors.grey};
26 | border-radius: var(--border-radius);
27 | margin: 2em 0;
28 | padding: 1.25em;
29 | overflow: auto;
30 | position: relative;
31 | font-family: var(--font-mono);
32 | font-size: var(--fz-md);
33 | }
34 |
35 | .gatsby-highlight code[class*='language-'],
36 | .gatsby-highlight pre[class*='language-'] {
37 | height: auto !important;
38 | font-size: var(--fz-sm);
39 | line-height: 1.5;
40 | white-space: pre;
41 | word-spacing: normal;
42 | word-break: normal;
43 | word-wrap: normal;
44 | tab-size: 2;
45 | hyphens: none;
46 | }
47 |
48 | /**
49 | * Remove the default PrismJS theme background-color, border-radius, margin,
50 | * padding and overflow.
51 | * 1. Make the element just wide enough to fit its content.
52 | * 2. Always fill the visible space in .gatsby-highlight.
53 | * 3. Adjust the position of the line numbers
54 | */
55 | .gatsby-highlight pre[class*='language-'] {
56 | background-color: transparent;
57 | margin: 0;
58 | padding: 0;
59 | overflow: initial;
60 | float: left; /* 1 */
61 | min-width: 100%; /* 2 */
62 | padding-top: 2em;
63 | }
64 |
65 | /* File names */
66 | .gatsby-code-title {
67 | padding: 1em 1.5em;
68 | font-family: var(--font-mono);
69 | font-size: var(--fz-xs);
70 | background-color: ${prismColors.bg};
71 | color: ${prismColors.grey};
72 | border-top-left-radius: var(--border-radius);
73 | border-top-right-radius: var(--border-radius);
74 | border-bottom: 1px solid ${prismColors.lineHighlight};
75 |
76 | & + .gatsby-highlight {
77 | margin-top: 0;
78 | border-top-left-radius: 0;
79 | border-top-right-radius: 0;
80 | }
81 | }
82 |
83 | /* Line highlighting */
84 | .gatsby-highlight-code-line {
85 | display: block;
86 | background-color: ${prismColors.lineHighlight};
87 | border-left: 2px solid var(--green);
88 | padding-left: calc(1em + 2px);
89 | padding-right: 1em;
90 | margin-right: -1.35em;
91 | margin-left: -1.35em;
92 | }
93 |
94 | /* Language badges */
95 | .gatsby-highlight pre[class*='language-']::before {
96 | background: var(--lightest-navy);
97 | color: var(--white);
98 | font-size: var(--fz-xxs);
99 | font-family: var(--font-mono);
100 | line-height: 1.5;
101 | letter-spacing: 0.1em;
102 | text-transform: uppercase;
103 | border-radius: 0 0 3px 3px;
104 | position: absolute;
105 | top: 0;
106 | left: 1.25rem;
107 | padding: 0.25rem 0.5rem;
108 | }
109 | .gatsby-highlight pre[class='language-javascript']::before {
110 | content: 'js';
111 | }
112 | .gatsby-highlight pre[class='language-js']::before {
113 | content: 'js';
114 | }
115 | .gatsby-highlight pre[class='language-jsx']::before {
116 | content: 'jsx';
117 | }
118 | .gatsby-highlight pre[class='language-graphql']::before {
119 | content: 'GraphQL';
120 | }
121 | .gatsby-highlight pre[class='language-html']::before {
122 | content: 'html';
123 | }
124 | .gatsby-highlight pre[class='language-css']::before {
125 | content: 'css';
126 | }
127 | .gatsby-highlight pre[class='language-mdx']::before {
128 | content: 'mdx';
129 | }
130 | .gatsby-highlight pre[class='language-shell']::before {
131 | content: 'shell';
132 | }
133 | .gatsby-highlight pre[class='language-sh']::before {
134 | content: 'sh';
135 | }
136 | .gatsby-highlight pre[class='language-bash']::before {
137 | content: 'bash';
138 | }
139 | .gatsby-highlight pre[class='language-yaml']::before {
140 | content: 'yaml';
141 | }
142 | .gatsby-highlight pre[class='language-markdown']::before {
143 | content: 'md';
144 | }
145 | .gatsby-highlight pre[class='language-json']::before,
146 | .gatsby-highlight pre[class='language-json5']::before {
147 | content: 'json';
148 | }
149 | .gatsby-highlight pre[class='language-diff']::before {
150 | content: 'diff';
151 | }
152 | .gatsby-highlight pre[class='language-text']::before {
153 | content: 'text';
154 | }
155 | .gatsby-highlight pre[class='language-flow']::before {
156 | content: 'flow';
157 | }
158 |
159 | /* Prism Styles */
160 | .token {
161 | display: inline;
162 | }
163 | .token.comment,
164 | .token.block-comment,
165 | .token.prolog,
166 | .token.doctype,
167 | .token.cdata {
168 | color: ${prismColors.comment};
169 | }
170 | .token.punctuation {
171 | color: ${prismColors.grey};
172 | }
173 | .token.namespace,
174 | .token.deleted {
175 | color: ${prismColors.red};
176 | }
177 | .token.function-name,
178 | .token.function,
179 | .token.class-name,
180 | .token.constant,
181 | .token.symbol {
182 | color: ${prismColors.yellow};
183 | }
184 | .token.attr-name,
185 | .token.operator,
186 | .token.rule {
187 | color: ${prismColors.orange};
188 | }
189 | .token.keyword,
190 | .token.boolean,
191 | .token.number,
192 | .token.property {
193 | color: ${prismColors.purple};
194 | }
195 | .token.tag,
196 | .token.selector,
197 | .token.important,
198 | .token.atrule,
199 | .token.builtin,
200 | .token.entity,
201 | .token.url {
202 | color: ${prismColors.blue};
203 | }
204 | .token.string,
205 | .token.char,
206 | .token.attr-value,
207 | .token.regex,
208 | .token.variable,
209 | .token.inserted {
210 | color: ${prismColors.green};
211 | }
212 | .token.important,
213 | .token.bold {
214 | font-weight: 600;
215 | }
216 | .token.italic {
217 | font-style: italic;
218 | }
219 | .token.entity {
220 | cursor: help;
221 | }
222 | .namespace {
223 | opacity: 0.7;
224 | }
225 | `;
226 |
227 | export default PrismStyles;
228 |
--------------------------------------------------------------------------------
/src/styles/TransitionStyles.js:
--------------------------------------------------------------------------------
1 | import { css } from 'styled-components';
2 |
3 | // https://reactcommunity.org/react-transition-group/css-transition
4 |
5 | const TransitionStyles = css`
6 | /* Fade up */
7 | .fadeup-enter {
8 | opacity: 0.01;
9 | transform: translateY(20px);
10 | transition: opacity 300ms var(--easing), transform 300ms var(--easing);
11 | }
12 |
13 | .fadeup-enter-active {
14 | opacity: 1;
15 | transform: translateY(0px);
16 | transition: opacity 300ms var(--easing), transform 300ms var(--easing);
17 | }
18 |
19 | /* Fade down */
20 | .fadedown-enter {
21 | opacity: 0.01;
22 | transform: translateY(-20px);
23 | transition: opacity 300ms var(--easing), transform 300ms var(--easing);
24 | }
25 |
26 | .fadedown-enter-active {
27 | opacity: 1;
28 | transform: translateY(0px);
29 | transition: opacity 300ms var(--easing), transform 300ms var(--easing);
30 | }
31 |
32 | /* Fade */
33 | .fade-enter {
34 | opacity: 0;
35 | }
36 | .fade-enter-active {
37 | opacity: 1;
38 | transition: opacity 300ms var(--easing);
39 | }
40 | .fade-exit {
41 | opacity: 1;
42 | }
43 | .fade-exit-active {
44 | opacity: 0;
45 | transition: opacity 300ms var(--easing);
46 | }
47 | `;
48 |
49 | export default TransitionStyles;
50 |
--------------------------------------------------------------------------------
/src/styles/fonts.js:
--------------------------------------------------------------------------------
1 | import { css } from 'styled-components';
2 |
3 | import CalibreRegularWoff from '@fonts/Calibre/Calibre-Regular.woff';
4 | import CalibreRegularWoff2 from '@fonts/Calibre/Calibre-Regular.woff2';
5 | import CalibreMediumWoff from '@fonts/Calibre/Calibre-Medium.woff';
6 | import CalibreMediumWoff2 from '@fonts/Calibre/Calibre-Medium.woff2';
7 | import CalibreSemiboldWoff from '@fonts/Calibre/Calibre-Semibold.woff';
8 | import CalibreSemiboldWoff2 from '@fonts/Calibre/Calibre-Semibold.woff2';
9 |
10 | import CalibreRegularItalicWoff from '@fonts/Calibre/Calibre-RegularItalic.woff';
11 | import CalibreRegularItalicWoff2 from '@fonts/Calibre/Calibre-RegularItalic.woff2';
12 | import CalibreMediumItalicWoff from '@fonts/Calibre/Calibre-MediumItalic.woff';
13 | import CalibreMediumItalicWoff2 from '@fonts/Calibre/Calibre-MediumItalic.woff2';
14 | import CalibreSemiboldItalicWoff from '@fonts/Calibre/Calibre-SemiboldItalic.woff';
15 | import CalibreSemiboldItalicWoff2 from '@fonts/Calibre/Calibre-SemiboldItalic.woff2';
16 |
17 | import SFMonoRegularWoff from '@fonts/SFMono/SFMono-Regular.woff';
18 | import SFMonoRegularWoff2 from '@fonts/SFMono/SFMono-Regular.woff2';
19 | import SFMonoSemiboldWoff from '@fonts/SFMono/SFMono-Semibold.woff';
20 | import SFMonoSemiboldWoff2 from '@fonts/SFMono/SFMono-Semibold.woff2';
21 |
22 | import SFMonoRegularItalicWoff from '@fonts/SFMono/SFMono-RegularItalic.woff';
23 | import SFMonoRegularItalicWoff2 from '@fonts/SFMono/SFMono-RegularItalic.woff2';
24 | import SFMonoSemiboldItalicWoff from '@fonts/SFMono/SFMono-SemiboldItalic.woff';
25 | import SFMonoSemiboldItalicWoff2 from '@fonts/SFMono/SFMono-SemiboldItalic.woff2';
26 |
27 | const calibreNormalWeights = {
28 | 400: [CalibreRegularWoff, CalibreRegularWoff2],
29 | 500: [CalibreMediumWoff, CalibreMediumWoff2],
30 | 600: [CalibreSemiboldWoff, CalibreSemiboldWoff2],
31 | };
32 |
33 | const calibreItalicWeights = {
34 | 400: [CalibreRegularItalicWoff, CalibreRegularItalicWoff2],
35 | 500: [CalibreMediumItalicWoff, CalibreMediumItalicWoff2],
36 | 600: [CalibreSemiboldItalicWoff, CalibreSemiboldItalicWoff2],
37 | };
38 |
39 | const sfMonoNormalWeights = {
40 | 400: [SFMonoRegularWoff, SFMonoRegularWoff2],
41 | 600: [SFMonoSemiboldWoff, SFMonoSemiboldWoff2],
42 | };
43 |
44 | const sfMonoItalicWeights = {
45 | 400: [SFMonoRegularItalicWoff, SFMonoRegularItalicWoff2],
46 | 600: [SFMonoSemiboldItalicWoff, SFMonoSemiboldItalicWoff2],
47 | };
48 |
49 | const calibre = {
50 | name: 'Calibre',
51 | normal: calibreNormalWeights,
52 | italic: calibreItalicWeights,
53 | };
54 |
55 | const sfMono = {
56 | name: 'SF Mono',
57 | normal: sfMonoNormalWeights,
58 | italic: sfMonoItalicWeights,
59 | };
60 |
61 | const createFontFaces = (family, style = 'normal') => {
62 | let styles = '';
63 |
64 | for (const [weight, formats] of Object.entries(family[style])) {
65 | const woff = formats[0];
66 | const woff2 = formats[1];
67 |
68 | styles += `
69 | @font-face {
70 | font-family: '${family.name}';
71 | src: url(${woff2}) format('woff2'),
72 | url(${woff}) format('woff');
73 | font-weight: ${weight};
74 | font-style: ${style};
75 | font-display: auto;
76 | }
77 | `;
78 | }
79 |
80 | return styles;
81 | };
82 |
83 | const calibreNormal = createFontFaces(calibre);
84 | const calibreItalic = createFontFaces(calibre, 'italic');
85 |
86 | const sfMonoNormal = createFontFaces(sfMono);
87 | const sfMonoItalic = createFontFaces(sfMono, 'italic');
88 |
89 | const Fonts = css`
90 | ${calibreNormal + calibreItalic + sfMonoNormal + sfMonoItalic}
91 | `;
92 |
93 | export default Fonts;
94 |
--------------------------------------------------------------------------------
/src/styles/index.js:
--------------------------------------------------------------------------------
1 | export { default as theme } from './theme';
2 | export { default as GlobalStyle } from './GlobalStyle';
3 | export { default as mixins } from './mixins';
4 |
--------------------------------------------------------------------------------
/src/styles/mixins.js:
--------------------------------------------------------------------------------
1 | import { css } from 'styled-components';
2 |
3 | const button = css`
4 | color: var(--green);
5 | background-color: transparent;
6 | border: 1px solid var(--green);
7 | border-radius: var(--border-radius);
8 | font-size: var(--fz-xs);
9 | font-family: var(--font-mono);
10 | line-height: 1;
11 | text-decoration: none;
12 | cursor: pointer;
13 | transition: var(--transition);
14 | padding: 1.25rem 1.75rem;
15 |
16 | &:hover,
17 | &:focus,
18 | &:active {
19 | background-color: var(--green-tint);
20 | outline: none;
21 | }
22 | &:after {
23 | display: none !important;
24 | }
25 | `;
26 |
27 | const mixins = {
28 | flexCenter: css`
29 | display: flex;
30 | justify-content: center;
31 | align-items: center;
32 | `,
33 |
34 | flexBetween: css`
35 | display: flex;
36 | justify-content: space-between;
37 | align-items: center;
38 | `,
39 |
40 | link: css`
41 | display: inline-block;
42 | text-decoration: none;
43 | text-decoration-skip-ink: auto;
44 | color: inherit;
45 | position: relative;
46 | transition: var(--transition);
47 | &:hover,
48 | &:active,
49 | &:focus {
50 | color: var(--green);
51 | outline: 0;
52 | }
53 | `,
54 |
55 | inlineLink: css`
56 | display: inline-block;
57 | text-decoration: none;
58 | text-decoration-skip-ink: auto;
59 | position: relative;
60 | transition: var(--transition);
61 | color: var(--green);
62 | &:hover,
63 | &:focus,
64 | &:active {
65 | color: var(--green);
66 | outline: 0;
67 | &:after {
68 | width: 100%;
69 | }
70 | & > * {
71 | color: var(--green) !important;
72 | transition: var(--transition);
73 | }
74 | }
75 | &:after {
76 | content: '';
77 | display: block;
78 | width: 0;
79 | height: 1px;
80 | position: relative;
81 | bottom: 0.37em;
82 | background-color: var(--green);
83 | transition: var(--transition);
84 | opacity: 0.5;
85 | }
86 | `,
87 |
88 | button,
89 |
90 | smallButton: css`
91 | color: var(--green);
92 | background-color: transparent;
93 | border: 1px solid var(--green);
94 | border-radius: var(--border-radius);
95 | padding: 0.75rem 1rem;
96 | font-size: var(--fz-xs);
97 | font-family: var(--font-mono);
98 | line-height: 1;
99 | text-decoration: none;
100 | cursor: pointer;
101 | transition: var(--transition);
102 | &:hover,
103 | &:focus,
104 | &:active {
105 | background-color: var(--green-tint);
106 | outline: none;
107 | }
108 | &:after {
109 | display: none !important;
110 | }
111 | `,
112 |
113 | bigButton: css`
114 | color: var(--green);
115 | background-color: transparent;
116 | border: 1px solid var(--green);
117 | border-radius: var(--border-radius);
118 | padding: 1.25rem 1.75rem;
119 | font-size: var(--fz-sm);
120 | font-family: var(--font-mono);
121 | line-height: 1;
122 | text-decoration: none;
123 | cursor: pointer;
124 | transition: var(--transition);
125 | &:hover,
126 | &:focus,
127 | &:active {
128 | background-color: var(--green-tint);
129 | outline: none;
130 | }
131 | &:after {
132 | display: none !important;
133 | }
134 | `,
135 |
136 | boxShadow: css`
137 | box-shadow: 0 10px 30px -15px var(--navy-shadow);
138 | transition: var(--transition);
139 |
140 | &:hover,
141 | &:focus {
142 | box-shadow: 0 20px 30px -15px var(--navy-shadow);
143 | }
144 | `,
145 |
146 | fancyList: css`
147 | padding: 0;
148 | margin: 0;
149 | list-style: none;
150 | font-size: var(--fz-lg);
151 | li {
152 | position: relative;
153 | padding-left: 30px;
154 | margin-bottom: 10px;
155 | &:before {
156 | content: '▹';
157 | position: absolute;
158 | left: 0;
159 | color: var(--green);
160 | }
161 | }
162 | `,
163 |
164 | resetList: css`
165 | list-style: none;
166 | padding: 0;
167 | margin: 0;
168 | `,
169 | };
170 |
171 | export default mixins;
172 |
--------------------------------------------------------------------------------
/src/styles/theme.js:
--------------------------------------------------------------------------------
1 | import mixins from './mixins';
2 |
3 | const theme = {
4 | bp: {
5 | mobileS: `max-width: 330px`,
6 | mobileM: `max-width: 400px`,
7 | mobileL: `max-width: 480px`,
8 | tabletS: `max-width: 600px`,
9 | tabletL: `max-width: 768px`,
10 | desktopXS: `max-width: 900px`,
11 | desktopS: `max-width: 1080px`,
12 | desktopM: `max-width: 1200px`,
13 | desktopL: `max-width: 1400px`,
14 | },
15 |
16 | mixins,
17 | };
18 |
19 | export default theme;
20 |
--------------------------------------------------------------------------------
/src/styles/variables.js:
--------------------------------------------------------------------------------
1 | import { css } from 'styled-components';
2 |
3 | const variables = css`
4 | :root {
5 | --dark-navy: #020c1b;
6 | --navy: #0a192f;
7 | --light-navy: #112240;
8 | --lightest-navy: #233554;
9 | --navy-shadow: rgba(2, 12, 27, 0.7);
10 | --dark-slate: #495670;
11 | --slate: #8892b0;
12 | --light-slate: #a8b2d1;
13 | --lightest-slate: #ccd6f6;
14 | --white: #e6f1ff;
15 | --green: #64ffda;
16 | --green-tint: rgba(100, 255, 218, 0.1);
17 | --pink: #f57dff;
18 | --blue: #57cbff;
19 |
20 | --font-sans: 'Calibre', 'Inter', 'San Francisco', 'SF Pro Text', -apple-system, system-ui,
21 | sans-serif;
22 | --font-mono: 'SF Mono', 'Fira Code', 'Fira Mono', 'Roboto Mono', monospace;
23 |
24 | --fz-xxs: 12px;
25 | --fz-xs: 13px;
26 | --fz-sm: 14px;
27 | --fz-md: 16px;
28 | --fz-lg: 18px;
29 | --fz-xl: 20px;
30 | --fz-xxl: 22px;
31 | --fz-heading: 32px;
32 |
33 | --border-radius: 2px;
34 | --nav-height: 70px;
35 | --nav-scroll-height: 60px;
36 |
37 | --tab-height: 42px;
38 | --tab-width: 120px;
39 |
40 | --easing: cubic-bezier(0.645, 0.045, 0.355, 1);
41 | --transition: all 0.25s cubic-bezier(0.645, 0.045, 0.355, 1);
42 |
43 | --hamburger-width: 30px;
44 |
45 | --ham-before: top 0.1s ease-in 0.25s, opacity 0.1s ease-in;
46 | --ham-before-active: top 0.1s ease-out, opacity 0.1s ease-out 0.12s;
47 | --ham-after: bottom 0.1s ease-in 0.25s, transform 0.22s cubic-bezier(0.55, 0.055, 0.675, 0.19);
48 | --ham-after-active: bottom 0.1s ease-out,
49 | transform 0.22s cubic-bezier(0.215, 0.61, 0.355, 1) 0.12s;
50 | }
51 | `;
52 |
53 | export default variables;
54 |
--------------------------------------------------------------------------------
/src/templates/post.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { graphql, Link } from 'gatsby';
3 | import kebabCase from 'lodash/kebabCase';
4 | import PropTypes from 'prop-types';
5 | import { Helmet } from 'react-helmet';
6 | import styled from 'styled-components';
7 | import { Layout } from '@components';
8 |
9 | const StyledPostContainer = styled.main`
10 | max-width: 1000px;
11 | `;
12 | const StyledPostHeader = styled.header`
13 | margin-bottom: 50px;
14 | .tag {
15 | margin-right: 10px;
16 | }
17 | `;
18 | const StyledPostContent = styled.div`
19 | margin-bottom: 100px;
20 | h1,
21 | h2,
22 | h3,
23 | h4,
24 | h5,
25 | h6 {
26 | margin: 2em 0 1em;
27 | }
28 |
29 | p {
30 | margin: 1em 0;
31 | line-height: 1.5;
32 | color: var(--light-slate);
33 | }
34 |
35 | a {
36 | ${({ theme }) => theme.mixins.inlineLink};
37 | }
38 |
39 | code {
40 | background-color: var(--lightest-navy);
41 | color: var(--lightest-slate);
42 | border-radius: var(--border-radius);
43 | font-size: var(--fz-sm);
44 | padding: 0.2em 0.4em;
45 | }
46 |
47 | pre code {
48 | background-color: transparent;
49 | padding: 0;
50 | }
51 | `;
52 |
53 | const PostTemplate = ({ data, location }) => {
54 | const { frontmatter, html } = data.markdownRemark;
55 | const { title, date, tags } = frontmatter;
56 |
57 | return (
58 |
59 |
60 |
61 |
62 |
63 | ←
64 | All memories
65 |
66 |
67 |
68 | {title}
69 |
70 |
71 | {new Date(date).toLocaleDateString('en-US', {
72 | year: 'numeric',
73 | month: 'long',
74 | day: 'numeric',
75 | })}
76 |
77 | —
78 | {tags &&
79 | tags.length > 0 &&
80 | tags.map((tag, i) => (
81 |
82 | #{tag}
83 |
84 | ))}
85 |
86 |
87 |
88 |
89 |
90 |
91 | );
92 | };
93 |
94 | export default PostTemplate;
95 |
96 | PostTemplate.propTypes = {
97 | data: PropTypes.object,
98 | location: PropTypes.object,
99 | };
100 |
101 | export const pageQuery = graphql`
102 | query($path: String!) {
103 | markdownRemark(frontmatter: { slug: { eq: $path } }) {
104 | html
105 | frontmatter {
106 | title
107 | description
108 | date
109 | slug
110 | tags
111 | }
112 | }
113 | }
114 | `;
115 |
--------------------------------------------------------------------------------
/src/templates/tag.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Link, graphql } from 'gatsby';
3 | import kebabCase from 'lodash/kebabCase';
4 | import PropTypes from 'prop-types';
5 | import { Helmet } from 'react-helmet';
6 | import styled from 'styled-components';
7 | import { Layout } from '@components';
8 |
9 | const StyledTagsContainer = styled.main`
10 | max-width: 1000px;
11 |
12 | a {
13 | ${({ theme }) => theme.mixins.inlineLink};
14 | }
15 |
16 | h1 {
17 | ${({ theme }) => theme.mixins.flexBetween};
18 | margin-bottom: 50px;
19 |
20 | a {
21 | font-size: var(--fz-lg);
22 | font-weight: 400;
23 | }
24 | }
25 |
26 | ul {
27 | li {
28 | font-size: 24px;
29 | h2 {
30 | font-size: inherit;
31 | margin: 0;
32 | a {
33 | color: var(--light-slate);
34 | }
35 | }
36 | .subtitle {
37 | color: var(--slate);
38 | font-size: var(--fz-sm);
39 |
40 | .tag {
41 | margin-right: 10px;
42 | }
43 | }
44 | }
45 | }
46 | `;
47 |
48 | const TagTemplate = ({ pageContext, data, location }) => {
49 | const { tag } = pageContext;
50 | const { edges } = data.allMarkdownRemark;
51 |
52 | return (
53 |
54 |
55 |
56 |
57 |
58 | ←
59 | All memories
60 |
61 |
62 |
63 | #{tag}
64 |
65 | View all tags
66 |
67 |
68 |
69 |
70 | {edges.map(({ node }) => {
71 | const { title, slug, date, tags } = node.frontmatter;
72 | return (
73 |
74 |
75 | {title}
76 |
77 |
78 |
79 | {new Date(date).toLocaleDateString('en-US', {
80 | year: 'numeric',
81 | month: 'long',
82 | day: 'numeric',
83 | })}
84 |
85 | —
86 | {tags &&
87 | tags.length > 0 &&
88 | tags.map((tag, i) => (
89 |
90 | #{tag}
91 |
92 | ))}
93 |
94 |
95 | );
96 | })}
97 |
98 |
99 |
100 | );
101 | };
102 |
103 | export default TagTemplate;
104 |
105 | TagTemplate.propTypes = {
106 | pageContext: PropTypes.shape({
107 | tag: PropTypes.string.isRequired,
108 | }),
109 | data: PropTypes.shape({
110 | allMarkdownRemark: PropTypes.shape({
111 | totalCount: PropTypes.number.isRequired,
112 | edges: PropTypes.arrayOf(
113 | PropTypes.shape({
114 | node: PropTypes.shape({
115 | frontmatter: PropTypes.shape({
116 | title: PropTypes.string.isRequired,
117 | }),
118 | }),
119 | }).isRequired,
120 | ),
121 | }),
122 | }),
123 | location: PropTypes.object,
124 | };
125 |
126 | export const pageQuery = graphql`
127 | query($tag: String!) {
128 | allMarkdownRemark(
129 | limit: 2000
130 | sort: { fields: [frontmatter___date], order: DESC }
131 | filter: { frontmatter: { tags: { in: [$tag] } } }
132 | ) {
133 | totalCount
134 | edges {
135 | node {
136 | frontmatter {
137 | title
138 | description
139 | date
140 | slug
141 | tags
142 | }
143 | }
144 | }
145 | }
146 | }
147 | `;
148 |
--------------------------------------------------------------------------------
/src/utils/index.js:
--------------------------------------------------------------------------------
1 | export const hex2rgba = (hex, alpha = 1) => {
2 | const [r, g, b] = hex.match(/\w\w/g).map(x => parseInt(x, 16));
3 | return `rgba(${r},${g},${b},${alpha})`;
4 | };
5 |
6 | export const navDelay = 1000;
7 | export const loaderDelay = 2000;
8 |
9 | export const KEY_CODES = {
10 | ARROW_LEFT: 'ArrowLeft',
11 | ARROW_LEFT_IE11: 'Left',
12 | ARROW_RIGHT: 'ArrowRight',
13 | ARROW_RIGHT_IE11: 'Right',
14 | ARROW_UP: 'ArrowUp',
15 | ARROW_UP_IE11: 'Up',
16 | ARROW_DOWN: 'ArrowDown',
17 | ARROW_DOWN_IE11: 'Down',
18 | ESCAPE: 'Escape',
19 | ESCAPE_IE11: 'Esc',
20 | TAB: 'Tab',
21 | SPACE: ' ',
22 | SPACE_IE11: 'Spacebar',
23 | ENTER: 'Enter',
24 | };
25 |
--------------------------------------------------------------------------------
/src/utils/sr.js:
--------------------------------------------------------------------------------
1 | import ScrollReveal from 'scrollreveal';
2 |
3 | const isSSR = typeof window === 'undefined';
4 | const sr = isSSR ? null : ScrollReveal();
5 |
6 | export default sr;
7 |
--------------------------------------------------------------------------------
/static/og.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pycoder2000/portfolio-v4/108af3f210879860f83bea6ab9e70b93e5286769/static/og.png
--------------------------------------------------------------------------------
/static/og@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pycoder2000/portfolio-v4/108af3f210879860f83bea6ab9e70b93e5286769/static/og@2x.png
--------------------------------------------------------------------------------
/static/resume.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pycoder2000/portfolio-v4/108af3f210879860f83bea6ab9e70b93e5286769/static/resume.pdf
--------------------------------------------------------------------------------