diff --git a/.babelrc b/.babelrc new file mode 100644 index 0000000..1167e82 --- /dev/null +++ b/.babelrc @@ -0,0 +1,6 @@ +{ + "presets": [ + "@babel/preset-react", + "babel-preset-gatsby" + ] +} diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..b6330b2 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,7 @@ +[*] +indent_style = space +indent_size = 2 +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true diff --git a/.eslintrc b/.eslintrc new file mode 100644 index 0000000..2e115a9 --- /dev/null +++ b/.eslintrc @@ -0,0 +1,4 @@ +{ + "root": true, + "extends": "@upstatement/eslint-config/react" +} diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..0a1ceba --- /dev/null +++ b/.gitignore @@ -0,0 +1,11 @@ +# Project dependencies +.cache +node_modules +yarn-error.log +package-lock.json + +# Build directory +/public +.DS_Store + +.vscode/ diff --git a/.husky/.gitignore b/.husky/.gitignore new file mode 100644 index 0000000..31354ec --- /dev/null +++ b/.husky/.gitignore @@ -0,0 +1 @@ +_ diff --git a/.nvmrc b/.nvmrc new file mode 100644 index 0000000..2a0dc9a --- /dev/null +++ b/.nvmrc @@ -0,0 +1 @@ +14.16.0 diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..d17fd03 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2018 Brittany Chiang + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..c492529 Binary files /dev/null and b/README.md differ diff --git a/content/featured/ExpenseTracker/SS1.png b/content/featured/ExpenseTracker/SS1.png new file mode 100644 index 0000000..3ba7e99 Binary files /dev/null and b/content/featured/ExpenseTracker/SS1.png differ diff --git a/content/featured/ExpenseTracker/index.md b/content/featured/ExpenseTracker/index.md new file mode 100644 index 0000000..62bd1d7 --- /dev/null +++ b/content/featured/ExpenseTracker/index.md @@ -0,0 +1,15 @@ +--- +date: '1' +title: 'Expense Tracker' +cover: './SS1.png' +github: 'https://github.com/sohamvsonar/expense_tracker' +external: 'https://github.com/sohamvsonar/expense_tracker' +tech: + - VS Code + - Flutter + - Dart + - API + - Android Studio +--- + + Designed to help users manage their expenses, track spending across different categories, and maintain a record of financial transactions. It provides an intuitive interface for creating and managing groups, allowing users to organize their expenses efficiently. diff --git a/content/featured/RestaurentBillSystem/SS.jpg b/content/featured/RestaurentBillSystem/SS.jpg new file mode 100644 index 0000000..efc8195 Binary files /dev/null and b/content/featured/RestaurentBillSystem/SS.jpg differ diff --git a/content/featured/RestaurentBillSystem/index.md b/content/featured/RestaurentBillSystem/index.md new file mode 100644 index 0000000..e2f7fda --- /dev/null +++ b/content/featured/RestaurentBillSystem/index.md @@ -0,0 +1,16 @@ +--- +date: '3' +title: '```Restaurent Bill +& Inventory Management System' +cover: './SS.jpg' +external: 'https://github.com/sohamvsonar/RestaurentBillMS' +cta: 'https://github.com/sohamvsonar/RestaurentBillMS' +tech: + - Python + - MySQL + - GUI + - Tkinter +--- + +Built a GUI based application with a graphical interface for a restaurant handling multiple operations. Data is stored and +connected to the backend using SQL. The system has a login window for admins and the staff. diff --git a/content/featured/StockPricePredictor/SS1.jpg b/content/featured/StockPricePredictor/SS1.jpg new file mode 100644 index 0000000..ab6f44b Binary files /dev/null and b/content/featured/StockPricePredictor/SS1.jpg differ diff --git a/content/featured/StockPricePredictor/index.md b/content/featured/StockPricePredictor/index.md new file mode 100644 index 0000000..8a57f2b --- /dev/null +++ b/content/featured/StockPricePredictor/index.md @@ -0,0 +1,18 @@ +--- +date: '2' +title: 'Stock Market Price Predictor' +cover: './SS1.jpg' +github: 'https://github.com/sohamvsonar/Stock-Market-Price-Predictor' +external: 'https://github.com/sohamvsonar/Stock-Market-Price-Predictor' +tech: + - Python + - Machine Learning + - API + - LSTM + - Pandas + - numpy + - matplotlib + - Tensorflow +--- + +Stock market price predictor using LSTM (Long Short-Term Memory) neural networks. The predictor is designed to forecast stock prices based on historical data. The LSTM model is built using TensorFlow and Keras, and the stock price data is fetched using the Yahoo Finance API. diff --git a/content/jobs/Globalshala/index.md b/content/jobs/Globalshala/index.md new file mode 100644 index 0000000..3a36368 --- /dev/null +++ b/content/jobs/Globalshala/index.md @@ -0,0 +1,11 @@ +--- +date: '2021-06-01' +title: 'Data Analyst Intern' +company: 'Globalshala' +location: 'Remote' +range: 'Summber 2021' +url: 'https://globalshala.com/' +--- + +- Collaborated and partnered with 3 cross-functional teams to identify and address business challenges through data analysis. +- Implemented tableau to analyze and identify flaws in unstructured data provided by the marketing team for a facebook advertisement resulting in 34% revenue growth. diff --git a/content/jobs/Hexaware/index.md b/content/jobs/Hexaware/index.md new file mode 100644 index 0000000..0c50211 --- /dev/null +++ b/content/jobs/Hexaware/index.md @@ -0,0 +1,13 @@ +--- +date: '2023-03-2' +title: 'Operations Executive' +company: 'Hexaware' +location: 'Mumbai, India' +range: 'Mar 2023 - June 2023' +url: 'https://hexaware.com/' +--- + +- Administered a software handling daily live requests for non-emergency medical transportation for a leading healthcare company in the United States. +- Assessed user feedback and reported software bugs, optimizing functioning by 60%; and also assisted other agents in clearing technical problems on the client software platform. +- Implemented and maintained database management systems to support the client software, entering personal data in centralized database; ensured 100 %accuracy of health record, studying new and revised information resource list. +- Collaborated with team of 10 to manage multiple user requests and successfully solve the requests diff --git a/content/jobs/Wolfizer/index.md b/content/jobs/Wolfizer/index.md new file mode 100644 index 0000000..88e4d5d --- /dev/null +++ b/content/jobs/Wolfizer/index.md @@ -0,0 +1,13 @@ +--- +date: '2022-04-01' +title: 'Web Development Intern' +company: 'Wolfizer' +location: 'Nashik, India' +range: 'April 2022 - November 2022' +url: 'https://wolfizer.com/' +--- + +- Cooperated with a team of web professionals, led development, design and technical support for a E-commerce website for a client; completed project 2 months prior to the deadline. +- Got exposure to agile development methodologies and project management processes, successfully executing 2 projects deploying these methodologies. +- Gained knowledge of front-end technologies such as html, css and javascript to create and handle multiple projects. + diff --git a/content/posts/clickable-cards/index.md b/content/posts/clickable-cards/index.md new file mode 100644 index 0000000..c0fecc6 --- /dev/null +++ b/content/posts/clickable-cards/index.md @@ -0,0 +1,54 @@ +--- +title: Accessible Clickable Cards +description: Clickable cards with multiple child links +date: 2021-04-21 +draft: false +slug: /pensieve/clickable-cards +tags: + - Accessibility + - CSS +--- + +[Codepen Demo](https://codepen.io/bchiang7/pen/xxRBvgd?editors=1100) + +Card layout where the card itself isn't an anchor link, but the whole card is clickable (with a `:before` pseudo element on the main ``). Links inside of the card are still clickable. + +## CSS + +```css +.grid__item { + &:hover, + &:focus-within { + background-color: #eee; + } + + a { + position: relative; + z-index: 1; + } + + h2 { + a { + position: static; + + &:hover, + &:focus { + color: blue; + } + + &:before { + content: ''; + display: block; + position: absolute; + z-index: 0; + width: 100%; + height: 100%; + top: 0; + left: 0; + transition: background-color 0.1s ease-out; + background-color: transparent; + } + } + } +} +``` diff --git a/content/posts/dark-mode-toggle/index.md b/content/posts/dark-mode-toggle/index.md new file mode 100644 index 0000000..43ccff2 --- /dev/null +++ b/content/posts/dark-mode-toggle/index.md @@ -0,0 +1,99 @@ +--- +title: Dark Mode Toggle +description: Dark mode without the flash of default theme +date: 2021-04-21 +draft: false +slug: /pensieve/dark-mode-toggle +tags: + - Theming + - Dark Mode +--- + +Dark mode toggle without the flash of default theme. Important bits: + +- CSS variables for color theming +- Put `data-theme` attribute on ``, not ``, so we can run the JS before the DOM finishes rendering +- Run local storage check in the `` +- JS for toggle button click handler can come after render + +## HTML + +```html + + + + + + ... + + + +
+ +
+ + + + +``` + +## CSS Variables + +```css +:root { + --bg: #ffffff; + --text: #000000; +} + +[data-theme='dark'] { + --bg: #000000; + --text: #ffffff; +} +``` + +## JavaScript + +```js:title=app.js +const themeToggleBtn = document.querySelector('.js-theme-toggle'); + +themeToggleBtn.addEventListener('click', () => onToggleClick()); + +const onToggleClick = () => { + const { theme } = document.documentElement.dataset; + const themeTo = theme && theme === 'light' ? 'dark' : 'light'; + const label = `Activate ${theme} mode`; + + document.documentElement.setAttribute('data-theme', themeTo); + localStorage.setItem('theme', themeTo); + + themeToggleBtn.setAttribute('aria-label', label); + themeToggleBtn.setAttribute('title', label); +}; +``` + +## Resources + +- +- +- +- +- diff --git a/content/posts/docker-compose-error/index.md b/content/posts/docker-compose-error/index.md new file mode 100644 index 0000000..d83a5bb --- /dev/null +++ b/content/posts/docker-compose-error/index.md @@ -0,0 +1,38 @@ +--- +title: Docker Compose Error +description: docker-compose version discrepancies +date: '2019-12-13' +draft: false +slug: '/pensieve/docker-error' +tags: + - WordPress + - Docker +--- + +## Problem + +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: + +```shell:title=bin/composer +#!/bin/bash +docker-compose exec -w /var/www/html/wp-content/themes/skela wordpress composer "$@" +``` + +When trying to run this script via `./bin/composer install`, I got this error in my terminal: + +```shell +ERROR: Setting workdir for exec is not supported in API < 1.35 (1.30) +``` + +The error was coming from the `-w` flag in the `docker-compose exec` command in the `composer` script. + +## Solution + +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 🤷‍♀️ + +```yaml:title=docker-compose.yml +version: '3.6' # highlight-line +services: + wordpress: + build: +``` diff --git a/content/posts/markdown-playground/image.jpg b/content/posts/markdown-playground/image.jpg new file mode 100644 index 0000000..3932f72 Binary files /dev/null and b/content/posts/markdown-playground/image.jpg differ diff --git a/content/posts/markdown-playground/index.md b/content/posts/markdown-playground/index.md new file mode 100644 index 0000000..48d94b8 --- /dev/null +++ b/content/posts/markdown-playground/index.md @@ -0,0 +1,359 @@ +--- +title: Markdown Test File +description: abc234 +date: 2019-12-07 +draft: true +slug: /pensieve/markdown-playground +tags: + - Testing +--- + +![Image Alt](./image.jpg) + +```jsx +class FlavorForm extends React.Component { // highlight-line + constructor(props) { + super(props); + this.state = {value: 'coconut'}; + + this.handleChange = this.handleChange.bind(this); + this.handleSubmit = this.handleSubmit.bind(this); + } + + handleChange(event) { + // highlight-next-line + this.setState({value: event.target.value}); + } + + // highlight-start + handleSubmit(event) { + alert('Your favorite flavor is: ' + this.state.value); + event.preventDefault(); + } + // highlight-end + + render() { + return ( + { /* highlight-range{1,4-9,12} */ } +
+ + +
+ ); + } +} +``` + +```javascript:title=highlight.js +// Here is a comment +function $initHighlight(block, cls) { + try { + if (cls.search(/\bno\-highlight\b/) != -1) + return process(block, true, 0x0F) + + ` class="${cls}"`; + } catch (e) { + /* handle exception */ + } + for (var i = 0 / 2; i < classes.length; i++) { + if (checkCondition(classes[i]) === undefined) { + console.log('undefined'); + } + } + + return ( +
+ {block} +
+ ) +} + +export $initHighlight; +``` + +This is a paragraph. + + This is a paragraph. + +# Header 1 + +## Header 2 + + Header 1 + ======== + + Header 2 + -------- + +```css +@import 'compass/reset'; + +// variables +$colorGreen: #008000; +$colorGreenDark: darken($colorGreen, 10); + +@mixin container { + max-width: 980px; +} + +// mixins with parameters +@mixin button($color: green) { + @if ($color == green) { + background-color: #008000; + } @else if ($color == red) { + background-color: #b22222; + } +} + +button { + @include button(red); +} + +div, +.navbar, +#header, +input[type='input'] { + font-family: 'Helvetica Neue', Arial, sans-serif; + width: auto; + margin: 0 auto; + display: block; +} + +.row-12 > [class*='spans'] { + border-left: 1px solid #b5c583; +} + +// nested definitions +ul { + width: 100%; + padding: { + left: 5px; + right: 5px; + } + li { + float: left; + margin-right: 10px; + .home { + background: url('http://placehold.it/20') scroll no-repeat 0 0; + } + } +} + +.banner { + @extend .container; +} + +a { + color: $colorGreen; + &:hover { + color: $colorGreenDark; + } + &:visited { + color: #c458cb; + } +} + +@for $i from 1 through 5 { + .span#{$i} { + width: 20px * $i; + } +} + +@mixin mobile { + @media screen and (max-width: 600px) { + @content; + } +} +``` + +```markdown +# hello world + +you can write text [with links](http://example.com) inline or [link references][1]. + +- one _thing_ has *em*phasis +- two **things** are **bold** + +[1]: http://example.com + +--- + +# hello world + + + +> markdown is so cool + + so are code segments + +1. one thing (yeah!) +2. two thing `i can write code`, and `more` wipee! +``` + +# Header 1 + +## Header 2 + +### Header 3 + +#### Header 4 + +##### Header 5 + +###### Header 6 + + # Header 1 + ## Header 2 + ### Header 3 + #### Header 4 + ##### Header 5 + ###### Header 6 + +# Header 1 + +## Header 2 + +### Header 3 + +#### Header 4 + +##### Header 5 + +###### Header 6 + + # Header 1 # + ## Header 2 ## + ### Header 3 ### + #### Header 4 #### + ##### Header 5 ##### + ###### Header 6 ###### + +> Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aliquam hendrerit mi posuere lectus. Vestibulum enim wisi, viverra nec, fringilla in, laoreet vitae, risus. + + > Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aliquam hendrerit mi posuere lectus. Vestibulum enim wisi, viverra nec, fringilla in, laoreet vitae, risus. + +> ## This is a header +> +> 1. This is the first list item. +> 2. This is the second list item. +> +> Here's some example code: +> +> Markdown.generate(); + + > ## This is a header. + > 1. This is the first list item. + > 2. This is the second list item. + > + > Here's some example code: + > + > Markdown.generate(); + +- Red +- Green +- Blue + +- Red +- Green +- Blue + +- Red +- Green +- Blue + +```markdown +- Red +- Green +- Blue + +* Red +* Green +* Blue + +- Red +- Green +- Blue +``` + +1. Buy flour and salt +2. Mix together with water +3. Bake + +```markdown +1. Buy flour and salt +1. Mix together with water +1. Bake +``` + +Paragraph: + + Code + + + + Paragraph: + + Code + +--- + +--- + +--- + +--- + +--- + + * * * + + *** + + ***** + + - - - + + --------------------------------------- + +This is [an example](http://example.com 'Example') link. + +[This link](http://example.com) has no title attr. + +This is [an example][id] reference-style link. + +[id]: http://example.com 'Optional Title' + + This is [an example](http://example.com "Example") link. + + [This link](http://example.com) has no title attr. + + This is [an example] [id] reference-style link. + + [id]: http://example.com "Optional Title" + +_single asterisks_ + +_single underscores_ + +**double asterisks** + +**double underscores** + + *single asterisks* + + _single underscores_ + + **double asterisks** + + __double underscores__ + +This paragraph has some `code` in it. + + This paragraph has some `code` in it. diff --git a/content/posts/wordpress-publish-error/console-errors.png b/content/posts/wordpress-publish-error/console-errors.png new file mode 100644 index 0000000..9eefb61 Binary files /dev/null and b/content/posts/wordpress-publish-error/console-errors.png differ diff --git a/content/posts/wordpress-publish-error/draft-fail.png b/content/posts/wordpress-publish-error/draft-fail.png new file mode 100644 index 0000000..086bda8 Binary files /dev/null and b/content/posts/wordpress-publish-error/draft-fail.png differ diff --git a/content/posts/wordpress-publish-error/index.md b/content/posts/wordpress-publish-error/index.md new file mode 100644 index 0000000..47b44a7 --- /dev/null +++ b/content/posts/wordpress-publish-error/index.md @@ -0,0 +1,31 @@ +--- +title: WordPress Publishing Error +description: Trying to create a simple post in WordPress +date: 2019-12-03 +draft: false +slug: /pensieve/wordpress-publish-error +tags: + - WordPress +--- + +## Problem + +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. + +It looked something like this: + +![Draft fail](./draft-fail.png) + +Sometimes the error message would be slightly more helpful: `Publishing failed. Error message: The response is not a valid JSON response.` + +![Publish error](./publish-error.png) + +And if I popped open the console, I saw these errors: + +![Console errors](./console-errors.png) + +## Solution + +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)). + +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. diff --git a/content/posts/wordpress-publish-error/publish-error.png b/content/posts/wordpress-publish-error/publish-error.png new file mode 100644 index 0000000..037f697 Binary files /dev/null and b/content/posts/wordpress-publish-error/publish-error.png differ diff --git a/content/projects/AMFM.md b/content/projects/AMFM.md new file mode 100644 index 0000000..c6cdff6 --- /dev/null +++ b/content/projects/AMFM.md @@ -0,0 +1,14 @@ +--- +date: '2017-11-01' +title: 'Apple Music Facebook Messenger Integration' +github: '' +external: 'https://www.theverge.com/2017/10/5/16433770/facebook-messenger-apple-music-bot-song-streaming' +tech: + - Ember + - JS + - SCSS +company: 'Apple' +showInProjects: true +--- + +Facebook Messenger chat bot extension featuring authentication and full song streaming from within the Messenger app. Read more about it on [The Verge](https://www.theverge.com/2017/10/5/16433770/facebook-messenger-apple-music-bot-song-streaming). diff --git a/content/projects/AlgoliaWordPressMediumPost.md b/content/projects/AlgoliaWordPressMediumPost.md new file mode 100644 index 0000000..b3baa16 --- /dev/null +++ b/content/projects/AlgoliaWordPressMediumPost.md @@ -0,0 +1,14 @@ +--- +date: '2020-03-27' +title: 'Integrating Algolia Search with WordPress Multisite' +github: '' +external: 'https://medium.com/stories-from-upstatement/integrating-algolia-search-with-wordpress-multisite-e2dea3ed449c' +tech: + - Algolia + - WordPress + - PHP +company: 'Upstatement' +showInProjects: true +--- + +Building a custom multisite compatible WordPress plugin to build global search with Algolia diff --git a/content/projects/AppleMusicEmbedPlayer.md b/content/projects/AppleMusicEmbedPlayer.md new file mode 100644 index 0000000..8d4ecbb --- /dev/null +++ b/content/projects/AppleMusicEmbedPlayer.md @@ -0,0 +1,14 @@ +--- +date: '2017-12-01' +title: 'Apple Music Embeddable Web Player Widget' +github: '' +external: 'https://tools.applemusic.com/en-us' +tech: + - MusicKit.js + - JS + - SCSS +company: 'Apple' +showInProjects: true +--- + +Embeddable web player widget for Apple Music that lets users log in and listen to full song playback in the browser leveraging [MusicKit.js](https://developer.apple.com/documentation/musickitjs). Read more about this project on [9to5Mac](https://9to5mac.com/2018/06/03/apple-music-embeddable-web-player-listen-browser/). diff --git a/content/projects/Blistabloc.md b/content/projects/Blistabloc.md new file mode 100644 index 0000000..0eafb79 --- /dev/null +++ b/content/projects/Blistabloc.md @@ -0,0 +1,14 @@ +--- +date: '2018-05-01' +title: 'blistabloc' +github: '' +external: 'https://blistabloc.com/' +tech: + - WordPress + - Timber + - WooCommerce +company: 'Scout' +showInProjects: false +--- + +Custom WordPress theme and e-commerce site built with Timber and WooCommerce for blistabloc, a start-up selling the only reactive shoe insert that prevents blisters from forming. diff --git a/content/projects/CourseSource.md b/content/projects/CourseSource.md new file mode 100644 index 0000000..577011e --- /dev/null +++ b/content/projects/CourseSource.md @@ -0,0 +1,15 @@ +--- +date: '2016-04-01' +title: 'CourseSource' +github: 'https://github.com/bchiang7/WebDevSpring2016/tree/master/public/project' +external: '' +tech: + - Angular + - Node + - Express + - MongoDB +company: 'Northeastern' +showInProjects: false +--- + +Web application built on the MEAN (MongoDB, Express, Angular, Node) stack with the intention of providing Northeastern students a better experience browsing the courses offered at Northeastern. diff --git a/content/projects/CrowdDJ.md b/content/projects/CrowdDJ.md new file mode 100644 index 0000000..0f07d41 --- /dev/null +++ b/content/projects/CrowdDJ.md @@ -0,0 +1,14 @@ +--- +date: '2017-03-01' +title: 'Crowd DJ' +github: 'https://github.com/crowddj/crowddj-react' +external: '' +tech: + - React + - Firebase + - Spotify API +company: HackBeanpot 2017 +showInProjects: false +--- + +Web app that allows people to crowdsource a party's music queue. Allows people to request songs, upvote songs, rate songs, etc. so the DJ can see how the crowd is feeling and queue songs accordingly. Won Best UI/UX Design at Hackbeanpot 2017. diff --git a/content/projects/Devoted.md b/content/projects/Devoted.md new file mode 100644 index 0000000..490fd2d --- /dev/null +++ b/content/projects/Devoted.md @@ -0,0 +1,14 @@ +--- +date: '2018-12-01' +title: 'Devoted Health' +github: '' +external: 'https://www.devoted.com/' +tech: + - Gatsby + - TypeScript + - Algolia +company: 'Upstatement' +showInProjects: false +--- + +A site for a revolutionary healthcare company, including an Algolia instant search integration diff --git a/content/projects/EverytownIdealState.md b/content/projects/EverytownIdealState.md new file mode 100644 index 0000000..14d8214 --- /dev/null +++ b/content/projects/EverytownIdealState.md @@ -0,0 +1,13 @@ +--- +date: '2022-01-20' +title: 'Everytown Gun Law Rankings' +github: '' +external: 'https://everytownresearch.org/rankings/' +tech: + - WordPress + - Timber + - PHP + - Airtable API +company: 'Upstatement' +showInProjects: false +--- diff --git a/content/projects/Flagship.md b/content/projects/Flagship.md new file mode 100644 index 0000000..4315373 --- /dev/null +++ b/content/projects/Flagship.md @@ -0,0 +1,13 @@ +--- +date: '2018-10-01' +title: 'Flagship Pioneering' +github: '' +external: 'https://www.flagshippioneering.com/' +tech: + - Craft CMS + - Chart.js +company: 'Upstatement' +showInProjects: false +--- + +A marketing site for an ambitious life sciences venture capital company. diff --git a/content/projects/Fontipsums.md b/content/projects/Fontipsums.md new file mode 100644 index 0000000..fda7bc1 --- /dev/null +++ b/content/projects/Fontipsums.md @@ -0,0 +1,12 @@ +--- +date: '2016-01-01' +title: 'Fontipsums' +github: 'https://github.com/bchiang7/fontipsums/' +external: 'http://bchiang7.github.io/fontipsums/' +tech: + - HTML + - SCSS +showInProjects: true +--- + +Simple website to display some of my favorite font pairings combined with some fun lorem ipsum variations found on the web. diff --git a/content/projects/GoogleKeepClone.md b/content/projects/GoogleKeepClone.md new file mode 100644 index 0000000..36091e3 --- /dev/null +++ b/content/projects/GoogleKeepClone.md @@ -0,0 +1,12 @@ +--- +date: '2018-12-29' +title: 'Google Keep Clone' +github: 'https://github.com/bchiang7/google-keep-vue-firebase' +external: 'https://keep-vue.netlify.com/' +tech: + - Vue + - Firebase +showInProjects: true +--- + +A simple Google Keep clone built with Vue and Firebase. diff --git a/content/projects/HBS.md b/content/projects/HBS.md new file mode 100644 index 0000000..2404659 --- /dev/null +++ b/content/projects/HBS.md @@ -0,0 +1,12 @@ +--- +date: '2022-10-08' +title: 'Harvard Business School Design System' +github: '' +external: '' +tech: + - Storybook + - React + - TypeScript +company: 'Upstatement' +showInProjects: false +--- diff --git a/content/projects/HalcyonTheme.md b/content/projects/HalcyonTheme.md new file mode 100644 index 0000000..8c5b506 --- /dev/null +++ b/content/projects/HalcyonTheme.md @@ -0,0 +1,15 @@ +--- +date: '2017-12-27' +title: 'Halcyon Theme' +github: 'https://github.com/bchiang7/halcyon-site' +external: 'https://halcyon-theme.netlify.com/' +tech: + - VS Code + - Sublime Text + - Atom + - iTerm2 + - Hyper +showInProjects: false +--- + +A minimal, dark blue theme for VS Code, Sublime Text, Atom, iTerm, and more. Available on [Visual Studio Marketplace](https://marketplace.visualstudio.com/items?itemName=brittanychiang.halcyon-vscode), [Package Control](https://packagecontrol.io/packages/Halcyon%20Theme), [Atom Package Manager](https://atom.io/themes/halcyon-syntax), and [npm](https://www.npmjs.com/package/hyper-halcyon-theme). diff --git a/content/projects/HeadlessCMSMediumPost.md b/content/projects/HeadlessCMSMediumPost.md new file mode 100644 index 0000000..9d18890 --- /dev/null +++ b/content/projects/HeadlessCMSMediumPost.md @@ -0,0 +1,15 @@ +--- +date: '2019-11-12' +title: 'Building a Headless Mobile App CMS From Scratch' +github: '' +external: 'https://medium.com/stories-from-upstatement/building-a-headless-mobile-app-cms-from-scratch-bab2d17744d9' +tech: + - Node + - Express + - Firebase + - Vue +company: 'Upstatement' +showInProjects: true +--- + +Find out how we built a custom headless CMS with Node, Express, and Firebase for a project at Upstatement diff --git a/content/projects/Interventions.md b/content/projects/Interventions.md new file mode 100644 index 0000000..8b7e5f8 --- /dev/null +++ b/content/projects/Interventions.md @@ -0,0 +1,14 @@ +--- +date: '2017-08-01' +title: 'Interventions' +github: '' +external: 'https://interventions.design/' +tech: + - Jekyll + - SCSS + - JS +company: 'Scout' +showInProjects: false +--- + +Interactive marketing website for Northeastern's first annual student-led design conference, Interventions. diff --git a/content/projects/JetBlueHumanKinda.md b/content/projects/JetBlueHumanKinda.md new file mode 100644 index 0000000..8a5778a --- /dev/null +++ b/content/projects/JetBlueHumanKinda.md @@ -0,0 +1,15 @@ +--- +date: '2015-10-01' +title: 'JetBlue HumanKinda' +github: '' +external: 'https://us.mullenlowe.com/work/humankinda/' +tech: + - Tumblr + - HTML + - CSS + - JS +company: 'MullenLowe' +showInProjects: false +--- + +Tumblr site complementing JetBlue's HumanKinda campaign and documentary. Includes an interactive quiz to determine how "HumanKinda" you are. Learn more about this project [here](https://us.mullenlowe.com/work/humankinda/). diff --git a/content/projects/KoalaHealth.md b/content/projects/KoalaHealth.md new file mode 100644 index 0000000..4348325 --- /dev/null +++ b/content/projects/KoalaHealth.md @@ -0,0 +1,18 @@ +--- +date: '2021-09-01' +title: 'Koala Health' +github: '' +external: 'https://www.koala.health/' +tech: + - Next.js + - TypeScript + - Redux Toolkit + - Stripe + - Algolia + - Firebase Auth + - Formik + - Yup + - Vercel +company: 'Upstatement' +showInProjects: false +--- diff --git a/content/projects/LonelyPlanetDBMS.md b/content/projects/LonelyPlanetDBMS.md new file mode 100644 index 0000000..b225094 --- /dev/null +++ b/content/projects/LonelyPlanetDBMS.md @@ -0,0 +1,15 @@ +--- +date: '2017-06-22' +title: 'Lonely Planet DBMS' +github: 'https://github.com/bchiang7/CS3200-Project' +external: '' +tech: + - Python + - MySQL + - Flask + - JS +company: 'Northeastern' +showInProjects: false +--- + +A simple web application that allows users to filter through and leave reviews in a database of Lonely Planet's Top 500 Travel Destinations. diff --git a/content/projects/MichelleWu.md b/content/projects/MichelleWu.md new file mode 100644 index 0000000..188ff3d --- /dev/null +++ b/content/projects/MichelleWu.md @@ -0,0 +1,11 @@ +--- +date: '2020-09-15' +title: 'Michelle Wu for Boston Grassroots Toolkit' +github: '' +external: 'https://toolkit.michelleforboston.com/' +tech: + - Gatsby + - Styled Components +company: 'Upstatement' +showInProjects: false +--- diff --git a/content/projects/MomsDemandAction.md b/content/projects/MomsDemandAction.md new file mode 100644 index 0000000..207a2a1 --- /dev/null +++ b/content/projects/MomsDemandAction.md @@ -0,0 +1,14 @@ +--- +date: '2019-11-12' +title: 'Moms Demand Action Mobile App' +github: '' +external: 'https://www.upstatement.com/work/moms-demand-action/' +ios: 'https://apps.apple.com/us/app/demand-action/id1475502876' +android: 'https://play.google.com/store/apps/details?id=com.momsdemandaction.app' +tech: + - NativeScript Vue + - iOS + - Android +company: 'Upstatement' +showInProjects: false +--- diff --git a/content/projects/MyNEURedesign.md b/content/projects/MyNEURedesign.md new file mode 100644 index 0000000..913188a --- /dev/null +++ b/content/projects/MyNEURedesign.md @@ -0,0 +1,14 @@ +--- +date: '2017-04-03' +title: 'myNEU Redesign' +github: 'https://github.com/bchiang7/Redesign-myNEU' +external: 'https://bchiang7.github.io/Redesign-myNEU/' +tech: + - Jekyll + - SCSS + - JS +company: 'Northeastern' +showInProjects: false +--- + +Student web portal prototype built after conducting multiple rounds of user testing that aimed to improve the current portal to provide students at Northeastern University with a better user experience. diff --git a/content/projects/NUWITSite.md b/content/projects/NUWITSite.md new file mode 100644 index 0000000..fac189c --- /dev/null +++ b/content/projects/NUWITSite.md @@ -0,0 +1,13 @@ +--- +date: '2015-12-20' +title: 'NU Women in Tech' +github: 'https://github.com/nuwit/website' +external: 'https://nuwit.ccs.neu.edu/' +tech: + - Jekyll + - Bootstrap +company: 'Northeastern' +showInProjects: true +--- + +Complete overhaul and redesign of NU Women in Tech’s club website using Jekyll, built while serving as web chair on the e-board. diff --git a/content/projects/NortheasternCSSH.md b/content/projects/NortheasternCSSH.md new file mode 100644 index 0000000..3cb7e72 --- /dev/null +++ b/content/projects/NortheasternCSSH.md @@ -0,0 +1,15 @@ +--- +date: '2020-07-16' +title: 'Northeastern CSSH' +github: '' +external: 'https://cssh.northeastern.edu/' +tech: + - WordPress + - Timber + - WordPress Multisite + - PHP + - Algolia + - JS +company: 'Upstatement' +showInProjects: false +--- diff --git a/content/projects/OctoProfile.md b/content/projects/OctoProfile.md new file mode 100644 index 0000000..5f19591 --- /dev/null +++ b/content/projects/OctoProfile.md @@ -0,0 +1,13 @@ +--- +date: '2019-07-15' +title: 'OctoProfile' +github: 'https://github.com/bchiang7/octoprofile' +external: 'https://octoprofile.now.sh' +tech: + - Next.js + - Chart.js + - GitHub API +showInProjects: true +--- + +A nicer look at your GitHub profile and repo stats. Includes data visualizations of your top languages, starred repositories, and sort through your top repos by number of stars, forks, and size. diff --git a/content/projects/OneCardForAll.md b/content/projects/OneCardForAll.md new file mode 100644 index 0000000..864925a --- /dev/null +++ b/content/projects/OneCardForAll.md @@ -0,0 +1,15 @@ +--- +date: '2015-12-01' +title: 'One Card For All' +github: '' +external: 'https://us.mullenlowe.com/work/one-card-for-all/' +tech: + - HTML + - SCSS + - JS + - jQuery +company: 'MullenLowe' +showInProjects: false +--- + +Interactive holiday site for MullenLowe built around an algorithm that generated a holiday greeting to each and every person on the planet. Check out this short [video](https://us.mullenlowe.com/work/one-card-for-all/) describing the project. diff --git a/content/projects/PhillySports.md b/content/projects/PhillySports.md new file mode 100644 index 0000000..463e044 --- /dev/null +++ b/content/projects/PhillySports.md @@ -0,0 +1,12 @@ +--- +date: '2021-07-01' +title: 'Philadelphia Inquirer Sports Scoreboards' +github: '' +external: 'https://www.inquirer.com/sports/' +tech: + - React + - TypeScript + - Stats Perform API +company: 'Upstatement' +showInProjects: false +--- diff --git a/content/projects/Pratt.md b/content/projects/Pratt.md new file mode 100644 index 0000000..821b985 --- /dev/null +++ b/content/projects/Pratt.md @@ -0,0 +1,14 @@ +--- +date: '2022-08-08' +title: 'Pratt' +github: '' +external: 'https://www.pratt.edu/' +tech: + - WordPress + - Timber + - WordPress Multisite + - Gutenberg + - JS +company: 'Upstatement' +showInProjects: false +--- diff --git a/content/projects/ReactResume.md b/content/projects/ReactResume.md new file mode 100644 index 0000000..31dff11 --- /dev/null +++ b/content/projects/ReactResume.md @@ -0,0 +1,12 @@ +--- +date: '2016-08-01' +title: 'React Profile' +github: 'https://github.com/bchiang7/react-profile' +external: 'https://bchiang7.github.io/react-profile/' +tech: + - React + - CSS +showInProjects: true +--- + +Online version of my 2016 resume made for fun. I was interested in learning React.js, so I found a simple tutorial and it spun into a weekend project. diff --git a/content/projects/Screentime.md b/content/projects/Screentime.md new file mode 100644 index 0000000..510c800 --- /dev/null +++ b/content/projects/Screentime.md @@ -0,0 +1,15 @@ +--- +date: '2016-11-01' +title: 'Screentime 2.0' +github: '' +external: 'https://starry.com/blog/product/whats-new-screentime-just-got-better-for-parents' +android: 'https://play.google.com/store/apps/details?id=com.starry.management&hl=en_US' +tech: + - Cordova + - Backbone + - Marionette +company: 'Starry' +showInProjects: true +--- + +Starry Station android app feature that provided users with the ability to easily filter content, pause the internet, and even create custom rules for blocking apps like Facebook and Twitter right from their phones. diff --git a/content/projects/SpotifyProfile.md b/content/projects/SpotifyProfile.md new file mode 100644 index 0000000..4c93c2b --- /dev/null +++ b/content/projects/SpotifyProfile.md @@ -0,0 +1,13 @@ +--- +date: '2018-12-18' +title: 'Spotify Profile' +github: 'https://github.com/bchiang7/spotify-profile' +external: 'https://spotify-profile.herokuapp.com/' +tech: + - React + - Express + - Styled Components +showInProjects: false +--- + +A web app for visualizing personalized Spotify data. View your top artists, top tracks, recently played tracks, and detailed audio information about each track. Create and save new playlists of recommended tracks based on your existing playlists and more. diff --git a/content/projects/SpotifyTopTracks2017.md b/content/projects/SpotifyTopTracks2017.md new file mode 100644 index 0000000..562020f --- /dev/null +++ b/content/projects/SpotifyTopTracks2017.md @@ -0,0 +1,13 @@ +--- +date: '2018-04-20' +title: 'Spotify’s Top Tracks of 2017' +github: 'https://github.com/bchiang7/spotify-top-tracks-2017' +external: '' +tech: + - R + - Spotify Web API +company: 'Northeastern' +showInProjects: false +--- + +R Project for my Data Science class at Northeastern to analyze the top Spotify tracks of 2017 and their audio features. diff --git a/content/projects/The19th.md b/content/projects/The19th.md new file mode 100644 index 0000000..3c40252 --- /dev/null +++ b/content/projects/The19th.md @@ -0,0 +1,16 @@ +--- +date: '2020-08-02' +title: 'The 19th News' +github: '' +external: 'https://19thnews.org/' +tech: + - WordPress + - Timber + - Gutenberg + - PHP + - JS + - Mailchimp + - AMP +company: 'Upstatement' +showInProjects: false +--- diff --git a/content/projects/Threadable.md b/content/projects/Threadable.md new file mode 100644 index 0000000..ae8b1aa --- /dev/null +++ b/content/projects/Threadable.md @@ -0,0 +1,13 @@ +--- +date: '2022-09-08' +title: 'Threadable' +github: '' +external: 'https://www.threadablebooks.com/' +ios: 'https://apps.apple.com/us/app/threadable/id1550995547' +tech: + - React Native + - Ruby on Rails + - Firebase +company: 'Upstatement' +showInProjects: false +--- diff --git a/content/projects/TimeToHaveMoreFun.md b/content/projects/TimeToHaveMoreFun.md new file mode 100644 index 0000000..c7357a9 --- /dev/null +++ b/content/projects/TimeToHaveMoreFun.md @@ -0,0 +1,14 @@ +--- +date: '2020-01-10' +title: 'Time to Have More Fun' +github: 'https://github.com/bchiang7/time-to-have-more-fun' +external: 'https://time-to-have-more-fun.now.sh/' +tech: + - Next.js + - Tailwind CSS + - Firebase +company: '' +showInProjects: true +--- + +A single page web app for helping me choose where to travel, built with Next.js, Firebase, and Tailwind CSS diff --git a/content/projects/UpstatementDotCom.md b/content/projects/UpstatementDotCom.md new file mode 100644 index 0000000..3cafa7a --- /dev/null +++ b/content/projects/UpstatementDotCom.md @@ -0,0 +1,12 @@ +--- +date: '2019-11-25' +title: 'Upstatement.com' +github: '' +external: 'https://www.upstatement.com/' +tech: + - Nuxt + - Vue + - Prismic +company: 'Upstatement' +showInProjects: false +--- diff --git a/content/projects/Vanderbilt.md b/content/projects/Vanderbilt.md new file mode 100644 index 0000000..afc43cf --- /dev/null +++ b/content/projects/Vanderbilt.md @@ -0,0 +1,12 @@ +--- +date: '2021-06-01' +title: 'Vanderbilt Design System' +github: '' +external: 'https://www.vanderbilt.edu/' +tech: + - Twig + - Puppy + - JS +company: 'Upstatement' +showInProjects: false +--- diff --git a/content/projects/WeatherWidget.md b/content/projects/WeatherWidget.md new file mode 100644 index 0000000..6d74ba5 --- /dev/null +++ b/content/projects/WeatherWidget.md @@ -0,0 +1,13 @@ +--- +date: '2016-11-16' +title: 'Weather Widget' +github: 'https://github.com/bchiang7/DemoWebApp' +external: 'http://quiet-dusk-89245.herokuapp.com/' +tech: + - Node + - Express + - EJS +showInProjects: false +--- + +Simple weather app made with Node.js, Express, and Heroku. Utilized the OpenWeatherMap API and Google Maps API. diff --git a/content/projects/images/SpotifyProfile.png b/content/projects/images/SpotifyProfile.png new file mode 100644 index 0000000..3350a30 Binary files /dev/null and b/content/projects/images/SpotifyProfile.png differ diff --git a/content/projects/images/blistabloc.png b/content/projects/images/blistabloc.png new file mode 100644 index 0000000..b4b7abb Binary files /dev/null and b/content/projects/images/blistabloc.png differ diff --git a/content/projects/images/google-keep-clone.png b/content/projects/images/google-keep-clone.png new file mode 100644 index 0000000..80c1ca7 Binary files /dev/null and b/content/projects/images/google-keep-clone.png differ diff --git a/content/projects/images/halcyon-demo.png b/content/projects/images/halcyon-demo.png new file mode 100644 index 0000000..af987f7 Binary files /dev/null and b/content/projects/images/halcyon-demo.png differ diff --git a/content/projects/images/halcyon.png b/content/projects/images/halcyon.png new file mode 100644 index 0000000..a5a68af Binary files /dev/null and b/content/projects/images/halcyon.png differ diff --git a/content/projects/images/v3-og.png b/content/projects/images/v3-og.png new file mode 100644 index 0000000..02cc889 Binary files /dev/null and b/content/projects/images/v3-og.png differ diff --git a/content/projects/v1.md b/content/projects/v1.md new file mode 100644 index 0000000..d4b53f7 --- /dev/null +++ b/content/projects/v1.md @@ -0,0 +1,14 @@ +--- +date: '2016-03-01' +title: 'Personal Website V1' +github: 'https://github.com/bchiang7/v1' +external: 'https://bchiang7.github.io/v1/' +tech: + - HTML + - CSS + - JS + - Bootstrap +showInProjects: true +--- + +My first portfolio website I designed and built in 2014. I learned quite a bit about HTML, CSS, and SEO. Since then, I think my web development and design skills have improved immensely. diff --git a/content/projects/v2.md b/content/projects/v2.md new file mode 100644 index 0000000..cf382db --- /dev/null +++ b/content/projects/v2.md @@ -0,0 +1,13 @@ +--- +date: '2016-12-01' +title: 'Personal Website V2' +github: 'https://github.com/bchiang7/v2' +external: 'https://bchiang7.github.io/v2/' +tech: + - Jekyll + - SCSS + - JS +showInProjects: true +--- + +Second iteration of my personal website. Designed and developed with a conscious effort to avoid using any superfluous frameworks like Bootstrap. diff --git a/content/projects/v3.md b/content/projects/v3.md new file mode 100644 index 0000000..651e30b --- /dev/null +++ b/content/projects/v3.md @@ -0,0 +1,13 @@ +--- +date: '2017-10-01' +title: 'Personal Website V3' +github: 'https://github.com/bchiang7/bchiang7.github.io' +external: 'https://bchiang7.github.io/v3/' +tech: + - Jekyll + - SCSS + - JS +showInProjects: true +--- + +Third iteration of my personal website built with Jekyll and hosted on GitHub Pages. diff --git a/gatsby-browser.js b/gatsby-browser.js new file mode 100644 index 0000000..50a4a2a --- /dev/null +++ b/gatsby-browser.js @@ -0,0 +1,5 @@ +/** + * Implement Gatsby's Browser APIs in this file. + * + * See: https://www.gatsbyjs.org/docs/browser-apis/ + */ diff --git a/gatsby-config.js b/gatsby-config.js new file mode 100644 index 0000000..d3ca973 --- /dev/null +++ b/gatsby-config.js @@ -0,0 +1,159 @@ +const config = require('./src/config'); + +module.exports = { + siteMetadata: { + title: 'Soham Sonar', + description: + 'I’m a software engineer specialized in building robust software using Python programming language . Currently, my passion is fueled by the captivating world of machine learning.', + siteUrl: 'https://sohamvsonar.github.io', + image: '/og.png', // Path to your image you placed in the 'static' folder + twitterUsername: '@SohamSonar', + }, + plugins: [ + `gatsby-plugin-react-helmet`, + `gatsby-plugin-styled-components`, + `gatsby-plugin-image`, + `gatsby-plugin-sharp`, + `gatsby-transformer-sharp`, + `gatsby-plugin-sitemap`, + `gatsby-plugin-robots-txt`, + { + resolve: `gatsby-plugin-manifest`, + options: { + name: 'Soham Sonar', + short_name: 'Soham Sonar', + start_url: '/', + background_color: config.colors.darkNavy, + theme_color: config.colors.navy, + display: 'minimal-ui', + icon: 'src/images/logo.png', + }, + }, + `gatsby-plugin-offline`, + { + resolve: `gatsby-source-filesystem`, + options: { + name: `images`, + path: `${__dirname}/src/images`, + }, + }, + { + resolve: 'gatsby-source-filesystem', + options: { + name: 'content', + path: `${__dirname}/content/`, + }, + }, + { + resolve: `gatsby-source-filesystem`, + options: { + name: `posts`, + path: `${__dirname}/content/posts`, + }, + }, + { + resolve: `gatsby-source-filesystem`, + options: { + name: `projects`, + path: `${__dirname}/content/projects`, + }, + }, + { + resolve: `gatsby-transformer-remark`, + options: { + plugins: [ + { + // https://www.gatsbyjs.org/packages/gatsby-remark-external-links + resolve: 'gatsby-remark-external-links', + options: { + target: '_blank', + rel: 'nofollow noopener noreferrer', + }, + }, + { + // https://www.gatsbyjs.org/packages/gatsby-remark-images + resolve: 'gatsby-remark-images', + options: { + maxWidth: 700, + linkImagesToOriginal: true, + quality: 90, + tracedSVG: { color: config.colors.green }, + }, + }, + { + // https://www.gatsbyjs.org/packages/gatsby-remark-code-titles/ + resolve: 'gatsby-remark-code-titles', + }, // IMPORTANT: this must be ahead of other plugins that use code blocks + { + // https://www.gatsbyjs.org/packages/gatsby-remark-prismjs + resolve: `gatsby-remark-prismjs`, + options: { + // Class prefix for
 tags containing syntax highlighting;
+              // defaults to 'language-' (e.g. 
).
+              // If your site loads Prism into the browser at runtime,
+              // (e.g. for use with libraries like react-live),
+              // you may use this to prevent Prism from re-processing syntax.
+              // This is an uncommon use-case though;
+              // If you're unsure, it's best to use the default value.
+              classPrefix: 'language-',
+              // This is used to allow setting a language for inline code
+              // (i.e. single backticks) by creating a separator.
+              // This separator is a string and will do no white-space
+              // stripping.
+              // A suggested value for English speakers is the non-ascii
+              // character '›'.
+              inlineCodeMarker: null,
+              // This lets you set up language aliases.  For example,
+              // setting this to '{ sh: "bash" }' will let you use
+              // the language "sh" which will highlight using the
+              // bash highlighter.
+              aliases: {},
+              // This toggles the display of line numbers globally alongside the code.
+              // To use it, add the following line in gatsby-browser.js
+              // right after importing the prism color scheme:
+              //  require("prismjs/plugins/line-numbers/prism-line-numbers.css")
+              // Defaults to false.
+              // If you wish to only show line numbers on certain code blocks,
+              // leave false and use the {numberLines: true} syntax below
+              showLineNumbers: false,
+              // If setting this to true, the parser won't handle and highlight inline
+              // code used in markdown i.e. single backtick code like `this`.
+              noInlineHighlight: false,
+              // This adds a new language definition to Prism or extend an already
+              // existing language definition. More details on this option can be
+              // found under the header "Add new language definition or extend an
+              // existing language" below.
+              languageExtensions: [
+                {
+                  language: 'superscript',
+                  extend: 'javascript',
+                  definition: {
+                    superscript_types: /(SuperType)/,
+                  },
+                  insertBefore: {
+                    function: {
+                      superscript_keywords: /(superif|superelse)/,
+                    },
+                  },
+                },
+              ],
+              // Customize the prompt used in shell output
+              // Values below are default
+              prompt: {
+                user: 'root',
+                host: 'localhost',
+                global: false,
+              },
+            },
+          },
+        ],
+      },
+    },
+    {
+      resolve: `gatsby-plugin-google-analytics`,
+      options: {
+        trackingId: 'UA-45666519-2',
+      },
+    },
+  ],
+};
diff --git a/gatsby-node.js b/gatsby-node.js
new file mode 100644
index 0000000..54a7120
--- /dev/null
+++ b/gatsby-node.js
@@ -0,0 +1,107 @@
+/**
+ * Implement Gatsby's Node APIs in this file.
+ *
+ * See: https://www.gatsbyjs.org/docs/node-apis/
+ */
+
+const path = require('path');
+const _ = require('lodash');
+
+exports.createPages = async ({ actions, graphql, reporter }) => {
+  const { createPage } = actions;
+  const postTemplate = path.resolve(`src/templates/post.js`);
+  const tagTemplate = path.resolve('src/templates/tag.js');
+
+  const result = await graphql(`
+    {
+      postsRemark: allMarkdownRemark(
+        filter: { fileAbsolutePath: { regex: "/content/posts/" } }
+        sort: { order: DESC, fields: [frontmatter___date] }
+        limit: 1000
+      ) {
+        edges {
+          node {
+            frontmatter {
+              slug
+            }
+          }
+        }
+      }
+      tagsGroup: allMarkdownRemark(limit: 2000) {
+        group(field: frontmatter___tags) {
+          fieldValue
+        }
+      }
+    }
+  `);
+
+  // Handle errors
+  if (result.errors) {
+    reporter.panicOnBuild(`Error while running GraphQL query.`);
+    return;
+  }
+
+  // Create post detail pages
+  const posts = result.data.postsRemark.edges;
+
+  posts.forEach(({ node }) => {
+    createPage({
+      path: node.frontmatter.slug,
+      component: postTemplate,
+      context: {},
+    });
+  });
+
+  // Extract tag data from query
+  const tags = result.data.tagsGroup.group;
+  // Make tag pages
+  tags.forEach(tag => {
+    createPage({
+      path: `/pensieve/tags/${_.kebabCase(tag.fieldValue)}/`,
+      component: tagTemplate,
+      context: {
+        tag: tag.fieldValue,
+      },
+    });
+  });
+};
+
+// https://www.gatsbyjs.org/docs/node-apis/#onCreateWebpackConfig
+exports.onCreateWebpackConfig = ({ stage, loaders, actions }) => {
+  // https://www.gatsbyjs.org/docs/debugging-html-builds/#fixing-third-party-modules
+  if (stage === 'build-html' || stage === 'develop-html') {
+    actions.setWebpackConfig({
+      module: {
+        rules: [
+          {
+            test: /scrollreveal/,
+            use: loaders.null(),
+          },
+          {
+            test: /animejs/,
+            use: loaders.null(),
+          },
+          {
+            test: /miniraf/,
+            use: loaders.null(),
+          },
+        ],
+      },
+    });
+  }
+
+  actions.setWebpackConfig({
+    resolve: {
+      alias: {
+        '@components': path.resolve(__dirname, 'src/components'),
+        '@config': path.resolve(__dirname, 'src/config'),
+        '@fonts': path.resolve(__dirname, 'src/fonts'),
+        '@hooks': path.resolve(__dirname, 'src/hooks'),
+        '@images': path.resolve(__dirname, 'src/images'),
+        '@pages': path.resolve(__dirname, 'src/pages'),
+        '@styles': path.resolve(__dirname, 'src/styles'),
+        '@utils': path.resolve(__dirname, 'src/utils'),
+      },
+    },
+  });
+};
diff --git a/gatsby-ssr.js b/gatsby-ssr.js
new file mode 100644
index 0000000..cdad094
--- /dev/null
+++ b/gatsby-ssr.js
@@ -0,0 +1,7 @@
+/**
+ * Implement Gatsby's SSR (Server Side Rendering) APIs in this file.
+ *
+ * See: https://www.gatsbyjs.org/docs/ssr-apis/
+ */
+
+ // You can delete this file if you're not using it
\ No newline at end of file
diff --git a/package.json b/package.json
new file mode 100644
index 0000000..1a39983
--- /dev/null
+++ b/package.json
@@ -0,0 +1,80 @@
+{
+  "name": "v1",
+  "description": "Personal Website V1",
+  "version": "1.0.0",
+  "author": "Soham Sonar ",
+  "repository": {
+    "type": "git",
+    "url": "https://github.com/sohamvsonar/sohamvsonar.github.io"
+  },
+  "keywords": [
+    "gatsby"
+  ],
+  "license": "MIT",
+  "browserslist": "> 0.25%, not dead",
+  "scripts": {
+    "build": "gatsby build",
+    "develop": "gatsby develop",
+    "format": "prettier --write \"**/*.{js,jsx,json,md}\"",
+    "start": "npm run develop",
+    "serve": "gatsby serve",
+    "clean": "gatsby clean",
+    "prepare": "husky install",
+    "lint-staged": "lint-staged"
+  },
+  "lint-staged": {
+    "*.{js,css,json,md}": [
+      "prettier --write"
+    ],
+    "*.js": [
+      "eslint --fix"
+    ]
+  },
+  "dependencies": {
+    "animejs": "^3.1.0",
+    "babel-plugin-styled-components": "^1.12.0",
+    "gatsby": "^3.4.1",
+    "gatsby-plugin-google-analytics": "^3.4.0",
+    "gatsby-plugin-image": "^1.4.0",
+    "gatsby-plugin-manifest": "^3.4.0",
+    "gatsby-plugin-netlify": "^3.4.0",
+    "gatsby-plugin-offline": "^4.4.0",
+    "gatsby-plugin-react-helmet": "^4.4.0",
+    "gatsby-plugin-robots-txt": "^1.5.6",
+    "gatsby-plugin-sharp": "^3.4.1",
+    "gatsby-plugin-sitemap": "^4.0.0",
+    "gatsby-plugin-styled-components": "^4.4.0",
+    "gatsby-remark-external-links": "0.0.4",
+    "gatsby-remark-images": "^5.1.0",
+    "gatsby-remark-prismjs": "^5.1.0",
+    "gatsby-source-filesystem": "^3.4.0",
+    "gatsby-transformer-remark": "^4.1.0",
+    "gatsby-transformer-sharp": "^3.4.0",
+    "lodash": "^4.17.19",
+    "prismjs": "^1.27.0",
+    "prop-types": "^15.7.2",
+    "react": "^17.0.2",
+    "react-dom": "^17.0.2",
+    "react-helmet": "^6.1.0",
+    "react-transition-group": "^4.3.0",
+    "scrollreveal": "^4.0.5",
+    "styled-components": "^5.3.0"
+  },
+  "devDependencies": {
+    "@babel/core": "^7.14.0",
+    "@babel/eslint-parser": "^7.13.14",
+    "@babel/preset-react": "^7.13.13",
+    "@upstatement/eslint-config": "^1.0.0",
+    "@upstatement/prettier-config": "^1.0.0",
+    "babel-preset-gatsby": "^1.4.0",
+    "eslint": "^7.25.0",
+    "eslint-config-prettier": "^8.3.0",
+    "eslint-plugin-jsx-a11y": "^6.4.1",
+    "eslint-plugin-react": "^7.23.2",
+    "gatsby-remark-code-titles": "^1.1.0",
+    "husky": "^6.0.0",
+    "lint-staged": "^10.1.2",
+    "prettier": "^2.2.1"
+  }
+  
+}
diff --git a/prettier.config.js b/prettier.config.js
new file mode 100644
index 0000000..992b968
--- /dev/null
+++ b/prettier.config.js
@@ -0,0 +1 @@
+module.exports = require('@upstatement/prettier-config');
diff --git a/src/components/email.js b/src/components/email.js
new file mode 100644
index 0000000..d60107f
--- /dev/null
+++ b/src/components/email.js
@@ -0,0 +1,50 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import styled from 'styled-components';
+import { email } from '@config';
+import { Side } from '@components';
+
+const StyledLinkWrapper = styled.div`
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  position: relative;
+
+  &:after {
+    content: '';
+    display: block;
+    width: 1px;
+    height: 150px;
+    margin: 0 auto;
+    background-color: var(--light-slate);
+  }
+
+  a {
+    margin: 20px auto;
+    padding: 10px;
+    font-family: var(--font-mono);
+    font-size: var(--fz-s);
+    line-height: var(--fz-lg);
+    letter-spacing: 0.1em;
+    writing-mode: vertical-rl;
+
+    &:hover,
+    &:focus {
+      transform: translateY(-3px);
+    }
+  }
+`;
+
+const Email = ({ isHome }) => (
+  
+    
+      {email}
+    
+  
+);
+
+Email.propTypes = {
+  isHome: PropTypes.bool,
+};
+
+export default Email;
diff --git a/src/components/footer.js b/src/components/footer.js
new file mode 100644
index 0000000..10b9078
--- /dev/null
+++ b/src/components/footer.js
@@ -0,0 +1,133 @@
+import React, { useState, useEffect } from 'react';
+import PropTypes from 'prop-types';
+import styled from 'styled-components';
+import { Icon } from '@components/icons';
+import { socialMedia } from '@config';
+
+const StyledFooter = styled.footer`
+  ${({ theme }) => theme.mixins.flexCenter};
+  flex-direction: column;
+  height: auto;
+  min-height: 70px;
+  padding: 15px;
+  text-align: center;
+`;
+
+const StyledSocialLinks = styled.div`
+  display: none;
+
+  @media (max-width: 768px) {
+    display: block;
+    width: 100%;
+    max-width: 270px;
+    margin: 0 auto 10px;
+    color: var(--light-slate);
+  }
+
+  ul {
+    ${({ theme }) => theme.mixins.flexBetween};
+    padding: 0;
+    margin: 0;
+    list-style: none;
+
+    a {
+      padding: 10px;
+      svg {
+        width: 20px;
+        height: 20px;
+      }
+    }
+  }
+`;
+
+const StyledCredit = styled.div`
+  color: var(--light-slate);
+  font-family: var(--font-mono);
+  font-size: var(--fz-xxs);
+  line-height: 1;
+
+  a {
+    padding: 10px;
+  }
+
+  .github-stats {
+    margin-top: 10px;
+
+    & > span {
+      display: inline-flex;
+      align-items: center;
+      margin: 0 7px;
+    }
+    svg {
+      display: inline-block;
+      margin-right: 5px;
+      width: 14px;
+      height: 14px;
+    }
+  }
+`;
+
+const Footer = () => {
+  const [githubInfo, setGitHubInfo] = useState({
+    stars: null,
+    forks: null,
+  });
+
+  useEffect(() => {
+    if (process.env.NODE_ENV !== 'production') {
+      return;
+    }
+    fetch('https://api.github.com/repos/bchiang7/v4')
+      .then(response => response.json())
+      .then(json => {
+        const { stargazers_count, forks_count } = json;
+        setGitHubInfo({
+          stars: stargazers_count,
+          forks: forks_count,
+        });
+      })
+      .catch(e => console.error(e));
+  }, []);
+
+  return (
+    
+      
+        
    + {socialMedia && + socialMedia.map(({ name, url }, i) => ( +
  • + + + +
  • + ))} +
+
+ + + +
Designed & Built by Brittany Chiang
+
Revised by Soham Sonar
+ {githubInfo.stars && githubInfo.forks && ( +
+ + + {githubInfo.stars.toLocaleString()} + + + + {githubInfo.forks.toLocaleString()} + +
+ )} +
+
+
+ ); +}; + +Footer.propTypes = { + githubInfo: PropTypes.object, +}; + +export default Footer; diff --git a/src/components/head.js b/src/components/head.js new file mode 100644 index 0000000..8378bb1 --- /dev/null +++ b/src/components/head.js @@ -0,0 +1,79 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { Helmet } from 'react-helmet'; +import { useLocation } from '@reach/router'; +import { useStaticQuery, graphql } from 'gatsby'; + +// https://www.gatsbyjs.com/docs/add-seo-component/ + +const Head = ({ title, description, image }) => { + const { pathname } = useLocation(); + + const { site } = useStaticQuery( + graphql` + query { + site { + siteMetadata { + defaultTitle: title + defaultDescription: description + siteUrl + defaultImage: image + twitterUsername + } + } + } + `, + ); + + const { + defaultTitle, + defaultDescription, + siteUrl, + defaultImage, + twitterUsername, + } = site.siteMetadata; + + const seo = { + title: title || defaultTitle, + description: description || defaultDescription, + image: `${siteUrl}${image || defaultImage}`, + url: `${siteUrl}${pathname}`, + }; + + return ( + + + + + + + + + + + + + + + + + + + + + ); +}; + +export default Head; + +Head.propTypes = { + title: PropTypes.string, + description: PropTypes.string, + image: PropTypes.string, +}; + +Head.defaultProps = { + title: null, + description: null, + image: null, +}; diff --git a/src/components/icons/appstore.js b/src/components/icons/appstore.js new file mode 100644 index 0000000..573fe19 --- /dev/null +++ b/src/components/icons/appstore.js @@ -0,0 +1,51 @@ +import React from 'react'; + +const IconAppStore = () => ( + + Apple App Store + + + + + + + + + + + + + + + + + + + + + +); + +export default IconAppStore; diff --git a/src/components/icons/bookmark.js b/src/components/icons/bookmark.js new file mode 100644 index 0000000..d77a285 --- /dev/null +++ b/src/components/icons/bookmark.js @@ -0,0 +1,18 @@ +import React from 'react'; + +const IconBookmark = () => ( + + Bookmark + + +); + +export default IconBookmark; diff --git a/src/components/icons/codepen.js b/src/components/icons/codepen.js new file mode 100644 index 0000000..657fdc4 --- /dev/null +++ b/src/components/icons/codepen.js @@ -0,0 +1,23 @@ +import React from 'react'; + +const IconCodepen = () => ( + + CodePen + + + + + + +); + +export default IconCodepen; diff --git a/src/components/icons/discord.js b/src/components/icons/discord.js new file mode 100644 index 0000000..25b7db1 --- /dev/null +++ b/src/components/icons/discord.js @@ -0,0 +1,21 @@ +import React from 'react'; + +const IconDiscord = () => ( + + Discord + + + + +); + +export default IconDiscord; diff --git a/src/components/icons/external.js b/src/components/icons/external.js new file mode 100644 index 0000000..b903d56 --- /dev/null +++ b/src/components/icons/external.js @@ -0,0 +1,21 @@ +import React from 'react'; + +const IconExternal = () => ( + + External Link + + + + +); + +export default IconExternal; diff --git a/src/components/icons/folder.js b/src/components/icons/folder.js new file mode 100644 index 0000000..8aaf805 --- /dev/null +++ b/src/components/icons/folder.js @@ -0,0 +1,19 @@ +import React from 'react'; + +const IconFolder = () => ( + + Folder + + +); + +export default IconFolder; diff --git a/src/components/icons/fork.js b/src/components/icons/fork.js new file mode 100644 index 0000000..a568022 --- /dev/null +++ b/src/components/icons/fork.js @@ -0,0 +1,20 @@ +import React from 'react'; + +const IconFork = () => ( + + Git Fork + + + + + +); + +export default IconFork; diff --git a/src/components/icons/github.js b/src/components/icons/github.js new file mode 100644 index 0000000..6be6665 --- /dev/null +++ b/src/components/icons/github.js @@ -0,0 +1,19 @@ +import React from 'react'; + +const IconGitHub = () => ( + + GitHub + + +); + +export default IconGitHub; diff --git a/src/components/icons/hex.js b/src/components/icons/hex.js new file mode 100644 index 0000000..2dd77d5 --- /dev/null +++ b/src/components/icons/hex.js @@ -0,0 +1,21 @@ +import React from 'react'; + +const IconHex = () => ( + + Hexagon + + + + + + +); + +export default IconHex; diff --git a/src/components/icons/icon.js b/src/components/icons/icon.js new file mode 100644 index 0000000..7e1f7a3 --- /dev/null +++ b/src/components/icons/icon.js @@ -0,0 +1,62 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { + IconAppStore, + IconBookmark, + IconCodepen, + IconExternal, + IconFolder, + IconFork, + IconGitHub, + IconInstagram, + IconLinkedin, + IconLoader, + IconLogo, + IconPlayStore, + IconStar, + IconTwitter, + IconDiscord, +} from '@components/icons'; + +const Icon = ({ name }) => { + switch (name) { + case 'AppStore': + return ; + case 'Bookmark': + return ; + case 'Codepen': + return ; + case 'External': + return ; + case 'Folder': + return ; + case 'Fork': + return ; + case 'GitHub': + return ; + case 'Instagram': + return ; + case 'Linkedin': + return ; + case 'Loader': + return ; + case 'Logo': + return ; + case 'PlayStore': + return ; + case 'Star': + return ; + case 'Twitter': + return ; + case 'Discord': + return ; + default: + return ; + } +}; + +Icon.propTypes = { + name: PropTypes.string.isRequired, +}; + +export default Icon; diff --git a/src/components/icons/index.js b/src/components/icons/index.js new file mode 100644 index 0000000..3037f02 --- /dev/null +++ b/src/components/icons/index.js @@ -0,0 +1,17 @@ +export { default as IconAppStore } from './appstore'; +export { default as IconBookmark } from './bookmark'; +export { default as IconCodepen } from './codepen'; +export { default as IconExternal } from './external'; +export { default as IconFolder } from './folder'; +export { default as IconFork } from './fork'; +export { default as Icon } from './icon'; +export { default as IconGitHub } from './github'; +export { default as IconHex } from './hex'; +export { default as IconInstagram } from './instagram'; +export { default as IconLinkedin } from './linkedin'; +export { default as IconLoader } from './loader'; +export { default as IconLogo } from './logo'; +export { default as IconPlayStore } from './playstore'; +export { default as IconStar } from './star'; +export { default as IconTwitter } from './twitter'; +export { default as IconDiscord } from './discord'; \ No newline at end of file diff --git a/src/components/icons/instagram.js b/src/components/icons/instagram.js new file mode 100644 index 0000000..777eaa6 --- /dev/null +++ b/src/components/icons/instagram.js @@ -0,0 +1,21 @@ +import React from 'react'; + +const IconInstagram = () => ( + + Instagram + + + + +); + +export default IconInstagram; diff --git a/src/components/icons/linkedin.js b/src/components/icons/linkedin.js new file mode 100644 index 0000000..ce2c21b --- /dev/null +++ b/src/components/icons/linkedin.js @@ -0,0 +1,21 @@ +import React from 'react'; + +const IconLinkedin = () => ( + + LinkedIn + + + + +); + +export default IconLinkedin; diff --git a/src/components/icons/loader.js b/src/components/icons/loader.js new file mode 100644 index 0000000..06de784 --- /dev/null +++ b/src/components/icons/loader.js @@ -0,0 +1,27 @@ +import React from 'react'; + +const IconLoader = () => ( + +); + +export default IconLoader; diff --git a/src/components/icons/logo.js b/src/components/icons/logo.js new file mode 100644 index 0000000..7869577 --- /dev/null +++ b/src/components/icons/logo.js @@ -0,0 +1,24 @@ +import React from 'react'; + +const IconLogo = () => ( + +); + +export default IconLogo; diff --git a/src/components/icons/playstore.js b/src/components/icons/playstore.js new file mode 100644 index 0000000..3d66a18 --- /dev/null +++ b/src/components/icons/playstore.js @@ -0,0 +1,17 @@ +import React from 'react'; + +const IconPlayStore = () => ( + + Google Play Store + + +); + +export default IconPlayStore; diff --git a/src/components/icons/star.js b/src/components/icons/star.js new file mode 100644 index 0000000..6bb90e7 --- /dev/null +++ b/src/components/icons/star.js @@ -0,0 +1,17 @@ +import React from 'react'; + +const IconStar = () => ( + + Star + + +); + +export default IconStar; diff --git a/src/components/icons/twitter.js b/src/components/icons/twitter.js new file mode 100644 index 0000000..623f035 --- /dev/null +++ b/src/components/icons/twitter.js @@ -0,0 +1,19 @@ +import React from 'react'; + +const IconTwitter = () => ( + + Twitter + + +); + +export default IconTwitter; diff --git a/src/components/index.js b/src/components/index.js new file mode 100644 index 0000000..0a5f6d3 --- /dev/null +++ b/src/components/index.js @@ -0,0 +1,16 @@ +export { default as Head } from './head'; +export { default as Layout } from './layout'; +export { default as Loader } from './loader'; +export { default as Nav } from './nav'; +export { default as Menu } from './menu'; +export { default as Side } from './side'; +export { default as Social } from './social'; +export { default as Email } from './email'; +export { default as Footer } from './footer'; +export { default as Hero } from './sections/hero'; +export { default as About } from './sections/about'; +export { default as Jobs } from './sections/jobs'; +export { default as Featured } from './sections/featured'; +export { default as Projects } from './sections/projects'; +export { default as Contact } from './sections/contact'; +export { default as Skills } from './sections/skills'; diff --git a/src/components/layout.js b/src/components/layout.js new file mode 100644 index 0000000..270b65f --- /dev/null +++ b/src/components/layout.js @@ -0,0 +1,86 @@ +import React, { useState, useEffect } from 'react'; +import PropTypes from 'prop-types'; +import styled, { ThemeProvider } from 'styled-components'; +import { Head, Loader, Nav, Social, Email, Footer } from '@components'; +import { GlobalStyle, theme } from '@styles'; + +const StyledContent = styled.div` + display: flex; + flex-direction: column; + min-height: 100vh; +`; + +const Layout = ({ children, location }) => { + const isHome = location.pathname === '/'; + const [isLoading, setIsLoading] = useState(isHome); + + // Sets target="_blank" rel="noopener noreferrer" on external links + const handleExternalLinks = () => { + const allLinks = Array.from(document.querySelectorAll('a')); + if (allLinks.length > 0) { + allLinks.forEach(link => { + if (link.host !== window.location.host) { + link.setAttribute('rel', 'noopener noreferrer'); + link.setAttribute('target', '_blank'); + } + }); + } + }; + + useEffect(() => { + if (isLoading) { + return; + } + + if (location.hash) { + const id = location.hash.substring(1); // location.hash without the '#' + setTimeout(() => { + const el = document.getElementById(id); + if (el) { + el.scrollIntoView(); + el.focus(); + } + }, 0); + } + + handleExternalLinks(); + }, [isLoading]); + + return ( + <> + + +
+ + + + + Skip to Content + + + {isLoading && isHome ? ( + setIsLoading(false)} /> + ) : ( + +
+ + ); +}; + +Layout.propTypes = { + children: PropTypes.node.isRequired, + location: PropTypes.object.isRequired, +}; + +export default Layout; diff --git a/src/components/loader.js b/src/components/loader.js new file mode 100644 index 0000000..82560e5 --- /dev/null +++ b/src/components/loader.js @@ -0,0 +1,99 @@ +import React, { useState, useEffect } from 'react'; +import { Helmet } from 'react-helmet'; +import PropTypes from 'prop-types'; +import anime from 'animejs'; +import styled from 'styled-components'; +import { IconLoader } from '@components/icons'; + +const StyledLoader = styled.div` + ${({ theme }) => theme.mixins.flexCenter}; + position: fixed; + top: 0; + bottom: 0; + left: 0; + right: 0; + width: 100%; + height: 100%; + background-color: var(--dark-navy); + z-index: 99; + + .logo-wrapper { + width: max-content; + max-width: 100px; + transition: var(--transition); + opacity: ${props => (props.isMounted ? 1 : 0)}; + svg { + display: block; + width: 100%; + height: 100%; + margin: 0 auto; + fill: none; + user-select: none; + #B { + opacity: 0; + } + } + } +`; + +const Loader = ({ finishLoading }) => { + const [isMounted, setIsMounted] = useState(false); + + const animate = () => { + const loader = anime.timeline({ + complete: () => finishLoading(), + }); + + loader + .add({ + targets: '#logo path', + delay: 300, + duration: 1500, + easing: 'easeInOutQuart', + strokeDashoffset: [anime.setDashoffset, 0], + }) + .add({ + targets: '#logo #B', + duration: 700, + easing: 'easeInOutQuart', + opacity: 1, + }) + .add({ + targets: '#logo', + delay: 500, + duration: 300, + easing: 'easeInOutQuart', + opacity: 0, + scale: 0.1, + }) + .add({ + targets: '.loader', + duration: 200, + easing: 'easeInOutQuart', + opacity: 0, + zIndex: -1, + }); + }; + + useEffect(() => { + const timeout = setTimeout(() => setIsMounted(true), 10); + animate(); + return () => clearTimeout(timeout); + }, []); + + return ( + + + +
+ +
+
+ ); +}; + +Loader.propTypes = { + finishLoading: PropTypes.func.isRequired, +}; + +export default Loader; diff --git a/src/components/menu.js b/src/components/menu.js new file mode 100644 index 0000000..0cfe24d --- /dev/null +++ b/src/components/menu.js @@ -0,0 +1,279 @@ +import React, { useState, useEffect, useRef } from 'react'; +import { Helmet } from 'react-helmet'; +import { Link } from 'gatsby'; +import styled from 'styled-components'; +import { navLinks } from '@config'; +import { KEY_CODES } from '@utils'; +import { useOnClickOutside } from '@hooks'; + +const StyledMenu = styled.div` + display: none; + + @media (max-width: 768px) { + display: block; + } +`; + +const StyledHamburgerButton = styled.button` + display: none; + + @media (max-width: 768px) { + ${({ theme }) => theme.mixins.flexCenter}; + position: relative; + z-index: 10; + margin-right: -15px; + padding: 15px; + border: 0; + background-color: transparent; + color: inherit; + text-transform: none; + transition-timing-function: linear; + transition-duration: 0.15s; + transition-property: opacity, filter; + } + + .ham-box { + display: inline-block; + position: relative; + width: var(--hamburger-width); + height: 24px; + } + + .ham-box-inner { + position: absolute; + top: 50%; + right: 0; + width: var(--hamburger-width); + height: 2px; + border-radius: var(--border-radius); + background-color: var(--green); + transition-duration: 0.22s; + transition-property: transform; + transition-delay: ${props => (props.menuOpen ? `0.12s` : `0s`)}; + transform: rotate(${props => (props.menuOpen ? `225deg` : `0deg`)}); + transition-timing-function: cubic-bezier( + ${props => (props.menuOpen ? `0.215, 0.61, 0.355, 1` : `0.55, 0.055, 0.675, 0.19`)} + ); + &:before, + &:after { + content: ''; + display: block; + position: absolute; + left: auto; + right: 0; + width: var(--hamburger-width); + height: 2px; + border-radius: 4px; + background-color: var(--green); + transition-timing-function: ease; + transition-duration: 0.15s; + transition-property: transform; + } + &:before { + width: ${props => (props.menuOpen ? `100%` : `120%`)}; + top: ${props => (props.menuOpen ? `0` : `-10px`)}; + opacity: ${props => (props.menuOpen ? 0 : 1)}; + transition: ${({ menuOpen }) => + menuOpen ? 'var(--ham-before-active)' : 'var(--ham-before)'}; + } + &:after { + width: ${props => (props.menuOpen ? `100%` : `80%`)}; + bottom: ${props => (props.menuOpen ? `0` : `-10px`)}; + transform: rotate(${props => (props.menuOpen ? `-90deg` : `0`)}); + transition: ${({ menuOpen }) => (menuOpen ? 'var(--ham-after-active)' : 'var(--ham-after)')}; + } + } +`; + +const StyledSidebar = styled.aside` + display: none; + + @media (max-width: 768px) { + ${({ theme }) => theme.mixins.flexCenter}; + position: fixed; + top: 0; + bottom: 0; + right: 0; + padding: 50px 10px; + width: min(75vw, 400px); + height: 100vh; + outline: 0; + background-color: var(--light-navy); + box-shadow: -10px 0px 30px -15px var(--navy-shadow); + z-index: 9; + transform: translateX(${props => (props.menuOpen ? 0 : 100)}vw); + visibility: ${props => (props.menuOpen ? 'visible' : 'hidden')}; + transition: var(--transition); + } + + nav { + ${({ theme }) => theme.mixins.flexBetween}; + width: 100%; + flex-direction: column; + color: var(--lightest-slate); + font-family: var(--font-mono); + text-align: center; + } + + ol { + padding: 0; + margin: 0; + list-style: none; + width: 100%; + + li { + position: relative; + margin: 0 auto 20px; + counter-increment: item 1; + font-size: clamp(var(--fz-sm), 4vw, var(--fz-lg)); + + @media (max-width: 600px) { + margin: 0 auto 10px; + } + + &:before { + content: '0' counter(item) '.'; + display: block; + margin-bottom: 5px; + color: var(--green); + font-size: var(--fz-sm); + } + } + + a { + ${({ theme }) => theme.mixins.link}; + width: 100%; + padding: 3px 20px 20px; + } + } + + .resume-link { + ${({ theme }) => theme.mixins.bigButton}; + padding: 18px 50px; + margin: 10% auto 0; + width: max-content; + } +`; + +const Menu = () => { + const [menuOpen, setMenuOpen] = useState(false); + + const toggleMenu = () => setMenuOpen(!menuOpen); + + const buttonRef = useRef(null); + const navRef = useRef(null); + + let menuFocusables; + let firstFocusableEl; + let lastFocusableEl; + + const setFocusables = () => { + menuFocusables = [buttonRef.current, ...Array.from(navRef.current.querySelectorAll('a'))]; + firstFocusableEl = menuFocusables[0]; + lastFocusableEl = menuFocusables[menuFocusables.length - 1]; + }; + + const handleBackwardTab = e => { + if (document.activeElement === firstFocusableEl) { + e.preventDefault(); + lastFocusableEl.focus(); + } + }; + + const handleForwardTab = e => { + if (document.activeElement === lastFocusableEl) { + e.preventDefault(); + firstFocusableEl.focus(); + } + }; + + const onKeyDown = e => { + switch (e.key) { + case KEY_CODES.ESCAPE: + case KEY_CODES.ESCAPE_IE11: { + setMenuOpen(false); + break; + } + + case KEY_CODES.TAB: { + if (menuFocusables && menuFocusables.length === 1) { + e.preventDefault(); + break; + } + if (e.shiftKey) { + handleBackwardTab(e); + } else { + handleForwardTab(e); + } + break; + } + + default: { + break; + } + } + }; + + const onResize = e => { + if (e.currentTarget.innerWidth > 768) { + setMenuOpen(false); + } + }; + + useEffect(() => { + document.addEventListener('keydown', onKeyDown); + window.addEventListener('resize', onResize); + + setFocusables(); + + return () => { + document.removeEventListener('keydown', onKeyDown); + window.removeEventListener('resize', onResize); + }; + }, []); + + const wrapperRef = useRef(); + useOnClickOutside(wrapperRef, () => setMenuOpen(false)); + + return ( + + + + + +
+ +
+
+
+ + + + + +
+ + ); +}; + +export default Menu; diff --git a/src/components/nav.js b/src/components/nav.js new file mode 100644 index 0000000..e48875c --- /dev/null +++ b/src/components/nav.js @@ -0,0 +1,289 @@ +import React, { useState, useEffect } from 'react'; +import { Link } from 'gatsby'; +import PropTypes from 'prop-types'; +import { CSSTransition, TransitionGroup } from 'react-transition-group'; +import styled, { css } from 'styled-components'; +import { navLinks } from '@config'; +import { loaderDelay } from '@utils'; +import { useScrollDirection, usePrefersReducedMotion } from '@hooks'; +import { Menu } from '@components'; +import { IconLogo, IconHex } from '@components/icons'; + +const StyledHeader = styled.header` + ${({ theme }) => theme.mixins.flexBetween}; + position: fixed; + top: 0; + z-index: 11; + padding: 0px 50px; + width: 100%; + height: var(--nav-height); + background-color: rgba(10, 25, 47, 0.85); + filter: none !important; + pointer-events: auto !important; + user-select: auto !important; + backdrop-filter: blur(10px); + transition: var(--transition); + + @media (max-width: 1080px) { + padding: 0 40px; + } + @media (max-width: 768px) { + padding: 0 25px; + } + + @media (prefers-reduced-motion: no-preference) { + ${props => + props.scrollDirection === 'up' && + !props.scrolledToTop && + css` + height: var(--nav-scroll-height); + transform: translateY(0px); + background-color: rgba(10, 25, 47, 0.85); + box-shadow: 0 10px 30px -10px var(--navy-shadow); + `}; + + ${props => + props.scrollDirection === 'down' && + !props.scrolledToTop && + css` + height: var(--nav-scroll-height); + transform: translateY(calc(var(--nav-scroll-height) * -1)); + box-shadow: 0 10px 30px -10px var(--navy-shadow); + `}; + } +`; + +const StyledNav = styled.nav` + ${({ theme }) => theme.mixins.flexBetween}; + position: relative; + width: 100%; + color: var(--lightest-slate); + font-family: var(--font-mono); + counter-reset: item 0; + z-index: 12; + + .logo { + ${({ theme }) => theme.mixins.flexCenter}; + + a { + color: var(--green); + width: 42px; + height: 42px; + position: relative; + z-index: 1; + + .hex-container { + position: absolute; + top: 0; + left: 0; + z-index: -1; + @media (prefers-reduced-motion: no-preference) { + transition: var(--transition); + } + } + + .logo-container { + position: relative; + z-index: 1; + svg { + fill: none; + user-select: none; + @media (prefers-reduced-motion: no-preference) { + transition: var(--transition); + } + polygon { + fill: var(--navy); + } + } + } + + &:hover, + &:focus { + outline: 0; + transform: translate(-4px, -4px); + .hex-container { + transform: translate(4px, 3px); + } + } + } + } +`; + +const StyledLinks = styled.div` + display: flex; + align-items: center; + + @media (max-width: 768px) { + display: none; + } + + ol { + ${({ theme }) => theme.mixins.flexBetween}; + padding: 0; + margin: 0; + list-style: none; + + li { + margin: 0 5px; + position: relative; + counter-increment: item 1; + font-size: var(--fz-xs); + + a { + padding: 10px; + + &:before { + content: '0' counter(item) '.'; + margin-right: 5px; + color: var(--green); + font-size: var(--fz-xxs); + text-align: right; + } + } + } + } + + .resume-button { + ${({ theme }) => theme.mixins.smallButton}; + margin-left: 15px; + font-size: var(--fz-xs); + } +`; + +const Nav = ({ isHome }) => { + const [isMounted, setIsMounted] = useState(!isHome); + const scrollDirection = useScrollDirection('down'); + const [scrolledToTop, setScrolledToTop] = useState(true); + const prefersReducedMotion = usePrefersReducedMotion(); + + const handleScroll = () => { + setScrolledToTop(window.pageYOffset < 50); + }; + + useEffect(() => { + if (prefersReducedMotion) { + return; + } + + const timeout = setTimeout(() => { + setIsMounted(true); + }, 100); + + window.addEventListener('scroll', handleScroll); + + return () => { + clearTimeout(timeout); + window.removeEventListener('scroll', handleScroll); + }; + }, []); + + const timeout = isHome ? loaderDelay : 0; + const fadeClass = isHome ? 'fade' : ''; + const fadeDownClass = isHome ? 'fadedown' : ''; + + const Logo = ( +
+ {isHome ? ( + +
+ +
+
+ +
+
+ ) : ( + +
+ +
+
+ +
+ + )} +
+ ); + + const ResumeLink = ( + + Resume + + ); + + return ( + + + {prefersReducedMotion ? ( + <> + {Logo} + + +
    + {navLinks && + navLinks.map(({ url, name }, i) => ( +
  1. + {name} +
  2. + ))} +
+
{ResumeLink}
+
+ + + + ) : ( + <> + + {isMounted && ( + + <>{Logo} + + )} + + + +
    + + {isMounted && + navLinks && + navLinks.map(({ url, name }, i) => ( + +
  1. + {name} +
  2. +
    + ))} +
    +
+ + + {isMounted && ( + +
+ {ResumeLink} +
+
+ )} +
+
+ + + {isMounted && ( + + + + )} + + + )} + + + ); +}; + +Nav.propTypes = { + isHome: PropTypes.bool, +}; + +export default Nav; diff --git a/src/components/sections/about.js b/src/components/sections/about.js new file mode 100644 index 0000000..a519517 --- /dev/null +++ b/src/components/sections/about.js @@ -0,0 +1,188 @@ +import React, { useEffect, useRef } from 'react'; +import { StaticImage } from 'gatsby-plugin-image'; +import styled from 'styled-components'; +import { srConfig } from '@config'; +import sr from '@utils/sr'; +import { usePrefersReducedMotion } from '@hooks'; + +const StyledAboutSection = styled.section` + max-width: 900px; + + .inner { + display: grid; + grid-template-columns: 3fr 2fr; + grid-gap: 50px; + + @media (max-width: 768px) { + display: block; + } + } +`; +const StyledText = styled.div` + ul.skills-list { + display: grid; + grid-template-columns: repeat(2, minmax(140px, 200px)); + grid-gap: 0 10px; + padding: 0; + margin: 20px 0 0 0; + overflow: hidden; + list-style: none; + + li { + position: relative; + margin-bottom: 10px; + padding-left: 20px; + font-family: var(--font-mono); + font-size: var(--fz-xs); + + &:before { + content: '▹'; + position: absolute; + left: 0; + color: var(--green); + font-size: var(--fz-sm); + line-height: 12px; + } + } + } +`; +const StyledPic = styled.div` + position: relative; + max-width: 300px; + + @media (max-width: 768px) { + margin: 50px auto 0; + width: 70%; + } + + .wrapper { + ${({ theme }) => theme.mixins.boxShadow}; + display: block; + position: relative; + width: 100%; + border-radius: var(--border-radius); + background-color: var(--green); + + &:hover, + &:focus { + outline: 0; + transform: translate(-4px, -4px); + + &:after { + transform: translate(8px, 8px); + } + + .img { + filter: none; + mix-blend-mode: normal; + } + } + + .img { + position: relative; + border-radius: var(--border-radius); + mix-blend-mode: multiply; + filter: grayscale(100%) contrast(1); + transition: var(--transition); + } + + &:before, + &:after { + content: ''; + display: block; + position: absolute; + width: 100%; + height: 100%; + border-radius: var(--border-radius); + transition: var(--transition); + } + + &:before { + top: 0; + left: 0; + background-color: var(--navy); + mix-blend-mode: screen; + } + + &:after { + border: 2px solid var(--green); + top: 14px; + left: 14px; + z-index: -1; + } + } +`; + +const About = () => { + const revealContainer = useRef(null); + const prefersReducedMotion = usePrefersReducedMotion(); + + useEffect(() => { + if (prefersReducedMotion) { + return; + } + + sr.reveal(revealContainer.current, srConfig()); + }, []); + + const skills = ['Flutter', 'Linux scripts', 'NLTK', 'Django', 'Node.js', 'Pandas']; + + return ( + +

About Me

+ +
+ +
+

+ Hello! My name is Soham and I enjoy developing new software and projects that enhance people's lives using + latest trends in technologies. My + interests primarily lies in Machine Learning and Python Software Development, with a heart that beats for innovation + and a mind wired for code. +

+ +

+ Currently, I am pursuing a Master's Degree in Computer Science at the {' '} + Illinois Institute of Technology, Chicago, IL,{' '} + maintaining a GPA of 3.7 out of 4. +

+ +

+ I have a Experience of working at + {' '}an Information Technology company,{' '} + and a start-up{' '}. + Recently, I have build a great interest in developing mobile applications and games using Flutter, + You can check some of the applications I made below. +

+ +

+ My Hobbies include + Playing Chess and competing in tournaments, Trekking and Weightlifting. +

+ +

Here are a few technologies I’ve been working with recently:

+
+ +
    + {skills && skills.map((skill, i) =>
  • {skill}
  • )} +
+
+ + +
+ +
+
+
+
+ ); +}; + +export default About; diff --git a/src/components/sections/contact.js b/src/components/sections/contact.js new file mode 100644 index 0000000..1d1d78e --- /dev/null +++ b/src/components/sections/contact.js @@ -0,0 +1,76 @@ +import React, { useEffect, useRef } from 'react'; +import styled from 'styled-components'; +import { srConfig, email } from '@config'; +import sr from '@utils/sr'; +import { usePrefersReducedMotion } from '@hooks'; + +const StyledContactSection = styled.section` + max-width: 600px; + margin: 0 auto 100px; + text-align: center; + + @media (max-width: 768px) { + margin: 0 auto 50px; + } + + .overline { + display: block; + margin-bottom: 20px; + color: var(--green); + font-family: var(--font-mono); + font-size: var(--fz-md); + font-weight: 400; + + &:before { + bottom: 0; + font-size: var(--fz-sm); + } + + &:after { + display: none; + } + } + + .title { + font-size: clamp(40px, 5vw, 60px); + } + + .email-link { + ${({ theme }) => theme.mixins.bigButton}; + margin-top: 50px; + } +`; + +const Contact = () => { + const revealContainer = useRef(null); + const prefersReducedMotion = usePrefersReducedMotion(); + + useEffect(() => { + if (prefersReducedMotion) { + return; + } + + sr.reveal(revealContainer.current, srConfig()); + }, []); + + return ( + +

What’s Next?

+ +

Get In Touch

+ +

+ I’m currently looking for new opportunities to work as a software Engineer Intern/ Software Development + Engineer for Summer 2024, Open to all locations in the United States/remote, my inbox is always open. + I Have a genuine interest in connecting with new people and exchange innovative thoughts and concept. + Please feel free to reach out to me! +

+ + + Say Hello + +
+ ); +}; + +export default Contact; diff --git a/src/components/sections/featured.js b/src/components/sections/featured.js new file mode 100644 index 0000000..3193006 --- /dev/null +++ b/src/components/sections/featured.js @@ -0,0 +1,417 @@ +import React, { useEffect, useRef } from 'react'; +import { useStaticQuery, graphql } from 'gatsby'; +import { GatsbyImage, getImage } from 'gatsby-plugin-image'; +import styled from 'styled-components'; +import sr from '@utils/sr'; +import { srConfig } from '@config'; +import { Icon } from '@components/icons'; +import { usePrefersReducedMotion } from '@hooks'; + +const StyledProjectsGrid = styled.ul` + ${({ theme }) => theme.mixins.resetList}; + + a { + position: relative; + z-index: 1; + } +`; + +const StyledProject = styled.li` + position: relative; + display: grid; + grid-gap: 10px; + grid-template-columns: repeat(12, 1fr); + align-items: center; + + @media (max-width: 768px) { + ${({ theme }) => theme.mixins.boxShadow}; + } + + &:not(:last-of-type) { + margin-bottom: 100px; + + @media (max-width: 768px) { + margin-bottom: 70px; + } + + @media (max-width: 480px) { + margin-bottom: 30px; + } + } + + &:nth-of-type(odd) { + .project-content { + grid-column: 7 / -1; + text-align: right; + + @media (max-width: 1080px) { + grid-column: 5 / -1; + } + @media (max-width: 768px) { + grid-column: 1 / -1; + padding: 40px 40px 30px; + text-align: left; + } + @media (max-width: 480px) { + padding: 25px 25px 20px; + } + } + .project-tech-list { + justify-content: flex-end; + + @media (max-width: 768px) { + justify-content: flex-start; + } + + li { + margin: 0 0 5px 20px; + + @media (max-width: 768px) { + margin: 0 10px 5px 0; + } + } + } + .project-links { + justify-content: flex-end; + margin-left: 0; + margin-right: -10px; + + @media (max-width: 768px) { + justify-content: flex-start; + margin-left: -10px; + margin-right: 0; + } + } + .project-image { + grid-column: 1 / 8; + + @media (max-width: 768px) { + grid-column: 1 / -1; + } + } + } + + .project-content { + position: relative; + grid-column: 1 / 7; + grid-row: 1 / -1; + + @media (max-width: 1080px) { + grid-column: 1 / 9; + } + + @media (max-width: 768px) { + display: flex; + flex-direction: column; + justify-content: center; + height: 100%; + grid-column: 1 / -1; + padding: 40px 40px 30px; + z-index: 5; + } + + @media (max-width: 480px) { + padding: 30px 25px 20px; + } + } + + .project-overline { + margin: 10px 0; + color: var(--green); + font-family: var(--font-mono); + font-size: var(--fz-xs); + font-weight: 400; + } + + .project-title { + color: var(--lightest-slate); + font-size: clamp(24px, 5vw, 28px); + + @media (min-width: 768px) { + margin: 0 0 20px; + } + + @media (max-width: 768px) { + color: var(--white); + + a { + position: static; + + &:before { + content: ''; + display: block; + position: absolute; + z-index: 0; + width: 100%; + height: 100%; + top: 0; + left: 0; + } + } + } + } + + .project-description { + ${({ theme }) => theme.mixins.boxShadow}; + position: relative; + z-index: 2; + padding: 25px; + border-radius: var(--border-radius); + background-color: var(--light-navy); + color: var(--light-slate); + font-size: var(--fz-lg); + + @media (max-width: 768px) { + padding: 20px 0; + background-color: transparent; + box-shadow: none; + + &:hover { + box-shadow: none; + } + } + + a { + ${({ theme }) => theme.mixins.inlineLink}; + } + + strong { + color: var(--white); + font-weight: normal; + } + } + + .project-tech-list { + display: flex; + flex-wrap: wrap; + position: relative; + z-index: 2; + margin: 25px 0 10px; + padding: 0; + list-style: none; + + li { + margin: 0 20px 5px 0; + color: var(--light-slate); + font-family: var(--font-mono); + font-size: var(--fz-xs); + white-space: nowrap; + } + + @media (max-width: 768px) { + margin: 10px 0; + + li { + margin: 0 10px 5px 0; + color: var(--lightest-slate); + } + } + } + + .project-links { + display: flex; + align-items: center; + position: relative; + margin-top: 10px; + margin-left: -10px; + color: var(--lightest-slate); + + a { + ${({ theme }) => theme.mixins.flexCenter}; + padding: 10px; + + &.external { + svg { + width: 22px; + height: 22px; + margin-top: -4px; + } + } + + svg { + width: 20px; + height: 20px; + } + } + + .cta { + ${({ theme }) => theme.mixins.smallButton}; + margin: 10px; + } + } + + .project-image { + ${({ theme }) => theme.mixins.boxShadow}; + grid-column: 6 / -1; + grid-row: 1 / -1; + position: relative; + z-index: 1; + + @media (max-width: 768px) { + grid-column: 1 / -1; + height: 100%; + opacity: 0.25; + } + + a { + width: 100%; + height: 100%; + background-color: var(--green); + border-radius: var(--border-radius); + vertical-align: middle; + + &:hover, + &:focus { + background: transparent; + outline: 0; + + &:before, + .img { + background: transparent; + filter: none; + } + } + + &:before { + content: ''; + position: absolute; + width: 100%; + height: 100%; + top: 0; + left: 0; + right: 0; + bottom: 0; + z-index: 3; + transition: var(--transition); + background-color: var(--navy); + mix-blend-mode: screen; + } + } + + .img { + border-radius: var(--border-radius); + mix-blend-mode: multiply; + filter: grayscale(100%) contrast(1) brightness(90%); + + @media (max-width: 768px) { + object-fit: cover; + width: auto; + height: 100%; + filter: grayscale(100%) contrast(1) brightness(50%); + } + } + } +`; + +const Featured = () => { + const data = useStaticQuery(graphql` + { + featured: allMarkdownRemark( + filter: { fileAbsolutePath: { regex: "/content/featured/" } } + sort: { fields: [frontmatter___date], order: ASC } + ) { + edges { + node { + frontmatter { + title + cover { + childImageSharp { + gatsbyImageData(width: 700, placeholder: BLURRED, formats: [AUTO, WEBP, AVIF]) + } + } + tech + github + external + cta + } + html + } + } + } + } + `); + + const featuredProjects = data.featured.edges.filter(({ node }) => node); + const revealTitle = useRef(null); + const revealProjects = useRef([]); + const prefersReducedMotion = usePrefersReducedMotion(); + + useEffect(() => { + if (prefersReducedMotion) { + return; + } + + sr.reveal(revealTitle.current, srConfig()); + revealProjects.current.forEach((ref, i) => sr.reveal(ref, srConfig(i * 100))); + }, []); + + return ( +
+

+ Some Things I’ve Built +

+ + + {featuredProjects && + featuredProjects.map(({ node }, i) => { + const { frontmatter, html } = node; + const { external, title, tech, github, cover, cta } = frontmatter; + const image = getImage(cover); + + return ( + (revealProjects.current[i] = el)}> +
+
+

Featured Project

+ +

+ {title} +

+ +
+ + {tech.length && ( +
    + {tech.map((tech, i) => ( +
  • {tech}
  • + ))} +
+ )} + +
+ {cta && ( + + Learn More + + )} + {github && ( + + + + )} + {external && !cta && ( + + + + )} +
+
+
+ +
+ + + +
+ + ); + })} + +
+ ); +}; + +export default Featured; diff --git a/src/components/sections/hero.js b/src/components/sections/hero.js new file mode 100644 index 0000000..82b5228 --- /dev/null +++ b/src/components/sections/hero.js @@ -0,0 +1,110 @@ +import React, { useState, useEffect } from 'react'; +import { CSSTransition, TransitionGroup } from 'react-transition-group'; +import styled from 'styled-components'; +import { navDelay, loaderDelay } from '@utils'; +import { usePrefersReducedMotion } from '@hooks'; + +const StyledHeroSection = styled.section` + ${({ theme }) => theme.mixins.flexCenter}; + flex-direction: column; + align-items: flex-start; + min-height: 100vh; + height: 100vh; + padding: 0; + + @media (max-height: 700px) and (min-width: 700px), (max-width: 360px) { + height: auto; + padding-top: var(--nav-height); + } + + h1 { + margin: 0 0 30px 4px; + color: var(--green); + font-family: var(--font-mono); + font-size: clamp(var(--fz-sm), 5vw, var(--fz-md)); + font-weight: 400; + + @media (max-width: 480px) { + margin: 0 0 20px 2px; + } + } + + h3 { + margin-top: 5px; + color: var(--slate); + line-height: 0.9; + } + + p { + margin: 20px 0 0; + max-width: 540px; + } + + .email-link { + ${({ theme }) => theme.mixins.bigButton}; + margin-top: 50px; + } +`; + +const Hero = () => { + const [isMounted, setIsMounted] = useState(false); + const prefersReducedMotion = usePrefersReducedMotion(); + + useEffect(() => { + if (prefersReducedMotion) { + return; + } + + const timeout = setTimeout(() => setIsMounted(true), navDelay); + return () => clearTimeout(timeout); + }, []); + + const one =

Hi, my name is

; + const two =

Soham Sonar.

; + const three =

I like to build software and applications.

; + const four = ( + <> +

+ I’m a software engineer specialized in building robust software using Python programming language. + Currently, my passion is fueled by the captivating world of machine learning. +

+

+ I enjoy learning new skills and implementing them! +

+ + ); + const five = ( + + Connect with me! + + ); + + const items = [one, two, three, four, five]; + + return ( + + {prefersReducedMotion ? ( + <> + {items.map((item, i) => ( +
{item}
+ ))} + + ) : ( + + {isMounted && + items.map((item, i) => ( + +
{item}
+
+ ))} +
+ )} +
+ ); +}; + +export default Hero; diff --git a/src/components/sections/jobs.js b/src/components/sections/jobs.js new file mode 100644 index 0000000..c5a6370 --- /dev/null +++ b/src/components/sections/jobs.js @@ -0,0 +1,310 @@ +import React, { useState, useEffect, useRef } from 'react'; +import { useStaticQuery, graphql } from 'gatsby'; +import { CSSTransition } from 'react-transition-group'; +import styled from 'styled-components'; +import { srConfig } from '@config'; +import { KEY_CODES } from '@utils'; +import sr from '@utils/sr'; +import { usePrefersReducedMotion } from '@hooks'; + +const StyledJobsSection = styled.section` + max-width: 700px; + + .inner { + display: flex; + + @media (max-width: 600px) { + display: block; + } + + // Prevent container from jumping + @media (min-width: 700px) { + min-height: 340px; + } + } +`; + +const StyledTabList = styled.div` + position: relative; + z-index: 3; + width: max-content; + padding: 0; + margin: 0; + list-style: none; + + @media (max-width: 600px) { + display: flex; + overflow-x: auto; + width: calc(100% + 100px); + padding-left: 50px; + margin-left: -50px; + margin-bottom: 30px; + } + @media (max-width: 480px) { + width: calc(100% + 50px); + padding-left: 25px; + margin-left: -25px; + } + + li { + &:first-of-type { + @media (max-width: 600px) { + margin-left: 50px; + } + @media (max-width: 480px) { + margin-left: 25px; + } + } + &:last-of-type { + @media (max-width: 600px) { + padding-right: 50px; + } + @media (max-width: 480px) { + padding-right: 25px; + } + } + } +`; + +const StyledTabButton = styled.button` + ${({ theme }) => theme.mixins.link}; + display: flex; + align-items: center; + width: 100%; + height: var(--tab-height); + padding: 0 20px 2px; + border-left: 2px solid var(--lightest-navy); + background-color: transparent; + color: ${({ isActive }) => (isActive ? 'var(--green)' : 'var(--slate)')}; + font-family: var(--font-mono); + font-size: var(--fz-xs); + text-align: left; + white-space: nowrap; + + @media (max-width: 768px) { + padding: 0 15px 2px; + } + @media (max-width: 600px) { + ${({ theme }) => theme.mixins.flexCenter}; + min-width: 120px; + padding: 0 15px; + border-left: 0; + border-bottom: 2px solid var(--lightest-navy); + text-align: center; + } + + &:hover, + &:focus { + background-color: var(--light-navy); + } +`; + +const StyledHighlight = styled.div` + position: absolute; + top: 0; + left: 0; + z-index: 10; + width: 2px; + height: var(--tab-height); + border-radius: var(--border-radius); + background: var(--green); + transform: translateY(calc(${({ activeTabId }) => activeTabId} * var(--tab-height))); + transition: transform 0.25s cubic-bezier(0.645, 0.045, 0.355, 1); + transition-delay: 0.1s; + + @media (max-width: 600px) { + top: auto; + bottom: 0; + width: 100%; + max-width: var(--tab-width); + height: 2px; + margin-left: 50px; + transform: translateX(calc(${({ activeTabId }) => activeTabId} * var(--tab-width))); + } + @media (max-width: 480px) { + margin-left: 25px; + } +`; + +const StyledTabPanels = styled.div` + position: relative; + width: 100%; + margin-left: 20px; + + @media (max-width: 600px) { + margin-left: 0; + } +`; + +const StyledTabPanel = styled.div` + width: 100%; + height: auto; + padding: 10px 5px; + + ul { + ${({ theme }) => theme.mixins.fancyList}; + } + + h3 { + margin-bottom: 2px; + font-size: var(--fz-xxl); + font-weight: 500; + line-height: 1.3; + + .company { + color: var(--green); + } + } + + .range { + margin-bottom: 25px; + color: var(--light-slate); + font-family: var(--font-mono); + font-size: var(--fz-xs); + } +`; + +const Jobs = () => { + const data = useStaticQuery(graphql` + query { + jobs: allMarkdownRemark( + filter: { fileAbsolutePath: { regex: "/content/jobs/" } } + sort: { fields: [frontmatter___date], order: DESC } + ) { + edges { + node { + frontmatter { + title + company + location + range + url + } + html + } + } + } + } + `); + + const jobsData = data.jobs.edges; + + const [activeTabId, setActiveTabId] = useState(0); + const [tabFocus, setTabFocus] = useState(null); + const tabs = useRef([]); + const revealContainer = useRef(null); + const prefersReducedMotion = usePrefersReducedMotion(); + + useEffect(() => { + if (prefersReducedMotion) { + return; + } + + sr.reveal(revealContainer.current, srConfig()); + }, []); + + const focusTab = () => { + if (tabs.current[tabFocus]) { + tabs.current[tabFocus].focus(); + return; + } + // If we're at the end, go to the start + if (tabFocus >= tabs.current.length) { + setTabFocus(0); + } + // If we're at the start, move to the end + if (tabFocus < 0) { + setTabFocus(tabs.current.length - 1); + } + }; + + // Only re-run the effect if tabFocus changes + useEffect(() => focusTab(), [tabFocus]); + + // Focus on tabs when using up & down arrow keys + const onKeyDown = e => { + switch (e.key) { + case KEY_CODES.ARROW_UP: { + e.preventDefault(); + setTabFocus(tabFocus - 1); + break; + } + + case KEY_CODES.ARROW_DOWN: { + e.preventDefault(); + setTabFocus(tabFocus + 1); + break; + } + + default: { + break; + } + } + }; + + return ( + +

Where I’ve Worked

+ +
+ onKeyDown(e)}> + {jobsData && + jobsData.map(({ node }, i) => { + const { company } = node.frontmatter; + return ( + setActiveTabId(i)} + ref={el => (tabs.current[i] = el)} + id={`tab-${i}`} + role="tab" + tabIndex={activeTabId === i ? '0' : '-1'} + aria-selected={activeTabId === i ? true : false} + aria-controls={`panel-${i}`}> + {company} + + ); + })} + + + + + {jobsData && + jobsData.map(({ node }, i) => { + const { frontmatter, html } = node; + const { title, url, company, range } = frontmatter; + + return ( + +