Repository: bchiang7/v4 Branch: main Commit: 539cef0bf60c Files: 123 Total size: 168.9 KB Directory structure: gitextract_o5gnh8vu/ ├── .babelrc ├── .editorconfig ├── .eslintrc ├── .gitignore ├── .husky/ │ ├── .gitignore │ └── pre-commit ├── .nvmrc ├── LICENSE ├── README.md ├── content/ │ ├── featured/ │ │ ├── HalcyonTheme/ │ │ │ └── index.md │ │ ├── SpotifyProfile/ │ │ │ └── index.md │ │ └── SpotifyProfileV2/ │ │ └── index.md │ ├── jobs/ │ │ ├── Apple/ │ │ │ └── index.md │ │ ├── Mullen/ │ │ │ └── index.md │ │ ├── Scout/ │ │ │ └── index.md │ │ ├── Starry/ │ │ │ └── index.md │ │ └── Upstatement/ │ │ └── index.md │ ├── posts/ │ │ ├── clickable-cards/ │ │ │ └── index.md │ │ ├── dark-mode-toggle/ │ │ │ └── index.md │ │ ├── docker-compose-error/ │ │ │ └── index.md │ │ ├── markdown-playground/ │ │ │ └── index.md │ │ └── wordpress-publish-error/ │ │ └── index.md │ └── projects/ │ ├── AMFM.md │ ├── AlgoliaWordPressMediumPost.md │ ├── AppleMusicEmbedPlayer.md │ ├── Blistabloc.md │ ├── CourseSource.md │ ├── CrowdDJ.md │ ├── Devoted.md │ ├── EverytownIdealState.md │ ├── Flagship.md │ ├── Fontipsums.md │ ├── GoogleKeepClone.md │ ├── HBS.md │ ├── HalcyonTheme.md │ ├── HeadlessCMSMediumPost.md │ ├── Interventions.md │ ├── JetBlueHumanKinda.md │ ├── KoalaHealth.md │ ├── LonelyPlanetDBMS.md │ ├── MichelleWu.md │ ├── MomsDemandAction.md │ ├── MyNEURedesign.md │ ├── NUWITSite.md │ ├── NortheasternCSSH.md │ ├── OctoProfile.md │ ├── OneCardForAll.md │ ├── PhillySports.md │ ├── Pratt.md │ ├── ReactResume.md │ ├── Screentime.md │ ├── SpotifyProfile.md │ ├── SpotifyTopTracks2017.md │ ├── The19th.md │ ├── Threadable.md │ ├── TimeToHaveMoreFun.md │ ├── UpstatementDotCom.md │ ├── Vanderbilt.md │ ├── WeatherWidget.md │ ├── v1.md │ ├── v2.md │ └── v3.md ├── gatsby-browser.js ├── gatsby-config.js ├── gatsby-node.js ├── gatsby-ssr.js ├── package.json ├── prettier.config.js └── src/ ├── components/ │ ├── email.js │ ├── footer.js │ ├── head.js │ ├── icons/ │ │ ├── appstore.js │ │ ├── bookmark.js │ │ ├── codepen.js │ │ ├── external.js │ │ ├── folder.js │ │ ├── fork.js │ │ ├── github.js │ │ ├── hex.js │ │ ├── icon.js │ │ ├── index.js │ │ ├── instagram.js │ │ ├── linkedin.js │ │ ├── loader.js │ │ ├── logo.js │ │ ├── playstore.js │ │ ├── star.js │ │ └── twitter.js │ ├── index.js │ ├── layout.js │ ├── loader.js │ ├── menu.js │ ├── nav.js │ ├── sections/ │ │ ├── about.js │ │ ├── contact.js │ │ ├── featured.js │ │ ├── hero.js │ │ ├── jobs.js │ │ └── projects.js │ ├── side.js │ └── social.js ├── config.js ├── hooks/ │ ├── index.js │ ├── useOnClickOutside.js │ ├── usePrefersReducedMotion.js │ └── useScrollDirection.js ├── pages/ │ ├── 404.js │ ├── archive.js │ ├── index.js │ └── pensieve/ │ ├── index.js │ └── tags.js ├── styles/ │ ├── GlobalStyle.js │ ├── PrismStyles.js │ ├── TransitionStyles.js │ ├── fonts.js │ ├── index.js │ ├── mixins.js │ ├── theme.js │ └── variables.js ├── templates/ │ ├── post.js │ └── tag.js └── utils/ ├── index.js └── sr.js ================================================ FILE CONTENTS ================================================ ================================================ FILE: .babelrc ================================================ { "presets": [ "@babel/preset-react", "babel-preset-gatsby" ] } ================================================ FILE: .editorconfig ================================================ [*] indent_style = space indent_size = 2 end_of_line = lf charset = utf-8 trim_trailing_whitespace = true insert_final_newline = true ================================================ FILE: .eslintrc ================================================ { "root": true, "extends": "@upstatement/eslint-config/react" } ================================================ FILE: .gitignore ================================================ # Project dependencies .cache node_modules yarn-error.log package-lock.json # Build directory /public .DS_Store .vscode/ ================================================ FILE: .husky/.gitignore ================================================ _ ================================================ FILE: .husky/pre-commit ================================================ #!/bin/sh . "$(dirname "$0")/_/husky.sh" npm run lint-staged ================================================ FILE: .nvmrc ================================================ 14.16.0 ================================================ FILE: LICENSE ================================================ 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. ================================================ FILE: README.md ================================================
Logo

brittanychiang.com - v4

The fourth iteration of brittanychiang.com built with Gatsby and hosted with Netlify

Previous iterations: v1, v2, v3

Netlify Status

![demo](https://raw.githubusercontent.com/bchiang7/v4/main/src/images/demo.png) ## 🚨 Forking this repo (please read!) Many people have contacted me asking me if they can use this code for their own website, and the answer to that question is usually **yes, with attribution**. I value keeping my site open source, but as you all know, _**plagiarism is bad**_. It's always disheartening whenever I find that someone has copied my site without giving me credit. I spent a non-trivial amount of effort building and designing this iteration of my website, and I am proud of it! All I ask of you all is to not claim this effort as your own. Please also note that I did not build this site with the intention of it being a starter theme, so if you have questions about implementation, please refer to the [Gatsby docs](https://www.gatsbyjs.org/docs/). ### TL;DR Yes, you can fork this repo. Please give me proper credit by linking back to [brittanychiang.com](https://brittanychiang.com). Thanks! ## 🛠 Installation & Set Up 1. Install the Gatsby CLI ```sh npm install -g gatsby-cli ``` 2. Install and use the correct version of Node using [NVM](https://github.com/nvm-sh/nvm) ```sh nvm install ``` 3. Install dependencies ```sh yarn ``` 4. Start the development server ```sh npm start ``` ## 🚀 Building and Running for Production 1. Generate a full static production build ```sh npm run build ``` 1. Preview the site as it will appear once deployed ```sh npm run serve ``` ## 🎨 Color Reference | Color | Hex | | -------------- | ------------------------------------------------------------------ | | Navy | ![#0a192f](https://via.placeholder.com/10/0a192f?text=+) `#0a192f` | | Light Navy | ![#112240](https://via.placeholder.com/10/0a192f?text=+) `#112240` | | Lightest Navy | ![#233554](https://via.placeholder.com/10/303C55?text=+) `#233554` | | Slate | ![#8892b0](https://via.placeholder.com/10/8892b0?text=+) `#8892b0` | | Light Slate | ![#a8b2d1](https://via.placeholder.com/10/a8b2d1?text=+) `#a8b2d1` | | Lightest Slate | ![#ccd6f6](https://via.placeholder.com/10/ccd6f6?text=+) `#ccd6f6` | | White | ![#e6f1ff](https://via.placeholder.com/10/e6f1ff?text=+) `#e6f1ff` | | Green | ![#64ffda](https://via.placeholder.com/10/64ffda?text=+) `#64ffda` | ================================================ FILE: content/featured/HalcyonTheme/index.md ================================================ --- date: '1' title: 'Halcyon Theme' cover: './halcyon.png' github: 'https://github.com/bchiang7/halcyon-site' external: 'https://halcyon-theme.netlify.com/' tech: - VS Code - Sublime Text - Atom - iTerm2 - Hyper --- 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). ================================================ FILE: content/featured/SpotifyProfile/index.md ================================================ --- date: '2' title: 'Spotify Profile' cover: './demo.png' github: 'https://github.com/bchiang7/spotify-profile' external: 'https://spotify-profile.herokuapp.com/' tech: - React - Styled Components - Express - Spotify API - Heroku --- 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. ================================================ FILE: content/featured/SpotifyProfileV2/index.md ================================================ --- date: '3' title: 'Build a Spotify Connected App' cover: './course-card.png' external: 'https://www.newline.co/courses/build-a-spotify-connected-app' cta: 'https://www.newline.co/courses/build-a-spotify-connected-app' tech: - React - Express - Spotify API - Styled Components --- Having struggled with understanding how the Spotify OAuth flow works, I made the course I wish I could have had. Unlike tutorials that only cover a few concepts and leave you with half-baked GitHub repositories, this course covers everything from explaining the principles of REST APIs to implementing Spotify's OAuth flow and fetching API data in a React app. By the end of the course, you’ll have an app deployed to the internet you can add to your portfolio. ================================================ FILE: content/jobs/Apple/index.md ================================================ --- date: '2017-12-21' title: 'UI Engineer Co-op' company: 'Apple' location: 'Cupertino, CA' range: 'July - December 2017' url: 'https://www.apple.com/music/' --- - Developed and styled interactive web applications for Apple Music using Ember and SCSS - Built and shipped the Apple Music Extension for Facebook Messenger leveraging third-party and internal API integrations - Architected and implemented the user interface of Apple Music's embeddable web player widget for in-browser user authorization and full song playback - Contributed extensively to the creation of MusicKit JS, a public-facing JavaScript SDK for embedding Apple Music players into web applications ================================================ FILE: content/jobs/Mullen/index.md ================================================ --- date: '2015-12-21' title: 'Creative Technologist Co-op' company: 'MullenLowe' location: 'Boston, MA' range: 'July - December 2015' url: 'https://us.mullenlowe.com/' --- - Developed, maintained, and shipped production code for client websites primarily using HTML, CSS, Sass, JavaScript, and jQuery - Performed quality assurance tests on various sites to ensure cross-browser compatibility and mobile responsiveness - Clients included JetBlue, Lovesac, U.S. Cellular, U.S. Department of Defense, and more ================================================ FILE: content/jobs/Scout/index.md ================================================ --- date: '2017-04-01' title: 'Developer' company: 'Scout Studio' location: 'Northeastern University' range: 'Spring 2016 & 2017' url: 'https://web.northeastern.edu/scout/' --- - Collaborated with other student designers and engineers on pro-bono projects to create new brands, design systems, and websites for organizations in the community - Built and delivered technical solutions according to stakeholder business requirements ================================================ FILE: content/jobs/Starry/index.md ================================================ --- date: '2016-12-21' title: 'Software Engineer Co-op' company: 'Starry' location: 'Boston, MA' range: 'July - December 2016' url: 'https://starry.com/' --- - Engineered and improved major features of Starry's customer-facing Android web app using ES6, Handlebars, Backbone, Marionette, and CSS - Proposed and implemented scalable solutions to issues identified with cloud services and applications responsible for communicating with the Starry Station internet router - Collaborated with designers and other developers to ensure thoughtful and consistent user experiences across Starry’s iOS and Android mobile apps ================================================ FILE: content/jobs/Upstatement/index.md ================================================ --- date: '2018-05-14' title: 'Lead Engineer' company: 'Upstatement' location: 'Boston, MA' range: 'May 2018 - Present' url: 'https://www.upstatement.com/' --- - Deliver high-quality, robust production code for a diverse array of projects for clients including Harvard Business School, Everytown for Gun Safety, Pratt Institute, Koala Health, Vanderbilt University, The 19th News, and more - Work alongside creative directors to lead the research, development, and architecture of technical solutions to fulfill business requirements - Collaborate with designers, project managers, and other engineers to transform creative concepts into production realities for clients and stakeholders - Provide leadership within engineering department through close collaboration, knowledge shares, and mentorship ================================================ FILE: content/posts/clickable-cards/index.md ================================================ --- 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; } } } } ``` ================================================ FILE: content/posts/dark-mode-toggle/index.md ================================================ --- 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 - - - - - ================================================ FILE: content/posts/docker-compose-error/index.md ================================================ --- 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: ``` ================================================ FILE: content/posts/markdown-playground/index.md ================================================ --- 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. ================================================ FILE: content/posts/wordpress-publish-error/index.md ================================================ --- 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. ================================================ FILE: content/projects/AMFM.md ================================================ --- 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). ================================================ FILE: content/projects/AlgoliaWordPressMediumPost.md ================================================ --- 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 ================================================ FILE: content/projects/AppleMusicEmbedPlayer.md ================================================ --- 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/). ================================================ FILE: content/projects/Blistabloc.md ================================================ --- 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. ================================================ FILE: content/projects/CourseSource.md ================================================ --- 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. ================================================ FILE: content/projects/CrowdDJ.md ================================================ --- 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. ================================================ FILE: content/projects/Devoted.md ================================================ --- 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 ================================================ FILE: content/projects/EverytownIdealState.md ================================================ --- 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 --- ================================================ FILE: content/projects/Flagship.md ================================================ --- 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. ================================================ FILE: content/projects/Fontipsums.md ================================================ --- 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. ================================================ FILE: content/projects/GoogleKeepClone.md ================================================ --- 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. ================================================ FILE: content/projects/HBS.md ================================================ --- date: '2022-10-08' title: 'Harvard Business School Design System' github: '' external: '' tech: - Storybook - React - TypeScript company: 'Upstatement' showInProjects: false --- ================================================ FILE: content/projects/HalcyonTheme.md ================================================ --- 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). ================================================ FILE: content/projects/HeadlessCMSMediumPost.md ================================================ --- 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 ================================================ FILE: content/projects/Interventions.md ================================================ --- 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. ================================================ FILE: content/projects/JetBlueHumanKinda.md ================================================ --- 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/). ================================================ FILE: content/projects/KoalaHealth.md ================================================ --- 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 --- ================================================ FILE: content/projects/LonelyPlanetDBMS.md ================================================ --- 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. ================================================ FILE: content/projects/MichelleWu.md ================================================ --- 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 --- ================================================ FILE: content/projects/MomsDemandAction.md ================================================ --- 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 --- ================================================ FILE: content/projects/MyNEURedesign.md ================================================ --- 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. ================================================ FILE: content/projects/NUWITSite.md ================================================ --- 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. ================================================ FILE: content/projects/NortheasternCSSH.md ================================================ --- 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 --- ================================================ FILE: content/projects/OctoProfile.md ================================================ --- 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. ================================================ FILE: content/projects/OneCardForAll.md ================================================ --- 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. ================================================ FILE: content/projects/PhillySports.md ================================================ --- 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 --- ================================================ FILE: content/projects/Pratt.md ================================================ --- date: '2022-08-08' title: 'Pratt' github: '' external: 'https://www.pratt.edu/' tech: - WordPress - Timber - WordPress Multisite - Gutenberg - JS company: 'Upstatement' showInProjects: false --- ================================================ FILE: content/projects/ReactResume.md ================================================ --- 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. ================================================ FILE: content/projects/Screentime.md ================================================ --- 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. ================================================ FILE: content/projects/SpotifyProfile.md ================================================ --- 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. ================================================ FILE: content/projects/SpotifyTopTracks2017.md ================================================ --- 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. ================================================ FILE: content/projects/The19th.md ================================================ --- 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 --- ================================================ FILE: content/projects/Threadable.md ================================================ --- 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 --- ================================================ FILE: content/projects/TimeToHaveMoreFun.md ================================================ --- 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 ================================================ FILE: content/projects/UpstatementDotCom.md ================================================ --- date: '2019-11-25' title: 'Upstatement.com' github: '' external: 'https://www.upstatement.com/' tech: - Nuxt - Vue - Prismic company: 'Upstatement' showInProjects: false --- ================================================ FILE: content/projects/Vanderbilt.md ================================================ --- date: '2021-06-01' title: 'Vanderbilt Design System' github: '' external: 'https://www.vanderbilt.edu/' tech: - Twig - Puppy - JS company: 'Upstatement' showInProjects: false --- ================================================ FILE: content/projects/WeatherWidget.md ================================================ --- 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. ================================================ FILE: content/projects/v1.md ================================================ --- 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. ================================================ FILE: content/projects/v2.md ================================================ --- 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. ================================================ FILE: content/projects/v3.md ================================================ --- 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. ================================================ FILE: gatsby-browser.js ================================================ /** * Implement Gatsby's Browser APIs in this file. * * See: https://www.gatsbyjs.org/docs/browser-apis/ */ ================================================ FILE: gatsby-config.js ================================================ const config = require('./src/config'); module.exports = { siteMetadata: { title: 'Brittany Chiang', description: 'Brittany Chiang is a software engineer who specializes in building (and occasionally designing) exceptional digital experiences.', siteUrl: 'https://brittanychiang.com', // No trailing slash allowed! image: '/og.png', // Path to your image you placed in the 'static' folder twitterUsername: '@bchiang7', }, 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: 'Brittany Chiang', short_name: 'Brittany Chiang', 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',
      },
    },
  ],
};


================================================
FILE: gatsby-node.js
================================================
/**
 * 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'),
      },
    },
  });
};


================================================
FILE: gatsby-ssr.js
================================================
/**
 * 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

================================================
FILE: package.json
================================================
{
  "name": "v4",
  "description": "Personal Website V4",
  "version": "1.0.0",
  "author": "Brittany Chiang ",
  "repository": {
    "type": "git",
    "url": "https://github.com/bchiang7/v4"
  },
  "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"
  }
}


================================================
FILE: prettier.config.js
================================================
module.exports = require('@upstatement/prettier-config');


================================================
FILE: src/components/email.js
================================================
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: 90px;
    margin: 0 auto;
    background-color: var(--light-slate);
  }

  a {
    margin: 20px auto;
    padding: 10px;
    font-family: var(--font-mono);
    font-size: var(--fz-xxs);
    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;


================================================
FILE: src/components/footer.js
================================================
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
{githubInfo.stars && githubInfo.forks && (
{githubInfo.stars.toLocaleString()} {githubInfo.forks.toLocaleString()}
)}
); }; Footer.propTypes = { githubInfo: PropTypes.object, }; export default Footer; ================================================ FILE: src/components/head.js ================================================ 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, }; ================================================ FILE: src/components/icons/appstore.js ================================================ import React from 'react'; const IconAppStore = () => ( Apple App Store ); export default IconAppStore; ================================================ FILE: src/components/icons/bookmark.js ================================================ import React from 'react'; const IconBookmark = () => ( Bookmark ); export default IconBookmark; ================================================ FILE: src/components/icons/codepen.js ================================================ import React from 'react'; const IconCodepen = () => ( CodePen ); export default IconCodepen; ================================================ FILE: src/components/icons/external.js ================================================ import React from 'react'; const IconExternal = () => ( External Link ); export default IconExternal; ================================================ FILE: src/components/icons/folder.js ================================================ import React from 'react'; const IconFolder = () => ( Folder ); export default IconFolder; ================================================ FILE: src/components/icons/fork.js ================================================ import React from 'react'; const IconFork = () => ( Git Fork ); export default IconFork; ================================================ FILE: src/components/icons/github.js ================================================ import React from 'react'; const IconGitHub = () => ( GitHub ); export default IconGitHub; ================================================ FILE: src/components/icons/hex.js ================================================ import React from 'react'; const IconHex = () => ( Hexagon ); export default IconHex; ================================================ FILE: src/components/icons/icon.js ================================================ import React from 'react'; import PropTypes from 'prop-types'; import { IconAppStore, IconBookmark, IconCodepen, IconExternal, IconFolder, IconFork, IconGitHub, IconInstagram, IconLinkedin, IconLoader, IconLogo, IconPlayStore, IconStar, IconTwitter, } 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 ; default: return ; } }; Icon.propTypes = { name: PropTypes.string.isRequired, }; export default Icon; ================================================ FILE: src/components/icons/index.js ================================================ 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'; ================================================ FILE: src/components/icons/instagram.js ================================================ import React from 'react'; const IconInstagram = () => ( Instagram ); export default IconInstagram; ================================================ FILE: src/components/icons/linkedin.js ================================================ import React from 'react'; const IconLinkedin = () => ( LinkedIn ); export default IconLinkedin; ================================================ FILE: src/components/icons/loader.js ================================================ import React from 'react'; const IconLoader = () => ( ); export default IconLoader; ================================================ FILE: src/components/icons/logo.js ================================================ import React from 'react'; const IconLogo = () => ( ); export default IconLogo; ================================================ FILE: src/components/icons/playstore.js ================================================ import React from 'react'; const IconPlayStore = () => ( Google Play Store ); export default IconPlayStore; ================================================ FILE: src/components/icons/star.js ================================================ import React from 'react'; const IconStar = () => ( Star ); export default IconStar; ================================================ FILE: src/components/icons/twitter.js ================================================ import React from 'react'; const IconTwitter = () => ( Twitter ); export default IconTwitter; ================================================ FILE: src/components/index.js ================================================ 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'; ================================================ FILE: src/components/layout.js ================================================ 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; ================================================ FILE: src/components/loader.js ================================================ 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; ================================================ FILE: src/components/menu.js ================================================ 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; ================================================ FILE: src/components/nav.js ================================================ 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; ================================================ FILE: src/components/sections/about.js ================================================ 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 = ['JavaScript (ES6+)', 'TypeScript', 'React', 'Eleventy', 'Node.js', 'WordPress']; return (

About Me

Hello! My name is Brittany and I enjoy creating things that live on the internet. My interest in web development started back in 2012 when I decided to try editing custom Tumblr themes — turns out hacking together a custom reblog button taught me a lot about HTML & CSS!

Fast-forward to today, and I’ve had the privilege of working at{' '} an advertising agency,{' '} a start-up,{' '} a huge corporation, and{' '} a student-led design studio. My main focus these days is building accessible, inclusive products and digital experiences at Upstatement for a variety of clients.

I also recently{' '} launched a course {' '} that covers everything you need to build a web app with the Spotify API using Node & React.

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

    {skills && skills.map((skill, i) =>
  • {skill}
  • )}
); }; export default About; ================================================ FILE: src/components/sections/contact.js ================================================ 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

Although I’m not currently looking for any new opportunities, my inbox is always open. Whether you have a question or just want to say hi, I’ll try my best to get back to you!

Say Hello
); }; export default Contact; ================================================ FILE: src/components/sections/featured.js ================================================ 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; ================================================ FILE: src/components/sections/hero.js ================================================ 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 =

Brittany Chiang.

; const three =

I build things for the web.

; const four = ( <>

I’m a software engineer specializing in building (and occasionally designing) exceptional digital experiences. Currently, I’m focused on building accessible, human-centered products at{' '} Upstatement .

); const five = ( Check out my course! ); const items = [one, two, three, four, five]; return ( {prefersReducedMotion ? ( <> {items.map((item, i) => (
{item}
))} ) : ( {isMounted && items.map((item, i) => (
{item}
))}
)}
); }; export default Hero; ================================================ FILE: src/components/sections/jobs.js ================================================ 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 (