[
  {
    "path": ".babelrc",
    "content": "{\n  \"presets\": [\n    \"@babel/preset-react\",\n    \"babel-preset-gatsby\"\n  ]\n}\n"
  },
  {
    "path": ".editorconfig",
    "content": "[*]\nindent_style = space\nindent_size = 2\nend_of_line = lf\ncharset = utf-8\ntrim_trailing_whitespace = true\ninsert_final_newline = true\n"
  },
  {
    "path": ".eslintrc",
    "content": "{\n  \"root\": true,\n  \"extends\": \"@upstatement/eslint-config/react\"\n}\n"
  },
  {
    "path": ".gitignore",
    "content": "# Project dependencies\n.cache\nnode_modules\nyarn-error.log\npackage-lock.json\n\n# Build directory\n/public\n.DS_Store\n\n.vscode/\n"
  },
  {
    "path": ".husky/.gitignore",
    "content": "_\n"
  },
  {
    "path": ".husky/pre-commit",
    "content": "#!/bin/sh\n. \"$(dirname \"$0\")/_/husky.sh\"\n\nnpm run lint-staged\n"
  },
  {
    "path": ".nvmrc",
    "content": "14.16.0\n"
  },
  {
    "path": "LICENSE",
    "content": "MIT License\n\nCopyright (c) 2018 Brittany Chiang\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "<div align=\"center\">\n  <img alt=\"Logo\" src=\"https://raw.githubusercontent.com/bchiang7/v4/main/src/images/logo.png\" width=\"100\" />\n</div>\n<h1 align=\"center\">\n  brittanychiang.com - v4\n</h1>\n<p align=\"center\">\n  The fourth iteration of <a href=\"https://brittanychiang.com\" target=\"_blank\">brittanychiang.com</a> built with <a href=\"https://www.gatsbyjs.org/\" target=\"_blank\">Gatsby</a> and hosted with <a href=\"https://www.netlify.com/\" target=\"_blank\">Netlify</a>\n</p>\n<p align=\"center\">\n  Previous iterations:\n  <a href=\"https://github.com/bchiang7/v1\" target=\"_blank\">v1</a>,\n  <a href=\"https://github.com/bchiang7/v2\" target=\"_blank\">v2</a>,\n  <a href=\"https://github.com/bchiang7/bchiang7.github.io\" target=\"_blank\">v3</a>\n</p>\n<p align=\"center\">\n  <a href=\"https://app.netlify.com/sites/brittanychiang/deploys\" target=\"_blank\">\n    <img src=\"https://api.netlify.com/api/v1/badges/1963b488-7b78-48c9-9e2d-6fb5e47ab3af/deploy-status\" alt=\"Netlify Status\" />\n  </a>\n</p>\n\n![demo](https://raw.githubusercontent.com/bchiang7/v4/main/src/images/demo.png)\n\n## 🚨 Forking this repo (please read!)\n\nMany 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**.\n\nI 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.\n\nPlease 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/).\n\n### TL;DR\n\nYes, you can fork this repo. Please give me proper credit by linking back to [brittanychiang.com](https://brittanychiang.com). Thanks!\n\n## 🛠 Installation & Set Up\n\n1. Install the Gatsby CLI\n\n   ```sh\n   npm install -g gatsby-cli\n   ```\n\n2. Install and use the correct version of Node using [NVM](https://github.com/nvm-sh/nvm)\n\n   ```sh\n   nvm install\n   ```\n\n3. Install dependencies\n\n   ```sh\n   yarn\n   ```\n\n4. Start the development server\n\n   ```sh\n   npm start\n   ```\n\n## 🚀 Building and Running for Production\n\n1. Generate a full static production build\n\n   ```sh\n   npm run build\n   ```\n\n1. Preview the site as it will appear once deployed\n\n   ```sh\n   npm run serve\n   ```\n\n## 🎨 Color Reference\n\n| Color          | Hex                                                                |\n| -------------- | ------------------------------------------------------------------ |\n| Navy           | ![#0a192f](https://via.placeholder.com/10/0a192f?text=+) `#0a192f` |\n| Light Navy     | ![#112240](https://via.placeholder.com/10/0a192f?text=+) `#112240` |\n| Lightest Navy  | ![#233554](https://via.placeholder.com/10/303C55?text=+) `#233554` |\n| Slate          | ![#8892b0](https://via.placeholder.com/10/8892b0?text=+) `#8892b0` |\n| Light Slate    | ![#a8b2d1](https://via.placeholder.com/10/a8b2d1?text=+) `#a8b2d1` |\n| Lightest Slate | ![#ccd6f6](https://via.placeholder.com/10/ccd6f6?text=+) `#ccd6f6` |\n| White          | ![#e6f1ff](https://via.placeholder.com/10/e6f1ff?text=+) `#e6f1ff` |\n| Green          | ![#64ffda](https://via.placeholder.com/10/64ffda?text=+) `#64ffda` |\n"
  },
  {
    "path": "content/featured/HalcyonTheme/index.md",
    "content": "---\ndate: '1'\ntitle: 'Halcyon Theme'\ncover: './halcyon.png'\ngithub: 'https://github.com/bchiang7/halcyon-site'\nexternal: 'https://halcyon-theme.netlify.com/'\ntech:\n  - VS Code\n  - Sublime Text\n  - Atom\n  - iTerm2\n  - Hyper\n---\n\nA 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).\n"
  },
  {
    "path": "content/featured/SpotifyProfile/index.md",
    "content": "---\ndate: '2'\ntitle: 'Spotify Profile'\ncover: './demo.png'\ngithub: 'https://github.com/bchiang7/spotify-profile'\nexternal: 'https://spotify-profile.herokuapp.com/'\ntech:\n  - React\n  - Styled Components\n  - Express\n  - Spotify API\n  - Heroku\n---\n\nA 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.\n"
  },
  {
    "path": "content/featured/SpotifyProfileV2/index.md",
    "content": "---\ndate: '3'\ntitle: 'Build a Spotify Connected App'\ncover: './course-card.png'\nexternal: 'https://www.newline.co/courses/build-a-spotify-connected-app'\ncta: 'https://www.newline.co/courses/build-a-spotify-connected-app'\ntech:\n  - React\n  - Express\n  - Spotify API\n  - Styled Components\n---\n\nHaving struggled with understanding how the Spotify OAuth flow works, I made the course I wish I could have had.\n\nUnlike 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.\n"
  },
  {
    "path": "content/jobs/Apple/index.md",
    "content": "---\ndate: '2017-12-21'\ntitle: 'UI Engineer Co-op'\ncompany: 'Apple'\nlocation: 'Cupertino, CA'\nrange: 'July - December 2017'\nurl: 'https://www.apple.com/music/'\n---\n\n- Developed and styled interactive web applications for Apple Music using Ember and SCSS\n- Built and shipped the Apple Music Extension for Facebook Messenger leveraging third-party and internal API integrations\n- Architected and implemented the user interface of Apple Music's embeddable web player widget for in-browser user authorization and full song playback\n- Contributed extensively to the creation of MusicKit JS, a public-facing JavaScript SDK for embedding Apple Music players into web applications\n"
  },
  {
    "path": "content/jobs/Mullen/index.md",
    "content": "---\ndate: '2015-12-21'\ntitle: 'Creative Technologist Co-op'\ncompany: 'MullenLowe'\nlocation: 'Boston, MA'\nrange: 'July - December 2015'\nurl: 'https://us.mullenlowe.com/'\n---\n\n- Developed, maintained, and shipped production code for client websites primarily using HTML, CSS, Sass, JavaScript, and jQuery\n- Performed quality assurance tests on various sites to ensure cross-browser compatibility and mobile responsiveness\n- Clients included JetBlue, Lovesac, U.S. Cellular, U.S. Department of Defense, and more\n"
  },
  {
    "path": "content/jobs/Scout/index.md",
    "content": "---\ndate: '2017-04-01'\ntitle: 'Developer'\ncompany: 'Scout Studio'\nlocation: 'Northeastern University'\nrange: 'Spring 2016 & 2017'\nurl: 'https://web.northeastern.edu/scout/'\n---\n\n- Collaborated with other student designers and engineers on pro-bono projects to create new brands, design systems, and websites for organizations in the community\n- Built and delivered technical solutions according to stakeholder business requirements\n"
  },
  {
    "path": "content/jobs/Starry/index.md",
    "content": "---\ndate: '2016-12-21'\ntitle: 'Software Engineer Co-op'\ncompany: 'Starry'\nlocation: 'Boston, MA'\nrange: 'July - December 2016'\nurl: 'https://starry.com/'\n---\n\n- Engineered and improved major features of Starry's customer-facing Android web app using ES6, Handlebars, Backbone, Marionette, and CSS\n- Proposed and implemented scalable solutions to issues identified with cloud services and applications responsible for communicating with the Starry Station internet router\n- Collaborated with designers and other developers to ensure thoughtful and consistent user experiences across Starry’s iOS and Android mobile apps\n"
  },
  {
    "path": "content/jobs/Upstatement/index.md",
    "content": "---\ndate: '2018-05-14'\ntitle: 'Lead Engineer'\ncompany: 'Upstatement'\nlocation: 'Boston, MA'\nrange: 'May 2018 - Present'\nurl: 'https://www.upstatement.com/'\n---\n\n- 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\n- Work alongside creative directors to lead the research, development, and architecture of technical solutions to fulfill business requirements\n- Collaborate with designers, project managers, and other engineers to transform creative concepts into production realities for clients and stakeholders\n- Provide leadership within engineering department through close collaboration, knowledge shares, and mentorship\n"
  },
  {
    "path": "content/posts/clickable-cards/index.md",
    "content": "---\ntitle: Accessible Clickable Cards\ndescription: Clickable cards with multiple child links\ndate: 2021-04-21\ndraft: false\nslug: /pensieve/clickable-cards\ntags:\n  - Accessibility\n  - CSS\n---\n\n[Codepen Demo](https://codepen.io/bchiang7/pen/xxRBvgd?editors=1100)\n\nCard layout where the card itself isn't an anchor link, but the whole card is clickable (with a `:before` pseudo element on the main `<a>`). Links inside of the card are still clickable.\n\n## CSS\n\n```css\n.grid__item {\n  &:hover,\n  &:focus-within {\n    background-color: #eee;\n  }\n\n  a {\n    position: relative;\n    z-index: 1;\n  }\n\n  h2 {\n    a {\n      position: static;\n\n      &:hover,\n      &:focus {\n        color: blue;\n      }\n\n      &:before {\n        content: '';\n        display: block;\n        position: absolute;\n        z-index: 0;\n        width: 100%;\n        height: 100%;\n        top: 0;\n        left: 0;\n        transition: background-color 0.1s ease-out;\n        background-color: transparent;\n      }\n    }\n  }\n}\n```\n"
  },
  {
    "path": "content/posts/dark-mode-toggle/index.md",
    "content": "---\ntitle: Dark Mode Toggle\ndescription: Dark mode without the flash of default theme\ndate: 2021-04-21\ndraft: false\nslug: /pensieve/dark-mode-toggle\ntags:\n  - Theming\n  - Dark Mode\n---\n\nDark mode toggle without the flash of default theme. Important bits:\n\n- CSS variables for color theming\n- Put `data-theme` attribute on `<html>`, not `<body>`, so we can run the JS before the DOM finishes rendering\n- Run local storage check in the `<head>`\n- JS for toggle button click handler can come after render\n\n## HTML\n\n```html\n<!DOCTYPE html>\n<html lang=\"en\" data-theme=\"light\">\n  <head>\n    <meta charset=\"utf-8\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n    ...\n    <script>\n      // If there's a theme stored in localStorage, use it on the <html>\n      const localStorageTheme = localStorage.getItem('theme');\n      if (localStorageTheme) {\n        document.documentElement.setAttribute('data-theme', localStorageTheme);\n      }\n    </script>\n  </head>\n  <body>\n    <div class=\"theme-toggle\">\n      <button\n        class=\"theme-toggle-btn js-theme-toggle\"\n        aria-label=\"Activate dark mode\"\n        title=\"Activate dark mode\"\n      >\n        <!--\n        <svg class=\"light-mode\">\n          <use xlink:href=\"#sun\"></use>\n        </svg>\n        <svg class=\"dark-mode\">\n          <use xlink:href=\"#moon\"></use>\n        </svg>\n        -->\n      </button>\n    </div>\n\n    <script src=\"app.js\"></script>\n  </body>\n</html>\n```\n\n## CSS Variables\n\n```css\n:root {\n  --bg: #ffffff;\n  --text: #000000;\n}\n\n[data-theme='dark'] {\n  --bg: #000000;\n  --text: #ffffff;\n}\n```\n\n## JavaScript\n\n```js:title=app.js\nconst themeToggleBtn = document.querySelector('.js-theme-toggle');\n\nthemeToggleBtn.addEventListener('click', () => onToggleClick());\n\nconst onToggleClick = () => {\n  const { theme } = document.documentElement.dataset;\n  const themeTo = theme && theme === 'light' ? 'dark' : 'light';\n  const label = `Activate ${theme} mode`;\n\n  document.documentElement.setAttribute('data-theme', themeTo);\n  localStorage.setItem('theme', themeTo);\n\n  themeToggleBtn.setAttribute('aria-label', label);\n  themeToggleBtn.setAttribute('title', label);\n};\n```\n\n## Resources\n\n- <https://css-tricks.com/a-complete-guide-to-dark-mode-on-the-web/>\n- <https://css-tricks.com/flash-of-inaccurate-color-theme-fart/>\n- <https://mxb.dev/blog/color-theme-switcher/>\n- <https://www.joshwcomeau.com/react/dark-mode/>\n- <https://web.dev/prefers-color-scheme/>\n"
  },
  {
    "path": "content/posts/docker-compose-error/index.md",
    "content": "---\ntitle: Docker Compose Error\ndescription: docker-compose version discrepancies\ndate: '2019-12-13'\ndraft: false\nslug: '/pensieve/docker-error'\ntags:\n  - WordPress\n  - Docker\n---\n\n## Problem\n\nRecently 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:\n\n```shell:title=bin/composer\n#!/bin/bash\ndocker-compose exec -w /var/www/html/wp-content/themes/skela wordpress composer \"$@\"\n```\n\nWhen trying to run this script via `./bin/composer install`, I got this error in my terminal:\n\n```shell\nERROR: Setting workdir for exec is not supported in API < 1.35 (1.30)\n```\n\nThe error was coming from the `-w` flag in the `docker-compose exec` command in the `composer` script.\n\n## Solution\n\nTurns 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 🤷‍♀️\n\n```yaml:title=docker-compose.yml\nversion: '3.6' # highlight-line\nservices:\n  wordpress:\n    build:\n```\n"
  },
  {
    "path": "content/posts/markdown-playground/index.md",
    "content": "---\ntitle: Markdown Test File\ndescription: abc234\ndate: 2019-12-07\ndraft: true\nslug: /pensieve/markdown-playground\ntags:\n  - Testing\n---\n\n![Image Alt](./image.jpg)\n\n```jsx\nclass FlavorForm extends React.Component { // highlight-line\n  constructor(props) {\n    super(props);\n    this.state = {value: 'coconut'};\n\n    this.handleChange = this.handleChange.bind(this);\n    this.handleSubmit = this.handleSubmit.bind(this);\n  }\n\n  handleChange(event) {\n    // highlight-next-line\n    this.setState({value: event.target.value});\n  }\n\n  // highlight-start\n  handleSubmit(event) {\n    alert('Your favorite flavor is: ' + this.state.value);\n    event.preventDefault();\n  }\n  // highlight-end\n\n  render() {\n    return (\n      { /* highlight-range{1,4-9,12} */ }\n      <form onSubmit={this.handleSubmit}>\n        <label>\n          Pick your favorite flavor:\n          <select value={this.state.value} onChange={this.handleChange}>\n            <option value=\"grapefruit\">Grapefruit</option>\n            <option value=\"lime\">Lime</option>\n            <option value=\"coconut\">Coconut</option>\n            <option value=\"mango\">Mango</option>\n          </select>\n        </label>\n        <input type=\"submit\" value=\"Submit\" />\n      </form>\n    );\n  }\n}\n```\n\n```javascript:title=highlight.js\n// Here is a comment\nfunction $initHighlight(block, cls) {\n  try {\n    if (cls.search(/\\bno\\-highlight\\b/) != -1)\n      return process(block, true, 0x0F) +\n             ` class=\"${cls}\"`;\n  } catch (e) {\n    /* handle exception */\n  }\n  for (var i = 0 / 2; i < classes.length; i++) {\n    if (checkCondition(classes[i]) === undefined) {\n      console.log('undefined');\n    }\n  }\n\n  return (\n    <div>\n      <web-component>{block}</web-component>\n    </div>\n  )\n}\n\nexport $initHighlight;\n```\n\nThis is a paragraph.\n\n    This is a paragraph.\n\n# Header 1\n\n## Header 2\n\n    Header 1\n    ========\n\n    Header 2\n    --------\n\n```css\n@import 'compass/reset';\n\n// variables\n$colorGreen: #008000;\n$colorGreenDark: darken($colorGreen, 10);\n\n@mixin container {\n  max-width: 980px;\n}\n\n// mixins with parameters\n@mixin button($color: green) {\n  @if ($color == green) {\n    background-color: #008000;\n  } @else if ($color == red) {\n    background-color: #b22222;\n  }\n}\n\nbutton {\n  @include button(red);\n}\n\ndiv,\n.navbar,\n#header,\ninput[type='input'] {\n  font-family: 'Helvetica Neue', Arial, sans-serif;\n  width: auto;\n  margin: 0 auto;\n  display: block;\n}\n\n.row-12 > [class*='spans'] {\n  border-left: 1px solid #b5c583;\n}\n\n// nested definitions\nul {\n  width: 100%;\n  padding: {\n    left: 5px;\n    right: 5px;\n  }\n  li {\n    float: left;\n    margin-right: 10px;\n    .home {\n      background: url('http://placehold.it/20') scroll no-repeat 0 0;\n    }\n  }\n}\n\n.banner {\n  @extend .container;\n}\n\na {\n  color: $colorGreen;\n  &:hover {\n    color: $colorGreenDark;\n  }\n  &:visited {\n    color: #c458cb;\n  }\n}\n\n@for $i from 1 through 5 {\n  .span#{$i} {\n    width: 20px * $i;\n  }\n}\n\n@mixin mobile {\n  @media screen and (max-width: 600px) {\n    @content;\n  }\n}\n```\n\n```markdown\n# hello world\n\nyou can write text [with links](http://example.com) inline or [link references][1].\n\n- one _thing_ has *em*phasis\n- two **things** are **bold**\n\n[1]: http://example.com\n\n---\n\n# hello world\n\n<this_is inline=\"xml\"></this_is>\n\n> markdown is so cool\n\n    so are code segments\n\n1. one thing (yeah!)\n2. two thing `i can write code`, and `more` wipee!\n```\n\n# Header 1\n\n## Header 2\n\n### Header 3\n\n#### Header 4\n\n##### Header 5\n\n###### Header 6\n\n    # Header 1\n    ## Header 2\n    ### Header 3\n    #### Header 4\n    ##### Header 5\n    ###### Header 6\n\n# Header 1\n\n## Header 2\n\n### Header 3\n\n#### Header 4\n\n##### Header 5\n\n###### Header 6\n\n    # Header 1 #\n    ## Header 2 ##\n    ### Header 3 ###\n    #### Header 4 ####\n    ##### Header 5 #####\n    ###### Header 6 ######\n\n> Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aliquam hendrerit mi posuere lectus. Vestibulum enim wisi, viverra nec, fringilla in, laoreet vitae, risus.\n\n    > Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aliquam hendrerit mi posuere lectus. Vestibulum enim wisi, viverra nec, fringilla in, laoreet vitae, risus.\n\n> ## This is a header\n>\n> 1. This is the first list item.\n> 2. This is the second list item.\n>\n> Here's some example code:\n>\n>     Markdown.generate();\n\n    > ## This is a header.\n    > 1. This is the first list item.\n    > 2. This is the second list item.\n    >\n    > Here's some example code:\n    >\n    >     Markdown.generate();\n\n- Red\n- Green\n- Blue\n\n- Red\n- Green\n- Blue\n\n- Red\n- Green\n- Blue\n\n```markdown\n- Red\n- Green\n- Blue\n\n* Red\n* Green\n* Blue\n\n- Red\n- Green\n- Blue\n```\n\n1. Buy flour and salt\n2. Mix together with water\n3. Bake\n\n```markdown\n1. Buy flour and salt\n1. Mix together with water\n1. Bake\n```\n\nParagraph:\n\n    Code\n\n<!-- -->\n\n    Paragraph:\n\n        Code\n\n---\n\n---\n\n---\n\n---\n\n---\n\n    * * *\n\n    ***\n\n    *****\n\n    - - -\n\n    ---------------------------------------\n\nThis is [an example](http://example.com 'Example') link.\n\n[This link](http://example.com) has no title attr.\n\nThis is [an example][id] reference-style link.\n\n[id]: http://example.com 'Optional Title'\n\n    This is [an example](http://example.com \"Example\") link.\n\n    [This link](http://example.com) has no title attr.\n\n    This is [an example] [id] reference-style link.\n\n    [id]: http://example.com \"Optional Title\"\n\n_single asterisks_\n\n_single underscores_\n\n**double asterisks**\n\n**double underscores**\n\n    *single asterisks*\n\n    _single underscores_\n\n    **double asterisks**\n\n    __double underscores__\n\nThis paragraph has some `code` in it.\n\n    This paragraph has some `code` in it.\n"
  },
  {
    "path": "content/posts/wordpress-publish-error/index.md",
    "content": "---\ntitle: WordPress Publishing Error\ndescription: Trying to create a simple post in WordPress\ndate: 2019-12-03\ndraft: false\nslug: /pensieve/wordpress-publish-error\ntags:\n  - WordPress\n---\n\n## Problem\n\nRecently 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.\n\nIt looked something like this:\n\n![Draft fail](./draft-fail.png)\n\nSometimes the error message would be slightly more helpful: `Publishing failed. Error message: The response is not a valid JSON response.`\n\n![Publish error](./publish-error.png)\n\nAnd if I popped open the console, I saw these errors:\n\n![Console errors](./console-errors.png)\n\n## Solution\n\nSince 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)).\n\nIt 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.\n"
  },
  {
    "path": "content/projects/AMFM.md",
    "content": "---\ndate: '2017-11-01'\ntitle: 'Apple Music Facebook Messenger Integration'\ngithub: ''\nexternal: 'https://www.theverge.com/2017/10/5/16433770/facebook-messenger-apple-music-bot-song-streaming'\ntech:\n  - Ember\n  - JS\n  - SCSS\ncompany: 'Apple'\nshowInProjects: true\n---\n\nFacebook 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).\n"
  },
  {
    "path": "content/projects/AlgoliaWordPressMediumPost.md",
    "content": "---\ndate: '2020-03-27'\ntitle: 'Integrating Algolia Search with WordPress Multisite'\ngithub: ''\nexternal: 'https://medium.com/stories-from-upstatement/integrating-algolia-search-with-wordpress-multisite-e2dea3ed449c'\ntech:\n  - Algolia\n  - WordPress\n  - PHP\ncompany: 'Upstatement'\nshowInProjects: true\n---\n\nBuilding a custom multisite compatible WordPress plugin to build global search with Algolia\n"
  },
  {
    "path": "content/projects/AppleMusicEmbedPlayer.md",
    "content": "---\ndate: '2017-12-01'\ntitle: 'Apple Music Embeddable Web Player Widget'\ngithub: ''\nexternal: 'https://tools.applemusic.com/en-us'\ntech:\n  - MusicKit.js\n  - JS\n  - SCSS\ncompany: 'Apple'\nshowInProjects: true\n---\n\nEmbeddable 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/).\n"
  },
  {
    "path": "content/projects/Blistabloc.md",
    "content": "---\ndate: '2018-05-01'\ntitle: 'blistabloc'\ngithub: ''\nexternal: 'https://blistabloc.com/'\ntech:\n  - WordPress\n  - Timber\n  - WooCommerce\ncompany: 'Scout'\nshowInProjects: false\n---\n\nCustom 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.\n"
  },
  {
    "path": "content/projects/CourseSource.md",
    "content": "---\ndate: '2016-04-01'\ntitle: 'CourseSource'\ngithub: 'https://github.com/bchiang7/WebDevSpring2016/tree/master/public/project'\nexternal: ''\ntech:\n  - Angular\n  - Node\n  - Express\n  - MongoDB\ncompany: 'Northeastern'\nshowInProjects: false\n---\n\nWeb 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.\n"
  },
  {
    "path": "content/projects/CrowdDJ.md",
    "content": "---\ndate: '2017-03-01'\ntitle: 'Crowd DJ'\ngithub: 'https://github.com/crowddj/crowddj-react'\nexternal: ''\ntech:\n  - React\n  - Firebase\n  - Spotify API\ncompany: HackBeanpot 2017\nshowInProjects: false\n---\n\nWeb 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.\n"
  },
  {
    "path": "content/projects/Devoted.md",
    "content": "---\ndate: '2018-12-01'\ntitle: 'Devoted Health'\ngithub: ''\nexternal: 'https://www.devoted.com/'\ntech:\n  - Gatsby\n  - TypeScript\n  - Algolia\ncompany: 'Upstatement'\nshowInProjects: false\n---\n\nA site for a revolutionary healthcare company, including an Algolia instant search integration\n"
  },
  {
    "path": "content/projects/EverytownIdealState.md",
    "content": "---\ndate: '2022-01-20'\ntitle: 'Everytown Gun Law Rankings'\ngithub: ''\nexternal: 'https://everytownresearch.org/rankings/'\ntech:\n  - WordPress\n  - Timber\n  - PHP\n  - Airtable API\ncompany: 'Upstatement'\nshowInProjects: false\n---\n"
  },
  {
    "path": "content/projects/Flagship.md",
    "content": "---\ndate: '2018-10-01'\ntitle: 'Flagship Pioneering'\ngithub: ''\nexternal: 'https://www.flagshippioneering.com/'\ntech:\n  - Craft CMS\n  - Chart.js\ncompany: 'Upstatement'\nshowInProjects: false\n---\n\nA marketing site for an ambitious life sciences venture capital company.\n"
  },
  {
    "path": "content/projects/Fontipsums.md",
    "content": "---\ndate: '2016-01-01'\ntitle: 'Fontipsums'\ngithub: 'https://github.com/bchiang7/fontipsums/'\nexternal: 'http://bchiang7.github.io/fontipsums/'\ntech:\n  - HTML\n  - SCSS\nshowInProjects: true\n---\n\nSimple website to display some of my favorite font pairings combined with some fun lorem ipsum variations found on the web.\n"
  },
  {
    "path": "content/projects/GoogleKeepClone.md",
    "content": "---\ndate: '2018-12-29'\ntitle: 'Google Keep Clone'\ngithub: 'https://github.com/bchiang7/google-keep-vue-firebase'\nexternal: 'https://keep-vue.netlify.com/'\ntech:\n  - Vue\n  - Firebase\nshowInProjects: true\n---\n\nA simple Google Keep clone built with Vue and Firebase.\n"
  },
  {
    "path": "content/projects/HBS.md",
    "content": "---\ndate: '2022-10-08'\ntitle: 'Harvard Business School Design System'\ngithub: ''\nexternal: ''\ntech:\n  - Storybook\n  - React\n  - TypeScript\ncompany: 'Upstatement'\nshowInProjects: false\n---\n"
  },
  {
    "path": "content/projects/HalcyonTheme.md",
    "content": "---\ndate: '2017-12-27'\ntitle: 'Halcyon Theme'\ngithub: 'https://github.com/bchiang7/halcyon-site'\nexternal: 'https://halcyon-theme.netlify.com/'\ntech:\n  - VS Code\n  - Sublime Text\n  - Atom\n  - iTerm2\n  - Hyper\nshowInProjects: false\n---\n\nA 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).\n"
  },
  {
    "path": "content/projects/HeadlessCMSMediumPost.md",
    "content": "---\ndate: '2019-11-12'\ntitle: 'Building a Headless Mobile App CMS From Scratch'\ngithub: ''\nexternal: 'https://medium.com/stories-from-upstatement/building-a-headless-mobile-app-cms-from-scratch-bab2d17744d9'\ntech:\n  - Node\n  - Express\n  - Firebase\n  - Vue\ncompany: 'Upstatement'\nshowInProjects: true\n---\n\nFind out how we built a custom headless CMS with Node, Express, and Firebase for a project at Upstatement\n"
  },
  {
    "path": "content/projects/Interventions.md",
    "content": "---\ndate: '2017-08-01'\ntitle: 'Interventions'\ngithub: ''\nexternal: 'https://interventions.design/'\ntech:\n  - Jekyll\n  - SCSS\n  - JS\ncompany: 'Scout'\nshowInProjects: false\n---\n\nInteractive marketing website for Northeastern's first annual student-led design conference, Interventions.\n"
  },
  {
    "path": "content/projects/JetBlueHumanKinda.md",
    "content": "---\ndate: '2015-10-01'\ntitle: 'JetBlue HumanKinda'\ngithub: ''\nexternal: 'https://us.mullenlowe.com/work/humankinda/'\ntech:\n  - Tumblr\n  - HTML\n  - CSS\n  - JS\ncompany: 'MullenLowe'\nshowInProjects: false\n---\n\nTumblr 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/).\n"
  },
  {
    "path": "content/projects/KoalaHealth.md",
    "content": "---\ndate: '2021-09-01'\ntitle: 'Koala Health'\ngithub: ''\nexternal: 'https://www.koala.health/'\ntech:\n  - Next.js\n  - TypeScript\n  - Redux Toolkit\n  - Stripe\n  - Algolia\n  - Firebase Auth\n  - Formik\n  - Yup\n  - Vercel\ncompany: 'Upstatement'\nshowInProjects: false\n---\n"
  },
  {
    "path": "content/projects/LonelyPlanetDBMS.md",
    "content": "---\ndate: '2017-06-22'\ntitle: 'Lonely Planet DBMS'\ngithub: 'https://github.com/bchiang7/CS3200-Project'\nexternal: ''\ntech:\n  - Python\n  - MySQL\n  - Flask\n  - JS\ncompany: 'Northeastern'\nshowInProjects: false\n---\n\nA simple web application that allows users to filter through and leave reviews in a database of Lonely Planet's Top 500 Travel Destinations.\n"
  },
  {
    "path": "content/projects/MichelleWu.md",
    "content": "---\ndate: '2020-09-15'\ntitle: 'Michelle Wu for Boston Grassroots Toolkit'\ngithub: ''\nexternal: 'https://toolkit.michelleforboston.com/'\ntech:\n  - Gatsby\n  - Styled Components\ncompany: 'Upstatement'\nshowInProjects: false\n---\n"
  },
  {
    "path": "content/projects/MomsDemandAction.md",
    "content": "---\ndate: '2019-11-12'\ntitle: 'Moms Demand Action Mobile App'\ngithub: ''\nexternal: 'https://www.upstatement.com/work/moms-demand-action/'\nios: 'https://apps.apple.com/us/app/demand-action/id1475502876'\nandroid: 'https://play.google.com/store/apps/details?id=com.momsdemandaction.app'\ntech:\n  - NativeScript Vue\n  - iOS\n  - Android\ncompany: 'Upstatement'\nshowInProjects: false\n---\n"
  },
  {
    "path": "content/projects/MyNEURedesign.md",
    "content": "---\ndate: '2017-04-03'\ntitle: 'myNEU Redesign'\ngithub: 'https://github.com/bchiang7/Redesign-myNEU'\nexternal: 'https://bchiang7.github.io/Redesign-myNEU/'\ntech:\n  - Jekyll\n  - SCSS\n  - JS\ncompany: 'Northeastern'\nshowInProjects: false\n---\n\nStudent 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.\n"
  },
  {
    "path": "content/projects/NUWITSite.md",
    "content": "---\ndate: '2015-12-20'\ntitle: 'NU Women in Tech'\ngithub: 'https://github.com/nuwit/website'\nexternal: 'https://nuwit.ccs.neu.edu/'\ntech:\n  - Jekyll\n  - Bootstrap\ncompany: 'Northeastern'\nshowInProjects: true\n---\n\nComplete overhaul and redesign of NU Women in Tech’s club website using Jekyll, built while serving as web chair on the e-board.\n"
  },
  {
    "path": "content/projects/NortheasternCSSH.md",
    "content": "---\ndate: '2020-07-16'\ntitle: 'Northeastern CSSH'\ngithub: ''\nexternal: 'https://cssh.northeastern.edu/'\ntech:\n  - WordPress\n  - Timber\n  - WordPress Multisite\n  - PHP\n  - Algolia\n  - JS\ncompany: 'Upstatement'\nshowInProjects: false\n---\n"
  },
  {
    "path": "content/projects/OctoProfile.md",
    "content": "---\ndate: '2019-07-15'\ntitle: 'OctoProfile'\ngithub: 'https://github.com/bchiang7/octoprofile'\nexternal: 'https://octoprofile.now.sh'\ntech:\n  - Next.js\n  - Chart.js\n  - GitHub API\nshowInProjects: true\n---\n\nA 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.\n"
  },
  {
    "path": "content/projects/OneCardForAll.md",
    "content": "---\ndate: '2015-12-01'\ntitle: 'One Card For All'\ngithub: ''\nexternal: 'https://us.mullenlowe.com/work/one-card-for-all/'\ntech:\n  - HTML\n  - SCSS\n  - JS\n  - jQuery\ncompany: 'MullenLowe'\nshowInProjects: false\n---\n\nInteractive 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.\n"
  },
  {
    "path": "content/projects/PhillySports.md",
    "content": "---\ndate: '2021-07-01'\ntitle: 'Philadelphia Inquirer Sports Scoreboards'\ngithub: ''\nexternal: 'https://www.inquirer.com/sports/'\ntech:\n  - React\n  - TypeScript\n  - Stats Perform API\ncompany: 'Upstatement'\nshowInProjects: false\n---\n"
  },
  {
    "path": "content/projects/Pratt.md",
    "content": "---\ndate: '2022-08-08'\ntitle: 'Pratt'\ngithub: ''\nexternal: 'https://www.pratt.edu/'\ntech:\n  - WordPress\n  - Timber\n  - WordPress Multisite\n  - Gutenberg\n  - JS\ncompany: 'Upstatement'\nshowInProjects: false\n---\n"
  },
  {
    "path": "content/projects/ReactResume.md",
    "content": "---\ndate: '2016-08-01'\ntitle: 'React Profile'\ngithub: 'https://github.com/bchiang7/react-profile'\nexternal: 'https://bchiang7.github.io/react-profile/'\ntech:\n  - React\n  - CSS\nshowInProjects: true\n---\n\nOnline 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.\n"
  },
  {
    "path": "content/projects/Screentime.md",
    "content": "---\ndate: '2016-11-01'\ntitle: 'Screentime 2.0'\ngithub: ''\nexternal: 'https://starry.com/blog/product/whats-new-screentime-just-got-better-for-parents'\nandroid: 'https://play.google.com/store/apps/details?id=com.starry.management&hl=en_US'\ntech:\n  - Cordova\n  - Backbone\n  - Marionette\ncompany: 'Starry'\nshowInProjects: true\n---\n\nStarry 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.\n"
  },
  {
    "path": "content/projects/SpotifyProfile.md",
    "content": "---\ndate: '2018-12-18'\ntitle: 'Spotify Profile'\ngithub: 'https://github.com/bchiang7/spotify-profile'\nexternal: 'https://spotify-profile.herokuapp.com/'\ntech:\n  - React\n  - Express\n  - Styled Components\nshowInProjects: false\n---\n\nA 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.\n"
  },
  {
    "path": "content/projects/SpotifyTopTracks2017.md",
    "content": "---\ndate: '2018-04-20'\ntitle: 'Spotify’s Top Tracks of 2017'\ngithub: 'https://github.com/bchiang7/spotify-top-tracks-2017'\nexternal: ''\ntech:\n  - R\n  - Spotify Web API\ncompany: 'Northeastern'\nshowInProjects: false\n---\n\nR Project for my Data Science class at Northeastern to analyze the top Spotify tracks of 2017 and their audio features.\n"
  },
  {
    "path": "content/projects/The19th.md",
    "content": "---\ndate: '2020-08-02'\ntitle: 'The 19th News'\ngithub: ''\nexternal: 'https://19thnews.org/'\ntech:\n  - WordPress\n  - Timber\n  - Gutenberg\n  - PHP\n  - JS\n  - Mailchimp\n  - AMP\ncompany: 'Upstatement'\nshowInProjects: false\n---\n"
  },
  {
    "path": "content/projects/Threadable.md",
    "content": "---\ndate: '2022-09-08'\ntitle: 'Threadable'\ngithub: ''\nexternal: 'https://www.threadablebooks.com/'\nios: 'https://apps.apple.com/us/app/threadable/id1550995547'\ntech:\n  - React Native\n  - Ruby on Rails\n  - Firebase\ncompany: 'Upstatement'\nshowInProjects: false\n---\n"
  },
  {
    "path": "content/projects/TimeToHaveMoreFun.md",
    "content": "---\ndate: '2020-01-10'\ntitle: 'Time to Have More Fun'\ngithub: 'https://github.com/bchiang7/time-to-have-more-fun'\nexternal: 'https://time-to-have-more-fun.now.sh/'\ntech:\n  - Next.js\n  - Tailwind CSS\n  - Firebase\ncompany: ''\nshowInProjects: true\n---\n\nA single page web app for helping me choose where to travel, built with Next.js, Firebase, and Tailwind CSS\n"
  },
  {
    "path": "content/projects/UpstatementDotCom.md",
    "content": "---\ndate: '2019-11-25'\ntitle: 'Upstatement.com'\ngithub: ''\nexternal: 'https://www.upstatement.com/'\ntech:\n  - Nuxt\n  - Vue\n  - Prismic\ncompany: 'Upstatement'\nshowInProjects: false\n---\n"
  },
  {
    "path": "content/projects/Vanderbilt.md",
    "content": "---\ndate: '2021-06-01'\ntitle: 'Vanderbilt Design System'\ngithub: ''\nexternal: 'https://www.vanderbilt.edu/'\ntech:\n  - Twig\n  - Puppy\n  - JS\ncompany: 'Upstatement'\nshowInProjects: false\n---\n"
  },
  {
    "path": "content/projects/WeatherWidget.md",
    "content": "---\ndate: '2016-11-16'\ntitle: 'Weather Widget'\ngithub: 'https://github.com/bchiang7/DemoWebApp'\nexternal: 'http://quiet-dusk-89245.herokuapp.com/'\ntech:\n  - Node\n  - Express\n  - EJS\nshowInProjects: false\n---\n\nSimple weather app made with Node.js, Express, and Heroku. Utilized the OpenWeatherMap API and Google Maps API.\n"
  },
  {
    "path": "content/projects/v1.md",
    "content": "---\ndate: '2016-03-01'\ntitle: 'Personal Website V1'\ngithub: 'https://github.com/bchiang7/v1'\nexternal: 'https://bchiang7.github.io/v1/'\ntech:\n  - HTML\n  - CSS\n  - JS\n  - Bootstrap\nshowInProjects: true\n---\n\nMy 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.\n"
  },
  {
    "path": "content/projects/v2.md",
    "content": "---\ndate: '2016-12-01'\ntitle: 'Personal Website V2'\ngithub: 'https://github.com/bchiang7/v2'\nexternal: 'https://bchiang7.github.io/v2/'\ntech:\n  - Jekyll\n  - SCSS\n  - JS\nshowInProjects: true\n---\n\nSecond iteration of my personal website. Designed and developed with a conscious effort to avoid using any superfluous frameworks like Bootstrap.\n"
  },
  {
    "path": "content/projects/v3.md",
    "content": "---\ndate: '2017-10-01'\ntitle: 'Personal Website V3'\ngithub: 'https://github.com/bchiang7/bchiang7.github.io'\nexternal: 'https://bchiang7.github.io/v3/'\ntech:\n  - Jekyll\n  - SCSS\n  - JS\nshowInProjects: true\n---\n\nThird iteration of my personal website built with Jekyll and hosted on GitHub Pages.\n"
  },
  {
    "path": "gatsby-browser.js",
    "content": "/**\n * Implement Gatsby's Browser APIs in this file.\n *\n * See: https://www.gatsbyjs.org/docs/browser-apis/\n */\n"
  },
  {
    "path": "gatsby-config.js",
    "content": "const config = require('./src/config');\n\nmodule.exports = {\n  siteMetadata: {\n    title: 'Brittany Chiang',\n    description:\n      'Brittany Chiang is a software engineer who specializes in building (and occasionally designing) exceptional digital experiences.',\n    siteUrl: 'https://brittanychiang.com', // No trailing slash allowed!\n    image: '/og.png', // Path to your image you placed in the 'static' folder\n    twitterUsername: '@bchiang7',\n  },\n  plugins: [\n    `gatsby-plugin-react-helmet`,\n    `gatsby-plugin-styled-components`,\n    `gatsby-plugin-image`,\n    `gatsby-plugin-sharp`,\n    `gatsby-transformer-sharp`,\n    `gatsby-plugin-sitemap`,\n    `gatsby-plugin-robots-txt`,\n    {\n      resolve: `gatsby-plugin-manifest`,\n      options: {\n        name: 'Brittany Chiang',\n        short_name: 'Brittany Chiang',\n        start_url: '/',\n        background_color: config.colors.darkNavy,\n        theme_color: config.colors.navy,\n        display: 'minimal-ui',\n        icon: 'src/images/logo.png',\n      },\n    },\n    `gatsby-plugin-offline`,\n    {\n      resolve: `gatsby-source-filesystem`,\n      options: {\n        name: `images`,\n        path: `${__dirname}/src/images`,\n      },\n    },\n    {\n      resolve: 'gatsby-source-filesystem',\n      options: {\n        name: 'content',\n        path: `${__dirname}/content/`,\n      },\n    },\n    {\n      resolve: `gatsby-source-filesystem`,\n      options: {\n        name: `posts`,\n        path: `${__dirname}/content/posts`,\n      },\n    },\n    {\n      resolve: `gatsby-source-filesystem`,\n      options: {\n        name: `projects`,\n        path: `${__dirname}/content/projects`,\n      },\n    },\n    {\n      resolve: `gatsby-transformer-remark`,\n      options: {\n        plugins: [\n          {\n            // https://www.gatsbyjs.org/packages/gatsby-remark-external-links\n            resolve: 'gatsby-remark-external-links',\n            options: {\n              target: '_blank',\n              rel: 'nofollow noopener noreferrer',\n            },\n          },\n          {\n            // https://www.gatsbyjs.org/packages/gatsby-remark-images\n            resolve: 'gatsby-remark-images',\n            options: {\n              maxWidth: 700,\n              linkImagesToOriginal: true,\n              quality: 90,\n              tracedSVG: { color: config.colors.green },\n            },\n          },\n          {\n            // https://www.gatsbyjs.org/packages/gatsby-remark-code-titles/\n            resolve: 'gatsby-remark-code-titles',\n          }, // IMPORTANT: this must be ahead of other plugins that use code blocks\n          {\n            // https://www.gatsbyjs.org/packages/gatsby-remark-prismjs\n            resolve: `gatsby-remark-prismjs`,\n            options: {\n              // Class prefix for <pre> tags containing syntax highlighting;\n              // defaults to 'language-' (e.g. <pre class=\"language-js\">).\n              // If your site loads Prism into the browser at runtime,\n              // (e.g. for use with libraries like react-live),\n              // you may use this to prevent Prism from re-processing syntax.\n              // This is an uncommon use-case though;\n              // If you're unsure, it's best to use the default value.\n              classPrefix: 'language-',\n              // This is used to allow setting a language for inline code\n              // (i.e. single backticks) by creating a separator.\n              // This separator is a string and will do no white-space\n              // stripping.\n              // A suggested value for English speakers is the non-ascii\n              // character '›'.\n              inlineCodeMarker: null,\n              // This lets you set up language aliases.  For example,\n              // setting this to '{ sh: \"bash\" }' will let you use\n              // the language \"sh\" which will highlight using the\n              // bash highlighter.\n              aliases: {},\n              // This toggles the display of line numbers globally alongside the code.\n              // To use it, add the following line in gatsby-browser.js\n              // right after importing the prism color scheme:\n              //  require(\"prismjs/plugins/line-numbers/prism-line-numbers.css\")\n              // Defaults to false.\n              // If you wish to only show line numbers on certain code blocks,\n              // leave false and use the {numberLines: true} syntax below\n              showLineNumbers: false,\n              // If setting this to true, the parser won't handle and highlight inline\n              // code used in markdown i.e. single backtick code like `this`.\n              noInlineHighlight: false,\n              // This adds a new language definition to Prism or extend an already\n              // existing language definition. More details on this option can be\n              // found under the header \"Add new language definition or extend an\n              // existing language\" below.\n              languageExtensions: [\n                {\n                  language: 'superscript',\n                  extend: 'javascript',\n                  definition: {\n                    superscript_types: /(SuperType)/,\n                  },\n                  insertBefore: {\n                    function: {\n                      superscript_keywords: /(superif|superelse)/,\n                    },\n                  },\n                },\n              ],\n              // Customize the prompt used in shell output\n              // Values below are default\n              prompt: {\n                user: 'root',\n                host: 'localhost',\n                global: false,\n              },\n            },\n          },\n        ],\n      },\n    },\n    {\n      resolve: `gatsby-plugin-google-analytics`,\n      options: {\n        trackingId: 'UA-45666519-2',\n      },\n    },\n  ],\n};\n"
  },
  {
    "path": "gatsby-node.js",
    "content": "/**\n * Implement Gatsby's Node APIs in this file.\n *\n * See: https://www.gatsbyjs.org/docs/node-apis/\n */\n\nconst path = require('path');\nconst _ = require('lodash');\n\nexports.createPages = async ({ actions, graphql, reporter }) => {\n  const { createPage } = actions;\n  const postTemplate = path.resolve(`src/templates/post.js`);\n  const tagTemplate = path.resolve('src/templates/tag.js');\n\n  const result = await graphql(`\n    {\n      postsRemark: allMarkdownRemark(\n        filter: { fileAbsolutePath: { regex: \"/content/posts/\" } }\n        sort: { order: DESC, fields: [frontmatter___date] }\n        limit: 1000\n      ) {\n        edges {\n          node {\n            frontmatter {\n              slug\n            }\n          }\n        }\n      }\n      tagsGroup: allMarkdownRemark(limit: 2000) {\n        group(field: frontmatter___tags) {\n          fieldValue\n        }\n      }\n    }\n  `);\n\n  // Handle errors\n  if (result.errors) {\n    reporter.panicOnBuild(`Error while running GraphQL query.`);\n    return;\n  }\n\n  // Create post detail pages\n  const posts = result.data.postsRemark.edges;\n\n  posts.forEach(({ node }) => {\n    createPage({\n      path: node.frontmatter.slug,\n      component: postTemplate,\n      context: {},\n    });\n  });\n\n  // Extract tag data from query\n  const tags = result.data.tagsGroup.group;\n  // Make tag pages\n  tags.forEach(tag => {\n    createPage({\n      path: `/pensieve/tags/${_.kebabCase(tag.fieldValue)}/`,\n      component: tagTemplate,\n      context: {\n        tag: tag.fieldValue,\n      },\n    });\n  });\n};\n\n// https://www.gatsbyjs.org/docs/node-apis/#onCreateWebpackConfig\nexports.onCreateWebpackConfig = ({ stage, loaders, actions }) => {\n  // https://www.gatsbyjs.org/docs/debugging-html-builds/#fixing-third-party-modules\n  if (stage === 'build-html' || stage === 'develop-html') {\n    actions.setWebpackConfig({\n      module: {\n        rules: [\n          {\n            test: /scrollreveal/,\n            use: loaders.null(),\n          },\n          {\n            test: /animejs/,\n            use: loaders.null(),\n          },\n          {\n            test: /miniraf/,\n            use: loaders.null(),\n          },\n        ],\n      },\n    });\n  }\n\n  actions.setWebpackConfig({\n    resolve: {\n      alias: {\n        '@components': path.resolve(__dirname, 'src/components'),\n        '@config': path.resolve(__dirname, 'src/config'),\n        '@fonts': path.resolve(__dirname, 'src/fonts'),\n        '@hooks': path.resolve(__dirname, 'src/hooks'),\n        '@images': path.resolve(__dirname, 'src/images'),\n        '@pages': path.resolve(__dirname, 'src/pages'),\n        '@styles': path.resolve(__dirname, 'src/styles'),\n        '@utils': path.resolve(__dirname, 'src/utils'),\n      },\n    },\n  });\n};\n"
  },
  {
    "path": "gatsby-ssr.js",
    "content": "/**\n * Implement Gatsby's SSR (Server Side Rendering) APIs in this file.\n *\n * See: https://www.gatsbyjs.org/docs/ssr-apis/\n */\n\n // You can delete this file if you're not using it"
  },
  {
    "path": "package.json",
    "content": "{\n  \"name\": \"v4\",\n  \"description\": \"Personal Website V4\",\n  \"version\": \"1.0.0\",\n  \"author\": \"Brittany Chiang <brittany.chiang@gmail.com>\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"https://github.com/bchiang7/v4\"\n  },\n  \"keywords\": [\n    \"gatsby\"\n  ],\n  \"license\": \"MIT\",\n  \"browserslist\": \"> 0.25%, not dead\",\n  \"scripts\": {\n    \"build\": \"gatsby build\",\n    \"develop\": \"gatsby develop\",\n    \"format\": \"prettier --write \\\"**/*.{js,jsx,json,md}\\\"\",\n    \"start\": \"npm run develop\",\n    \"serve\": \"gatsby serve\",\n    \"clean\": \"gatsby clean\",\n    \"prepare\": \"husky install\",\n    \"lint-staged\": \"lint-staged\"\n  },\n  \"lint-staged\": {\n    \"*.{js,css,json,md}\": [\n      \"prettier --write\"\n    ],\n    \"*.js\": [\n      \"eslint --fix\"\n    ]\n  },\n  \"dependencies\": {\n    \"animejs\": \"^3.1.0\",\n    \"babel-plugin-styled-components\": \"^1.12.0\",\n    \"gatsby\": \"^3.4.1\",\n    \"gatsby-plugin-google-analytics\": \"^3.4.0\",\n    \"gatsby-plugin-image\": \"^1.4.0\",\n    \"gatsby-plugin-manifest\": \"^3.4.0\",\n    \"gatsby-plugin-netlify\": \"^3.4.0\",\n    \"gatsby-plugin-offline\": \"^4.4.0\",\n    \"gatsby-plugin-react-helmet\": \"^4.4.0\",\n    \"gatsby-plugin-robots-txt\": \"^1.5.6\",\n    \"gatsby-plugin-sharp\": \"^3.4.1\",\n    \"gatsby-plugin-sitemap\": \"^4.0.0\",\n    \"gatsby-plugin-styled-components\": \"^4.4.0\",\n    \"gatsby-remark-external-links\": \"0.0.4\",\n    \"gatsby-remark-images\": \"^5.1.0\",\n    \"gatsby-remark-prismjs\": \"^5.1.0\",\n    \"gatsby-source-filesystem\": \"^3.4.0\",\n    \"gatsby-transformer-remark\": \"^4.1.0\",\n    \"gatsby-transformer-sharp\": \"^3.4.0\",\n    \"lodash\": \"^4.17.19\",\n    \"prismjs\": \"^1.27.0\",\n    \"prop-types\": \"^15.7.2\",\n    \"react\": \"^17.0.2\",\n    \"react-dom\": \"^17.0.2\",\n    \"react-helmet\": \"^6.1.0\",\n    \"react-transition-group\": \"^4.3.0\",\n    \"scrollreveal\": \"^4.0.5\",\n    \"styled-components\": \"^5.3.0\"\n  },\n  \"devDependencies\": {\n    \"@babel/core\": \"^7.14.0\",\n    \"@babel/eslint-parser\": \"^7.13.14\",\n    \"@babel/preset-react\": \"^7.13.13\",\n    \"@upstatement/eslint-config\": \"^1.0.0\",\n    \"@upstatement/prettier-config\": \"^1.0.0\",\n    \"babel-preset-gatsby\": \"^1.4.0\",\n    \"eslint\": \"^7.25.0\",\n    \"eslint-config-prettier\": \"^8.3.0\",\n    \"eslint-plugin-jsx-a11y\": \"^6.4.1\",\n    \"eslint-plugin-react\": \"^7.23.2\",\n    \"gatsby-remark-code-titles\": \"^1.1.0\",\n    \"husky\": \"^6.0.0\",\n    \"lint-staged\": \"^10.1.2\",\n    \"prettier\": \"^2.2.1\"\n  }\n}\n"
  },
  {
    "path": "prettier.config.js",
    "content": "module.exports = require('@upstatement/prettier-config');\n"
  },
  {
    "path": "src/components/email.js",
    "content": "import React from 'react';\nimport PropTypes from 'prop-types';\nimport styled from 'styled-components';\nimport { email } from '@config';\nimport { Side } from '@components';\n\nconst StyledLinkWrapper = styled.div`\n  display: flex;\n  flex-direction: column;\n  align-items: center;\n  position: relative;\n\n  &:after {\n    content: '';\n    display: block;\n    width: 1px;\n    height: 90px;\n    margin: 0 auto;\n    background-color: var(--light-slate);\n  }\n\n  a {\n    margin: 20px auto;\n    padding: 10px;\n    font-family: var(--font-mono);\n    font-size: var(--fz-xxs);\n    line-height: var(--fz-lg);\n    letter-spacing: 0.1em;\n    writing-mode: vertical-rl;\n\n    &:hover,\n    &:focus {\n      transform: translateY(-3px);\n    }\n  }\n`;\n\nconst Email = ({ isHome }) => (\n  <Side isHome={isHome} orientation=\"right\">\n    <StyledLinkWrapper>\n      <a href={`mailto:${email}`}>{email}</a>\n    </StyledLinkWrapper>\n  </Side>\n);\n\nEmail.propTypes = {\n  isHome: PropTypes.bool,\n};\n\nexport default Email;\n"
  },
  {
    "path": "src/components/footer.js",
    "content": "import React, { useState, useEffect } from 'react';\nimport PropTypes from 'prop-types';\nimport styled from 'styled-components';\nimport { Icon } from '@components/icons';\nimport { socialMedia } from '@config';\n\nconst StyledFooter = styled.footer`\n  ${({ theme }) => theme.mixins.flexCenter};\n  flex-direction: column;\n  height: auto;\n  min-height: 70px;\n  padding: 15px;\n  text-align: center;\n`;\n\nconst StyledSocialLinks = styled.div`\n  display: none;\n\n  @media (max-width: 768px) {\n    display: block;\n    width: 100%;\n    max-width: 270px;\n    margin: 0 auto 10px;\n    color: var(--light-slate);\n  }\n\n  ul {\n    ${({ theme }) => theme.mixins.flexBetween};\n    padding: 0;\n    margin: 0;\n    list-style: none;\n\n    a {\n      padding: 10px;\n      svg {\n        width: 20px;\n        height: 20px;\n      }\n    }\n  }\n`;\n\nconst StyledCredit = styled.div`\n  color: var(--light-slate);\n  font-family: var(--font-mono);\n  font-size: var(--fz-xxs);\n  line-height: 1;\n\n  a {\n    padding: 10px;\n  }\n\n  .github-stats {\n    margin-top: 10px;\n\n    & > span {\n      display: inline-flex;\n      align-items: center;\n      margin: 0 7px;\n    }\n    svg {\n      display: inline-block;\n      margin-right: 5px;\n      width: 14px;\n      height: 14px;\n    }\n  }\n`;\n\nconst Footer = () => {\n  const [githubInfo, setGitHubInfo] = useState({\n    stars: null,\n    forks: null,\n  });\n\n  useEffect(() => {\n    if (process.env.NODE_ENV !== 'production') {\n      return;\n    }\n    fetch('https://api.github.com/repos/bchiang7/v4')\n      .then(response => response.json())\n      .then(json => {\n        const { stargazers_count, forks_count } = json;\n        setGitHubInfo({\n          stars: stargazers_count,\n          forks: forks_count,\n        });\n      })\n      .catch(e => console.error(e));\n  }, []);\n\n  return (\n    <StyledFooter>\n      <StyledSocialLinks>\n        <ul>\n          {socialMedia &&\n            socialMedia.map(({ name, url }, i) => (\n              <li key={i}>\n                <a href={url} aria-label={name}>\n                  <Icon name={name} />\n                </a>\n              </li>\n            ))}\n        </ul>\n      </StyledSocialLinks>\n\n      <StyledCredit tabindex=\"-1\">\n        <a href=\"https://github.com/bchiang7/v4\">\n          <div>Designed &amp; Built by Brittany Chiang</div>\n\n          {githubInfo.stars && githubInfo.forks && (\n            <div className=\"github-stats\">\n              <span>\n                <Icon name=\"Star\" />\n                <span>{githubInfo.stars.toLocaleString()}</span>\n              </span>\n              <span>\n                <Icon name=\"Fork\" />\n                <span>{githubInfo.forks.toLocaleString()}</span>\n              </span>\n            </div>\n          )}\n        </a>\n      </StyledCredit>\n    </StyledFooter>\n  );\n};\n\nFooter.propTypes = {\n  githubInfo: PropTypes.object,\n};\n\nexport default Footer;\n"
  },
  {
    "path": "src/components/head.js",
    "content": "import React from 'react';\nimport PropTypes from 'prop-types';\nimport { Helmet } from 'react-helmet';\nimport { useLocation } from '@reach/router';\nimport { useStaticQuery, graphql } from 'gatsby';\n\n// https://www.gatsbyjs.com/docs/add-seo-component/\n\nconst Head = ({ title, description, image }) => {\n  const { pathname } = useLocation();\n\n  const { site } = useStaticQuery(\n    graphql`\n      query {\n        site {\n          siteMetadata {\n            defaultTitle: title\n            defaultDescription: description\n            siteUrl\n            defaultImage: image\n            twitterUsername\n          }\n        }\n      }\n    `,\n  );\n\n  const {\n    defaultTitle,\n    defaultDescription,\n    siteUrl,\n    defaultImage,\n    twitterUsername,\n  } = site.siteMetadata;\n\n  const seo = {\n    title: title || defaultTitle,\n    description: description || defaultDescription,\n    image: `${siteUrl}${image || defaultImage}`,\n    url: `${siteUrl}${pathname}`,\n  };\n\n  return (\n    <Helmet title={title} defaultTitle={seo.title} titleTemplate={`%s | ${defaultTitle}`}>\n      <html lang=\"en\" />\n\n      <meta name=\"description\" content={seo.description} />\n      <meta name=\"image\" content={seo.image} />\n\n      <meta property=\"og:title\" content={seo.title} />\n      <meta property=\"og:description\" content={seo.description} />\n      <meta property=\"og:image\" content={seo.image} />\n      <meta property=\"og:url\" content={seo.url} />\n      <meta property=\"og:type\" content=\"website\" />\n\n      <meta name=\"twitter:card\" content=\"summary_large_image\" />\n      <meta name=\"twitter:creator\" content={twitterUsername} />\n      <meta name=\"twitter:title\" content={seo.title} />\n      <meta name=\"twitter:description\" content={seo.description} />\n      <meta name=\"twitter:image\" content={seo.image} />\n\n      <meta name=\"google-site-verification\" content=\"DCl7VAf9tcz6eD9gb67NfkNnJ1PKRNcg8qQiwpbx9Lk\" />\n    </Helmet>\n  );\n};\n\nexport default Head;\n\nHead.propTypes = {\n  title: PropTypes.string,\n  description: PropTypes.string,\n  image: PropTypes.string,\n};\n\nHead.defaultProps = {\n  title: null,\n  description: null,\n  image: null,\n};\n"
  },
  {
    "path": "src/components/icons/appstore.js",
    "content": "import React from 'react';\n\nconst IconAppStore = () => (\n  <svg\n    version=\"1.1\"\n    xmlns=\"http://www.w3.org/2000/svg\"\n    x=\"0px\"\n    y=\"0px\"\n    viewBox=\"0 0 512 512\"\n    xmlSpace=\"preserve\">\n    <title>Apple App Store</title>\n    <g>\n      <g>\n        <path\n          d=\"M407,0H105C47.103,0,0,47.103,0,105v302c0,57.897,47.103,105,105,105h302c57.897,0,105-47.103,105-105V105\n      C512,47.103,464.897,0,407,0z M482,407c0,41.355-33.645,75-75,75H105c-41.355,0-75-33.645-75-75V105c0-41.355,33.645-75,75-75h302\n      c41.355,0,75,33.645,75,75V407z\"\n        />\n      </g>\n    </g>\n    <g>\n      <g>\n        <path\n          d=\"M305.646,123.531c-1.729-6.45-5.865-11.842-11.648-15.18c-11.936-6.892-27.256-2.789-34.15,9.151L256,124.166\n      l-3.848-6.665c-6.893-11.937-22.212-16.042-34.15-9.151h-0.001c-11.938,6.893-16.042,22.212-9.15,34.151l18.281,31.664\n      L159.678,291H110.5c-13.785,0-25,11.215-25,25c0,13.785,11.215,25,25,25h189.86l-28.868-50h-54.079l85.735-148.498\n      C306.487,136.719,307.375,129.981,305.646,123.531z\"\n        />\n      </g>\n    </g>\n    <g>\n      <g>\n        <path\n          d=\"M401.5,291h-49.178l-55.907-96.834l-28.867,50l86.804,150.348c3.339,5.784,8.729,9.921,15.181,11.65\n      c2.154,0.577,4.339,0.863,6.511,0.863c4.332,0,8.608-1.136,12.461-3.361c11.938-6.893,16.042-22.213,9.149-34.15L381.189,341\n      H401.5c13.785,0,25-11.215,25-25C426.5,302.215,415.285,291,401.5,291z\"\n        />\n      </g>\n    </g>\n    <g>\n      <g>\n        <path\n          d=\"M119.264,361l-4.917,8.516c-6.892,11.938-2.787,27.258,9.151,34.15c3.927,2.267,8.219,3.345,12.458,3.344\n      c8.646,0,17.067-4.484,21.693-12.495L176.999,361H119.264z\"\n        />\n      </g>\n    </g>\n  </svg>\n);\n\nexport default IconAppStore;\n"
  },
  {
    "path": "src/components/icons/bookmark.js",
    "content": "import React from 'react';\n\nconst IconBookmark = () => (\n  <svg\n    xmlns=\"http://www.w3.org/2000/svg\"\n    viewBox=\"0 0 24 24\"\n    fill=\"none\"\n    stroke=\"currentColor\"\n    strokeWidth=\"1\"\n    strokeLinecap=\"round\"\n    strokeLinejoin=\"round\"\n    className=\"feather feather-bookmark\">\n    <title>Bookmark</title>\n    <path d=\"M19 21l-7-5-7 5V5a2 2 0 0 1 2-2h10a2 2 0 0 1 2 2z\"></path>\n  </svg>\n);\n\nexport default IconBookmark;\n"
  },
  {
    "path": "src/components/icons/codepen.js",
    "content": "import React from 'react';\n\nconst IconCodepen = () => (\n  <svg\n    xmlns=\"http://www.w3.org/2000/svg\"\n    role=\"img\"\n    viewBox=\"0 0 24 24\"\n    fill=\"none\"\n    stroke=\"currentColor\"\n    strokeWidth=\"2\"\n    strokeLinecap=\"round\"\n    strokeLinejoin=\"round\"\n    className=\"feather feather-codepen\">\n    <title>CodePen</title>\n    <polygon points=\"12 2 22 8.5 22 15.5 12 22 2 15.5 2 8.5 12 2\"></polygon>\n    <line x1=\"12\" y1=\"22\" x2=\"12\" y2=\"15.5\"></line>\n    <polyline points=\"22 8.5 12 15.5 2 8.5\"></polyline>\n    <polyline points=\"2 15.5 12 8.5 22 15.5\"></polyline>\n    <line x1=\"12\" y1=\"2\" x2=\"12\" y2=\"8.5\"></line>\n  </svg>\n);\n\nexport default IconCodepen;\n"
  },
  {
    "path": "src/components/icons/external.js",
    "content": "import React from 'react';\n\nconst IconExternal = () => (\n  <svg\n    xmlns=\"http://www.w3.org/2000/svg\"\n    role=\"img\"\n    viewBox=\"0 0 24 24\"\n    fill=\"none\"\n    stroke=\"currentColor\"\n    strokeWidth=\"2\"\n    strokeLinecap=\"round\"\n    strokeLinejoin=\"round\"\n    className=\"feather feather-external-link\">\n    <title>External Link</title>\n    <path d=\"M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6\"></path>\n    <polyline points=\"15 3 21 3 21 9\"></polyline>\n    <line x1=\"10\" y1=\"14\" x2=\"21\" y2=\"3\"></line>\n  </svg>\n);\n\nexport default IconExternal;\n"
  },
  {
    "path": "src/components/icons/folder.js",
    "content": "import React from 'react';\n\nconst IconFolder = () => (\n  <svg\n    xmlns=\"http://www.w3.org/2000/svg\"\n    role=\"img\"\n    viewBox=\"0 0 24 24\"\n    fill=\"none\"\n    stroke=\"currentColor\"\n    strokeWidth=\"1\"\n    strokeLinecap=\"round\"\n    strokeLinejoin=\"round\"\n    className=\"feather feather-folder\">\n    <title>Folder</title>\n    <path d=\"M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z\"></path>\n  </svg>\n);\n\nexport default IconFolder;\n"
  },
  {
    "path": "src/components/icons/fork.js",
    "content": "import React from 'react';\n\nconst IconFork = () => (\n  <svg\n    viewBox=\"0 0 24 24\"\n    fill=\"none\"\n    stroke=\"currentColor\"\n    strokeWidth=\"2\"\n    strokeLinecap=\"round\"\n    strokeLinejoin=\"round\"\n    className=\"feather feather-git-branch\">\n    <title>Git Fork</title>\n    <line x1=\"6\" y1=\"3\" x2=\"6\" y2=\"15\"></line>\n    <circle cx=\"18\" cy=\"6\" r=\"3\"></circle>\n    <circle cx=\"6\" cy=\"18\" r=\"3\"></circle>\n    <path d=\"M18 9a9 9 0 0 1-9 9\"></path>\n  </svg>\n);\n\nexport default IconFork;\n"
  },
  {
    "path": "src/components/icons/github.js",
    "content": "import React from 'react';\n\nconst IconGitHub = () => (\n  <svg\n    xmlns=\"http://www.w3.org/2000/svg\"\n    role=\"img\"\n    viewBox=\"0 0 24 24\"\n    fill=\"none\"\n    stroke=\"currentColor\"\n    strokeWidth=\"2\"\n    strokeLinecap=\"round\"\n    strokeLinejoin=\"round\"\n    className=\"feather feather-github\">\n    <title>GitHub</title>\n    <path d=\"M9 19c-5 1.5-5-2.5-7-3m14 6v-3.87a3.37 3.37 0 0 0-.94-2.61c3.14-.35 6.44-1.54 6.44-7A5.44 5.44 0 0 0 20 4.77 5.07 5.07 0 0 0 19.91 1S18.73.65 16 2.48a13.38 13.38 0 0 0-7 0C6.27.65 5.09 1 5.09 1A5.07 5.07 0 0 0 5 4.77a5.44 5.44 0 0 0-1.5 3.78c0 5.42 3.3 6.61 6.44 7A3.37 3.37 0 0 0 9 18.13V22\"></path>\n  </svg>\n);\n\nexport default IconGitHub;\n"
  },
  {
    "path": "src/components/icons/hex.js",
    "content": "import React from 'react';\n\nconst IconHex = () => (\n  <svg id=\"hex\" xmlns=\"http://www.w3.org/2000/svg\" role=\"img\" viewBox=\"0 0 84 96\">\n    <title>Hexagon</title>\n    <g transform=\"translate(-8.000000, -2.000000)\">\n      <g transform=\"translate(11.000000, 5.000000)\">\n        <polygon\n          stroke=\"currentColor\"\n          strokeWidth=\"5\"\n          strokeLinecap=\"round\"\n          strokeLinejoin=\"round\"\n          points=\"39 0 0 22 0 67 39 90 78 68 78 23\"\n          fill=\"currentColor\"\n        />\n      </g>\n    </g>\n  </svg>\n);\n\nexport default IconHex;\n"
  },
  {
    "path": "src/components/icons/icon.js",
    "content": "import React from 'react';\nimport PropTypes from 'prop-types';\nimport {\n  IconAppStore,\n  IconBookmark,\n  IconCodepen,\n  IconExternal,\n  IconFolder,\n  IconFork,\n  IconGitHub,\n  IconInstagram,\n  IconLinkedin,\n  IconLoader,\n  IconLogo,\n  IconPlayStore,\n  IconStar,\n  IconTwitter,\n} from '@components/icons';\n\nconst Icon = ({ name }) => {\n  switch (name) {\n    case 'AppStore':\n      return <IconAppStore />;\n    case 'Bookmark':\n      return <IconBookmark />;\n    case 'Codepen':\n      return <IconCodepen />;\n    case 'External':\n      return <IconExternal />;\n    case 'Folder':\n      return <IconFolder />;\n    case 'Fork':\n      return <IconFork />;\n    case 'GitHub':\n      return <IconGitHub />;\n    case 'Instagram':\n      return <IconInstagram />;\n    case 'Linkedin':\n      return <IconLinkedin />;\n    case 'Loader':\n      return <IconLoader />;\n    case 'Logo':\n      return <IconLogo />;\n    case 'PlayStore':\n      return <IconPlayStore />;\n    case 'Star':\n      return <IconStar />;\n    case 'Twitter':\n      return <IconTwitter />;\n    default:\n      return <IconExternal />;\n  }\n};\n\nIcon.propTypes = {\n  name: PropTypes.string.isRequired,\n};\n\nexport default Icon;\n"
  },
  {
    "path": "src/components/icons/index.js",
    "content": "export { default as IconAppStore } from './appstore';\nexport { default as IconBookmark } from './bookmark';\nexport { default as IconCodepen } from './codepen';\nexport { default as IconExternal } from './external';\nexport { default as IconFolder } from './folder';\nexport { default as IconFork } from './fork';\nexport { default as Icon } from './icon';\nexport { default as IconGitHub } from './github';\nexport { default as IconHex } from './hex';\nexport { default as IconInstagram } from './instagram';\nexport { default as IconLinkedin } from './linkedin';\nexport { default as IconLoader } from './loader';\nexport { default as IconLogo } from './logo';\nexport { default as IconPlayStore } from './playstore';\nexport { default as IconStar } from './star';\nexport { default as IconTwitter } from './twitter';\n"
  },
  {
    "path": "src/components/icons/instagram.js",
    "content": "import React from 'react';\n\nconst IconInstagram = () => (\n  <svg\n    xmlns=\"http://www.w3.org/2000/svg\"\n    role=\"img\"\n    viewBox=\"0 0 24 24\"\n    fill=\"none\"\n    stroke=\"currentColor\"\n    strokeWidth=\"2\"\n    strokeLinecap=\"round\"\n    strokeLinejoin=\"round\"\n    className=\"feather feather-instagram\">\n    <title>Instagram</title>\n    <rect x=\"2\" y=\"2\" width=\"20\" height=\"20\" rx=\"5\" ry=\"5\"></rect>\n    <path d=\"M16 11.37A4 4 0 1 1 12.63 8 4 4 0 0 1 16 11.37z\"></path>\n    <line x1=\"17.5\" y1=\"6.5\" x2=\"17.51\" y2=\"6.5\"></line>\n  </svg>\n);\n\nexport default IconInstagram;\n"
  },
  {
    "path": "src/components/icons/linkedin.js",
    "content": "import React from 'react';\n\nconst IconLinkedin = () => (\n  <svg\n    xmlns=\"http://www.w3.org/2000/svg\"\n    role=\"img\"\n    viewBox=\"0 0 24 24\"\n    fill=\"none\"\n    stroke=\"currentColor\"\n    strokeWidth=\"2\"\n    strokeLinecap=\"round\"\n    strokeLinejoin=\"round\"\n    className=\"feather feather-linkedin\">\n    <title>LinkedIn</title>\n    <path d=\"M16 8a6 6 0 0 1 6 6v7h-4v-7a2 2 0 0 0-2-2 2 2 0 0 0-2 2v7h-4v-7a6 6 0 0 1 6-6z\"></path>\n    <rect x=\"2\" y=\"9\" width=\"4\" height=\"12\"></rect>\n    <circle cx=\"4\" cy=\"4\" r=\"2\"></circle>\n  </svg>\n);\n\nexport default IconLinkedin;\n"
  },
  {
    "path": "src/components/icons/loader.js",
    "content": "import React from 'react';\n\nconst IconLoader = () => (\n  <svg id=\"logo\" xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 100 100\">\n    <title>Loader Logo</title>\n    <g>\n      <g id=\"B\" transform=\"translate(11.000000, 5.000000)\">\n        <path\n          d=\"M45.691667,45.15 C48.591667,46.1 50.691667,48.95 50.691667,52.2 C50.691667,57.95 46.691667,61 40.291667,61 L28.541667,61 L28.541667,30.3 L39.291667,30.3 C45.691667,30.3 49.691667,33.15 49.691667,38.65 C49.691667,41.95 47.941667,44.35 45.691667,45.15 Z M33.591667,43.2 L39.241667,43.2 C42.791667,43.2 44.691667,41.85 44.691667,38.95 C44.691667,36.05 42.791667,34.8 39.241667,34.8 L33.591667,34.8 L33.591667,43.2 Z M33.591667,47.5 L33.591667,56.5 L40.191667,56.5 C43.691667,56.5 45.591667,54.75 45.591667,52 C45.591667,49.2 43.691667,47.5 40.191667,47.5 L33.591667,47.5 Z\"\n          fill=\"currentColor\"\n        />\n      </g>\n      <path\n        stroke=\"currentColor\"\n        strokeWidth=\"5\"\n        strokeLinecap=\"round\"\n        strokeLinejoin=\"round\"\n        d=\"M 50, 5\n                  L 11, 27\n                  L 11, 72\n                  L 50, 95\n                  L 89, 73\n                  L 89, 28 z\"\n      />\n    </g>\n  </svg>\n);\n\nexport default IconLoader;\n"
  },
  {
    "path": "src/components/icons/logo.js",
    "content": "import React from 'react';\n\nconst IconLogo = () => (\n  <svg id=\"logo\" xmlns=\"http://www.w3.org/2000/svg\" role=\"img\" viewBox=\"0 0 84 96\">\n    <title>Logo</title>\n    <g transform=\"translate(-8.000000, -2.000000)\">\n      <g transform=\"translate(11.000000, 5.000000)\">\n        <polygon\n          id=\"Shape\"\n          stroke=\"currentColor\"\n          strokeWidth=\"5\"\n          strokeLinecap=\"round\"\n          strokeLinejoin=\"round\"\n          points=\"39 0 0 22 0 67 39 90 78 68 78 23\"\n        />\n        <path\n          d=\"M45.691667,45.15 C48.591667,46.1 50.691667,48.95 50.691667,52.2 C50.691667,57.95 46.691667,61 40.291667,61 L28.541667,61 L28.541667,30.3 L39.291667,30.3 C45.691667,30.3 49.691667,33.15 49.691667,38.65 C49.691667,41.95 47.941667,44.35 45.691667,45.15 Z M33.591667,43.2 L39.241667,43.2 C42.791667,43.2 44.691667,41.85 44.691667,38.95 C44.691667,36.05 42.791667,34.8 39.241667,34.8 L33.591667,34.8 L33.591667,43.2 Z M33.591667,47.5 L33.591667,56.5 L40.191667,56.5 C43.691667,56.5 45.591667,54.75 45.591667,52 C45.591667,49.2 43.691667,47.5 40.191667,47.5 L33.591667,47.5 Z\"\n          fill=\"currentColor\"\n        />\n      </g>\n    </g>\n  </svg>\n);\n\nexport default IconLogo;\n"
  },
  {
    "path": "src/components/icons/playstore.js",
    "content": "import React from 'react';\n\nconst IconPlayStore = () => (\n  <svg xmlns=\"http://www.w3.org/2000/svg\" role=\"img\" x=\"0px\" y=\"0px\" viewBox=\"0 0 512.001 512.001\">\n    <title>Google Play Store</title>\n    <path\n      d=\"M464.252,212.09L99.624,8.07C84.247-1.873,64.754-2.691,48.574,5.967C32.183,14.74,22,31.737,22,50.329v411.342\n      c0,18.592,10.183,35.59,26.573,44.361c16.097,8.617,35.593,7.891,51.052-2.101l364.628-204.022\n      c16.121-9.02,25.747-25.435,25.747-43.908C490,237.527,480.374,221.111,464.252,212.09z M341.677,181.943l-50.339,50.339\n      L113.108,54.051L341.677,181.943z M55.544,467.323V44.676L267.621,256L55.544,467.323z M113.108,457.949l178.232-178.231\n      l50.339,50.339L113.108,457.949z M447.874,270.637l-75.779,42.401l-57.038-57.037l57.037-57.037l75.778,42.4\n      c7.746,4.335,8.583,11.68,8.583,14.637C456.455,258.958,455.62,266.302,447.874,270.637z\"\n    />\n  </svg>\n);\n\nexport default IconPlayStore;\n"
  },
  {
    "path": "src/components/icons/star.js",
    "content": "import React from 'react';\n\nconst IconStar = () => (\n  <svg\n    viewBox=\"0 0 24 24\"\n    fill=\"none\"\n    stroke=\"currentColor\"\n    strokeWidth=\"2\"\n    strokeLinecap=\"round\"\n    strokeLinejoin=\"round\"\n    className=\"feather feather-star\">\n    <title>Star</title>\n    <polygon points=\"12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2\" />\n  </svg>\n);\n\nexport default IconStar;\n"
  },
  {
    "path": "src/components/icons/twitter.js",
    "content": "import React from 'react';\n\nconst IconTwitter = () => (\n  <svg\n    xmlns=\"http://www.w3.org/2000/svg\"\n    role=\"img\"\n    viewBox=\"0 0 24 24\"\n    fill=\"none\"\n    stroke=\"currentColor\"\n    strokeWidth=\"2\"\n    strokeLinecap=\"round\"\n    strokeLinejoin=\"round\"\n    className=\"feather feather-twitter\">\n    <title>Twitter</title>\n    <path d=\"M23 3a10.9 10.9 0 0 1-3.14 1.53 4.48 4.48 0 0 0-7.86 3v1A10.66 10.66 0 0 1 3 4s-4 9 5 13a11.64 11.64 0 0 1-7 2c9 5 20 0 20-11.5a4.5 4.5 0 0 0-.08-.83A7.72 7.72 0 0 0 23 3z\"></path>\n  </svg>\n);\n\nexport default IconTwitter;\n"
  },
  {
    "path": "src/components/index.js",
    "content": "export { default as Head } from './head';\nexport { default as Layout } from './layout';\nexport { default as Loader } from './loader';\nexport { default as Nav } from './nav';\nexport { default as Menu } from './menu';\nexport { default as Side } from './side';\nexport { default as Social } from './social';\nexport { default as Email } from './email';\nexport { default as Footer } from './footer';\nexport { default as Hero } from './sections/hero';\nexport { default as About } from './sections/about';\nexport { default as Jobs } from './sections/jobs';\nexport { default as Featured } from './sections/featured';\nexport { default as Projects } from './sections/projects';\nexport { default as Contact } from './sections/contact';\n"
  },
  {
    "path": "src/components/layout.js",
    "content": "import React, { useState, useEffect } from 'react';\nimport PropTypes from 'prop-types';\nimport styled, { ThemeProvider } from 'styled-components';\nimport { Head, Loader, Nav, Social, Email, Footer } from '@components';\nimport { GlobalStyle, theme } from '@styles';\n\nconst StyledContent = styled.div`\n  display: flex;\n  flex-direction: column;\n  min-height: 100vh;\n`;\n\nconst Layout = ({ children, location }) => {\n  const isHome = location.pathname === '/';\n  const [isLoading, setIsLoading] = useState(isHome);\n\n  // Sets target=\"_blank\" rel=\"noopener noreferrer\" on external links\n  const handleExternalLinks = () => {\n    const allLinks = Array.from(document.querySelectorAll('a'));\n    if (allLinks.length > 0) {\n      allLinks.forEach(link => {\n        if (link.host !== window.location.host) {\n          link.setAttribute('rel', 'noopener noreferrer');\n          link.setAttribute('target', '_blank');\n        }\n      });\n    }\n  };\n\n  useEffect(() => {\n    if (isLoading) {\n      return;\n    }\n\n    if (location.hash) {\n      const id = location.hash.substring(1); // location.hash without the '#'\n      setTimeout(() => {\n        const el = document.getElementById(id);\n        if (el) {\n          el.scrollIntoView();\n          el.focus();\n        }\n      }, 0);\n    }\n\n    handleExternalLinks();\n  }, [isLoading]);\n\n  return (\n    <>\n      <Head />\n\n      <div id=\"root\">\n        <ThemeProvider theme={theme}>\n          <GlobalStyle />\n\n          <a className=\"skip-to-content\" href=\"#content\">\n            Skip to Content\n          </a>\n\n          {isLoading && isHome ? (\n            <Loader finishLoading={() => setIsLoading(false)} />\n          ) : (\n            <StyledContent>\n              <Nav isHome={isHome} />\n              <Social isHome={isHome} />\n              <Email isHome={isHome} />\n\n              <div id=\"content\">\n                {children}\n                <Footer />\n              </div>\n            </StyledContent>\n          )}\n        </ThemeProvider>\n      </div>\n    </>\n  );\n};\n\nLayout.propTypes = {\n  children: PropTypes.node.isRequired,\n  location: PropTypes.object.isRequired,\n};\n\nexport default Layout;\n"
  },
  {
    "path": "src/components/loader.js",
    "content": "import React, { useState, useEffect } from 'react';\nimport { Helmet } from 'react-helmet';\nimport PropTypes from 'prop-types';\nimport anime from 'animejs';\nimport styled from 'styled-components';\nimport { IconLoader } from '@components/icons';\n\nconst StyledLoader = styled.div`\n  ${({ theme }) => theme.mixins.flexCenter};\n  position: fixed;\n  top: 0;\n  bottom: 0;\n  left: 0;\n  right: 0;\n  width: 100%;\n  height: 100%;\n  background-color: var(--dark-navy);\n  z-index: 99;\n\n  .logo-wrapper {\n    width: max-content;\n    max-width: 100px;\n    transition: var(--transition);\n    opacity: ${props => (props.isMounted ? 1 : 0)};\n    svg {\n      display: block;\n      width: 100%;\n      height: 100%;\n      margin: 0 auto;\n      fill: none;\n      user-select: none;\n      #B {\n        opacity: 0;\n      }\n    }\n  }\n`;\n\nconst Loader = ({ finishLoading }) => {\n  const [isMounted, setIsMounted] = useState(false);\n\n  const animate = () => {\n    const loader = anime.timeline({\n      complete: () => finishLoading(),\n    });\n\n    loader\n      .add({\n        targets: '#logo path',\n        delay: 300,\n        duration: 1500,\n        easing: 'easeInOutQuart',\n        strokeDashoffset: [anime.setDashoffset, 0],\n      })\n      .add({\n        targets: '#logo #B',\n        duration: 700,\n        easing: 'easeInOutQuart',\n        opacity: 1,\n      })\n      .add({\n        targets: '#logo',\n        delay: 500,\n        duration: 300,\n        easing: 'easeInOutQuart',\n        opacity: 0,\n        scale: 0.1,\n      })\n      .add({\n        targets: '.loader',\n        duration: 200,\n        easing: 'easeInOutQuart',\n        opacity: 0,\n        zIndex: -1,\n      });\n  };\n\n  useEffect(() => {\n    const timeout = setTimeout(() => setIsMounted(true), 10);\n    animate();\n    return () => clearTimeout(timeout);\n  }, []);\n\n  return (\n    <StyledLoader className=\"loader\" isMounted={isMounted}>\n      <Helmet bodyAttributes={{ class: `hidden` }} />\n\n      <div className=\"logo-wrapper\">\n        <IconLoader />\n      </div>\n    </StyledLoader>\n  );\n};\n\nLoader.propTypes = {\n  finishLoading: PropTypes.func.isRequired,\n};\n\nexport default Loader;\n"
  },
  {
    "path": "src/components/menu.js",
    "content": "import React, { useState, useEffect, useRef } from 'react';\nimport { Helmet } from 'react-helmet';\nimport { Link } from 'gatsby';\nimport styled from 'styled-components';\nimport { navLinks } from '@config';\nimport { KEY_CODES } from '@utils';\nimport { useOnClickOutside } from '@hooks';\n\nconst StyledMenu = styled.div`\n  display: none;\n\n  @media (max-width: 768px) {\n    display: block;\n  }\n`;\n\nconst StyledHamburgerButton = styled.button`\n  display: none;\n\n  @media (max-width: 768px) {\n    ${({ theme }) => theme.mixins.flexCenter};\n    position: relative;\n    z-index: 10;\n    margin-right: -15px;\n    padding: 15px;\n    border: 0;\n    background-color: transparent;\n    color: inherit;\n    text-transform: none;\n    transition-timing-function: linear;\n    transition-duration: 0.15s;\n    transition-property: opacity, filter;\n  }\n\n  .ham-box {\n    display: inline-block;\n    position: relative;\n    width: var(--hamburger-width);\n    height: 24px;\n  }\n\n  .ham-box-inner {\n    position: absolute;\n    top: 50%;\n    right: 0;\n    width: var(--hamburger-width);\n    height: 2px;\n    border-radius: var(--border-radius);\n    background-color: var(--green);\n    transition-duration: 0.22s;\n    transition-property: transform;\n    transition-delay: ${props => (props.menuOpen ? `0.12s` : `0s`)};\n    transform: rotate(${props => (props.menuOpen ? `225deg` : `0deg`)});\n    transition-timing-function: cubic-bezier(\n      ${props => (props.menuOpen ? `0.215, 0.61, 0.355, 1` : `0.55, 0.055, 0.675, 0.19`)}\n    );\n    &:before,\n    &:after {\n      content: '';\n      display: block;\n      position: absolute;\n      left: auto;\n      right: 0;\n      width: var(--hamburger-width);\n      height: 2px;\n      border-radius: 4px;\n      background-color: var(--green);\n      transition-timing-function: ease;\n      transition-duration: 0.15s;\n      transition-property: transform;\n    }\n    &:before {\n      width: ${props => (props.menuOpen ? `100%` : `120%`)};\n      top: ${props => (props.menuOpen ? `0` : `-10px`)};\n      opacity: ${props => (props.menuOpen ? 0 : 1)};\n      transition: ${({ menuOpen }) =>\n    menuOpen ? 'var(--ham-before-active)' : 'var(--ham-before)'};\n    }\n    &:after {\n      width: ${props => (props.menuOpen ? `100%` : `80%`)};\n      bottom: ${props => (props.menuOpen ? `0` : `-10px`)};\n      transform: rotate(${props => (props.menuOpen ? `-90deg` : `0`)});\n      transition: ${({ menuOpen }) => (menuOpen ? 'var(--ham-after-active)' : 'var(--ham-after)')};\n    }\n  }\n`;\n\nconst StyledSidebar = styled.aside`\n  display: none;\n\n  @media (max-width: 768px) {\n    ${({ theme }) => theme.mixins.flexCenter};\n    position: fixed;\n    top: 0;\n    bottom: 0;\n    right: 0;\n    padding: 50px 10px;\n    width: min(75vw, 400px);\n    height: 100vh;\n    outline: 0;\n    background-color: var(--light-navy);\n    box-shadow: -10px 0px 30px -15px var(--navy-shadow);\n    z-index: 9;\n    transform: translateX(${props => (props.menuOpen ? 0 : 100)}vw);\n    visibility: ${props => (props.menuOpen ? 'visible' : 'hidden')};\n    transition: var(--transition);\n  }\n\n  nav {\n    ${({ theme }) => theme.mixins.flexBetween};\n    width: 100%;\n    flex-direction: column;\n    color: var(--lightest-slate);\n    font-family: var(--font-mono);\n    text-align: center;\n  }\n\n  ol {\n    padding: 0;\n    margin: 0;\n    list-style: none;\n    width: 100%;\n\n    li {\n      position: relative;\n      margin: 0 auto 20px;\n      counter-increment: item 1;\n      font-size: clamp(var(--fz-sm), 4vw, var(--fz-lg));\n\n      @media (max-width: 600px) {\n        margin: 0 auto 10px;\n      }\n\n      &:before {\n        content: '0' counter(item) '.';\n        display: block;\n        margin-bottom: 5px;\n        color: var(--green);\n        font-size: var(--fz-sm);\n      }\n    }\n\n    a {\n      ${({ theme }) => theme.mixins.link};\n      width: 100%;\n      padding: 3px 20px 20px;\n    }\n  }\n\n  .resume-link {\n    ${({ theme }) => theme.mixins.bigButton};\n    padding: 18px 50px;\n    margin: 10% auto 0;\n    width: max-content;\n  }\n`;\n\nconst Menu = () => {\n  const [menuOpen, setMenuOpen] = useState(false);\n\n  const toggleMenu = () => setMenuOpen(!menuOpen);\n\n  const buttonRef = useRef(null);\n  const navRef = useRef(null);\n\n  let menuFocusables;\n  let firstFocusableEl;\n  let lastFocusableEl;\n\n  const setFocusables = () => {\n    menuFocusables = [buttonRef.current, ...Array.from(navRef.current.querySelectorAll('a'))];\n    firstFocusableEl = menuFocusables[0];\n    lastFocusableEl = menuFocusables[menuFocusables.length - 1];\n  };\n\n  const handleBackwardTab = e => {\n    if (document.activeElement === firstFocusableEl) {\n      e.preventDefault();\n      lastFocusableEl.focus();\n    }\n  };\n\n  const handleForwardTab = e => {\n    if (document.activeElement === lastFocusableEl) {\n      e.preventDefault();\n      firstFocusableEl.focus();\n    }\n  };\n\n  const onKeyDown = e => {\n    switch (e.key) {\n      case KEY_CODES.ESCAPE:\n      case KEY_CODES.ESCAPE_IE11: {\n        setMenuOpen(false);\n        break;\n      }\n\n      case KEY_CODES.TAB: {\n        if (menuFocusables && menuFocusables.length === 1) {\n          e.preventDefault();\n          break;\n        }\n        if (e.shiftKey) {\n          handleBackwardTab(e);\n        } else {\n          handleForwardTab(e);\n        }\n        break;\n      }\n\n      default: {\n        break;\n      }\n    }\n  };\n\n  const onResize = e => {\n    if (e.currentTarget.innerWidth > 768) {\n      setMenuOpen(false);\n    }\n  };\n\n  useEffect(() => {\n    document.addEventListener('keydown', onKeyDown);\n    window.addEventListener('resize', onResize);\n\n    setFocusables();\n\n    return () => {\n      document.removeEventListener('keydown', onKeyDown);\n      window.removeEventListener('resize', onResize);\n    };\n  }, []);\n\n  const wrapperRef = useRef();\n  useOnClickOutside(wrapperRef, () => setMenuOpen(false));\n\n  return (\n    <StyledMenu>\n      <Helmet>\n        <body className={menuOpen ? 'blur' : ''} />\n      </Helmet>\n\n      <div ref={wrapperRef}>\n        <StyledHamburgerButton\n          onClick={toggleMenu}\n          menuOpen={menuOpen}\n          ref={buttonRef}\n          aria-label=\"Menu\">\n          <div className=\"ham-box\">\n            <div className=\"ham-box-inner\" />\n          </div>\n        </StyledHamburgerButton>\n\n        <StyledSidebar menuOpen={menuOpen} aria-hidden={!menuOpen} tabIndex={menuOpen ? 1 : -1}>\n          <nav ref={navRef}>\n            {navLinks && (\n              <ol>\n                {navLinks.map(({ url, name }, i) => (\n                  <li key={i}>\n                    <Link to={url} onClick={() => setMenuOpen(false)}>\n                      {name}\n                    </Link>\n                  </li>\n                ))}\n              </ol>\n            )}\n\n            <a href=\"/resume.pdf\" className=\"resume-link\">\n              Resume\n            </a>\n          </nav>\n        </StyledSidebar>\n      </div>\n    </StyledMenu>\n  );\n};\n\nexport default Menu;\n"
  },
  {
    "path": "src/components/nav.js",
    "content": "import React, { useState, useEffect } from 'react';\nimport { Link } from 'gatsby';\nimport PropTypes from 'prop-types';\nimport { CSSTransition, TransitionGroup } from 'react-transition-group';\nimport styled, { css } from 'styled-components';\nimport { navLinks } from '@config';\nimport { loaderDelay } from '@utils';\nimport { useScrollDirection, usePrefersReducedMotion } from '@hooks';\nimport { Menu } from '@components';\nimport { IconLogo, IconHex } from '@components/icons';\n\nconst StyledHeader = styled.header`\n  ${({ theme }) => theme.mixins.flexBetween};\n  position: fixed;\n  top: 0;\n  z-index: 11;\n  padding: 0px 50px;\n  width: 100%;\n  height: var(--nav-height);\n  background-color: rgba(10, 25, 47, 0.85);\n  filter: none !important;\n  pointer-events: auto !important;\n  user-select: auto !important;\n  backdrop-filter: blur(10px);\n  transition: var(--transition);\n\n  @media (max-width: 1080px) {\n    padding: 0 40px;\n  }\n  @media (max-width: 768px) {\n    padding: 0 25px;\n  }\n\n  @media (prefers-reduced-motion: no-preference) {\n    ${props =>\n    props.scrollDirection === 'up' &&\n      !props.scrolledToTop &&\n      css`\n        height: var(--nav-scroll-height);\n        transform: translateY(0px);\n        background-color: rgba(10, 25, 47, 0.85);\n        box-shadow: 0 10px 30px -10px var(--navy-shadow);\n      `};\n\n    ${props =>\n    props.scrollDirection === 'down' &&\n      !props.scrolledToTop &&\n      css`\n        height: var(--nav-scroll-height);\n        transform: translateY(calc(var(--nav-scroll-height) * -1));\n        box-shadow: 0 10px 30px -10px var(--navy-shadow);\n      `};\n  }\n`;\n\nconst StyledNav = styled.nav`\n  ${({ theme }) => theme.mixins.flexBetween};\n  position: relative;\n  width: 100%;\n  color: var(--lightest-slate);\n  font-family: var(--font-mono);\n  counter-reset: item 0;\n  z-index: 12;\n\n  .logo {\n    ${({ theme }) => theme.mixins.flexCenter};\n\n    a {\n      color: var(--green);\n      width: 42px;\n      height: 42px;\n      position: relative;\n      z-index: 1;\n\n      .hex-container {\n        position: absolute;\n        top: 0;\n        left: 0;\n        z-index: -1;\n        @media (prefers-reduced-motion: no-preference) {\n          transition: var(--transition);\n        }\n      }\n\n      .logo-container {\n        position: relative;\n        z-index: 1;\n        svg {\n          fill: none;\n          user-select: none;\n          @media (prefers-reduced-motion: no-preference) {\n            transition: var(--transition);\n          }\n          polygon {\n            fill: var(--navy);\n          }\n        }\n      }\n\n      &:hover,\n      &:focus {\n        outline: 0;\n        transform: translate(-4px, -4px);\n        .hex-container {\n          transform: translate(4px, 3px);\n        }\n      }\n    }\n  }\n`;\n\nconst StyledLinks = styled.div`\n  display: flex;\n  align-items: center;\n\n  @media (max-width: 768px) {\n    display: none;\n  }\n\n  ol {\n    ${({ theme }) => theme.mixins.flexBetween};\n    padding: 0;\n    margin: 0;\n    list-style: none;\n\n    li {\n      margin: 0 5px;\n      position: relative;\n      counter-increment: item 1;\n      font-size: var(--fz-xs);\n\n      a {\n        padding: 10px;\n\n        &:before {\n          content: '0' counter(item) '.';\n          margin-right: 5px;\n          color: var(--green);\n          font-size: var(--fz-xxs);\n          text-align: right;\n        }\n      }\n    }\n  }\n\n  .resume-button {\n    ${({ theme }) => theme.mixins.smallButton};\n    margin-left: 15px;\n    font-size: var(--fz-xs);\n  }\n`;\n\nconst Nav = ({ isHome }) => {\n  const [isMounted, setIsMounted] = useState(!isHome);\n  const scrollDirection = useScrollDirection('down');\n  const [scrolledToTop, setScrolledToTop] = useState(true);\n  const prefersReducedMotion = usePrefersReducedMotion();\n\n  const handleScroll = () => {\n    setScrolledToTop(window.pageYOffset < 50);\n  };\n\n  useEffect(() => {\n    if (prefersReducedMotion) {\n      return;\n    }\n\n    const timeout = setTimeout(() => {\n      setIsMounted(true);\n    }, 100);\n\n    window.addEventListener('scroll', handleScroll);\n\n    return () => {\n      clearTimeout(timeout);\n      window.removeEventListener('scroll', handleScroll);\n    };\n  }, []);\n\n  const timeout = isHome ? loaderDelay : 0;\n  const fadeClass = isHome ? 'fade' : '';\n  const fadeDownClass = isHome ? 'fadedown' : '';\n\n  const Logo = (\n    <div className=\"logo\" tabIndex=\"-1\">\n      {isHome ? (\n        <a href=\"/\" aria-label=\"home\">\n          <div className=\"hex-container\">\n            <IconHex />\n          </div>\n          <div className=\"logo-container\">\n            <IconLogo />\n          </div>\n        </a>\n      ) : (\n        <Link to=\"/\" aria-label=\"home\">\n          <div className=\"hex-container\">\n            <IconHex />\n          </div>\n          <div className=\"logo-container\">\n            <IconLogo />\n          </div>\n        </Link>\n      )}\n    </div>\n  );\n\n  const ResumeLink = (\n    <a className=\"resume-button\" href=\"/resume.pdf\" target=\"_blank\" rel=\"noopener noreferrer\">\n      Resume\n    </a>\n  );\n\n  return (\n    <StyledHeader scrollDirection={scrollDirection} scrolledToTop={scrolledToTop}>\n      <StyledNav>\n        {prefersReducedMotion ? (\n          <>\n            {Logo}\n\n            <StyledLinks>\n              <ol>\n                {navLinks &&\n                  navLinks.map(({ url, name }, i) => (\n                    <li key={i}>\n                      <Link to={url}>{name}</Link>\n                    </li>\n                  ))}\n              </ol>\n              <div>{ResumeLink}</div>\n            </StyledLinks>\n\n            <Menu />\n          </>\n        ) : (\n          <>\n            <TransitionGroup component={null}>\n              {isMounted && (\n                <CSSTransition classNames={fadeClass} timeout={timeout}>\n                  <>{Logo}</>\n                </CSSTransition>\n              )}\n            </TransitionGroup>\n\n            <StyledLinks>\n              <ol>\n                <TransitionGroup component={null}>\n                  {isMounted &&\n                    navLinks &&\n                    navLinks.map(({ url, name }, i) => (\n                      <CSSTransition key={i} classNames={fadeDownClass} timeout={timeout}>\n                        <li key={i} style={{ transitionDelay: `${isHome ? i * 100 : 0}ms` }}>\n                          <Link to={url}>{name}</Link>\n                        </li>\n                      </CSSTransition>\n                    ))}\n                </TransitionGroup>\n              </ol>\n\n              <TransitionGroup component={null}>\n                {isMounted && (\n                  <CSSTransition classNames={fadeDownClass} timeout={timeout}>\n                    <div style={{ transitionDelay: `${isHome ? navLinks.length * 100 : 0}ms` }}>\n                      {ResumeLink}\n                    </div>\n                  </CSSTransition>\n                )}\n              </TransitionGroup>\n            </StyledLinks>\n\n            <TransitionGroup component={null}>\n              {isMounted && (\n                <CSSTransition classNames={fadeClass} timeout={timeout}>\n                  <Menu />\n                </CSSTransition>\n              )}\n            </TransitionGroup>\n          </>\n        )}\n      </StyledNav>\n    </StyledHeader>\n  );\n};\n\nNav.propTypes = {\n  isHome: PropTypes.bool,\n};\n\nexport default Nav;\n"
  },
  {
    "path": "src/components/sections/about.js",
    "content": "import React, { useEffect, useRef } from 'react';\nimport { StaticImage } from 'gatsby-plugin-image';\nimport styled from 'styled-components';\nimport { srConfig } from '@config';\nimport sr from '@utils/sr';\nimport { usePrefersReducedMotion } from '@hooks';\n\nconst StyledAboutSection = styled.section`\n  max-width: 900px;\n\n  .inner {\n    display: grid;\n    grid-template-columns: 3fr 2fr;\n    grid-gap: 50px;\n\n    @media (max-width: 768px) {\n      display: block;\n    }\n  }\n`;\nconst StyledText = styled.div`\n  ul.skills-list {\n    display: grid;\n    grid-template-columns: repeat(2, minmax(140px, 200px));\n    grid-gap: 0 10px;\n    padding: 0;\n    margin: 20px 0 0 0;\n    overflow: hidden;\n    list-style: none;\n\n    li {\n      position: relative;\n      margin-bottom: 10px;\n      padding-left: 20px;\n      font-family: var(--font-mono);\n      font-size: var(--fz-xs);\n\n      &:before {\n        content: '▹';\n        position: absolute;\n        left: 0;\n        color: var(--green);\n        font-size: var(--fz-sm);\n        line-height: 12px;\n      }\n    }\n  }\n`;\nconst StyledPic = styled.div`\n  position: relative;\n  max-width: 300px;\n\n  @media (max-width: 768px) {\n    margin: 50px auto 0;\n    width: 70%;\n  }\n\n  .wrapper {\n    ${({ theme }) => theme.mixins.boxShadow};\n    display: block;\n    position: relative;\n    width: 100%;\n    border-radius: var(--border-radius);\n    background-color: var(--green);\n\n    &:hover,\n    &:focus {\n      outline: 0;\n      transform: translate(-4px, -4px);\n\n      &:after {\n        transform: translate(8px, 8px);\n      }\n\n      .img {\n        filter: none;\n        mix-blend-mode: normal;\n      }\n    }\n\n    .img {\n      position: relative;\n      border-radius: var(--border-radius);\n      mix-blend-mode: multiply;\n      filter: grayscale(100%) contrast(1);\n      transition: var(--transition);\n    }\n\n    &:before,\n    &:after {\n      content: '';\n      display: block;\n      position: absolute;\n      width: 100%;\n      height: 100%;\n      border-radius: var(--border-radius);\n      transition: var(--transition);\n    }\n\n    &:before {\n      top: 0;\n      left: 0;\n      background-color: var(--navy);\n      mix-blend-mode: screen;\n    }\n\n    &:after {\n      border: 2px solid var(--green);\n      top: 14px;\n      left: 14px;\n      z-index: -1;\n    }\n  }\n`;\n\nconst About = () => {\n  const revealContainer = useRef(null);\n  const prefersReducedMotion = usePrefersReducedMotion();\n\n  useEffect(() => {\n    if (prefersReducedMotion) {\n      return;\n    }\n\n    sr.reveal(revealContainer.current, srConfig());\n  }, []);\n\n  const skills = ['JavaScript (ES6+)', 'TypeScript', 'React', 'Eleventy', 'Node.js', 'WordPress'];\n\n  return (\n    <StyledAboutSection id=\"about\" ref={revealContainer}>\n      <h2 className=\"numbered-heading\">About Me</h2>\n\n      <div className=\"inner\">\n        <StyledText>\n          <div>\n            <p>\n              Hello! My name is Brittany and I enjoy creating things that live on the internet. My\n              interest in web development started back in 2012 when I decided to try editing custom\n              Tumblr themes — turns out hacking together a custom reblog button taught me a lot\n              about HTML &amp; CSS!\n            </p>\n\n            <p>\n              Fast-forward to today, and I’ve had the privilege of working at{' '}\n              <a href=\"https://us.mullenlowe.com/\">an advertising agency</a>,{' '}\n              <a href=\"https://starry.com/\">a start-up</a>,{' '}\n              <a href=\"https://www.apple.com/\">a huge corporation</a>, and{' '}\n              <a href=\"https://scout.camd.northeastern.edu/\">a student-led design studio</a>. My\n              main focus these days is building accessible, inclusive products and digital\n              experiences at <a href=\"https://upstatement.com/\">Upstatement</a> for a variety of\n              clients.\n            </p>\n\n            <p>\n              I also recently{' '}\n              <a href=\"https://www.newline.co/courses/build-a-spotify-connected-app\">\n                launched a course\n              </a>{' '}\n              that covers everything you need to build a web app with the Spotify API using Node\n              &amp; React.\n            </p>\n\n            <p>Here are a few technologies I’ve been working with recently:</p>\n          </div>\n\n          <ul className=\"skills-list\">\n            {skills && skills.map((skill, i) => <li key={i}>{skill}</li>)}\n          </ul>\n        </StyledText>\n\n        <StyledPic>\n          <div className=\"wrapper\">\n            <StaticImage\n              className=\"img\"\n              src=\"../../images/me.jpg\"\n              width={500}\n              quality={95}\n              formats={['AUTO', 'WEBP', 'AVIF']}\n              alt=\"Headshot\"\n            />\n          </div>\n        </StyledPic>\n      </div>\n    </StyledAboutSection>\n  );\n};\n\nexport default About;\n"
  },
  {
    "path": "src/components/sections/contact.js",
    "content": "import React, { useEffect, useRef } from 'react';\nimport styled from 'styled-components';\nimport { srConfig, email } from '@config';\nimport sr from '@utils/sr';\nimport { usePrefersReducedMotion } from '@hooks';\n\nconst StyledContactSection = styled.section`\n  max-width: 600px;\n  margin: 0 auto 100px;\n  text-align: center;\n\n  @media (max-width: 768px) {\n    margin: 0 auto 50px;\n  }\n\n  .overline {\n    display: block;\n    margin-bottom: 20px;\n    color: var(--green);\n    font-family: var(--font-mono);\n    font-size: var(--fz-md);\n    font-weight: 400;\n\n    &:before {\n      bottom: 0;\n      font-size: var(--fz-sm);\n    }\n\n    &:after {\n      display: none;\n    }\n  }\n\n  .title {\n    font-size: clamp(40px, 5vw, 60px);\n  }\n\n  .email-link {\n    ${({ theme }) => theme.mixins.bigButton};\n    margin-top: 50px;\n  }\n`;\n\nconst Contact = () => {\n  const revealContainer = useRef(null);\n  const prefersReducedMotion = usePrefersReducedMotion();\n\n  useEffect(() => {\n    if (prefersReducedMotion) {\n      return;\n    }\n\n    sr.reveal(revealContainer.current, srConfig());\n  }, []);\n\n  return (\n    <StyledContactSection id=\"contact\" ref={revealContainer}>\n      <h2 className=\"numbered-heading overline\">What’s Next?</h2>\n\n      <h2 className=\"title\">Get In Touch</h2>\n\n      <p>\n        Although I’m not currently looking for any new opportunities, my inbox is always open.\n        Whether you have a question or just want to say hi, I’ll try my best to get back to you!\n      </p>\n\n      <a className=\"email-link\" href={`mailto:${email}`}>\n        Say Hello\n      </a>\n    </StyledContactSection>\n  );\n};\n\nexport default Contact;\n"
  },
  {
    "path": "src/components/sections/featured.js",
    "content": "import React, { useEffect, useRef } from 'react';\nimport { useStaticQuery, graphql } from 'gatsby';\nimport { GatsbyImage, getImage } from 'gatsby-plugin-image';\nimport styled from 'styled-components';\nimport sr from '@utils/sr';\nimport { srConfig } from '@config';\nimport { Icon } from '@components/icons';\nimport { usePrefersReducedMotion } from '@hooks';\n\nconst StyledProjectsGrid = styled.ul`\n  ${({ theme }) => theme.mixins.resetList};\n\n  a {\n    position: relative;\n    z-index: 1;\n  }\n`;\n\nconst StyledProject = styled.li`\n  position: relative;\n  display: grid;\n  grid-gap: 10px;\n  grid-template-columns: repeat(12, 1fr);\n  align-items: center;\n\n  @media (max-width: 768px) {\n    ${({ theme }) => theme.mixins.boxShadow};\n  }\n\n  &:not(:last-of-type) {\n    margin-bottom: 100px;\n\n    @media (max-width: 768px) {\n      margin-bottom: 70px;\n    }\n\n    @media (max-width: 480px) {\n      margin-bottom: 30px;\n    }\n  }\n\n  &:nth-of-type(odd) {\n    .project-content {\n      grid-column: 7 / -1;\n      text-align: right;\n\n      @media (max-width: 1080px) {\n        grid-column: 5 / -1;\n      }\n      @media (max-width: 768px) {\n        grid-column: 1 / -1;\n        padding: 40px 40px 30px;\n        text-align: left;\n      }\n      @media (max-width: 480px) {\n        padding: 25px 25px 20px;\n      }\n    }\n    .project-tech-list {\n      justify-content: flex-end;\n\n      @media (max-width: 768px) {\n        justify-content: flex-start;\n      }\n\n      li {\n        margin: 0 0 5px 20px;\n\n        @media (max-width: 768px) {\n          margin: 0 10px 5px 0;\n        }\n      }\n    }\n    .project-links {\n      justify-content: flex-end;\n      margin-left: 0;\n      margin-right: -10px;\n\n      @media (max-width: 768px) {\n        justify-content: flex-start;\n        margin-left: -10px;\n        margin-right: 0;\n      }\n    }\n    .project-image {\n      grid-column: 1 / 8;\n\n      @media (max-width: 768px) {\n        grid-column: 1 / -1;\n      }\n    }\n  }\n\n  .project-content {\n    position: relative;\n    grid-column: 1 / 7;\n    grid-row: 1 / -1;\n\n    @media (max-width: 1080px) {\n      grid-column: 1 / 9;\n    }\n\n    @media (max-width: 768px) {\n      display: flex;\n      flex-direction: column;\n      justify-content: center;\n      height: 100%;\n      grid-column: 1 / -1;\n      padding: 40px 40px 30px;\n      z-index: 5;\n    }\n\n    @media (max-width: 480px) {\n      padding: 30px 25px 20px;\n    }\n  }\n\n  .project-overline {\n    margin: 10px 0;\n    color: var(--green);\n    font-family: var(--font-mono);\n    font-size: var(--fz-xs);\n    font-weight: 400;\n  }\n\n  .project-title {\n    color: var(--lightest-slate);\n    font-size: clamp(24px, 5vw, 28px);\n\n    @media (min-width: 768px) {\n      margin: 0 0 20px;\n    }\n\n    @media (max-width: 768px) {\n      color: var(--white);\n\n      a {\n        position: static;\n\n        &:before {\n          content: '';\n          display: block;\n          position: absolute;\n          z-index: 0;\n          width: 100%;\n          height: 100%;\n          top: 0;\n          left: 0;\n        }\n      }\n    }\n  }\n\n  .project-description {\n    ${({ theme }) => theme.mixins.boxShadow};\n    position: relative;\n    z-index: 2;\n    padding: 25px;\n    border-radius: var(--border-radius);\n    background-color: var(--light-navy);\n    color: var(--light-slate);\n    font-size: var(--fz-lg);\n\n    @media (max-width: 768px) {\n      padding: 20px 0;\n      background-color: transparent;\n      box-shadow: none;\n\n      &:hover {\n        box-shadow: none;\n      }\n    }\n\n    a {\n      ${({ theme }) => theme.mixins.inlineLink};\n    }\n\n    strong {\n      color: var(--white);\n      font-weight: normal;\n    }\n  }\n\n  .project-tech-list {\n    display: flex;\n    flex-wrap: wrap;\n    position: relative;\n    z-index: 2;\n    margin: 25px 0 10px;\n    padding: 0;\n    list-style: none;\n\n    li {\n      margin: 0 20px 5px 0;\n      color: var(--light-slate);\n      font-family: var(--font-mono);\n      font-size: var(--fz-xs);\n      white-space: nowrap;\n    }\n\n    @media (max-width: 768px) {\n      margin: 10px 0;\n\n      li {\n        margin: 0 10px 5px 0;\n        color: var(--lightest-slate);\n      }\n    }\n  }\n\n  .project-links {\n    display: flex;\n    align-items: center;\n    position: relative;\n    margin-top: 10px;\n    margin-left: -10px;\n    color: var(--lightest-slate);\n\n    a {\n      ${({ theme }) => theme.mixins.flexCenter};\n      padding: 10px;\n\n      &.external {\n        svg {\n          width: 22px;\n          height: 22px;\n          margin-top: -4px;\n        }\n      }\n\n      svg {\n        width: 20px;\n        height: 20px;\n      }\n    }\n\n    .cta {\n      ${({ theme }) => theme.mixins.smallButton};\n      margin: 10px;\n    }\n  }\n\n  .project-image {\n    ${({ theme }) => theme.mixins.boxShadow};\n    grid-column: 6 / -1;\n    grid-row: 1 / -1;\n    position: relative;\n    z-index: 1;\n\n    @media (max-width: 768px) {\n      grid-column: 1 / -1;\n      height: 100%;\n      opacity: 0.25;\n    }\n\n    a {\n      width: 100%;\n      height: 100%;\n      background-color: var(--green);\n      border-radius: var(--border-radius);\n      vertical-align: middle;\n\n      &:hover,\n      &:focus {\n        background: transparent;\n        outline: 0;\n\n        &:before,\n        .img {\n          background: transparent;\n          filter: none;\n        }\n      }\n\n      &:before {\n        content: '';\n        position: absolute;\n        width: 100%;\n        height: 100%;\n        top: 0;\n        left: 0;\n        right: 0;\n        bottom: 0;\n        z-index: 3;\n        transition: var(--transition);\n        background-color: var(--navy);\n        mix-blend-mode: screen;\n      }\n    }\n\n    .img {\n      border-radius: var(--border-radius);\n      mix-blend-mode: multiply;\n      filter: grayscale(100%) contrast(1) brightness(90%);\n\n      @media (max-width: 768px) {\n        object-fit: cover;\n        width: auto;\n        height: 100%;\n        filter: grayscale(100%) contrast(1) brightness(50%);\n      }\n    }\n  }\n`;\n\nconst Featured = () => {\n  const data = useStaticQuery(graphql`\n    {\n      featured: allMarkdownRemark(\n        filter: { fileAbsolutePath: { regex: \"/content/featured/\" } }\n        sort: { fields: [frontmatter___date], order: ASC }\n      ) {\n        edges {\n          node {\n            frontmatter {\n              title\n              cover {\n                childImageSharp {\n                  gatsbyImageData(width: 700, placeholder: BLURRED, formats: [AUTO, WEBP, AVIF])\n                }\n              }\n              tech\n              github\n              external\n              cta\n            }\n            html\n          }\n        }\n      }\n    }\n  `);\n\n  const featuredProjects = data.featured.edges.filter(({ node }) => node);\n  const revealTitle = useRef(null);\n  const revealProjects = useRef([]);\n  const prefersReducedMotion = usePrefersReducedMotion();\n\n  useEffect(() => {\n    if (prefersReducedMotion) {\n      return;\n    }\n\n    sr.reveal(revealTitle.current, srConfig());\n    revealProjects.current.forEach((ref, i) => sr.reveal(ref, srConfig(i * 100)));\n  }, []);\n\n  return (\n    <section id=\"projects\">\n      <h2 className=\"numbered-heading\" ref={revealTitle}>\n        Some Things I’ve Built\n      </h2>\n\n      <StyledProjectsGrid>\n        {featuredProjects &&\n          featuredProjects.map(({ node }, i) => {\n            const { frontmatter, html } = node;\n            const { external, title, tech, github, cover, cta } = frontmatter;\n            const image = getImage(cover);\n\n            return (\n              <StyledProject key={i} ref={el => (revealProjects.current[i] = el)}>\n                <div className=\"project-content\">\n                  <div>\n                    <p className=\"project-overline\">Featured Project</p>\n\n                    <h3 className=\"project-title\">\n                      <a href={external}>{title}</a>\n                    </h3>\n\n                    <div\n                      className=\"project-description\"\n                      dangerouslySetInnerHTML={{ __html: html }}\n                    />\n\n                    {tech.length && (\n                      <ul className=\"project-tech-list\">\n                        {tech.map((tech, i) => (\n                          <li key={i}>{tech}</li>\n                        ))}\n                      </ul>\n                    )}\n\n                    <div className=\"project-links\">\n                      {cta && (\n                        <a href={cta} aria-label=\"Course Link\" className=\"cta\">\n                          Learn More\n                        </a>\n                      )}\n                      {github && (\n                        <a href={github} aria-label=\"GitHub Link\">\n                          <Icon name=\"GitHub\" />\n                        </a>\n                      )}\n                      {external && !cta && (\n                        <a href={external} aria-label=\"External Link\" className=\"external\">\n                          <Icon name=\"External\" />\n                        </a>\n                      )}\n                    </div>\n                  </div>\n                </div>\n\n                <div className=\"project-image\">\n                  <a href={external ? external : github ? github : '#'}>\n                    <GatsbyImage image={image} alt={title} className=\"img\" />\n                  </a>\n                </div>\n              </StyledProject>\n            );\n          })}\n      </StyledProjectsGrid>\n    </section>\n  );\n};\n\nexport default Featured;\n"
  },
  {
    "path": "src/components/sections/hero.js",
    "content": "import React, { useState, useEffect } from 'react';\nimport { CSSTransition, TransitionGroup } from 'react-transition-group';\nimport styled from 'styled-components';\nimport { navDelay, loaderDelay } from '@utils';\nimport { usePrefersReducedMotion } from '@hooks';\n\nconst StyledHeroSection = styled.section`\n  ${({ theme }) => theme.mixins.flexCenter};\n  flex-direction: column;\n  align-items: flex-start;\n  min-height: 100vh;\n  height: 100vh;\n  padding: 0;\n\n  @media (max-height: 700px) and (min-width: 700px), (max-width: 360px) {\n    height: auto;\n    padding-top: var(--nav-height);\n  }\n\n  h1 {\n    margin: 0 0 30px 4px;\n    color: var(--green);\n    font-family: var(--font-mono);\n    font-size: clamp(var(--fz-sm), 5vw, var(--fz-md));\n    font-weight: 400;\n\n    @media (max-width: 480px) {\n      margin: 0 0 20px 2px;\n    }\n  }\n\n  h3 {\n    margin-top: 5px;\n    color: var(--slate);\n    line-height: 0.9;\n  }\n\n  p {\n    margin: 20px 0 0;\n    max-width: 540px;\n  }\n\n  .email-link {\n    ${({ theme }) => theme.mixins.bigButton};\n    margin-top: 50px;\n  }\n`;\n\nconst Hero = () => {\n  const [isMounted, setIsMounted] = useState(false);\n  const prefersReducedMotion = usePrefersReducedMotion();\n\n  useEffect(() => {\n    if (prefersReducedMotion) {\n      return;\n    }\n\n    const timeout = setTimeout(() => setIsMounted(true), navDelay);\n    return () => clearTimeout(timeout);\n  }, []);\n\n  const one = <h1>Hi, my name is</h1>;\n  const two = <h2 className=\"big-heading\">Brittany Chiang.</h2>;\n  const three = <h3 className=\"big-heading\">I build things for the web.</h3>;\n  const four = (\n    <>\n      <p>\n        I’m a software engineer specializing in building (and occasionally designing) exceptional\n        digital experiences. Currently, I’m focused on building accessible, human-centered products\n        at{' '}\n        <a href=\"https://upstatement.com/\" target=\"_blank\" rel=\"noreferrer\">\n          Upstatement\n        </a>\n        .\n      </p>\n    </>\n  );\n  const five = (\n    <a\n      className=\"email-link\"\n      href=\"https://www.newline.co/courses/build-a-spotify-connected-app\"\n      target=\"_blank\"\n      rel=\"noreferrer\">\n      Check out my course!\n    </a>\n  );\n\n  const items = [one, two, three, four, five];\n\n  return (\n    <StyledHeroSection>\n      {prefersReducedMotion ? (\n        <>\n          {items.map((item, i) => (\n            <div key={i}>{item}</div>\n          ))}\n        </>\n      ) : (\n        <TransitionGroup component={null}>\n          {isMounted &&\n            items.map((item, i) => (\n              <CSSTransition key={i} classNames=\"fadeup\" timeout={loaderDelay}>\n                <div style={{ transitionDelay: `${i + 1}00ms` }}>{item}</div>\n              </CSSTransition>\n            ))}\n        </TransitionGroup>\n      )}\n    </StyledHeroSection>\n  );\n};\n\nexport default Hero;\n"
  },
  {
    "path": "src/components/sections/jobs.js",
    "content": "import React, { useState, useEffect, useRef } from 'react';\nimport { useStaticQuery, graphql } from 'gatsby';\nimport { CSSTransition } from 'react-transition-group';\nimport styled from 'styled-components';\nimport { srConfig } from '@config';\nimport { KEY_CODES } from '@utils';\nimport sr from '@utils/sr';\nimport { usePrefersReducedMotion } from '@hooks';\n\nconst StyledJobsSection = styled.section`\n  max-width: 700px;\n\n  .inner {\n    display: flex;\n\n    @media (max-width: 600px) {\n      display: block;\n    }\n\n    // Prevent container from jumping\n    @media (min-width: 700px) {\n      min-height: 340px;\n    }\n  }\n`;\n\nconst StyledTabList = styled.div`\n  position: relative;\n  z-index: 3;\n  width: max-content;\n  padding: 0;\n  margin: 0;\n  list-style: none;\n\n  @media (max-width: 600px) {\n    display: flex;\n    overflow-x: auto;\n    width: calc(100% + 100px);\n    padding-left: 50px;\n    margin-left: -50px;\n    margin-bottom: 30px;\n  }\n  @media (max-width: 480px) {\n    width: calc(100% + 50px);\n    padding-left: 25px;\n    margin-left: -25px;\n  }\n\n  li {\n    &:first-of-type {\n      @media (max-width: 600px) {\n        margin-left: 50px;\n      }\n      @media (max-width: 480px) {\n        margin-left: 25px;\n      }\n    }\n    &:last-of-type {\n      @media (max-width: 600px) {\n        padding-right: 50px;\n      }\n      @media (max-width: 480px) {\n        padding-right: 25px;\n      }\n    }\n  }\n`;\n\nconst StyledTabButton = styled.button`\n  ${({ theme }) => theme.mixins.link};\n  display: flex;\n  align-items: center;\n  width: 100%;\n  height: var(--tab-height);\n  padding: 0 20px 2px;\n  border-left: 2px solid var(--lightest-navy);\n  background-color: transparent;\n  color: ${({ isActive }) => (isActive ? 'var(--green)' : 'var(--slate)')};\n  font-family: var(--font-mono);\n  font-size: var(--fz-xs);\n  text-align: left;\n  white-space: nowrap;\n\n  @media (max-width: 768px) {\n    padding: 0 15px 2px;\n  }\n  @media (max-width: 600px) {\n    ${({ theme }) => theme.mixins.flexCenter};\n    min-width: 120px;\n    padding: 0 15px;\n    border-left: 0;\n    border-bottom: 2px solid var(--lightest-navy);\n    text-align: center;\n  }\n\n  &:hover,\n  &:focus {\n    background-color: var(--light-navy);\n  }\n`;\n\nconst StyledHighlight = styled.div`\n  position: absolute;\n  top: 0;\n  left: 0;\n  z-index: 10;\n  width: 2px;\n  height: var(--tab-height);\n  border-radius: var(--border-radius);\n  background: var(--green);\n  transform: translateY(calc(${({ activeTabId }) => activeTabId} * var(--tab-height)));\n  transition: transform 0.25s cubic-bezier(0.645, 0.045, 0.355, 1);\n  transition-delay: 0.1s;\n\n  @media (max-width: 600px) {\n    top: auto;\n    bottom: 0;\n    width: 100%;\n    max-width: var(--tab-width);\n    height: 2px;\n    margin-left: 50px;\n    transform: translateX(calc(${({ activeTabId }) => activeTabId} * var(--tab-width)));\n  }\n  @media (max-width: 480px) {\n    margin-left: 25px;\n  }\n`;\n\nconst StyledTabPanels = styled.div`\n  position: relative;\n  width: 100%;\n  margin-left: 20px;\n\n  @media (max-width: 600px) {\n    margin-left: 0;\n  }\n`;\n\nconst StyledTabPanel = styled.div`\n  width: 100%;\n  height: auto;\n  padding: 10px 5px;\n\n  ul {\n    ${({ theme }) => theme.mixins.fancyList};\n  }\n\n  h3 {\n    margin-bottom: 2px;\n    font-size: var(--fz-xxl);\n    font-weight: 500;\n    line-height: 1.3;\n\n    .company {\n      color: var(--green);\n    }\n  }\n\n  .range {\n    margin-bottom: 25px;\n    color: var(--light-slate);\n    font-family: var(--font-mono);\n    font-size: var(--fz-xs);\n  }\n`;\n\nconst Jobs = () => {\n  const data = useStaticQuery(graphql`\n    query {\n      jobs: allMarkdownRemark(\n        filter: { fileAbsolutePath: { regex: \"/content/jobs/\" } }\n        sort: { fields: [frontmatter___date], order: DESC }\n      ) {\n        edges {\n          node {\n            frontmatter {\n              title\n              company\n              location\n              range\n              url\n            }\n            html\n          }\n        }\n      }\n    }\n  `);\n\n  const jobsData = data.jobs.edges;\n\n  const [activeTabId, setActiveTabId] = useState(0);\n  const [tabFocus, setTabFocus] = useState(null);\n  const tabs = useRef([]);\n  const revealContainer = useRef(null);\n  const prefersReducedMotion = usePrefersReducedMotion();\n\n  useEffect(() => {\n    if (prefersReducedMotion) {\n      return;\n    }\n\n    sr.reveal(revealContainer.current, srConfig());\n  }, []);\n\n  const focusTab = () => {\n    if (tabs.current[tabFocus]) {\n      tabs.current[tabFocus].focus();\n      return;\n    }\n    // If we're at the end, go to the start\n    if (tabFocus >= tabs.current.length) {\n      setTabFocus(0);\n    }\n    // If we're at the start, move to the end\n    if (tabFocus < 0) {\n      setTabFocus(tabs.current.length - 1);\n    }\n  };\n\n  // Only re-run the effect if tabFocus changes\n  useEffect(() => focusTab(), [tabFocus]);\n\n  // Focus on tabs when using up & down arrow keys\n  const onKeyDown = e => {\n    switch (e.key) {\n      case KEY_CODES.ARROW_UP: {\n        e.preventDefault();\n        setTabFocus(tabFocus - 1);\n        break;\n      }\n\n      case KEY_CODES.ARROW_DOWN: {\n        e.preventDefault();\n        setTabFocus(tabFocus + 1);\n        break;\n      }\n\n      default: {\n        break;\n      }\n    }\n  };\n\n  return (\n    <StyledJobsSection id=\"jobs\" ref={revealContainer}>\n      <h2 className=\"numbered-heading\">Where I’ve Worked</h2>\n\n      <div className=\"inner\">\n        <StyledTabList role=\"tablist\" aria-label=\"Job tabs\" onKeyDown={e => onKeyDown(e)}>\n          {jobsData &&\n            jobsData.map(({ node }, i) => {\n              const { company } = node.frontmatter;\n              return (\n                <StyledTabButton\n                  key={i}\n                  isActive={activeTabId === i}\n                  onClick={() => setActiveTabId(i)}\n                  ref={el => (tabs.current[i] = el)}\n                  id={`tab-${i}`}\n                  role=\"tab\"\n                  tabIndex={activeTabId === i ? '0' : '-1'}\n                  aria-selected={activeTabId === i ? true : false}\n                  aria-controls={`panel-${i}`}>\n                  <span>{company}</span>\n                </StyledTabButton>\n              );\n            })}\n          <StyledHighlight activeTabId={activeTabId} />\n        </StyledTabList>\n\n        <StyledTabPanels>\n          {jobsData &&\n            jobsData.map(({ node }, i) => {\n              const { frontmatter, html } = node;\n              const { title, url, company, range } = frontmatter;\n\n              return (\n                <CSSTransition key={i} in={activeTabId === i} timeout={250} classNames=\"fade\">\n                  <StyledTabPanel\n                    id={`panel-${i}`}\n                    role=\"tabpanel\"\n                    tabIndex={activeTabId === i ? '0' : '-1'}\n                    aria-labelledby={`tab-${i}`}\n                    aria-hidden={activeTabId !== i}\n                    hidden={activeTabId !== i}>\n                    <h3>\n                      <span>{title}</span>\n                      <span className=\"company\">\n                        &nbsp;@&nbsp;\n                        <a href={url} className=\"inline-link\">\n                          {company}\n                        </a>\n                      </span>\n                    </h3>\n\n                    <p className=\"range\">{range}</p>\n\n                    <div dangerouslySetInnerHTML={{ __html: html }} />\n                  </StyledTabPanel>\n                </CSSTransition>\n              );\n            })}\n        </StyledTabPanels>\n      </div>\n    </StyledJobsSection>\n  );\n};\n\nexport default Jobs;\n"
  },
  {
    "path": "src/components/sections/projects.js",
    "content": "import React, { useState, useEffect, useRef } from 'react';\nimport { Link, useStaticQuery, graphql } from 'gatsby';\nimport { CSSTransition, TransitionGroup } from 'react-transition-group';\nimport styled from 'styled-components';\nimport { srConfig } from '@config';\nimport sr from '@utils/sr';\nimport { Icon } from '@components/icons';\nimport { usePrefersReducedMotion } from '@hooks';\n\nconst StyledProjectsSection = styled.section`\n  display: flex;\n  flex-direction: column;\n  align-items: center;\n\n  h2 {\n    font-size: clamp(24px, 5vw, var(--fz-heading));\n  }\n\n  .archive-link {\n    font-family: var(--font-mono);\n    font-size: var(--fz-sm);\n    &:after {\n      bottom: 0.1em;\n    }\n  }\n\n  .projects-grid {\n    ${({ theme }) => theme.mixins.resetList};\n    display: grid;\n    grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));\n    grid-gap: 15px;\n    position: relative;\n    margin-top: 50px;\n\n    @media (max-width: 1080px) {\n      grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));\n    }\n  }\n\n  .more-button {\n    ${({ theme }) => theme.mixins.button};\n    margin: 80px auto 0;\n  }\n`;\n\nconst StyledProject = styled.li`\n  position: relative;\n  cursor: default;\n  transition: var(--transition);\n\n  @media (prefers-reduced-motion: no-preference) {\n    &:hover,\n    &:focus-within {\n      .project-inner {\n        transform: translateY(-7px);\n      }\n    }\n  }\n\n  a {\n    position: relative;\n    z-index: 1;\n  }\n\n  .project-inner {\n    ${({ theme }) => theme.mixins.boxShadow};\n    ${({ theme }) => theme.mixins.flexBetween};\n    flex-direction: column;\n    align-items: flex-start;\n    position: relative;\n    height: 100%;\n    padding: 2rem 1.75rem;\n    border-radius: var(--border-radius);\n    background-color: var(--light-navy);\n    transition: var(--transition);\n    overflow: auto;\n  }\n\n  .project-top {\n    ${({ theme }) => theme.mixins.flexBetween};\n    margin-bottom: 35px;\n\n    .folder {\n      color: var(--green);\n      svg {\n        width: 40px;\n        height: 40px;\n      }\n    }\n\n    .project-links {\n      display: flex;\n      align-items: center;\n      margin-right: -10px;\n      color: var(--light-slate);\n\n      a {\n        ${({ theme }) => theme.mixins.flexCenter};\n        padding: 5px 7px;\n\n        &.external {\n          svg {\n            width: 22px;\n            height: 22px;\n            margin-top: -4px;\n          }\n        }\n\n        svg {\n          width: 20px;\n          height: 20px;\n        }\n      }\n    }\n  }\n\n  .project-title {\n    margin: 0 0 10px;\n    color: var(--lightest-slate);\n    font-size: var(--fz-xxl);\n\n    a {\n      position: static;\n\n      &:before {\n        content: '';\n        display: block;\n        position: absolute;\n        z-index: 0;\n        width: 100%;\n        height: 100%;\n        top: 0;\n        left: 0;\n      }\n    }\n  }\n\n  .project-description {\n    color: var(--light-slate);\n    font-size: 17px;\n\n    a {\n      ${({ theme }) => theme.mixins.inlineLink};\n    }\n  }\n\n  .project-tech-list {\n    display: flex;\n    align-items: flex-end;\n    flex-grow: 1;\n    flex-wrap: wrap;\n    padding: 0;\n    margin: 20px 0 0 0;\n    list-style: none;\n\n    li {\n      font-family: var(--font-mono);\n      font-size: var(--fz-xxs);\n      line-height: 1.75;\n\n      &:not(:last-of-type) {\n        margin-right: 15px;\n      }\n    }\n  }\n`;\n\nconst Projects = () => {\n  const data = useStaticQuery(graphql`\n    query {\n      projects: allMarkdownRemark(\n        filter: {\n          fileAbsolutePath: { regex: \"/content/projects/\" }\n          frontmatter: { showInProjects: { ne: false } }\n        }\n        sort: { fields: [frontmatter___date], order: DESC }\n      ) {\n        edges {\n          node {\n            frontmatter {\n              title\n              tech\n              github\n              external\n            }\n            html\n          }\n        }\n      }\n    }\n  `);\n\n  const [showMore, setShowMore] = useState(false);\n  const revealTitle = useRef(null);\n  const revealArchiveLink = useRef(null);\n  const revealProjects = useRef([]);\n  const prefersReducedMotion = usePrefersReducedMotion();\n\n  useEffect(() => {\n    if (prefersReducedMotion) {\n      return;\n    }\n\n    sr.reveal(revealTitle.current, srConfig());\n    sr.reveal(revealArchiveLink.current, srConfig());\n    revealProjects.current.forEach((ref, i) => sr.reveal(ref, srConfig(i * 100)));\n  }, []);\n\n  const GRID_LIMIT = 6;\n  const projects = data.projects.edges.filter(({ node }) => node);\n  const firstSix = projects.slice(0, GRID_LIMIT);\n  const projectsToShow = showMore ? projects : firstSix;\n\n  const projectInner = node => {\n    const { frontmatter, html } = node;\n    const { github, external, title, tech } = frontmatter;\n\n    return (\n      <div className=\"project-inner\">\n        <header>\n          <div className=\"project-top\">\n            <div className=\"folder\">\n              <Icon name=\"Folder\" />\n            </div>\n            <div className=\"project-links\">\n              {github && (\n                <a href={github} aria-label=\"GitHub Link\" target=\"_blank\" rel=\"noreferrer\">\n                  <Icon name=\"GitHub\" />\n                </a>\n              )}\n              {external && (\n                <a\n                  href={external}\n                  aria-label=\"External Link\"\n                  className=\"external\"\n                  target=\"_blank\"\n                  rel=\"noreferrer\">\n                  <Icon name=\"External\" />\n                </a>\n              )}\n            </div>\n          </div>\n\n          <h3 className=\"project-title\">\n            <a href={external} target=\"_blank\" rel=\"noreferrer\">\n              {title}\n            </a>\n          </h3>\n\n          <div className=\"project-description\" dangerouslySetInnerHTML={{ __html: html }} />\n        </header>\n\n        <footer>\n          {tech && (\n            <ul className=\"project-tech-list\">\n              {tech.map((tech, i) => (\n                <li key={i}>{tech}</li>\n              ))}\n            </ul>\n          )}\n        </footer>\n      </div>\n    );\n  };\n\n  return (\n    <StyledProjectsSection>\n      <h2 ref={revealTitle}>Other Noteworthy Projects</h2>\n\n      <Link className=\"inline-link archive-link\" to=\"/archive\" ref={revealArchiveLink}>\n        view the archive\n      </Link>\n\n      <ul className=\"projects-grid\">\n        {prefersReducedMotion ? (\n          <>\n            {projectsToShow &&\n              projectsToShow.map(({ node }, i) => (\n                <StyledProject key={i}>{projectInner(node)}</StyledProject>\n              ))}\n          </>\n        ) : (\n          <TransitionGroup component={null}>\n            {projectsToShow &&\n              projectsToShow.map(({ node }, i) => (\n                <CSSTransition\n                  key={i}\n                  classNames=\"fadeup\"\n                  timeout={i >= GRID_LIMIT ? (i - GRID_LIMIT) * 300 : 300}\n                  exit={false}>\n                  <StyledProject\n                    key={i}\n                    ref={el => (revealProjects.current[i] = el)}\n                    style={{\n                      transitionDelay: `${i >= GRID_LIMIT ? (i - GRID_LIMIT) * 100 : 0}ms`,\n                    }}>\n                    {projectInner(node)}\n                  </StyledProject>\n                </CSSTransition>\n              ))}\n          </TransitionGroup>\n        )}\n      </ul>\n\n      <button className=\"more-button\" onClick={() => setShowMore(!showMore)}>\n        Show {showMore ? 'Less' : 'More'}\n      </button>\n    </StyledProjectsSection>\n  );\n};\n\nexport default Projects;\n"
  },
  {
    "path": "src/components/side.js",
    "content": "import React, { useState, useEffect } from 'react';\nimport PropTypes from 'prop-types';\nimport { CSSTransition, TransitionGroup } from 'react-transition-group';\nimport styled from 'styled-components';\nimport { loaderDelay } from '@utils';\nimport { usePrefersReducedMotion } from '@hooks';\n\nconst StyledSideElement = styled.div`\n  width: 40px;\n  position: fixed;\n  bottom: 0;\n  left: ${props => (props.orientation === 'left' ? '40px' : 'auto')};\n  right: ${props => (props.orientation === 'left' ? 'auto' : '40px')};\n  z-index: 10;\n  color: var(--light-slate);\n\n  @media (max-width: 1080px) {\n    left: ${props => (props.orientation === 'left' ? '20px' : 'auto')};\n    right: ${props => (props.orientation === 'left' ? 'auto' : '20px')};\n  }\n\n  @media (max-width: 768px) {\n    display: none;\n  }\n`;\n\nconst Side = ({ children, isHome, orientation }) => {\n  const [isMounted, setIsMounted] = useState(!isHome);\n  const prefersReducedMotion = usePrefersReducedMotion();\n\n  useEffect(() => {\n    if (!isHome || prefersReducedMotion) {\n      return;\n    }\n    const timeout = setTimeout(() => setIsMounted(true), loaderDelay);\n    return () => clearTimeout(timeout);\n  }, []);\n\n  return (\n    <StyledSideElement orientation={orientation}>\n      {prefersReducedMotion ? (\n        <>{children}</>\n      ) : (\n        <TransitionGroup component={null}>\n          {isMounted && (\n            <CSSTransition classNames={isHome ? 'fade' : ''} timeout={isHome ? loaderDelay : 0}>\n              {children}\n            </CSSTransition>\n          )}\n        </TransitionGroup>\n      )}\n    </StyledSideElement>\n  );\n};\n\nSide.propTypes = {\n  children: PropTypes.node.isRequired,\n  isHome: PropTypes.bool,\n  orientation: PropTypes.string,\n};\n\nexport default Side;\n"
  },
  {
    "path": "src/components/social.js",
    "content": "import React from 'react';\nimport PropTypes from 'prop-types';\nimport styled from 'styled-components';\nimport { socialMedia } from '@config';\nimport { Side } from '@components';\nimport { Icon } from '@components/icons';\n\nconst StyledSocialList = styled.ul`\n  display: flex;\n  flex-direction: column;\n  align-items: center;\n  margin: 0;\n  padding: 0;\n  list-style: none;\n\n  &:after {\n    content: '';\n    display: block;\n    width: 1px;\n    height: 90px;\n    margin: 0 auto;\n    background-color: var(--light-slate);\n  }\n\n  li {\n    &:last-of-type {\n      margin-bottom: 20px;\n    }\n\n    a {\n      padding: 10px;\n\n      &:hover,\n      &:focus {\n        transform: translateY(-3px);\n      }\n\n      svg {\n        width: 20px;\n        height: 20px;\n      }\n    }\n  }\n`;\n\nconst Social = ({ isHome }) => (\n  <Side isHome={isHome} orientation=\"left\">\n    <StyledSocialList>\n      {socialMedia &&\n        socialMedia.map(({ url, name }, i) => (\n          <li key={i}>\n            <a href={url} aria-label={name} target=\"_blank\" rel=\"noreferrer\">\n              <Icon name={name} />\n            </a>\n          </li>\n        ))}\n    </StyledSocialList>\n  </Side>\n);\n\nSocial.propTypes = {\n  isHome: PropTypes.bool,\n};\n\nexport default Social;\n"
  },
  {
    "path": "src/config.js",
    "content": "module.exports = {\n  email: 'brittany.chiang@gmail.com',\n\n  socialMedia: [\n    {\n      name: 'GitHub',\n      url: 'https://github.com/bchiang7',\n    },\n    {\n      name: 'Instagram',\n      url: 'https://www.instagram.com/bchiang7',\n    },\n    {\n      name: 'Twitter',\n      url: 'https://twitter.com/bchiang7',\n    },\n    {\n      name: 'Linkedin',\n      url: 'https://www.linkedin.com/in/bchiang7',\n    },\n    {\n      name: 'Codepen',\n      url: 'https://codepen.io/bchiang7',\n    },\n  ],\n\n  navLinks: [\n    {\n      name: 'About',\n      url: '/#about',\n    },\n    {\n      name: 'Experience',\n      url: '/#jobs',\n    },\n    {\n      name: 'Work',\n      url: '/#projects',\n    },\n    {\n      name: 'Contact',\n      url: '/#contact',\n    },\n  ],\n\n  colors: {\n    green: '#64ffda',\n    navy: '#0a192f',\n    darkNavy: '#020c1b',\n  },\n\n  srConfig: (delay = 200, viewFactor = 0.25) => ({\n    origin: 'bottom',\n    distance: '20px',\n    duration: 500,\n    delay,\n    rotate: { x: 0, y: 0, z: 0 },\n    opacity: 0,\n    scale: 1,\n    easing: 'cubic-bezier(0.645, 0.045, 0.355, 1)',\n    mobile: true,\n    reset: false,\n    useDelay: 'always',\n    viewFactor,\n    viewOffset: { top: 0, right: 0, bottom: 0, left: 0 },\n  }),\n};\n"
  },
  {
    "path": "src/hooks/index.js",
    "content": "export { default as useOnClickOutside } from './useOnClickOutside';\nexport { default as usePrefersReducedMotion } from './usePrefersReducedMotion';\nexport { default as useScrollDirection } from './useScrollDirection';\n"
  },
  {
    "path": "src/hooks/useOnClickOutside.js",
    "content": "import { useEffect } from 'react';\n\n// https://usehooks.com/useOnClickOutside/\n\nconst useOnClickOutside = (ref, handler) => {\n  useEffect(\n    () => {\n      const listener = event => {\n        // Do nothing if clicking ref's element or descendent elements\n        if (!ref.current || ref.current.contains(event.target)) {\n          return;\n        }\n\n        handler(event);\n      };\n\n      document.addEventListener('mousedown', listener);\n      document.addEventListener('touchstart', listener);\n\n      return () => {\n        document.removeEventListener('mousedown', listener);\n        document.removeEventListener('touchstart', listener);\n      };\n    },\n    // Add ref and handler to effect dependencies\n    // It's worth noting that because passed in handler is a new ...\n    // ... function on every render that will cause this effect ...\n    // ... callback/cleanup to run every render. It's not a big deal ...\n    // ... but to optimize you can wrap handler in useCallback before ...\n    // ... passing it into this hook.\n    [ref, handler],\n  );\n};\n\nexport default useOnClickOutside;\n"
  },
  {
    "path": "src/hooks/usePrefersReducedMotion.js",
    "content": "/**\n * https://www.joshwcomeau.com/snippets/react-hooks/use-prefers-reduced-motion/\n */\n\nimport { useState, useEffect } from 'react';\nconst QUERY = '(prefers-reduced-motion: no-preference)';\nconst isRenderingOnServer = typeof window === 'undefined';\n\nconst getInitialState = () =>\n  // For our initial server render, we won't know if the user\n  // prefers reduced motion, but it doesn't matter. This value\n  // will be overwritten on the client, before any animations\n  // occur.\n  isRenderingOnServer ? true : !window.matchMedia(QUERY).matches;\nfunction usePrefersReducedMotion() {\n  const [prefersReducedMotion, setPrefersReducedMotion] = useState(getInitialState);\n  useEffect(() => {\n    const mediaQueryList = window.matchMedia(QUERY);\n    const listener = event => {\n      setPrefersReducedMotion(!event.matches);\n    };\n    mediaQueryList.addListener(listener);\n    return () => {\n      mediaQueryList.removeListener(listener);\n    };\n  }, []);\n  return prefersReducedMotion;\n}\n\nexport default usePrefersReducedMotion;\n"
  },
  {
    "path": "src/hooks/useScrollDirection.js",
    "content": "const SCROLL_UP = 'up';\nconst SCROLL_DOWN = 'down';\n\nimport { useState, useEffect } from 'react';\n\nconst useScrollDirection = ({ initialDirection, thresholdPixels, off } = {}) => {\n  const [scrollDir, setScrollDir] = useState(initialDirection);\n\n  useEffect(() => {\n    const threshold = thresholdPixels || 0;\n    let lastScrollY = window.pageYOffset;\n    let ticking = false;\n\n    const updateScrollDir = () => {\n      const scrollY = window.pageYOffset;\n\n      if (Math.abs(scrollY - lastScrollY) < threshold) {\n        // We haven't exceeded the threshold\n        ticking = false;\n        return;\n      }\n\n      setScrollDir(scrollY > lastScrollY ? SCROLL_DOWN : SCROLL_UP);\n      lastScrollY = scrollY > 0 ? scrollY : 0;\n      ticking = false;\n    };\n\n    const onScroll = () => {\n      if (!ticking) {\n        window.requestAnimationFrame(updateScrollDir);\n        ticking = true;\n      }\n    };\n\n    /**\n     * Bind the scroll handler if `off` is set to false.\n     * If `off` is set to true reset the scroll direction.\n     */\n    !off ? window.addEventListener('scroll', onScroll) : setScrollDir(initialDirection);\n\n    return () => window.removeEventListener('scroll', onScroll);\n  }, [initialDirection, thresholdPixels, off]);\n\n  return scrollDir;\n};\n\nexport default useScrollDirection;\n"
  },
  {
    "path": "src/pages/404.js",
    "content": "import React, { useState, useEffect } from 'react';\nimport { Link } from 'gatsby';\nimport { Helmet } from 'react-helmet';\nimport { CSSTransition, TransitionGroup } from 'react-transition-group';\nimport PropTypes from 'prop-types';\nimport styled from 'styled-components';\nimport { navDelay } from '@utils';\nimport { Layout } from '@components';\nimport { usePrefersReducedMotion } from '@hooks';\n\nconst StyledMainContainer = styled.main`\n  ${({ theme }) => theme.mixins.flexCenter};\n  flex-direction: column;\n`;\nconst StyledTitle = styled.h1`\n  color: var(--green);\n  font-family: var(--font-mono);\n  font-size: clamp(100px, 25vw, 200px);\n  line-height: 1;\n`;\nconst StyledSubtitle = styled.h2`\n  font-size: clamp(30px, 5vw, 50px);\n  font-weight: 400;\n`;\nconst StyledHomeButton = styled(Link)`\n  ${({ theme }) => theme.mixins.bigButton};\n  margin-top: 40px;\n`;\n\nconst NotFoundPage = ({ location }) => {\n  const [isMounted, setIsMounted] = useState(false);\n  const prefersReducedMotion = usePrefersReducedMotion();\n\n  useEffect(() => {\n    if (prefersReducedMotion) {\n      return;\n    }\n\n    const timeout = setTimeout(() => setIsMounted(true), navDelay);\n    return () => clearTimeout(timeout);\n  }, []);\n\n  const content = (\n    <StyledMainContainer className=\"fillHeight\">\n      <StyledTitle>404</StyledTitle>\n      <StyledSubtitle>Page Not Found</StyledSubtitle>\n      <StyledHomeButton to=\"/\">Go Home</StyledHomeButton>\n    </StyledMainContainer>\n  );\n\n  return (\n    <Layout location={location}>\n      <Helmet title=\"Page Not Found\" />\n\n      {prefersReducedMotion ? (\n        <>{content}</>\n      ) : (\n        <TransitionGroup component={null}>\n          {isMounted && (\n            <CSSTransition timeout={500} classNames=\"fadeup\">\n              {content}\n            </CSSTransition>\n          )}\n        </TransitionGroup>\n      )}\n    </Layout>\n  );\n};\n\nNotFoundPage.propTypes = {\n  location: PropTypes.object.isRequired,\n};\n\nexport default NotFoundPage;\n"
  },
  {
    "path": "src/pages/archive.js",
    "content": "import React, { useRef, useEffect } from 'react';\nimport { graphql } from 'gatsby';\nimport PropTypes from 'prop-types';\nimport { Helmet } from 'react-helmet';\nimport styled from 'styled-components';\nimport { srConfig } from '@config';\nimport sr from '@utils/sr';\nimport { Layout } from '@components';\nimport { Icon } from '@components/icons';\nimport { usePrefersReducedMotion } from '@hooks';\n\nconst StyledTableContainer = styled.div`\n  margin: 100px -20px;\n\n  @media (max-width: 768px) {\n    margin: 50px -10px;\n  }\n\n  table {\n    width: 100%;\n    border-collapse: collapse;\n\n    .hide-on-mobile {\n      @media (max-width: 768px) {\n        display: none;\n      }\n    }\n\n    tbody tr {\n      &:hover,\n      &:focus {\n        background-color: var(--light-navy);\n      }\n    }\n\n    th,\n    td {\n      padding: 10px;\n      text-align: left;\n\n      &:first-child {\n        padding-left: 20px;\n\n        @media (max-width: 768px) {\n          padding-left: 10px;\n        }\n      }\n      &:last-child {\n        padding-right: 20px;\n\n        @media (max-width: 768px) {\n          padding-right: 10px;\n        }\n      }\n\n      svg {\n        width: 20px;\n        height: 20px;\n      }\n    }\n\n    tr {\n      cursor: default;\n\n      td:first-child {\n        border-top-left-radius: var(--border-radius);\n        border-bottom-left-radius: var(--border-radius);\n      }\n      td:last-child {\n        border-top-right-radius: var(--border-radius);\n        border-bottom-right-radius: var(--border-radius);\n      }\n    }\n\n    td {\n      &.year {\n        padding-right: 20px;\n\n        @media (max-width: 768px) {\n          padding-right: 10px;\n          font-size: var(--fz-sm);\n        }\n      }\n\n      &.title {\n        padding-top: 15px;\n        padding-right: 20px;\n        color: var(--lightest-slate);\n        font-size: var(--fz-xl);\n        font-weight: 600;\n        line-height: 1.25;\n      }\n\n      &.company {\n        font-size: var(--fz-lg);\n        white-space: nowrap;\n      }\n\n      &.tech {\n        font-size: var(--fz-xxs);\n        font-family: var(--font-mono);\n        line-height: 1.5;\n        .separator {\n          margin: 0 5px;\n        }\n        span {\n          display: inline-block;\n        }\n      }\n\n      &.links {\n        min-width: 100px;\n\n        div {\n          display: flex;\n          align-items: center;\n\n          a {\n            ${({ theme }) => theme.mixins.flexCenter};\n            flex-shrink: 0;\n          }\n\n          a + a {\n            margin-left: 10px;\n          }\n        }\n      }\n    }\n  }\n`;\n\nconst ArchivePage = ({ location, data }) => {\n  const projects = data.allMarkdownRemark.edges;\n  const revealTitle = useRef(null);\n  const revealTable = useRef(null);\n  const revealProjects = useRef([]);\n  const prefersReducedMotion = usePrefersReducedMotion();\n\n  useEffect(() => {\n    if (prefersReducedMotion) {\n      return;\n    }\n\n    sr.reveal(revealTitle.current, srConfig());\n    sr.reveal(revealTable.current, srConfig(200, 0));\n    revealProjects.current.forEach((ref, i) => sr.reveal(ref, srConfig(i * 10)));\n  }, []);\n\n  return (\n    <Layout location={location}>\n      <Helmet title=\"Archive\" />\n\n      <main>\n        <header ref={revealTitle}>\n          <h1 className=\"big-heading\">Archive</h1>\n          <p className=\"subtitle\">A big list of things I’ve worked on</p>\n        </header>\n\n        <StyledTableContainer ref={revealTable}>\n          <table>\n            <thead>\n              <tr>\n                <th>Year</th>\n                <th>Title</th>\n                <th className=\"hide-on-mobile\">Made at</th>\n                <th className=\"hide-on-mobile\">Built with</th>\n                <th>Link</th>\n              </tr>\n            </thead>\n            <tbody>\n              {projects.length > 0 &&\n                projects.map(({ node }, i) => {\n                  const {\n                    date,\n                    github,\n                    external,\n                    ios,\n                    android,\n                    title,\n                    tech,\n                    company,\n                  } = node.frontmatter;\n                  return (\n                    <tr key={i} ref={el => (revealProjects.current[i] = el)}>\n                      <td className=\"overline year\">{`${new Date(date).getFullYear()}`}</td>\n\n                      <td className=\"title\">{title}</td>\n\n                      <td className=\"company hide-on-mobile\">\n                        {company ? <span>{company}</span> : <span>—</span>}\n                      </td>\n\n                      <td className=\"tech hide-on-mobile\">\n                        {tech?.length > 0 &&\n                          tech.map((item, i) => (\n                            <span key={i}>\n                              {item}\n                              {''}\n                              {i !== tech.length - 1 && <span className=\"separator\">&middot;</span>}\n                            </span>\n                          ))}\n                      </td>\n\n                      <td className=\"links\">\n                        <div>\n                          {external && (\n                            <a href={external} aria-label=\"External Link\">\n                              <Icon name=\"External\" />\n                            </a>\n                          )}\n                          {github && (\n                            <a href={github} aria-label=\"GitHub Link\">\n                              <Icon name=\"GitHub\" />\n                            </a>\n                          )}\n                          {ios && (\n                            <a href={ios} aria-label=\"Apple App Store Link\">\n                              <Icon name=\"AppStore\" />\n                            </a>\n                          )}\n                          {android && (\n                            <a href={android} aria-label=\"Google Play Store Link\">\n                              <Icon name=\"PlayStore\" />\n                            </a>\n                          )}\n                        </div>\n                      </td>\n                    </tr>\n                  );\n                })}\n            </tbody>\n          </table>\n        </StyledTableContainer>\n      </main>\n    </Layout>\n  );\n};\nArchivePage.propTypes = {\n  location: PropTypes.object.isRequired,\n  data: PropTypes.object.isRequired,\n};\n\nexport default ArchivePage;\n\nexport const pageQuery = graphql`\n  {\n    allMarkdownRemark(\n      filter: { fileAbsolutePath: { regex: \"/content/projects/\" } }\n      sort: { fields: [frontmatter___date], order: DESC }\n    ) {\n      edges {\n        node {\n          frontmatter {\n            date\n            title\n            tech\n            github\n            external\n            ios\n            android\n            company\n          }\n          html\n        }\n      }\n    }\n  }\n`;\n"
  },
  {
    "path": "src/pages/index.js",
    "content": "import React from 'react';\nimport PropTypes from 'prop-types';\nimport styled from 'styled-components';\nimport { Layout, Hero, About, Jobs, Featured, Projects, Contact } from '@components';\n\nconst StyledMainContainer = styled.main`\n  counter-reset: section;\n`;\n\nconst IndexPage = ({ location }) => (\n  <Layout location={location}>\n    <StyledMainContainer className=\"fillHeight\">\n      <Hero />\n      <About />\n      <Jobs />\n      <Featured />\n      <Projects />\n      <Contact />\n    </StyledMainContainer>\n  </Layout>\n);\n\nIndexPage.propTypes = {\n  location: PropTypes.object.isRequired,\n};\n\nexport default IndexPage;\n"
  },
  {
    "path": "src/pages/pensieve/index.js",
    "content": "import React from 'react';\nimport { graphql, Link } from 'gatsby';\nimport kebabCase from 'lodash/kebabCase';\nimport PropTypes from 'prop-types';\nimport { Helmet } from 'react-helmet';\nimport styled from 'styled-components';\nimport { Layout } from '@components';\nimport { IconBookmark } from '@components/icons';\n\nconst StyledMainContainer = styled.main`\n  & > header {\n    margin-bottom: 100px;\n    text-align: center;\n\n    a {\n      &:hover,\n      &:focus {\n        cursor: url(\"data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='40' height='48' viewport='0 0 100 100' style='fill:black;font-size:24px;'><text y='50%'>⚡</text></svg>\")\n            20 0,\n          auto;\n      }\n    }\n  }\n\n  footer {\n    ${({ theme }) => theme.mixins.flexBetween};\n    width: 100%;\n    margin-top: 20px;\n  }\n`;\nconst StyledGrid = styled.ul`\n  ${({ theme }) => theme.mixins.resetList};\n  display: grid;\n  grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));\n  grid-gap: 15px;\n  margin-top: 50px;\n  position: relative;\n\n  @media (max-width: 1080px) {\n    grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));\n  }\n`;\nconst StyledPost = styled.li`\n  transition: var(--transition);\n  cursor: default;\n\n  @media (prefers-reduced-motion: no-preference) {\n    &:hover,\n    &:focus-within {\n      .post__inner {\n        transform: translateY(-7px);\n      }\n    }\n  }\n\n  a {\n    position: relative;\n    z-index: 1;\n  }\n\n  .post__inner {\n    ${({ theme }) => theme.mixins.boxShadow};\n    ${({ theme }) => theme.mixins.flexBetween};\n    flex-direction: column;\n    align-items: flex-start;\n    position: relative;\n    height: 100%;\n    padding: 2rem 1.75rem;\n    border-radius: var(--border-radius);\n    transition: var(--transition);\n    background-color: var(--light-navy);\n\n    header,\n    a {\n      width: 100%;\n    }\n  }\n\n  .post__icon {\n    ${({ theme }) => theme.mixins.flexBetween};\n    color: var(--green);\n    margin-bottom: 30px;\n    margin-left: -5px;\n\n    svg {\n      width: 40px;\n      height: 40px;\n    }\n  }\n\n  .post__title {\n    margin: 0 0 10px;\n    color: var(--lightest-slate);\n    font-size: var(--fz-xxl);\n\n    a {\n      position: static;\n\n      &:before {\n        content: '';\n        display: block;\n        position: absolute;\n        z-index: 0;\n        width: 100%;\n        height: 100%;\n        top: 0;\n        left: 0;\n      }\n    }\n  }\n\n  .post__desc {\n    color: var(--light-slate);\n    font-size: 17px;\n  }\n\n  .post__date {\n    color: var(--light-slate);\n    font-family: var(--font-mono);\n    font-size: var(--fz-xxs);\n    text-transform: uppercase;\n  }\n\n  ul.post__tags {\n    display: flex;\n    align-items: flex-end;\n    flex-wrap: wrap;\n    padding: 0;\n    margin: 0;\n    list-style: none;\n\n    li {\n      color: var(--green);\n      font-family: var(--font-mono);\n      font-size: var(--fz-xxs);\n      line-height: 1.75;\n\n      &:not(:last-of-type) {\n        margin-right: 15px;\n      }\n    }\n  }\n`;\n\nconst PensievePage = ({ location, data }) => {\n  const posts = data.allMarkdownRemark.edges;\n\n  return (\n    <Layout location={location}>\n      <Helmet title=\"Pensieve\" />\n\n      <StyledMainContainer>\n        <header>\n          <h1 className=\"big-heading\">Pensieve</h1>\n          <p className=\"subtitle\">\n            <a href=\"https://www.wizardingworld.com/writing-by-jk-rowling/pensieve\">\n              a collection of memories\n            </a>\n          </p>\n        </header>\n\n        <StyledGrid>\n          {posts.length > 0 &&\n            posts.map(({ node }, i) => {\n              const { frontmatter } = node;\n              const { title, description, slug, date, tags } = frontmatter;\n              const formattedDate = new Date(date).toLocaleDateString();\n\n              return (\n                <StyledPost key={i}>\n                  <div className=\"post__inner\">\n                    <header>\n                      <div className=\"post__icon\">\n                        <IconBookmark />\n                      </div>\n                      <h5 className=\"post__title\">\n                        <Link to={slug}>{title}</Link>\n                      </h5>\n                      <p className=\"post__desc\">{description}</p>\n                    </header>\n\n                    <footer>\n                      <span className=\"post__date\">{formattedDate}</span>\n                      <ul className=\"post__tags\">\n                        {tags.map((tag, i) => (\n                          <li key={i}>\n                            <Link to={`/pensieve/tags/${kebabCase(tag)}/`} className=\"inline-link\">\n                              #{tag}\n                            </Link>\n                          </li>\n                        ))}\n                      </ul>\n                    </footer>\n                  </div>\n                </StyledPost>\n              );\n            })}\n        </StyledGrid>\n      </StyledMainContainer>\n    </Layout>\n  );\n};\n\nPensievePage.propTypes = {\n  location: PropTypes.object.isRequired,\n  data: PropTypes.object.isRequired,\n};\n\nexport default PensievePage;\n\nexport const pageQuery = graphql`\n  {\n    allMarkdownRemark(\n      filter: { fileAbsolutePath: { regex: \"/content/posts/\" }, frontmatter: { draft: { ne: true } } }\n      sort: { fields: [frontmatter___date], order: DESC }\n    ) {\n      edges {\n        node {\n          frontmatter {\n            title\n            description\n            slug\n            date\n            tags\n            draft\n          }\n          html\n        }\n      }\n    }\n  }\n`;\n"
  },
  {
    "path": "src/pages/pensieve/tags.js",
    "content": "import React from 'react';\nimport { Link, graphql } from 'gatsby';\nimport kebabCase from 'lodash/kebabCase';\nimport PropTypes from 'prop-types';\nimport { Helmet } from 'react-helmet';\nimport styled from 'styled-components';\nimport { Layout } from '@components';\n\nconst StyledTagsContainer = styled.main`\n  max-width: 1000px;\n\n  h1 {\n    margin-bottom: 50px;\n  }\n  ul {\n    color: var(--light-slate);\n\n    li {\n      font-size: var(--fz-xxl);\n\n      a {\n        color: var(--light-slate);\n\n        .count {\n          color: var(--slate);\n          font-family: var(--font-mono);\n          font-size: var(--fz-md);\n        }\n      }\n    }\n  }\n`;\n\nconst TagsPage = ({\n  data: {\n    allMarkdownRemark: { group },\n  },\n  location,\n}) => (\n  <Layout location={location}>\n    <Helmet title=\"Tags\" />\n\n    <StyledTagsContainer>\n      <span className=\"breadcrumb\">\n        <span className=\"arrow\">&larr;</span>\n        <Link to=\"/pensieve\">All memories</Link>\n      </span>\n\n      <h1>Tags</h1>\n      <ul className=\"fancy-list\">\n        {group.map(tag => (\n          <li key={tag.fieldValue}>\n            <Link to={`/pensieve/tags/${kebabCase(tag.fieldValue)}/`} className=\"inline-link\">\n              {tag.fieldValue} <span className=\"count\">({tag.totalCount})</span>\n            </Link>\n          </li>\n        ))}\n      </ul>\n    </StyledTagsContainer>\n  </Layout>\n);\n\nTagsPage.propTypes = {\n  data: PropTypes.shape({\n    allMarkdownRemark: PropTypes.shape({\n      group: PropTypes.arrayOf(\n        PropTypes.shape({\n          fieldValue: PropTypes.string.isRequired,\n          totalCount: PropTypes.number.isRequired,\n        }).isRequired,\n      ),\n    }),\n    site: PropTypes.shape({\n      siteMetadata: PropTypes.shape({\n        title: PropTypes.string.isRequired,\n      }),\n    }),\n  }),\n  location: PropTypes.object,\n};\n\nexport default TagsPage;\n\nexport const pageQuery = graphql`\n  query {\n    allMarkdownRemark(limit: 2000, filter: { frontmatter: { draft: { ne: true } } }) {\n      group(field: frontmatter___tags) {\n        fieldValue\n        totalCount\n      }\n    }\n  }\n`;\n"
  },
  {
    "path": "src/styles/GlobalStyle.js",
    "content": "import { createGlobalStyle } from 'styled-components';\nimport fonts from './fonts';\nimport variables from './variables';\nimport TransitionStyles from './TransitionStyles';\nimport PrismStyles from './PrismStyles';\n\nconst GlobalStyle = createGlobalStyle`\n  ${fonts};\n  ${variables};\n\n  html {\n    box-sizing: border-box;\n    width: 100%;\n    scroll-behavior: smooth;\n  }\n\n  *,\n  *:before,\n  *:after {\n    box-sizing: inherit;\n  }\n\n  ::selection {\n    background-color: var(--lightest-navy);\n    color: var(--lightest-slate);\n  }\n\n  /* Provide basic, default focus styles.*/\n  :focus {\n    outline: 2px dashed var(--green);\n    outline-offset: 3px;\n  }\n\n  /*\n    Remove default focus styles for mouse users ONLY if\n    :focus-visible is supported on this platform.\n  */\n  :focus:not(:focus-visible) {\n    outline: none;\n    outline-offset: 0px;\n  }\n\n  /*\n    Optionally: If :focus-visible is supported on this\n    platform, provide enhanced focus styles for keyboard\n    focus.\n  */\n  :focus-visible {\n    outline: 2px dashed var(--green);\n    outline-offset: 3px;\n  }\n\n  /* Scrollbar Styles */\n  html {\n    scrollbar-width: thin;\n    scrollbar-color: var(--dark-slate) var(--navy);\n  }\n  ::-webkit-scrollbar {\n    width: 12px;\n  }\n  ::-webkit-scrollbar-track {\n    background: var(--navy);\n  }\n  ::-webkit-scrollbar-thumb {\n    background-color: var(--dark-slate);\n    border: 3px solid var(--navy);\n    border-radius: 10px;\n  }\n\n  body {\n    margin: 0;\n    width: 100%;\n    min-height: 100%;\n    overflow-x: hidden;\n    -moz-osx-font-smoothing: grayscale;\n    -webkit-font-smoothing: antialiased;\n    background-color: var(--navy);\n    color: var(--slate);\n    font-family: var(--font-sans);\n    font-size: var(--fz-xl);\n    line-height: 1.3;\n\n    @media (max-width: 480px) {\n      font-size: var(--fz-lg);\n    }\n\n    &.hidden {\n      overflow: hidden;\n    }\n\n    &.blur {\n      overflow: hidden;\n\n      header {\n        background-color: transparent;\n      }\n\n      #content > * {\n        filter: blur(5px) brightness(0.7);\n        transition: var(--transition);\n        pointer-events: none;\n        user-select: none;\n      }\n    }\n  }\n\n  #root {\n    min-height: 100vh;\n    display: grid;\n    grid-template-rows: 1fr auto;\n    grid-template-columns: 100%;\n  }\n\n  main {\n    margin: 0 auto;\n    width: 100%;\n    max-width: 1600px;\n    min-height: 100vh;\n    padding: 200px 150px;\n\n    @media (max-width: 1080px) {\n      padding: 200px 100px;\n    }\n    @media (max-width: 768px) {\n      padding: 150px 50px;\n    }\n    @media (max-width: 480px) {\n      padding: 125px 25px;\n    }\n\n    &.fillHeight {\n      padding: 0 150px;\n\n      @media (max-width: 1080px) {\n        padding: 0 100px;\n      }\n      @media (max-width: 768px) {\n        padding: 0 50px;\n      }\n      @media (max-width: 480px) {\n        padding: 0 25px;\n      }\n    }\n  }\n\n  section {\n    margin: 0 auto;\n    padding: 100px 0;\n    max-width: 1000px;\n\n    @media (max-width: 768px) {\n      padding: 80px 0;\n    }\n\n    @media (max-width: 480px) {\n      padding: 60px 0;\n    }\n  }\n\n  h1,\n  h2,\n  h3,\n  h4,\n  h5,\n  h6 {\n    margin: 0 0 10px 0;\n    font-weight: 600;\n    color: var(--lightest-slate);\n    line-height: 1.1;\n  }\n\n  .big-heading {\n    margin: 0;\n    font-size: clamp(40px, 8vw, 80px);\n  }\n\n  .medium-heading {\n    margin: 0;\n    font-size: clamp(40px, 8vw, 60px);\n  }\n\n  .numbered-heading {\n    display: flex;\n    align-items: center;\n    position: relative;\n    margin: 10px 0 40px;\n    width: 100%;\n    font-size: clamp(26px, 5vw, var(--fz-heading));\n    white-space: nowrap;\n\n    &:before {\n      position: relative;\n      bottom: 4px;\n      counter-increment: section;\n      content: '0' counter(section) '.';\n      margin-right: 10px;\n      color: var(--green);\n      font-family: var(--font-mono);\n      font-size: clamp(var(--fz-md), 3vw, var(--fz-xl));\n      font-weight: 400;\n\n      @media (max-width: 480px) {\n        margin-bottom: -3px;\n        margin-right: 5px;\n      }\n    }\n\n    &:after {\n      content: '';\n      display: block;\n      position: relative;\n      top: -5px;\n      width: 300px;\n      height: 1px;\n      margin-left: 20px;\n      background-color: var(--lightest-navy);\n\n      @media (max-width: 1080px) {\n        width: 200px;\n      }\n      @media (max-width: 768px) {\n        width: 100%;\n      }\n      @media (max-width: 600px) {\n        margin-left: 10px;\n      }\n    }\n  }\n\n  img,\n  svg,\n  .gatsby-image-wrapper {\n    width: 100%;\n    max-width: 100%;\n    vertical-align: middle;\n  }\n\n  img[alt=\"\"],\n  img:not([alt]) {\n    filter: blur(5px);\n  }\n\n  svg {\n    width: 100%;\n    height: 100%;\n    fill: currentColor;\n    vertical-align: middle;\n\n    &.feather {\n      fill: none;\n    }\n  }\n\n  a {\n    display: inline-block;\n    text-decoration: none;\n    text-decoration-skip-ink: auto;\n    color: inherit;\n    position: relative;\n    transition: var(--transition);\n\n    &:hover,\n    &:focus {\n      color: var(--green);\n    }\n\n    &.inline-link {\n      ${({ theme }) => theme.mixins.inlineLink};\n    }\n  }\n\n  button {\n    cursor: pointer;\n    border: 0;\n    border-radius: 0;\n  }\n\n  input, textarea {\n    border-radius: 0;\n    outline: 0;\n\n    &:focus {\n      outline: 0;\n    }\n    &:focus,\n    &:active {\n      &::placeholder {\n        opacity: 0.5;\n      }\n    }\n  }\n\n  p {\n    margin: 0 0 15px 0;\n\n    &:last-child,\n    &:last-of-type {\n      margin: 0;\n    }\n\n    & > a {\n      ${({ theme }) => theme.mixins.inlineLink};\n    }\n\n    & > code {\n      background-color: var(--light-navy);\n      color: var(--white);\n      font-size: var(--fz-sm);\n      border-radius: var(--border-radius);\n      padding: 0.3em 0.5em;\n    }\n  }\n\n  ul {\n    &.fancy-list {\n      padding: 0;\n      margin: 0;\n      list-style: none;\n      font-size: var(--fz-lg);\n      li {\n        position: relative;\n        padding-left: 30px;\n        margin-bottom: 10px;\n        &:before {\n          content: '▹';\n          position: absolute;\n          left: 0;\n          color: var(--green);\n        }\n      }\n    }\n  }\n\n  blockquote {\n    border-left-color: var(--green);\n    border-left-style: solid;\n    border-left-width: 1px;\n    margin-left: 0px;\n    margin-right: 0px;\n    padding-left: 1.5rem;\n\n    p {\n      font-style: italic;\n      font-size: 24px;\n    }\n  }\n\n  hr {\n    background-color: var(--lightest-navy);\n    height: 1px;\n    border-width: 0px;\n    border-style: initial;\n    border-color: initial;\n    border-image: initial;\n    margin: 1rem;\n  }\n\n  code {\n    font-family: var(--font-mono);\n    font-size: var(--fz-md);\n  }\n\n  .skip-to-content {\n    ${({ theme }) => theme.mixins.button};\n    position: absolute;\n    top: auto;\n    left: -999px;\n    width: 1px;\n    height: 1px;\n    overflow: hidden;\n    z-index: -99;\n\n    &:hover,\n    &:focus {\n      background-color: var(--green);\n      color: var(--navy);\n      top: 0;\n      left: 0;\n      width: auto;\n      height: auto;\n      overflow: auto;\n      z-index: 99;\n      box-shadow: none;\n      transform: none;\n    }\n  }\n\n  #logo {\n    color: var(--green);\n  }\n\n  .overline {\n    color: var(--green);\n    font-family: var(--font-mono);\n    font-size: var(--fz-md);\n    font-weight: 400;\n  }\n\n  .subtitle {\n    color: var(--green);\n    margin: 0 0 20px 0;\n    font-size: var(--fz-md);\n    font-family: var(--font-mono);\n    font-weight: 400;\n    line-height: 1.5;\n    @media (max-width: 1080px) {\n      font-size: var(--fz-sm);\n    }\n    @media (max-width: 768px) {\n      font-size: var(--fz-xs);\n    }\n\n    a {\n      ${({ theme }) => theme.mixins.inlineLink};\n      line-height: 1.5;\n    }\n  }\n\n  .breadcrumb {\n    display: flex;\n    align-items: center;\n    margin-bottom: 50px;\n    color: var(--green);\n\n    .arrow {\n      display: block;\n      margin-right: 10px;\n      padding-top: 4px;\n    }\n\n    a {\n      ${({ theme }) => theme.mixins.inlineLink};\n      font-family: var(--font-mono);\n      font-size: var(--fz-sm);\n      font-weight: 600;\n      line-height: 1.5;\n      text-transform: uppercase;\n      letter-spacing: 0.1em;\n    }\n  }\n\n  .gatsby-image-outer-wrapper {\n    height: 100%;\n  }\n\n  ${TransitionStyles};\n\n  ${PrismStyles};\n`;\n\nexport default GlobalStyle;\n"
  },
  {
    "path": "src/styles/PrismStyles.js",
    "content": "import { css } from 'styled-components';\n\nconst prismColors = {\n  bg: `#112340`,\n  lineHighlight: `#1d2d50`,\n  blue: `#5ccfe6`,\n  purple: `#c3a6ff`,\n  green: `#bae67e`,\n  yellow: `#ffd580`,\n  orange: `#ffae57`,\n  red: `#ef6b73`,\n  grey: `#a2aabc`,\n  comment: `#8695b799`,\n};\n\n// https://www.gatsbyjs.org/packages/gatsby-remark-prismjs\n\nconst PrismStyles = css`\n  /**\n  * Add back the container background-color, border-radius, padding, margin\n  * and overflow that we removed from <pre>.\n  */\n  .gatsby-highlight {\n    background-color: ${prismColors.bg};\n    color: ${prismColors.grey};\n    border-radius: var(--border-radius);\n    margin: 2em 0;\n    padding: 1.25em;\n    overflow: auto;\n    position: relative;\n    font-family: var(--font-mono);\n    font-size: var(--fz-md);\n  }\n\n  .gatsby-highlight code[class*='language-'],\n  .gatsby-highlight pre[class*='language-'] {\n    height: auto !important;\n    font-size: var(--fz-sm);\n    line-height: 1.5;\n    white-space: pre;\n    word-spacing: normal;\n    word-break: normal;\n    word-wrap: normal;\n    tab-size: 2;\n    hyphens: none;\n  }\n\n  /**\n  * Remove the default PrismJS theme background-color, border-radius, margin,\n  * padding and overflow.\n  * 1. Make the element just wide enough to fit its content.\n  * 2. Always fill the visible space in .gatsby-highlight.\n  * 3. Adjust the position of the line numbers\n  */\n  .gatsby-highlight pre[class*='language-'] {\n    background-color: transparent;\n    margin: 0;\n    padding: 0;\n    overflow: initial;\n    float: left; /* 1 */\n    min-width: 100%; /* 2 */\n    padding-top: 2em;\n  }\n\n  /* File names */\n  .gatsby-code-title {\n    padding: 1em 1.5em;\n    font-family: var(--font-mono);\n    font-size: var(--fz-xs);\n    background-color: ${prismColors.bg};\n    color: ${prismColors.grey};\n    border-top-left-radius: var(--border-radius);\n    border-top-right-radius: var(--border-radius);\n    border-bottom: 1px solid ${prismColors.lineHighlight};\n\n    & + .gatsby-highlight {\n      margin-top: 0;\n      border-top-left-radius: 0;\n      border-top-right-radius: 0;\n    }\n  }\n\n  /* Line highlighting */\n  .gatsby-highlight-code-line {\n    display: block;\n    background-color: ${prismColors.lineHighlight};\n    border-left: 2px solid var(--green);\n    padding-left: calc(1em + 2px);\n    padding-right: 1em;\n    margin-right: -1.35em;\n    margin-left: -1.35em;\n  }\n\n  /* Language badges */\n  .gatsby-highlight pre[class*='language-']::before {\n    background: var(--lightest-navy);\n    color: var(--white);\n    font-size: var(--fz-xxs);\n    font-family: var(--font-mono);\n    line-height: 1.5;\n    letter-spacing: 0.1em;\n    text-transform: uppercase;\n    border-radius: 0 0 3px 3px;\n    position: absolute;\n    top: 0;\n    left: 1.25rem;\n    padding: 0.25rem 0.5rem;\n  }\n  .gatsby-highlight pre[class='language-javascript']::before {\n    content: 'js';\n  }\n  .gatsby-highlight pre[class='language-js']::before {\n    content: 'js';\n  }\n  .gatsby-highlight pre[class='language-jsx']::before {\n    content: 'jsx';\n  }\n  .gatsby-highlight pre[class='language-graphql']::before {\n    content: 'GraphQL';\n  }\n  .gatsby-highlight pre[class='language-html']::before {\n    content: 'html';\n  }\n  .gatsby-highlight pre[class='language-css']::before {\n    content: 'css';\n  }\n  .gatsby-highlight pre[class='language-mdx']::before {\n    content: 'mdx';\n  }\n  .gatsby-highlight pre[class='language-shell']::before {\n    content: 'shell';\n  }\n  .gatsby-highlight pre[class='language-sh']::before {\n    content: 'sh';\n  }\n  .gatsby-highlight pre[class='language-bash']::before {\n    content: 'bash';\n  }\n  .gatsby-highlight pre[class='language-yaml']::before {\n    content: 'yaml';\n  }\n  .gatsby-highlight pre[class='language-markdown']::before {\n    content: 'md';\n  }\n  .gatsby-highlight pre[class='language-json']::before,\n  .gatsby-highlight pre[class='language-json5']::before {\n    content: 'json';\n  }\n  .gatsby-highlight pre[class='language-diff']::before {\n    content: 'diff';\n  }\n  .gatsby-highlight pre[class='language-text']::before {\n    content: 'text';\n  }\n  .gatsby-highlight pre[class='language-flow']::before {\n    content: 'flow';\n  }\n\n  /* Prism Styles */\n  .token {\n    display: inline;\n  }\n  .token.comment,\n  .token.block-comment,\n  .token.prolog,\n  .token.doctype,\n  .token.cdata {\n    color: ${prismColors.comment};\n  }\n  .token.punctuation {\n    color: ${prismColors.grey};\n  }\n  .token.namespace,\n  .token.deleted {\n    color: ${prismColors.red};\n  }\n  .token.function-name,\n  .token.function,\n  .token.class-name,\n  .token.constant,\n  .token.symbol {\n    color: ${prismColors.yellow};\n  }\n  .token.attr-name,\n  .token.operator,\n  .token.rule {\n    color: ${prismColors.orange};\n  }\n  .token.keyword,\n  .token.boolean,\n  .token.number,\n  .token.property {\n    color: ${prismColors.purple};\n  }\n  .token.tag,\n  .token.selector,\n  .token.important,\n  .token.atrule,\n  .token.builtin,\n  .token.entity,\n  .token.url {\n    color: ${prismColors.blue};\n  }\n  .token.string,\n  .token.char,\n  .token.attr-value,\n  .token.regex,\n  .token.variable,\n  .token.inserted {\n    color: ${prismColors.green};\n  }\n  .token.important,\n  .token.bold {\n    font-weight: 600;\n  }\n  .token.italic {\n    font-style: italic;\n  }\n  .token.entity {\n    cursor: help;\n  }\n  .namespace {\n    opacity: 0.7;\n  }\n`;\n\nexport default PrismStyles;\n"
  },
  {
    "path": "src/styles/TransitionStyles.js",
    "content": "import { css } from 'styled-components';\n\n// https://reactcommunity.org/react-transition-group/css-transition\n\nconst TransitionStyles = css`\n  /* Fade up */\n  .fadeup-enter {\n    opacity: 0.01;\n    transform: translateY(20px);\n    transition: opacity 300ms var(--easing), transform 300ms var(--easing);\n  }\n\n  .fadeup-enter-active {\n    opacity: 1;\n    transform: translateY(0px);\n    transition: opacity 300ms var(--easing), transform 300ms var(--easing);\n  }\n\n  /* Fade down */\n  .fadedown-enter {\n    opacity: 0.01;\n    transform: translateY(-20px);\n    transition: opacity 300ms var(--easing), transform 300ms var(--easing);\n  }\n\n  .fadedown-enter-active {\n    opacity: 1;\n    transform: translateY(0px);\n    transition: opacity 300ms var(--easing), transform 300ms var(--easing);\n  }\n\n  /* Fade */\n  .fade-enter {\n    opacity: 0;\n  }\n  .fade-enter-active {\n    opacity: 1;\n    transition: opacity 300ms var(--easing);\n  }\n  .fade-exit {\n    opacity: 1;\n  }\n  .fade-exit-active {\n    opacity: 0;\n    transition: opacity 300ms var(--easing);\n  }\n`;\n\nexport default TransitionStyles;\n"
  },
  {
    "path": "src/styles/fonts.js",
    "content": "import { css } from 'styled-components';\n\nimport CalibreRegularWoff from '@fonts/Calibre/Calibre-Regular.woff';\nimport CalibreRegularWoff2 from '@fonts/Calibre/Calibre-Regular.woff2';\nimport CalibreMediumWoff from '@fonts/Calibre/Calibre-Medium.woff';\nimport CalibreMediumWoff2 from '@fonts/Calibre/Calibre-Medium.woff2';\nimport CalibreSemiboldWoff from '@fonts/Calibre/Calibre-Semibold.woff';\nimport CalibreSemiboldWoff2 from '@fonts/Calibre/Calibre-Semibold.woff2';\n\nimport CalibreRegularItalicWoff from '@fonts/Calibre/Calibre-RegularItalic.woff';\nimport CalibreRegularItalicWoff2 from '@fonts/Calibre/Calibre-RegularItalic.woff2';\nimport CalibreMediumItalicWoff from '@fonts/Calibre/Calibre-MediumItalic.woff';\nimport CalibreMediumItalicWoff2 from '@fonts/Calibre/Calibre-MediumItalic.woff2';\nimport CalibreSemiboldItalicWoff from '@fonts/Calibre/Calibre-SemiboldItalic.woff';\nimport CalibreSemiboldItalicWoff2 from '@fonts/Calibre/Calibre-SemiboldItalic.woff2';\n\nimport SFMonoRegularWoff from '@fonts/SFMono/SFMono-Regular.woff';\nimport SFMonoRegularWoff2 from '@fonts/SFMono/SFMono-Regular.woff2';\nimport SFMonoSemiboldWoff from '@fonts/SFMono/SFMono-Semibold.woff';\nimport SFMonoSemiboldWoff2 from '@fonts/SFMono/SFMono-Semibold.woff2';\n\nimport SFMonoRegularItalicWoff from '@fonts/SFMono/SFMono-RegularItalic.woff';\nimport SFMonoRegularItalicWoff2 from '@fonts/SFMono/SFMono-RegularItalic.woff2';\nimport SFMonoSemiboldItalicWoff from '@fonts/SFMono/SFMono-SemiboldItalic.woff';\nimport SFMonoSemiboldItalicWoff2 from '@fonts/SFMono/SFMono-SemiboldItalic.woff2';\n\nconst calibreNormalWeights = {\n  400: [CalibreRegularWoff, CalibreRegularWoff2],\n  500: [CalibreMediumWoff, CalibreMediumWoff2],\n  600: [CalibreSemiboldWoff, CalibreSemiboldWoff2],\n};\n\nconst calibreItalicWeights = {\n  400: [CalibreRegularItalicWoff, CalibreRegularItalicWoff2],\n  500: [CalibreMediumItalicWoff, CalibreMediumItalicWoff2],\n  600: [CalibreSemiboldItalicWoff, CalibreSemiboldItalicWoff2],\n};\n\nconst sfMonoNormalWeights = {\n  400: [SFMonoRegularWoff, SFMonoRegularWoff2],\n  600: [SFMonoSemiboldWoff, SFMonoSemiboldWoff2],\n};\n\nconst sfMonoItalicWeights = {\n  400: [SFMonoRegularItalicWoff, SFMonoRegularItalicWoff2],\n  600: [SFMonoSemiboldItalicWoff, SFMonoSemiboldItalicWoff2],\n};\n\nconst calibre = {\n  name: 'Calibre',\n  normal: calibreNormalWeights,\n  italic: calibreItalicWeights,\n};\n\nconst sfMono = {\n  name: 'SF Mono',\n  normal: sfMonoNormalWeights,\n  italic: sfMonoItalicWeights,\n};\n\nconst createFontFaces = (family, style = 'normal') => {\n  let styles = '';\n\n  for (const [weight, formats] of Object.entries(family[style])) {\n    const woff = formats[0];\n    const woff2 = formats[1];\n\n    styles += `\n      @font-face {\n        font-family: '${family.name}';\n        src: url(${woff2}) format('woff2'),\n            url(${woff}) format('woff');\n        font-weight: ${weight};\n        font-style: ${style};\n        font-display: auto;\n      }\n    `;\n  }\n\n  return styles;\n};\n\nconst calibreNormal = createFontFaces(calibre);\nconst calibreItalic = createFontFaces(calibre, 'italic');\n\nconst sfMonoNormal = createFontFaces(sfMono);\nconst sfMonoItalic = createFontFaces(sfMono, 'italic');\n\nconst Fonts = css`\n  ${calibreNormal + calibreItalic + sfMonoNormal + sfMonoItalic}\n`;\n\nexport default Fonts;\n"
  },
  {
    "path": "src/styles/index.js",
    "content": "export { default as theme } from './theme';\nexport { default as GlobalStyle } from './GlobalStyle';\nexport { default as mixins } from './mixins';\n"
  },
  {
    "path": "src/styles/mixins.js",
    "content": "import { css } from 'styled-components';\n\nconst button = css`\n  color: var(--green);\n  background-color: transparent;\n  border: 1px solid var(--green);\n  border-radius: var(--border-radius);\n  font-size: var(--fz-xs);\n  font-family: var(--font-mono);\n  line-height: 1;\n  text-decoration: none;\n  padding: 1.25rem 1.75rem;\n  transition: var(--transition);\n\n  &:hover,\n  &:focus-visible {\n    outline: none;\n    box-shadow: 4px 4px 0 0 var(--green);\n    transform: translate(-5px, -5px);\n  }\n  &:after {\n    display: none !important;\n  }\n`;\n\nconst mixins = {\n  flexCenter: css`\n    display: flex;\n    justify-content: center;\n    align-items: center;\n  `,\n\n  flexBetween: css`\n    display: flex;\n    justify-content: space-between;\n    align-items: center;\n  `,\n\n  link: css`\n    display: inline-block;\n    text-decoration: none;\n    text-decoration-skip-ink: auto;\n    color: inherit;\n    position: relative;\n    transition: var(--transition);\n\n    &:hover,\n    &:focus-visible {\n      color: var(--green);\n      outline: 0;\n    }\n  `,\n\n  inlineLink: css`\n    display: inline-block;\n    position: relative;\n    color: var(--green);\n    transition: var(--transition);\n\n    &:hover,\n    &:focus-visible {\n      color: var(--green);\n      outline: 0;\n      &:after {\n        width: 100%;\n      }\n      & > * {\n        color: var(--green) !important;\n        transition: var(--transition);\n      }\n    }\n    &:after {\n      content: '';\n      display: block;\n      width: 0;\n      height: 1px;\n      position: relative;\n      bottom: 0.37em;\n      background-color: var(--green);\n      opacity: 0.5;\n      @media (prefers-reduced-motion: no-preference) {\n        transition: var(--transition);\n      }\n    }\n  `,\n\n  button,\n\n  smallButton: css`\n    color: var(--green);\n    background-color: transparent;\n    border: 1px solid var(--green);\n    border-radius: var(--border-radius);\n    padding: 0.75rem 1rem;\n    font-size: var(--fz-xs);\n    font-family: var(--font-mono);\n    line-height: 1;\n    text-decoration: none;\n    transition: var(--transition);\n\n    &:hover,\n    &:focus-visible {\n      outline: none;\n      box-shadow: 3px 3px 0 0 var(--green);\n      transform: translate(-4px, -4px);\n    }\n    &:after {\n      display: none !important;\n    }\n  `,\n\n  bigButton: css`\n    color: var(--green);\n    background-color: transparent;\n    border: 1px solid var(--green);\n    border-radius: var(--border-radius);\n    padding: 1.25rem 1.75rem;\n    font-size: var(--fz-sm);\n    font-family: var(--font-mono);\n    line-height: 1;\n    text-decoration: none;\n    transition: var(--transition);\n\n    &:hover,\n    &:focus-visible {\n      outline: none;\n      box-shadow: 4px 4px 0 0 var(--green);\n      transform: translate(-5px, -5px);\n    }\n    &:after {\n      display: none !important;\n    }\n  `,\n\n  boxShadow: css`\n    box-shadow: 0 10px 30px -15px var(--navy-shadow);\n    transition: var(--transition);\n\n    &:hover,\n    &:focus-visible {\n      box-shadow: 0 20px 30px -15px var(--navy-shadow);\n    }\n  `,\n\n  fancyList: css`\n    padding: 0;\n    margin: 0;\n    list-style: none;\n    font-size: var(--fz-lg);\n    li {\n      position: relative;\n      padding-left: 30px;\n      margin-bottom: 10px;\n      &:before {\n        content: '▹';\n        position: absolute;\n        left: 0;\n        color: var(--green);\n      }\n    }\n  `,\n\n  resetList: css`\n    list-style: none;\n    padding: 0;\n    margin: 0;\n  `,\n};\n\nexport default mixins;\n"
  },
  {
    "path": "src/styles/theme.js",
    "content": "import mixins from './mixins';\n\nconst theme = {\n  bp: {\n    mobileS: `max-width: 330px`,\n    mobileM: `max-width: 400px`,\n    mobileL: `max-width: 480px`,\n    tabletS: `max-width: 600px`,\n    tabletL: `max-width: 768px`,\n    desktopXS: `max-width: 900px`,\n    desktopS: `max-width: 1080px`,\n    desktopM: `max-width: 1200px`,\n    desktopL: `max-width: 1400px`,\n  },\n\n  mixins,\n};\n\nexport default theme;\n"
  },
  {
    "path": "src/styles/variables.js",
    "content": "import { css } from 'styled-components';\n\nconst variables = css`\n  :root {\n    --dark-navy: #020c1b;\n    --navy: #0a192f;\n    --light-navy: #112240;\n    --lightest-navy: #233554;\n    --navy-shadow: rgba(2, 12, 27, 0.7);\n    --dark-slate: #495670;\n    --slate: #8892b0;\n    --light-slate: #a8b2d1;\n    --lightest-slate: #ccd6f6;\n    --white: #e6f1ff;\n    --green: #64ffda;\n    --green-tint: rgba(100, 255, 218, 0.1);\n    --pink: #f57dff;\n    --blue: #57cbff;\n\n    --font-sans: 'Calibre', 'Inter', 'San Francisco', 'SF Pro Text', -apple-system, system-ui,\n      sans-serif;\n    --font-mono: 'SF Mono', 'Fira Code', 'Fira Mono', 'Roboto Mono', monospace;\n\n    --fz-xxs: 12px;\n    --fz-xs: 13px;\n    --fz-sm: 14px;\n    --fz-md: 16px;\n    --fz-lg: 18px;\n    --fz-xl: 20px;\n    --fz-xxl: 22px;\n    --fz-heading: 32px;\n\n    --border-radius: 4px;\n    --nav-height: 100px;\n    --nav-scroll-height: 70px;\n\n    --tab-height: 42px;\n    --tab-width: 120px;\n\n    --easing: cubic-bezier(0.645, 0.045, 0.355, 1);\n    --transition: all 0.25s cubic-bezier(0.645, 0.045, 0.355, 1);\n\n    --hamburger-width: 30px;\n\n    --ham-before: top 0.1s ease-in 0.25s, opacity 0.1s ease-in;\n    --ham-before-active: top 0.1s ease-out, opacity 0.1s ease-out 0.12s;\n    --ham-after: bottom 0.1s ease-in 0.25s, transform 0.22s cubic-bezier(0.55, 0.055, 0.675, 0.19);\n    --ham-after-active: bottom 0.1s ease-out,\n      transform 0.22s cubic-bezier(0.215, 0.61, 0.355, 1) 0.12s;\n  }\n`;\n\nexport default variables;\n"
  },
  {
    "path": "src/templates/post.js",
    "content": "import React from 'react';\nimport { graphql, Link } from 'gatsby';\nimport kebabCase from 'lodash/kebabCase';\nimport PropTypes from 'prop-types';\nimport { Helmet } from 'react-helmet';\nimport styled from 'styled-components';\nimport { Layout } from '@components';\n\nconst StyledPostContainer = styled.main`\n  max-width: 1000px;\n`;\nconst StyledPostHeader = styled.header`\n  margin-bottom: 50px;\n  .tag {\n    margin-right: 10px;\n  }\n`;\nconst StyledPostContent = styled.div`\n  margin-bottom: 100px;\n  h1,\n  h2,\n  h3,\n  h4,\n  h5,\n  h6 {\n    margin: 2em 0 1em;\n  }\n\n  p {\n    margin: 1em 0;\n    line-height: 1.5;\n    color: var(--light-slate);\n  }\n\n  a {\n    ${({ theme }) => theme.mixins.inlineLink};\n  }\n\n  code {\n    background-color: var(--lightest-navy);\n    color: var(--lightest-slate);\n    border-radius: var(--border-radius);\n    font-size: var(--fz-sm);\n    padding: 0.2em 0.4em;\n  }\n\n  pre code {\n    background-color: transparent;\n    padding: 0;\n  }\n`;\n\nconst PostTemplate = ({ data, location }) => {\n  const { frontmatter, html } = data.markdownRemark;\n  const { title, date, tags } = frontmatter;\n\n  return (\n    <Layout location={location}>\n      <Helmet title={title} />\n\n      <StyledPostContainer>\n        <span className=\"breadcrumb\">\n          <span className=\"arrow\">&larr;</span>\n          <Link to=\"/pensieve\">All memories</Link>\n        </span>\n\n        <StyledPostHeader>\n          <h1 className=\"medium-heading\">{title}</h1>\n          <p className=\"subtitle\">\n            <time>\n              {new Date(date).toLocaleDateString('en-US', {\n                year: 'numeric',\n                month: 'long',\n                day: 'numeric',\n              })}\n            </time>\n            <span>&nbsp;&mdash;&nbsp;</span>\n            {tags &&\n              tags.length > 0 &&\n              tags.map((tag, i) => (\n                <Link key={i} to={`/pensieve/tags/${kebabCase(tag)}/`} className=\"tag\">\n                  #{tag}\n                </Link>\n              ))}\n          </p>\n        </StyledPostHeader>\n\n        <StyledPostContent dangerouslySetInnerHTML={{ __html: html }} />\n      </StyledPostContainer>\n    </Layout>\n  );\n};\n\nexport default PostTemplate;\n\nPostTemplate.propTypes = {\n  data: PropTypes.object,\n  location: PropTypes.object,\n};\n\nexport const pageQuery = graphql`\n  query($path: String!) {\n    markdownRemark(frontmatter: { slug: { eq: $path } }) {\n      html\n      frontmatter {\n        title\n        description\n        date\n        slug\n        tags\n      }\n    }\n  }\n`;\n"
  },
  {
    "path": "src/templates/tag.js",
    "content": "import React from 'react';\nimport { Link, graphql } from 'gatsby';\nimport kebabCase from 'lodash/kebabCase';\nimport PropTypes from 'prop-types';\nimport { Helmet } from 'react-helmet';\nimport styled from 'styled-components';\nimport { Layout } from '@components';\n\nconst StyledTagsContainer = styled.main`\n  max-width: 1000px;\n\n  a {\n    ${({ theme }) => theme.mixins.inlineLink};\n  }\n\n  h1 {\n    ${({ theme }) => theme.mixins.flexBetween};\n    margin-bottom: 50px;\n\n    a {\n      font-size: var(--fz-lg);\n      font-weight: 400;\n    }\n  }\n\n  ul {\n    li {\n      font-size: 24px;\n      h2 {\n        font-size: inherit;\n        margin: 0;\n        a {\n          color: var(--light-slate);\n        }\n      }\n      .subtitle {\n        color: var(--slate);\n        font-size: var(--fz-sm);\n\n        .tag {\n          margin-right: 10px;\n        }\n      }\n    }\n  }\n`;\n\nconst TagTemplate = ({ pageContext, data, location }) => {\n  const { tag } = pageContext;\n  const { edges } = data.allMarkdownRemark;\n\n  return (\n    <Layout location={location}>\n      <Helmet title={`Tagged: #${tag}`} />\n\n      <StyledTagsContainer>\n        <span className=\"breadcrumb\">\n          <span className=\"arrow\">&larr;</span>\n          <Link to=\"/pensieve\">All memories</Link>\n        </span>\n\n        <h1>\n          <span>#{tag}</span>\n          <span>\n            <Link to=\"/pensieve/tags\">View all tags</Link>\n          </span>\n        </h1>\n\n        <ul className=\"fancy-list\">\n          {edges.map(({ node }) => {\n            const { title, slug, date, tags } = node.frontmatter;\n            return (\n              <li key={slug}>\n                <h2>\n                  <Link to={slug}>{title}</Link>\n                </h2>\n                <p className=\"subtitle\">\n                  <time>\n                    {new Date(date).toLocaleDateString('en-US', {\n                      year: 'numeric',\n                      month: 'long',\n                      day: 'numeric',\n                    })}\n                  </time>\n                  <span>&nbsp;&mdash;&nbsp;</span>\n                  {tags &&\n                    tags.length > 0 &&\n                    tags.map((tag, i) => (\n                      <Link key={i} to={`/pensieve/tags/${kebabCase(tag)}/`} className=\"tag\">\n                        #{tag}\n                      </Link>\n                    ))}\n                </p>\n              </li>\n            );\n          })}\n        </ul>\n      </StyledTagsContainer>\n    </Layout>\n  );\n};\n\nexport default TagTemplate;\n\nTagTemplate.propTypes = {\n  pageContext: PropTypes.shape({\n    tag: PropTypes.string.isRequired,\n  }),\n  data: PropTypes.shape({\n    allMarkdownRemark: PropTypes.shape({\n      totalCount: PropTypes.number.isRequired,\n      edges: PropTypes.arrayOf(\n        PropTypes.shape({\n          node: PropTypes.shape({\n            frontmatter: PropTypes.shape({\n              title: PropTypes.string.isRequired,\n            }),\n          }),\n        }).isRequired,\n      ),\n    }),\n  }),\n  location: PropTypes.object,\n};\n\nexport const pageQuery = graphql`\n  query($tag: String!) {\n    allMarkdownRemark(\n      limit: 2000\n      sort: { fields: [frontmatter___date], order: DESC }\n      filter: { frontmatter: { tags: { in: [$tag] } } }\n    ) {\n      totalCount\n      edges {\n        node {\n          frontmatter {\n            title\n            description\n            date\n            slug\n            tags\n          }\n        }\n      }\n    }\n  }\n`;\n"
  },
  {
    "path": "src/utils/index.js",
    "content": "export const hex2rgba = (hex, alpha = 1) => {\n  const [r, g, b] = hex.match(/\\w\\w/g).map(x => parseInt(x, 16));\n  return `rgba(${r},${g},${b},${alpha})`;\n};\n\nexport const navDelay = 1000;\nexport const loaderDelay = 2000;\n\nexport const KEY_CODES = {\n  ARROW_LEFT: 'ArrowLeft',\n  ARROW_LEFT_IE11: 'Left',\n  ARROW_RIGHT: 'ArrowRight',\n  ARROW_RIGHT_IE11: 'Right',\n  ARROW_UP: 'ArrowUp',\n  ARROW_UP_IE11: 'Up',\n  ARROW_DOWN: 'ArrowDown',\n  ARROW_DOWN_IE11: 'Down',\n  ESCAPE: 'Escape',\n  ESCAPE_IE11: 'Esc',\n  TAB: 'Tab',\n  SPACE: ' ',\n  SPACE_IE11: 'Spacebar',\n  ENTER: 'Enter',\n};\n"
  },
  {
    "path": "src/utils/sr.js",
    "content": "import ScrollReveal from 'scrollreveal';\n\nconst isSSR = typeof window === 'undefined';\nconst sr = isSSR ? null : ScrollReveal();\n\nexport default sr;\n"
  }
]