diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 8d01c840..a9c166f6 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -13,14 +13,14 @@ }, // Use 'postCreateCommand' to run commands after the container is created. - "postStartCommand": "yarn install --frozen-lockfile && git update-index --assume-unchanged data/manifest.json && git config pull.rebase true", + "postStartCommand": "yarn install --frozen-lockfile && ./node_modules/.bin/next telemetry disable", // Use 'forwardPorts' to make a list of ports inside the container available locally. - "forwardPorts": [1337], + "forwardPorts": [3000], "portsAttributes": { - "1337": { - "label": "Webpack", + "3000": { + "label": "next dev", "onAutoForward": "notify" } }, @@ -32,12 +32,11 @@ // Add the IDs of extensions you want installed when the container is created. "extensions": [ - "editorconfig.editorconfig", - "budparr.language-hugo-vscode", "dbaeumer.vscode-eslint", - "davidanson.vscode-markdownlint", + "divlo.vscode-styled-jsx-languageserver", + "divlo.vscode-styled-jsx-syntax", + "editorconfig.editorconfig", "esbenp.prettier-vscode", - "stylelint.vscode-stylelint", - "ms-vscode.wordcount" + "stylelint.vscode-stylelint" ] } diff --git a/.env.example b/.env.example deleted file mode 100644 index 9061586a..00000000 --- a/.env.example +++ /dev/null @@ -1,16 +0,0 @@ -AIRTABLE_API_KEY= -AIRTABLE_BASE= -FAUNADB_SERVER_SECRET= -GH_PUBLIC_TOKEN= -HCAPTCHA_SECRET_KEY= -HCAPTCHA_SITE_KEY= -LHCI_ADMIN_TOKEN= -LHCI_GITHUB_APP_TOKEN= -LHCI_TOKEN= -PERCY_TOKEN= -SENTRY_AUTH_TOKEN= -SENTRY_DSN= -SPOTIFY_CLIENT_ID= -SPOTIFY_CLIENT_SECRET= -SPOTIFY_REFRESH_TOKEN= -WEBMENTIONS_TOKEN= diff --git a/.eslintrc.cjs b/.eslintrc.cjs deleted file mode 100644 index b36ea101..00000000 --- a/.eslintrc.cjs +++ /dev/null @@ -1,67 +0,0 @@ -module.exports = { - root: true, - extends: [ - "@jakejarvis/eslint-config", - "plugin:compat/recommended", - "plugin:react/recommended", - "plugin:react-hooks/recommended", - "plugin:jsx-a11y/recommended", - "plugin:prettier/recommended", - ], - plugins: ["jsx-a11y", "prettier"], - parser: "@babel/eslint-parser", - parserOptions: { - ecmaVersion: 2020, - ecmaFeatures: { - modules: true, - impliedStrict: true, - jsx: true, - }, - jsxPragma: null, - }, - env: { - browser: true, - es6: true, - node: true, - }, - settings: { - polyfills: ["fetch", "Promise"], - react: { - pragma: "h", - version: "17.0", - }, - }, - rules: { - // Mostly cherry-picked from eslint-config-preact: - // https://github.com/preactjs/eslint-config-preact/blob/v1.3.0/index.js - "react/display-name": ["warn", { ignoreTranspilerName: false }], - "react/jsx-key": ["error", { checkFragmentShorthand: true }], - "react/jsx-no-bind": [ - "warn", - { - ignoreRefs: true, - allowFunctions: true, - allowArrowFunctions: true, - }, - ], - "react/jsx-no-comment-textnodes": "error", - "react/jsx-no-duplicate-props": "error", - "react/jsx-no-target-blank": "error", - "react/jsx-no-undef": "error", - "react/jsx-tag-spacing": ["error", { beforeSelfClosing: "always" }], - "react/jsx-uses-react": "error", - "react/jsx-uses-vars": "error", - "react/no-danger": "warn", - "react/no-deprecated": "error", - "react/no-unknown-property": ["error", { ignore: ["class"] }], // className in react == class in preact - "react/prefer-es6-class": "error", - "react/prefer-stateless-function": "warn", - "react/prop-types": "off", - "react/react-in-jsx-scope": "off", - "react/require-render-return": "error", - "react/self-closing-comp": "error", - "react-hooks/exhaustive-deps": "warn", - "react-hooks/rules-of-hooks": "error", - }, - ignorePatterns: ["public/**", "static/assets/**"], -}; diff --git a/.eslintrc.js b/.eslintrc.js new file mode 100644 index 00000000..a79b229c --- /dev/null +++ b/.eslintrc.js @@ -0,0 +1,8 @@ +module.exports = { + root: true, + extends: ["@jakejarvis/eslint-config", "next/core-web-vitals", "plugin:prettier/recommended"], + plugins: ["prettier"], + rules: { + "react/no-unescaped-entities": "off", + }, +}; diff --git a/.gitattributes b/.gitattributes index 62b89be3..7ee25328 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,6 +1,2 @@ # Set default behavior to automatically normalize line endings. * text=auto eol=lf - -# Mark cached Hugo resources/assets as generated to suppress in Git diffs and repo stats. -# https://github.com/github/linguist/blob/master/README.md#generated-code -resources/_gen/** linguist-generated=true diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml deleted file mode 100644 index d367d5d4..00000000 --- a/.github/workflows/ci.yml +++ /dev/null @@ -1,29 +0,0 @@ -name: CI - -on: - push: - branches: - - main - pull_request: - -jobs: - test: - runs-on: ubuntu-latest - if: "!contains(github.event.head_commit.message, '[skip ci]')" - steps: - - uses: actions/checkout@v2 - with: - fetch-depth: 1 - - uses: actions/setup-node@v2 - with: - node-version: 16.x - cache: yarn - - run: yarn install --frozen-lockfile - - run: yarn build - env: - SKIP_OPTIMIZE_IMAGES: true - - run: yarn lint - - run: npx -p @percy/cli percy snapshot ./public - if: github.actor == 'jakejarvis' - env: - PERCY_TOKEN: ${{ secrets.PERCY_TOKEN }} diff --git a/.github/workflows/post-deploy.yml b/.github/workflows/post-deploy.yml deleted file mode 100644 index d58eb34a..00000000 --- a/.github/workflows/post-deploy.yml +++ /dev/null @@ -1,61 +0,0 @@ -name: Release - -on: deployment_status - -jobs: - release: - runs-on: ubuntu-latest - if: github.event.deployment_status.state == 'success' - steps: - - uses: actions/checkout@v2 - with: - fetch-depth: 2 - - uses: actions/setup-node@v2 - with: - node-version: 16.x - - run: | - LOWERENV=$(echo "${{ github.event.deployment_status.environment }}" | tr "[:upper:]" "[:lower:]") - echo "NORMALIZED_ENV=${LOWERENV}" >> $GITHUB_ENV - - uses: getsentry/action-release@v1 - if: ${{ env.NORMALIZED_ENV == 'production' }} - env: - SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }} - SENTRY_ORG: jakejarvis - SENTRY_PROJECT: jarvis - with: - environment: ${{ env.NORMALIZED_ENV }} - - uses: browser-actions/setup-chrome@latest - with: - chrome-version: stable - - run: PUPPETEER_SKIP_CHROMIUM_DOWNLOAD=true npm install -g @lhci/cli - - run: | - chrome --version - lhci --version - curl https://lhci.jrvs.io/version || true - echo "ref: ${{ github.event.deployment.ref }}" - echo "env: ${{ env.NORMALIZED_ENV }}" - echo "url: ${{ github.event.deployment_status.target_url }}" - - if: ${{ env.NORMALIZED_ENV == 'production' }} - run: | - echo "BASE_DEPLOY_URL=https://jarv.is" >> $GITHUB_ENV - echo "LHCI_EXTRA_FLAGS=" >> $GITHUB_ENV - echo "LHCI_BUILD_CONTEXT__CURRENT_BRANCH=main" >> $GITHUB_ENV - - if: ${{ env.NORMALIZED_ENV == 'preview' }} - run: | - echo "BASE_DEPLOY_URL=${{ github.event.deployment_status.target_url }}" >> $GITHUB_ENV - echo "LHCI_EXTRA_FLAGS=--assert.assertions.is-crawlable=off --assert.assertions.canonical=off" >> $GITHUB_ENV - echo "LHCI_BUILD_CONTEXT__CURRENT_BRANCH=${{ github.event.deployment.ref }}" >> $GITHUB_ENV - - continue-on-error: true - env: - LHCI_TOKEN: ${{ secrets.LHCI_TOKEN }} - LHCI_GITHUB_APP_TOKEN: ${{ secrets.LHCI_GITHUB_APP_TOKEN }} - run: | - lhci autorun ${{ env.LHCI_EXTRA_FLAGS }} \ - --collect.url=${{ env.BASE_DEPLOY_URL }}/ \ - --collect.url=${{ env.BASE_DEPLOY_URL }}/notes/how-to-pull-request-fork-github/ \ - --collect.url=${{ env.BASE_DEPLOY_URL }}/projects/ \ - --collect.url=${{ env.BASE_DEPLOY_URL }}/contact/ - - uses: actions/upload-artifact@v2 - with: - name: lhci-results - path: ./.lighthouseci diff --git a/.github/workflows/purge-artifacts.yml b/.github/workflows/purge-artifacts.yml deleted file mode 100644 index c5dc6c6c..00000000 --- a/.github/workflows/purge-artifacts.yml +++ /dev/null @@ -1,15 +0,0 @@ -name: Purge old artifacts - -on: - schedule: - - cron: "0 9 * * 3" # every Wednesday at 9 AM UTC == 5 AM EDT - workflow_dispatch: - -jobs: - purge-artifacts: - runs-on: ubuntu-latest - steps: - - uses: kolpav/purge-artifacts-action@v1 - with: - token: ${{ secrets.GITHUB_TOKEN }} - expire-in: 0 diff --git a/.gitignore b/.gitignore index 1923c663..d1812d51 100644 --- a/.gitignore +++ b/.gitignore @@ -1,15 +1,10 @@ -# Hugo artifacts -public/ -builds/ -_vendor/ -assets/jsconfig.json -hugo_stats.json -.hugo_build.lock +# next.js +.next/ +out/ -# webpack output -static/assets/ -data/manifest.json -webpack_stats.json +# generated at build-time by next-sitemap +public/robots.txt +public/sitemap.xml # node/npm/yarn node_modules/ @@ -18,12 +13,10 @@ npm-debug.log* yarn-debug.log* yarn-error.log* -# Lighthouse CI -.lighthouseci/ - # here be secrets *.env* !.env.example +*.pem # macOS/iCloud junk .DS_Store @@ -34,5 +27,11 @@ yarn-error.log* *.icloud .nosync -# local Vercel config +# local env files +.env.local +.env.development.local +.env.test.local +.env.production.local + +# vercel .vercel diff --git a/.lighthouserc.json b/.lighthouserc.json deleted file mode 100644 index af46a19b..00000000 --- a/.lighthouserc.json +++ /dev/null @@ -1,38 +0,0 @@ -{ - "ci": { - "collect": { - "numberOfRuns": 3, - "settings": { - "preset": "desktop", - "chromeFlags": "--no-sandbox" - } - }, - "assert": { - "preset": "lighthouse:no-pwa", - "assertions": { - "color-contrast": "warn", - "csp-xss": "warn", - "link-text": "warn", - "long-tasks": "warn", - "total-byte-weight": ["warn", { "minScore": 0.9 }], - "unminified-css": "warn", - "unminified-javascript": "warn", - "unsized-images": "warn", - "unused-css-rules": "warn", - "unused-javascript": "warn", - "uses-optimized-images": "warn", - "uses-rel-preconnect": "warn", - "valid-source-maps": "warn", - "modern-image-formats": "off", - "offscreen-images": "off", - "uses-long-cache-ttl": "off", - "uses-responsive-images": "off" - }, - "includePassedAssertions": true - }, - "upload": { - "target": "lhci", - "serverBaseUrl": "https://lhci.jrvs.io" - } - } -} diff --git a/.markdownlintrc.json b/.markdownlintrc.json deleted file mode 100644 index 56ad99ad..00000000 --- a/.markdownlintrc.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "default": true, - "code-block-style": { "style": "fenced" }, - "code-fence-style": { "style": "backtick" }, - "fenced-code-language": true, - "heading-style": { "style": "atx" }, - "hr-style": { "style": "---" }, - "line-length": false, - "no-empty-links": false, - "no-inline-html": { - "allowed_elements": ["a", "span", "img", "svg", "path", "polygon", "circle", "g", "sup"] - }, - "no-trailing-punctuation": false, - "single-h1": false, - "whitespace": false -} diff --git a/.percy.yml b/.percy.yml deleted file mode 100644 index dedc1be2..00000000 --- a/.percy.yml +++ /dev/null @@ -1,34 +0,0 @@ -version: 2 -snapshot: - widths: - - 450 - - 1050 - enable-javascript: true - percy-css: > - .embed, - iframe, - video, - img[src$=".gif"], - .loading, - #meta-hits, - #contact-form-captcha { - display: none !important; - } - - *, - ::before, - ::after { - animation: none !important; - transition-duration: 0s !important; - transition-delay: 0s !important; - } -static: - include: - - "index.html" - - "uses/index.html" - - "notes/how-to-pull-request-fork-github/index.html" - - "notes/shodan-search-queries/index.html" - - "contact/index.html" -discovery: - network-idle-timeout: 750 - disable-cache: true diff --git a/.prettierignore b/.prettierignore index 1fc678c9..e3e7e87f 100644 --- a/.prettierignore +++ b/.prettierignore @@ -1,20 +1,3 @@ -# Hugo/Webpack stuff that doesn't play well with Prettier -assets/**/vendor/ -assets/jsconfig.json -layouts/ -static/ -hugo_stats.json -webpack_stats.json -data/manifest.json - -# output from Hugo -public/ -builds/ -resources/ -_vendor/ - -# miscellaneous -.vercel/ -.lighthouseci/ -.vscode/ -/content/notes/dark-mode/example/ +/.next +/out +/public diff --git a/.stylelintrc.json b/.stylelintrc.json index 194688bf..5539420c 100644 --- a/.stylelintrc.json +++ b/.stylelintrc.json @@ -1,17 +1,11 @@ { - "extends": ["stylelint-config-standard-scss", "stylelint-config-sass-guidelines", "stylelint-prettier/recommended"], - "plugins": ["stylelint-no-unsupported-browser-features", "stylelint-prettier"], + "extends": ["stylelint-config-standard-scss", "stylelint-prettier/recommended"], + "plugins": ["stylelint-scss", "stylelint-prettier"], "rules": { "color-hex-length": "long", - "max-nesting-depth": 6, "no-descending-specificity": null, - "order/order": null, - "order/properties-alphabetical-order": null, - "plugin/no-unsupported-browser-features": [true, { "severity": "warning", "ignore": ["flexbox"] }], - "selector-max-compound-selectors": null, - "selector-max-id": null, - "selector-no-qualifying-type": null, - "shorthand-property-no-redundant-values": null + "rule-empty-line-before": null, + "selector-class-pattern": null }, - "ignoreFiles": ["public/**", "static/assets/**", "node_modules/**"] + "ignoreFiles": ["public/**"] } diff --git a/.vscode/extensions.json b/.vscode/extensions.json index 648d8a9d..df069eb0 100644 --- a/.vscode/extensions.json +++ b/.vscode/extensions.json @@ -1,13 +1,10 @@ { - // See https://go.microsoft.com/fwlink/?LinkId=827846 - // for the documentation about the extensions.json format "recommendations": [ - "editorconfig.editorconfig", - "budparr.language-hugo-vscode", "dbaeumer.vscode-eslint", - "davidanson.vscode-markdownlint", + "divlo.vscode-styled-jsx-languageserver", + "divlo.vscode-styled-jsx-syntax", + "editorconfig.editorconfig", "esbenp.prettier-vscode", - "stylelint.vscode-stylelint", - "ms-vscode.wordcount" + "stylelint.vscode-stylelint" ] } diff --git a/.vscode/settings.json b/.vscode/settings.json index f8c6607e..67ce2c23 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -5,29 +5,18 @@ "files.eol": "\n", "files.insertFinalNewline": true, "files.trimTrailingWhitespace": true, + "search.exclude": { + "**/.next": true, + "**/node_modules": true + }, "css.validate": false, "scss.validate": false, - "html.format.templating": true, - "go-template.languages": [ - "html", - "json", - "xml", - "atom", - "webmanifest" - ], - "go-template.patterns": [ - "layouts/**/*" - ], "prettier.requireConfig": true, "prettier.configPath": ".prettierrc.json", - "markdown.preview.lineHeight": 1.75, "stylelint.packageManager": "yarn", "stylelint.reportNeedlessDisables": true, "stylelint.reportInvalidScopeDisables": true, - "stylelint.validate": [ - "css", - "scss" - ], + "stylelint.validate": ["css", "scss"], "npm.packageManager": "yarn", "eslint.packageManager": "yarn" } diff --git a/README.md b/README.md index 2c1cf0b8..021a40d7 100644 --- a/README.md +++ b/README.md @@ -1,34 +1,22 @@ # ๐Ÿก  [jarv.is](https://jarv.is/) -[![GitHub Workflow Status (branch)](https://img.shields.io/github/workflow/status/jakejarvis/jarv.is/CI/main?label=build&logo=github&logoColor=white)](https://github.com/jakejarvis/jarv.is/actions?query=workflow%3ACI+branch%3Amain) [![Vercel deployment](https://img.shields.io/github/deployments/jakejarvis/jarv.is/production?label=vercel&logo=vercel&logoColor=white)](https://vercel.com/deployments/jarv.is) -[![Hugo version](https://img.shields.io/github/package-json/dependency-version/jakejarvis/jarv.is/dev/hugo-extended/main?color=ff4088&label=hugo&logo=hugo&logoColor=white)](https://github.com/gohugoio/hugo) [![Licensed under CC-BY-4.0](https://img.shields.io/badge/license-CC--BY--4.0-fb7828?logo=creative-commons&logoColor=white)](https://creativecommons.org/licenses/by/4.0/) [![GitHub repo size](https://img.shields.io/github/repo-size/jakejarvis/jarv.is?color=009cdf&label=repo%20size&logo=git&logoColor=white)](https://github.com/jakejarvis/jarv.is) [![Tor mirror uptime](https://img.shields.io/uptimerobot/ratio/m788172098-a4fcb769c8779f9a37a60775?color=7e4798&label=tor%20mirror&logo=tor-project&logoColor=white)](http://jarvis2i2vp4j4tbxjogsnqdemnte5xhzyi7hziiyzxwge3hzmh57zad.onion/) -Personal website of [@jakejarvis](https://github.com/jakejarvis), created and deployed using [Hugo](https://gohugo.io/), [Preact](https://preactjs.com/), [Vercel](https://vercel.com/), [and more](https://jarv.is/humans.txt). +Personal website of [@jakejarvis](https://github.com/jakejarvis), created and deployed using [Next.js](https://nextjs.org/), [Vercel](https://vercel.com/), [and more](https://jarv.is/humans.txt). I keep an ongoing list of [post ideas](https://github.com/jakejarvis/jarv.is/issues/1) and [coding to-dos](https://github.com/jakejarvis/jarv.is/issues/11) as issues in this repo. Outside contributions, improvements, and/or corrections are welcome too! -## ๐Ÿ’พ  Starting a local development server +## ๐Ÿงถ  Getting Started -### ๐Ÿงถ  Using Yarn: +Run `yarn install` and `yarn dev`, then open [http://localhost:3000/](http://localhost:3000/). ([Yarn must be installed](https://yarnpkg.com/en/docs/install) first; NPM _should_ also work at your own risk.) Pages will live-refresh when source files are changed. -Run `yarn install` and `yarn start`, then open [http://localhost:1337/](http://localhost:1337/). ([Yarn must be installed](https://yarnpkg.com/en/docs/install) first; NPM _should_ also work at your own risk.) Hugo, [Webpack](https://webpack.js.org/), and [Gulp](https://gulpjs.com/) will automatically work together to build the site, and pages will live-refresh via [Browsersync](https://browsersync.io/) when source files are changed. - -### โ–ฒ  Using [`vercel dev`](https://vercel.com/docs/cli#commands/dev): - -The [Vercel CLI](https://vercel.com/docs/cli) is not included as a project dependency here, but [installing it globally](https://vercel.com/cli) (`npm i -g vercel`) and running `vercel dev` in this repository will build and serve the static site automatically via Yarn **as well as the [serverless functions](/api)** used in production. Pretty nifty! (Note: the CLI will usually start the server at [http://localhost:3000/](http://localhost:3000/) instead of port 1337.) - -### ๐Ÿคฏ  Why does this look _way_ more complex than it needs to be?! - -[Because it is.](https://www.jvt.me/talks/overengineering-your-personal-website/) - -## ๐Ÿ“œ  Licenses +## ๐Ÿ“œ  License ![Creative Commons Attribution 4.0 International License](https://raw.githubusercontent.com/creativecommons/cc-cert-core/master/images/cc-by-88x31.png "CC BY") -Site content (everything in [`content/notes`](content/notes/)) is published under the [**Creative Commons Attribution 4.0 International License**](LICENSE) (CC-BY-4.0), which means that you can copy, redistribute, remix, transform, and build upon the content for any purpose as long as you give appropriate credit. +Site content (everything in [`/notes`](notes/)) is published under the [**Creative Commons Attribution 4.0 International License**](LICENSE) (CC-BY-4.0), which means that you can copy, redistribute, remix, transform, and build upon the content for any purpose as long as you give appropriate credit. -All original code in this repository is published under the [**MIT License**](https://opensource.org/licenses/MIT). [See licenses for third-party libraries here.](https://jarv.is/assets/third_party.txt) +All original code in this repository is published under the [**MIT License**](https://opensource.org/licenses/MIT). diff --git a/api/report.js b/api/report.js deleted file mode 100644 index 2c8936d4..00000000 --- a/api/report.js +++ /dev/null @@ -1,100 +0,0 @@ -import * as Sentry from "@sentry/node"; -import fetch from "node-fetch"; -import getStream from "get-stream"; - -Sentry.init({ - dsn: process.env.SENTRY_DSN || "", - environment: process.env.NODE_ENV || process.env.VERCEL_ENV || "", -}); - -// this "proxy" to report-uri.com is temporary until I'm bored enough to make my own reporting API from scratch -// https://report-uri.com/account/setup/ -const REPORT_URI_SUBDOMAIN = "jarvis"; - -export default async (req, res) => { - try { - // permissive access control headers - res.setHeader("Access-Control-Allow-Methods", "POST"); - res.setHeader("Access-Control-Allow-Origin", "*"); - // disable caching on both ends - res.setHeader("Cache-Control", "private, no-cache, no-store, must-revalidate"); - res.setHeader("Expires", 0); - res.setHeader("Pragma", "no-cache"); - - if (req.method !== "POST") { - return res.status(405).send(); // 405 Method Not Allowed - } - - // start parsing body manually, since the serverless helper functions don't recognize `application/csp-report` and - // `application/reports` as JSON to be parsed: - // https://vercel.com/docs/runtimes#official-runtimes/node-js/node-js-request-and-response-objects/request-body - const body = JSON.parse(await getStream(req)); - - // default to returning 400 Bad Request for an invalid POST request - let statusCode = 400; - - // TODO: add Expect-CT reporting endpoint - if (typeof req.query.csp !== "undefined" && req.headers["content-type"].startsWith("application/csp-report")) { - // send a CSP violation: - // https://docs.report-uri.com/setup/csp/ - statusCode = await sendCsp(body, req.headers); - } else if (req.headers["content-type"].startsWith("application/reports")) { - // send a report: - // https://docs.report-uri.com/setup/reporting-api/ - statusCode = await sendReport(body, req.headers); - } - - return res.status(statusCode).send(); - } catch (error) { - console.error(error); - - // log error to sentry, give it 2 seconds to finish sending - Sentry.captureException(error); - await Sentry.flush(2000); - - return res.status(500).send(); // 500 Internal Server Error - } -}; - -const sendCsp = async (body, headers) => { - // filter out any last invalid reports (JSON must have at least one csp-report object) - if (Object.hasOwnProperty.call(body, "csp-report")) { - const response = await fetch(`https://${REPORT_URI_SUBDOMAIN}.report-uri.com/r/d/csp/enforce`, { - method: "POST", - headers: { - "Content-Type": "application/csp-report", - "User-Agent": headers["user-agent"], - Origin: headers["origin"], - }, - body: JSON.stringify(body), - }); - - // API returns 201 Created if successful - if (response.status !== 201) { - console.error(`[CSP] ${response.status}: ${await response.text()}`); - } - - return response.status; - } - - return 400; // 400 Bad Request -}; - -const sendReport = async (body, headers) => { - const response = await fetch(`https://${REPORT_URI_SUBDOMAIN}.report-uri.com/a/d/g`, { - method: "POST", - headers: { - "Content-Type": "application/reports+json", - "User-Agent": headers["user-agent"], - Origin: headers["origin"], - }, - body: JSON.stringify(body), - }); - - // API returns 201 Created if successful - if (response.status !== 201) { - console.error(`[REPORT] ${response.status}: ${await response.text()}`); - } - - return response.status; -}; diff --git a/archetypes/default.md b/archetypes/default.md deleted file mode 100644 index 0a82087d..00000000 --- a/archetypes/default.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -title: "{{ replace .Name "-" " " | title }}" -date: {{ dateFormat "2006-01-02 15:04:05-0700" .Date }} -description: "" -image: "" -draft: true ---- diff --git a/archetypes/etc.md b/archetypes/etc.md deleted file mode 100644 index 5c5ad23e..00000000 --- a/archetypes/etc.md +++ /dev/null @@ -1,8 +0,0 @@ ---- -title: "{{ replace .Name "-" " " | title }}" -date: {{ dateFormat "2006-01-02 15:04:05-0700" .Date }} -description: "" -image: "" -layout: etc -draft: true ---- diff --git a/archetypes/notes/images/.gitkeep b/archetypes/notes/images/.gitkeep deleted file mode 100644 index e69de29b..00000000 diff --git a/archetypes/notes/index.md b/archetypes/notes/index.md deleted file mode 100644 index 44ad0f8d..00000000 --- a/archetypes/notes/index.md +++ /dev/null @@ -1,16 +0,0 @@ ---- -title: "{{ replace .Name "-" " " | title }}" -date: {{ dateFormat "2006-01-02 15:04:05-0700" .Date }} -description: "" -tags: - - -image: "" -css: | - div#content { - - } -aliases: - - -draft: true -comments: true ---- diff --git a/archetypes/video.md b/archetypes/video.md deleted file mode 100644 index 7646f555..00000000 --- a/archetypes/video.md +++ /dev/null @@ -1,13 +0,0 @@ ---- -title: "{{ replace .Name "-" " " | title }}" -date: {{ dateFormat "2006-01-02 15:04:05-0700" .Date }} -description: "" -image: "thumb.png" -layout: video -resources: -- src: "{{ .Name }}.mp4" -- src: "{{ .Name }}.webm" -- src: "subs.en.vtt" -- src: "thumb.png" -draft: true ---- diff --git a/assets/images/tiny-selfie.jpg b/assets/images/tiny-selfie.jpg deleted file mode 100644 index c809b91e..00000000 Binary files a/assets/images/tiny-selfie.jpg and /dev/null differ diff --git a/assets/js/main.js b/assets/js/main.js deleted file mode 100644 index 6aeda464..00000000 --- a/assets/js/main.js +++ /dev/null @@ -1,9 +0,0 @@ -import "./src/theme.js"; -import "./src/emoji.js"; -import "./src/hits.js"; -import "./src/clipboard.js"; -import "./src/anchor.js"; -import "./src/contact.js"; -import "./src/projects.js"; - -export default () => {}; diff --git a/assets/js/restore-theme.js b/assets/js/restore-theme.js deleted file mode 100644 index d33a54fd..00000000 --- a/assets/js/restore-theme.js +++ /dev/null @@ -1,13 +0,0 @@ -// A super tiny script to restore dark mode off the bat (to hopefully avoid blinding flashes of white). -// NOTE: This is inlined by Hugo/esbuild (see layouts/partials/head/restore-theme.html) instead of Webpack. -// ANOTHER NOTE: Whenever this code is changed, its (minified) CSP hash *MUST* be updated manually in vercel.json. - -import { getDarkPref, updateDOM } from "./src/utils/theme.js"; - -try { - // Set root class and color-scheme property to dark if either... - // - the user has explicitly toggled it previously. - // - the user's OS is in dark mode. - const pref = getDarkPref(); - updateDOM(pref === "true" || (!pref && window.matchMedia("(prefers-color-scheme: dark)").matches)); -} catch (e) {} // eslint-disable-line no-empty diff --git a/assets/js/src/anchor.js b/assets/js/src/anchor.js deleted file mode 100644 index b7554986..00000000 --- a/assets/js/src/anchor.js +++ /dev/null @@ -1,23 +0,0 @@ -import { h, render } from "preact"; - -// react components: -import Anchor from "./components/Anchor.js"; - -// loop through each h2, h3, h4 in this page's content area -// prettier-ignore -document.querySelectorAll([ - "div#content h2", - "div#content h3", - "div#content h4", -]).forEach((heading) => { - // don't add to elements without a pre-existing ID (e.g. `

`) - if (!heading.hasAttribute("id")) { - return; - } - - // TODO: little hacky hack to make the anchor appear AFTER the existing h tag - const linkTarget = document.createElement("a"); - heading.appendChild(linkTarget); - - render(, heading, linkTarget); -}); diff --git a/assets/js/src/assets/bulb-off.svg b/assets/js/src/assets/bulb-off.svg deleted file mode 100644 index 2c48664b..00000000 --- a/assets/js/src/assets/bulb-off.svg +++ /dev/null @@ -1 +0,0 @@ - diff --git a/assets/js/src/assets/bulb-on.svg b/assets/js/src/assets/bulb-on.svg deleted file mode 100644 index eea57264..00000000 --- a/assets/js/src/assets/bulb-on.svg +++ /dev/null @@ -1 +0,0 @@ - diff --git a/assets/js/src/assets/send.svg b/assets/js/src/assets/send.svg deleted file mode 100644 index d2b4d36e..00000000 --- a/assets/js/src/assets/send.svg +++ /dev/null @@ -1 +0,0 @@ - diff --git a/assets/js/src/clipboard.js b/assets/js/src/clipboard.js deleted file mode 100644 index 7e622709..00000000 --- a/assets/js/src/clipboard.js +++ /dev/null @@ -1,15 +0,0 @@ -import { h, render } from "preact"; - -// react components: -import CopyButton from "./components/CopyButton.js"; - -// loop through each code fence on page (if any) -document.querySelectorAll("div.highlight").forEach((highlightDiv) => { - // actual code element will have class "language-*" (even if plaintext) - const codeElement = highlightDiv.querySelector('code[class^="language-"]'); - - if (codeElement) { - // add the button as a sibling to the original Hugo block whose contents we're copying - render(, highlightDiv); - } -}); diff --git a/assets/js/src/components/Anchor.js b/assets/js/src/components/Anchor.js deleted file mode 100644 index e7a28da0..00000000 --- a/assets/js/src/components/Anchor.js +++ /dev/null @@ -1,21 +0,0 @@ -import { h } from "preact"; -import isTouchDevice from "is-touch-device"; - -const Anchor = (props) => { - return ( - // eslint-disable-next-line jsx-a11y/anchor-has-content - - ); -}; - -export default Anchor; diff --git a/assets/js/src/components/Counter.js b/assets/js/src/components/Counter.js deleted file mode 100644 index 0b9a0d8c..00000000 --- a/assets/js/src/components/Counter.js +++ /dev/null @@ -1,31 +0,0 @@ -import { h } from "preact"; -import { useState, useEffect } from "preact/hooks"; -import fetch from "unfetch"; - -// react components: -import Loading from "./Loading.js"; - -const Counter = (props) => { - const [hits, setHits] = useState(); - - // start fetching hits from API once slug is set - useEffect(() => { - fetch(`/api/hits/?slug=${encodeURIComponent(props.slug)}`) - .then((response) => response.json()) - .then((data) => setHits(data.hits || 0)); - }, [props.slug]); - - // show spinning loading indicator if data isn't fetched yet - if (!hits) { - return ; - } - - // we have data! - return ( - - {hits.toLocaleString("en-US")} - - ); -}; - -export default Counter; diff --git a/assets/js/src/components/Loading.js b/assets/js/src/components/Loading.js deleted file mode 100644 index da1f3f60..00000000 --- a/assets/js/src/components/Loading.js +++ /dev/null @@ -1,43 +0,0 @@ -import { h } from "preact"; - -const Loading = (props) => { - // allow a custom number of pulsing boxes (defaults to 3) - const boxes = props.boxes || 3; - // each individual box's animation has a staggered start in corresponding order - const animationTiming = props.timing || 0.1; // seconds - // each box is just an empty div - const divs = []; - - for (let i = 0; i < boxes; i++) { - divs.push( -
- ); - } - - return ( -
- {divs} -
- ); -}; - -export default Loading; diff --git a/assets/js/src/components/RepositoryCard.js b/assets/js/src/components/RepositoryCard.js deleted file mode 100644 index b6f3c9f9..00000000 --- a/assets/js/src/components/RepositoryCard.js +++ /dev/null @@ -1,81 +0,0 @@ -import { h } from "preact"; -import { intlFormat, formatDistanceToNowStrict } from "date-fns"; -import parseEmoji from "../utils/parseEmoji.js"; - -// react components: -import { StarIcon, RepoForkedIcon } from "@primer/octicons-react"; - -const RepositoryCard = (props) => ( -
- - {props.name} - - - {props.description && ( -

- )} - -

- {props.language && ( -
- - {props.language.name} -
- )} - - {props.stars > 0 && ( - - )} - - {props.forks > 0 && ( - - )} - -
- Updated {formatDistanceToNowStrict(new Date(props.updatedAt), { addSuffix: true })} -
-
-
-); - -export default RepositoryCard; diff --git a/assets/js/src/components/RepositoryGrid.js b/assets/js/src/components/RepositoryGrid.js deleted file mode 100644 index e156991e..00000000 --- a/assets/js/src/components/RepositoryGrid.js +++ /dev/null @@ -1,36 +0,0 @@ -import { h, Fragment } from "preact"; -import { useState, useEffect } from "preact/hooks"; -import fetch from "unfetch"; - -// react components: -import Loading from "./Loading.js"; -import RepositoryCard from "./RepositoryCard.js"; - -const RepositoryGrid = () => { - const [repos, setRepos] = useState([]); - - // start fetching repos from API immediately - useEffect(() => { - // API endpoint (sort by stars, limit to 12) - fetch("/api/projects/?top&limit=12") - .then((response) => response.json()) - .then((data) => setRepos(data || [])); - }, []); - - // show spinning loading indicator if data isn't fetched yet - if (repos.length === 0) { - return ; - } - - // we have data! - return ( - <> - {repos.map((repo) => ( - // eslint-disable-next-line react/jsx-key - - ))} - - ); -}; - -export default RepositoryGrid; diff --git a/assets/js/src/contact.js b/assets/js/src/contact.js deleted file mode 100644 index 30c05a54..00000000 --- a/assets/js/src/contact.js +++ /dev/null @@ -1,9 +0,0 @@ -import { h, render } from "preact"; - -// react components: -import ContactForm from "./components/ContactForm.js"; - -// don't continue if there isn't a contact form on this page -if (typeof window !== "undefined" && document.querySelector(".layout-contact #contact-form-wrapper")) { - render(, document.querySelector(".layout-contact #contact-form-wrapper")); -} diff --git a/assets/js/src/emoji.js b/assets/js/src/emoji.js deleted file mode 100644 index f113c84c..00000000 --- a/assets/js/src/emoji.js +++ /dev/null @@ -1,4 +0,0 @@ -import parseEmoji from "./utils/parseEmoji.js"; - -// apply to the entire body automatically on load... -parseEmoji(document.body); diff --git a/assets/js/src/hits.js b/assets/js/src/hits.js deleted file mode 100644 index 35eeff16..00000000 --- a/assets/js/src/hits.js +++ /dev/null @@ -1,23 +0,0 @@ -import { h, render } from "preact"; -import canonicalUrl from "get-canonical-url"; - -// react components: -import Counter from "./components/Counter.js"; - -// page must have a div#meta-hits-counter element to continue -if (typeof window !== "undefined" && document.querySelector(".layout-single #meta-hits-counter")) { - // use to deduce a consistent identifier for this page - const canonical = canonicalUrl({ - normalize: true, - normalizeOptions: { - removeTrailingSlash: true, - removeQueryParameters: true, - stripHash: true, - }, - }); - - // get path and strip beginning and ending forward slash - const slug = new URL(canonical).pathname.replace(/^\/|\/$/g, "") || "/"; - - render(, document.querySelector(".layout-single #meta-hits-counter")); -} diff --git a/assets/js/src/projects.js b/assets/js/src/projects.js deleted file mode 100644 index 44a257ef..00000000 --- a/assets/js/src/projects.js +++ /dev/null @@ -1,9 +0,0 @@ -import { h, render } from "preact"; - -// react components: -import RepositoryGrid from "./components/RepositoryGrid.js"; - -// detect if these cards are wanted on this page (only /projects) -if (typeof window !== "undefined" && document.querySelector(".layout-projects #github-cards")) { - render(, document.querySelector(".layout-projects #github-cards")); -} diff --git a/assets/js/src/theme.js b/assets/js/src/theme.js deleted file mode 100644 index d21b48d6..00000000 --- a/assets/js/src/theme.js +++ /dev/null @@ -1,9 +0,0 @@ -import { h, render } from "preact"; - -// react components: -import ThemeToggle from "./components/ThemeToggle.js"; - -// finally render the nifty lightbulb in the header -if (typeof window !== "undefined" && document.querySelector(".theme-toggle")) { - render(, document.querySelector(".theme-toggle")); -} diff --git a/assets/js/src/utils/getData.js b/assets/js/src/utils/getData.js deleted file mode 100644 index 1374732a..00000000 --- a/assets/js/src/utils/getData.js +++ /dev/null @@ -1,8 +0,0 @@ -import fetch from "unfetch"; - -const getData = (url) => - fetch(url) - .then((response) => response.json()) - .then((data) => data || []); - -export default getData; diff --git a/assets/js/src/utils/parseEmoji.js b/assets/js/src/utils/parseEmoji.js deleted file mode 100644 index 07d801e7..00000000 --- a/assets/js/src/utils/parseEmoji.js +++ /dev/null @@ -1,8 +0,0 @@ -import * as imagemoji from "imagemoji"; - -const parseEmoji = (what) => - // we're hosting twemojis locally instead of from Twitter's CDN - imagemoji.parse(what, (icon) => `/assets/emoji/${icon}.svg`); - -// reuse this so the URL above doesn't need to be changed in multiple places -export default parseEmoji; diff --git a/assets/js/src/utils/theme.js b/assets/js/src/utils/theme.js deleted file mode 100644 index 7a7976fb..00000000 --- a/assets/js/src/utils/theme.js +++ /dev/null @@ -1,25 +0,0 @@ -// class names (``) for each theme -const lightClass = "light"; -const darkClass = "dark"; - -// store preference in local storage -const storageKey = "dark_mode"; -export const getDarkPref = () => localStorage.getItem(storageKey); -export const setDarkPref = (pref) => localStorage.setItem(storageKey, pref); - -// use the `` as a hint to what the theme was set to outside of the button component -// there's probably (definitely) a cleaner way to do this..? -export const isDark = () => document.documentElement.classList.contains(darkClass); - -// sets appropriate `` and `color-scheme` CSS property -export const updateDOM = (dark) => { - const root = document.documentElement; - - // set `` - root.classList.remove(darkClass, lightClass); - root.classList.add(dark ? darkClass : lightClass); - - // ...and the CSS `color-scheme` property: - // https://developer.mozilla.org/en-US/docs/Web/CSS/color-scheme - root.style.colorScheme = dark ? "dark" : "light"; -}; diff --git a/assets/sass/abstracts/_functions.scss b/assets/sass/abstracts/_functions.scss deleted file mode 100644 index abf8ae74..00000000 --- a/assets/sass/abstracts/_functions.scss +++ /dev/null @@ -1,14 +0,0 @@ -@use "sass:color"; - -@use "settings"; - -// Figure out the color of the "transparent" link underlines: -@function underline-hack($color, $background: #ffffff) { - // Calculate underline color by mix()'ing it with a given background to give the impression of opacity but with much - // better efficiency and compatibility. - $color-transparentized: color.mix($color, $background, settings.$link-underline-opacity); - - // Return a "gradient" as a hack to get the fancy underline to wrap: - // https://www.dannyguo.com/blog/animated-multiline-link-underlines-with-css/ - @return linear-gradient($color-transparentized, $color-transparentized); -} diff --git a/assets/sass/abstracts/_settings.scss b/assets/sass/abstracts/_settings.scss deleted file mode 100644 index 7dc9fcf0..00000000 --- a/assets/sass/abstracts/_settings.scss +++ /dev/null @@ -1,29 +0,0 @@ -@use "sass:list"; - -// The maximum width of the content area: -$max-width: 865px; - -// Width at which to switch to mobile styles: -$responsive-width: 800px; - -// Web fonts: -$webfont-sans: "Inter"; -$webfont-sans-variable: "Inter var"; -$webfont-mono: "Roboto Mono"; -$webfont-mono-variable: "Roboto Mono var"; - -// System fonts: -// https://primer.style/design/foundations/typography#font-stack -// stylelint-disable-next-line value-keyword-case -$system-fonts-sans: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Helvetica", "Arial", sans-serif; -$system-fonts-mono: ui-monospace, "SFMono-Regular", "Consolas", "Liberation Mono", "Menlo", "Courier", monospace; - -// Prefer web fonts with system fonts as backup: -$font-stack-sans: list.join($webfont-sans, $system-fonts-sans); -$font-stack-sans-variable: list.join($webfont-sans-variable, $system-fonts-sans); -$font-stack-mono: list.join($webfont-mono, $system-fonts-mono); -$font-stack-mono-variable: list.join($webfont-mono-variable, $system-fonts-mono); - -// Fancy link underline settings: -$link-underline-opacity: 40%; -$link-underline-size: 2px; diff --git a/assets/sass/abstracts/_themes.scss b/assets/sass/abstracts/_themes.scss deleted file mode 100644 index 6273454c..00000000 --- a/assets/sass/abstracts/_themes.scss +++ /dev/null @@ -1,55 +0,0 @@ -@use "sass:map"; - -// Takes a map of CSS properties and theme keys (see below) and set both html.light and html.dark selectors. -// ex. @include themes.themed((color: "text", background-color: "background-inner")); -@mixin themed($properties) { - // keep track of the original selector(s) calling this mixin for below - $selectors: #{&}; - - // add corresponding html.light and html.dark root selectors - @each $theme, $map in $themes { - @at-root html.#{$theme} { - #{$selectors} { - @each $property, $color in $properties { - #{$property}: map.get($map, $color); - } - } - } - } -} - -// ---------------- - -// Dark & Light Themes -$themes: ( - light: ( - background-inner: #ffffff, - background-outer: #fcfcfc, - text: #202020, - medium-dark: #515151, - medium: #5e5e5e, - medium-light: #757575, - light: #d2d2d2, - kinda-light: #e3e3e3, - super-light: #f4f4f4, - super-duper-light: #fbfbfb, - links: #0e6dc2, - success: #44a248, - error: #ff1b1b, - ), - dark: ( - background-inner: #1e1e1e, - background-outer: #252525, - text: #f1f1f1, - medium-dark: #d7d7d7, - medium: #b1b1b1, - medium-light: #959595, - light: #646464, - kinda-light: #535353, - super-light: #272727, - super-duper-light: #1f1f1f, - links: #88c7ff, - success: #78df55, - error: #ff5151, - ), -); diff --git a/assets/sass/components/_animation.scss b/assets/sass/components/_animation.scss deleted file mode 100644 index 7486cc8a..00000000 --- a/assets/sass/components/_animation.scss +++ /dev/null @@ -1,100 +0,0 @@ -@use "../abstracts/themes"; - -.wave, -.beat { - display: inline-block; -} - -.wave { - animation: wave 5s infinite; - animation-delay: 1s; - transform-origin: 65% 80%; -} - -.beat { - animation: beat 10s infinite; // 6 bpm, call 911 if you see this please. - animation-delay: 7.5s; // offset from wave animation -} - -@keyframes wave { - // stylelint-disable rule-empty-line-before - 0% { - transform: rotate(0deg); - } - 5% { - transform: rotate(14deg); - } - 10% { - transform: rotate(-8deg); - } - 15% { - transform: rotate(14deg); - } - 20% { - transform: rotate(-4deg); - } - 25% { - transform: rotate(10deg); - } - 30% { - transform: rotate(0deg); - } - // stylelint-enable rule-empty-line-before - - // pause for 3.5 out of 5 seconds - 100% { - transform: rotate(0deg); - } -} - -@keyframes beat { - // stylelint-disable rule-empty-line-before - 0% { - transform: scale(1); - } - 2% { - transform: scale(1.25); - } - 4% { - transform: scale(1); - } - 6% { - transform: scale(1.2); - } - 8% { - transform: scale(1); - } - // stylelint-enable rule-empty-line-before - - // pause for ~9 out of 10 seconds - 100% { - transform: scale(1); - } -} - -// modified from https://tobiasahlin.com/spinkit/ -@keyframes loading { - // stylelint-disable rule-empty-line-before - 0%, - 80%, - 100% { - transform: scale(0); - } - 40% { - transform: scale(0.6); - } - // stylelint-enable rule-empty-line-before -} - -// https://web.dev/prefers-reduced-motion/#(bonus)-forcing-reduced-motion-on-all-websites -@media (prefers-reduced-motion: reduce) { - *, - ::before, - ::after { - animation-delay: -1ms !important; - animation-duration: 1ms !important; - animation-iteration-count: 1 !important; - transition-duration: 0s !important; - transition-delay: 0s !important; - } -} diff --git a/assets/sass/components/_content.scss b/assets/sass/components/_content.scss deleted file mode 100644 index a1efd0a0..00000000 --- a/assets/sass/components/_content.scss +++ /dev/null @@ -1,134 +0,0 @@ -@use "../abstracts/themes"; - -// Main content -div#content { - font-size: 0.925em; - letter-spacing: -0.004em; - line-height: 1.7; - - b, - strong { - letter-spacing: 0.008em; // not sure why the discrepancy between weights - } - - blockquote { - margin-left: 0; - padding-left: 1.5em; - border-left: 3px solid; - - @include themes.themed( - ( - color: "medium-dark", - border-color: "links", - ) - ); - } - - h2, - h3, - h4 { - margin-top: 1.25em; - margin-bottom: 0.5em; - letter-spacing: 0.001em; - line-height: 1.5; - - &:hover > .anchorjs-link { - opacity: 1; // '#' link appears on hover over entire sub-heading line - } - } - - // special bottom border for H2s - h2 { - padding-bottom: 0.25em; - border-bottom: 1px solid; - - @include themes.themed( - ( - border-color: "kinda-light", - ) - ); - } - - // AnchorJS styles - .anchorjs-link { - margin: 0 0.25em; - padding: 0 0.25em; - background: none; - font-weight: 300; - line-height: 1; - opacity: 0; // overridden by JS on mobile devices - user-select: none; - - &::before { - content: "\0023"; // pound sign - } - - @include themes.themed( - ( - color: "medium-light", - ) - ); - - &:hover { - @include themes.themed( - ( - color: "links", - ) - ); - } - } - - p.center { - text-align: center; - } - - figure { - margin: 1em auto; - text-align: center; - line-height: 1; - - img { - height: auto; - max-width: 100%; - } - - figcaption { - font-size: 0.95em; - line-height: 1.5; - margin-top: 0.5em; - - @include themes.themed( - ( - color: "medium", - ) - ); - } - } - - ul, - ol { - margin-left: 1.5em; - padding-left: 0; - - li { - padding-left: 0.25em; - } - } - - hr { - margin: 1.5em auto; - height: 2px; - border: 0; - - @include themes.themed( - ( - background-color: "light", - ) - ); - } -} - -// Responsive -// stylelint-disable-next-line block-no-empty -@mixin responsive() { -} diff --git a/assets/sass/components/_embeds.scss b/assets/sass/components/_embeds.scss deleted file mode 100644 index 0135050d..00000000 --- a/assets/sass/components/_embeds.scss +++ /dev/null @@ -1,72 +0,0 @@ -// External social embeds -.embed { - &.codepen { - iframe { - width: 100%; - border: 0; - } - } - - &.tweet { - margin: 0.5em 0; - - > .twitter-tweet-rendered { - margin: 0 auto; - } - } - - &.youtube { - position: relative; - padding-bottom: 56.25%; - margin: 1em auto; - height: 0; - overflow: hidden; - - iframe.youtube-player { - position: absolute; - top: 0; - left: 0; - width: 100%; - height: 100%; - display: inline-block; - border: 0; - } - } - - &.video { - video { - display: block; - margin: 1em auto; - max-width: 100%; - height: auto; - } - } - - &.vimeo { - padding: 75% 0 0 0; - position: relative; - - iframe { - position: absolute; - top: 0; - left: 0; - width: 100%; - height: 100%; - } - } - - &.gh-buttons { - text-align: center; - - // GitHub's script replaces with , so cover both - > a, - > span { - margin: 0 0.5em; - } - } -} - -// Responsive -// stylelint-disable-next-line block-no-empty -@mixin responsive() { -} diff --git a/assets/sass/components/_footer.scss b/assets/sass/components/_footer.scss deleted file mode 100644 index a485baea..00000000 --- a/assets/sass/components/_footer.scss +++ /dev/null @@ -1,61 +0,0 @@ -@use "../abstracts/settings"; -@use "../abstracts/themes"; - -// Global Footer Styles -footer { - width: 100%; - letter-spacing: -0.005em; - padding: 1.25em 1.5em; - border-top: 1px solid; - - @include themes.themed( - ( - color: "medium-dark", - border-color: "kinda-light", - ) - ); - - a { - @include themes.themed( - ( - color: "medium-dark", - ) - ); - } - - div.footer-row { - display: flex; - width: 100%; - max-width: settings.$max-width; - margin: 0 auto; - justify-content: space-between; - font-size: 0.85em; - line-height: 2.3; - } - - // underline View Source link - a#footer-view-source { - padding-bottom: 2px; - border-bottom: 1px solid; - - @include themes.themed( - ( - color: "medium-dark", - border-color: "light", - ) - ); - } -} - -// Responsive -@mixin responsive() { - footer { - padding: 1em 1.25em 0 1.25em; - - // stack columns on left instead of flexboxing across - div.footer-row { - display: block; - line-height: 2; - } - } -} diff --git a/assets/sass/components/_global.scss b/assets/sass/components/_global.scss deleted file mode 100644 index d0cd9253..00000000 --- a/assets/sass/components/_global.scss +++ /dev/null @@ -1,182 +0,0 @@ -@use "sass:map"; - -@use "../abstracts/settings"; -@use "../abstracts/themes"; -@use "../abstracts/functions"; - -// Global Styles -body { - width: 100%; - height: 100%; - margin: 0 auto; - scroll-behavior: smooth; - font-family: settings.$font-stack-sans; - font-kerning: normal; - font-variant-ligatures: normal; - font-feature-settings: "kern", "liga", "calt", "clig", "ss01"; - - // global base font size: - font-size: 0.975em; - line-height: 1.5; - - @include themes.themed( - ( - background-color: "background-outer", - ) - ); -} - -code, -kbd, -samp, -pre, -.monospace { - font-family: settings.$font-stack-mono; - letter-spacing: normal; -} - -// override above font-family if browser supports variable fonts -// https://caniuse.com/#feat=variable-fonts -@supports (font-variation-settings: normal) { - body { - font-family: settings.$font-stack-sans-variable; - font-optical-sizing: auto; - } - - code, - kbd, - samp, - pre, - .monospace { - font-family: settings.$font-stack-mono-variable; - } - - // Chrome doesn't automatically slant multi-axis Inter var, for some reason. - // Adding "slnt" -10 fixes Chrome but then over-italicizes in Firefox. AHHHHHHHHHH. - em { - font-style: normal !important; - font-variation-settings: "ital" 1, "slnt" -10; - - // Roboto Mono doesn't have this problem, but the above fix breaks it, of course. - code, - kbd, - samp, - pre, - .monospace { - font-style: italic !important; - } - } -} - -a { - text-decoration: none; -} - -// this is what's extended by different layouts (in ../pages) -div.layout { - max-width: settings.$max-width; - margin: 0 auto; - display: block; // https://stackoverflow.com/questions/28794718/max-width-not-working-for-ie-11 -} - -// stretch background for entire width of content area -main { - width: 100%; - padding: 0 1.5em; - - @include themes.themed( - ( - color: "text", - background-color: "background-inner", - ) - ); - - a { - background-position: 0% 100%; - background-repeat: no-repeat; - background-size: 0% settings.$link-underline-size; - padding-bottom: settings.$link-underline-size; - transition: background-size 0.25s ease-in-out; - - @include themes.themed( - ( - color: "links", - ) - ); - - // cool link underlines via messy SCSS hacking (see ../abstracts/_functions) - @each $theme, $map in themes.$themes { - @at-root html.#{$theme} #{&} { - background-image: functions.underline-hack(map.get($map, "links"), map.get($map, "background-inner")); - } - } - - &:hover { - background-size: 100% settings.$link-underline-size; - } - - // set an anchor's class to `no-underline` to disable all of this - &.no-underline { - background: none !important; - padding-bottom: 0; - } - } - - // page titles - h1 { - margin-top: 0; - letter-spacing: -0.005em; - - a { - // disable fancy underline without `.no-underline` - color: inherit !important; - background: none !important; - padding-bottom: 0; - } - } -} - -// make SVG twemojis relative to surrounding text -// https://github.com/twitter/twemoji#inline-styles -.emoji { - height: 1.2em; - width: 1.2em; - margin: 0 0.05em; - vertical-align: -0.22em; - border: 0; - display: inline-block; - - // have cursor act like it's hovering a regular unicode emoji, especially since twemojis can still be copied/pasted - cursor: text; -} - -// need to manually unset text cursor above when emoji's within a link -a .emoji { - cursor: inherit; -} - -// pulsating loading spinner -div.loading > div { - @include themes.themed( - ( - background-color: "medium-light", - ) - ); -} - -// Responsive -@mixin responsive() { - body { - // Safari iOS menu bar reappears below 44px: - // https://www.eventbrite.com/engineering/mobile-safari-why/ - padding-bottom: 45px !important; - - // Allows you to scroll below the viewport; default value is visible - overflow-y: scroll; - } - - main { - padding-left: 1.25em; - padding-right: 1.25em; - } -} diff --git a/assets/sass/components/_header.scss b/assets/sass/components/_header.scss deleted file mode 100644 index a9fabfad..00000000 --- a/assets/sass/components/_header.scss +++ /dev/null @@ -1,199 +0,0 @@ -@use "../abstracts/settings"; -@use "../abstracts/themes"; - -// Global Header Styles -header { - width: 100%; - padding: 0.7em 1.5em; - border-bottom: 1px solid; - - @include themes.themed( - ( - border-color: "kinda-light", - ) - ); - - nav { - width: 100%; - max-width: settings.$max-width; - margin: 0 auto; - display: flex; - align-items: center; - justify-content: space-between; - - a#header-logo { - display: flex; - align-items: center; - - @include themes.themed( - ( - color: "medium-dark", - ) - ); - - img#header-selfie { - width: 50px; - height: 50px; - border: 1px solid; - border-radius: 50%; - user-select: none; - - @include themes.themed( - ( - border-color: "light", - ) - ); - } - - span#header-name { - margin: 0 0.6em; - font-size: 1.25em; - font-weight: 500; - letter-spacing: -0.01em; - } - - &:hover { - @include themes.themed( - ( - color: "links", - ) - ); - - img#header-selfie { - opacity: 0.9; - } - } - } - - // Horizontal emoji links - ul { - list-style: none; - display: flex; - align-items: center; - margin: 0; - padding: 0; - - li { - margin-left: 1.75em; - - a { - display: inline-flex; - align-items: center; - - @include themes.themed( - ( - color: "medium-dark", - ) - ); - - &:hover { - @include themes.themed( - ( - color: "links", - ) - ); - } - - span { - &.header-menu-icon { - width: 1.3em; - font-size: 1.3em; - user-select: none; - } - - &.header-menu-text { - font-size: 0.95em; - font-weight: 500; - letter-spacing: -0.002em; - margin-left: 0.65em; - } - } - } - - // Dark mode toggle - &.theme-toggle { - width: 1.3em; - margin-left: 1.4em; - - button { - border: 0; - padding: 0; - background: none; - margin: 0.3em -0.3em 0 0; - cursor: pointer; - - svg { - width: 1.56em; // 24.33px, don't ask - height: 1.56em; - } - } - } - - // no margin on the first child to make more room on narrow windows (before responsiveness kicks in) - &:first-child { - margin-left: 0; - } - } - } - } -} - -// Responsive -@mixin responsive() { - header { - padding: 0.5em 1.25em; - - nav { - a#header-logo { - img#header-selfie { - width: 70px; - height: 70px; - } - - span#header-name { - display: none; - } - - &:hover { - img#header-selfie { - opacity: 1; - } - } - } - - ul { - font-size: 1.6em; - - li { - margin-left: 1.15em; - - a.header-menu-item { - span.header-menu-icon { - font-size: 0.9em; - } - - // hide text next to emojis on mobile - span.header-menu-text { - display: none; - } - } - - // Dark mode toggle - &.theme-toggle { - width: 0.9em; - margin-left: 1em; - - button { - margin-right: -0.2em; // weirdness w/ svg ratio - - svg { - width: 1.08em; // ~27px, don't ask - height: 1.08em; - } - } - } - } - } - } - } -} diff --git a/assets/sass/components/_syntax.scss b/assets/sass/components/_syntax.scss deleted file mode 100644 index b85dc2b7..00000000 --- a/assets/sass/components/_syntax.scss +++ /dev/null @@ -1,301 +0,0 @@ -@use "../abstracts/themes"; - -// all code -code { - font-size: 0.925em; - letter-spacing: normal; - page-break-inside: avoid; // stylelint-disable-line plugin/no-unsupported-browser-features -} - -// inline code in paragraphs/elsewhere (single backticks) -:not(pre) > code { - padding: 0.075em 0.25em; - border: 1px solid; -} - -// allow for inline code in post/page titles -- override above styles and match title -.title code { - font-size: 1em; - background: none !important; - border: 0 !important; - margin: 0 0.075em !important; - padding: 0 !important; -} - -// same as above but different font weight for big titles -h1.title code { - font-weight: 600; -} - -// code fences -div.highlight { - line-height: 1.6; - max-width: 100%; - overflow-x: scroll; - margin: 1em 0; - border: 1px solid; - position: relative; - - pre { - padding-left: 1.5em; - margin: 1em 0; - } - - // code fences without syntax highlighting - > pre > code { - padding-right: 1.5em; - } - - button.copy-button { - position: absolute; - top: 0; - right: 0; - padding: 0.75em; - border-width: 0 0 1px 1px; - border-style: solid; - cursor: pointer; - - &:hover { - @include themes.themed( - ( - color: "links", - ) - ); - } - - .octicon { - width: 1em; - height: 1em; - } - - .octicon-check { - @include themes.themed( - ( - color: "success", - ) - ); - } - } -} - -// global table styles for line numbers and font styles -.chroma { - .lntable { - border-spacing: 0; - padding: 0; - margin: 0; - border: 0; - width: auto; - overflow: auto; - display: block; - } - - // columns - .lntd { - vertical-align: top; - padding: 0; - margin: 0; - border: 0; - - // add right padding to second column (the code) when fence overflows - &:nth-of-type(2) { - padding-right: 1em; - } - } - - // line numbers - .ln, - .lnt { - user-select: none; - } - - .gh, - .gi, - .gu { - font-weight: bold; - } - - .kd, - .vc, - .vg, - .vi, - .ge { - font-style: italic; - } - - .gl { - text-decoration: underline; - } -} - -// Syntax Highlighting (light) - modified from Monokai Light: https://github.com/mlgill/pygments-style-monokailight -html.light { - div.highlight, - button.copy-button, - :not(pre) > code { - background-color: #fbfbfb; - border-color: #d5d5d5; - } - - div.highlight, - button.copy-button { - color: #313131; - } - - .chroma { - .k, - .kc, - .kd, - .kp, - .kr, - .kt, - .no { - color: #029cb9; - } - - .na, - .nc, - .nd, - .ne, - .nf, - .nx { - color: #70a800; - } - - .nt, - .o, - .ow, - .kn { - color: #f92672; - } - - .l, - .se, - .m, - .mb, - .mf, - .mh, - .mi, - .il, - .mo { - color: #ae81ff; - } - - .ld, - .s, - .sa, - .sb, - .sc, - .dl, - .sd, - .s2, - .sh, - .si, - .sx, - .sr, - .s1, - .ss { - color: #d88200; - } - - .c, - .ch, - .cm, - .c1, - .cs, - .cp, - .cpf { - color: #75715e; - } - - .lnt { - color: #8a8a8a; - } - } -} - -// Syntax Highlighting (dark) - modified from Dracula: https://github.com/dracula/pygments -html.dark { - div.highlight, - button.copy-button, - :not(pre) > code { - background-color: #252525; - border-color: #535353; - } - - div.highlight, - button.copy-button { - color: #e4e4e4; - } - - .chroma { - .k, - .kc, - .kd, - .kp, - .kr, - .kt, - .no { - color: #3b9dd2; - } - - .na, - .nc, - .nd, - .ne, - .nf, - .nx { - color: #78df55; - } - - .nt, - .o, - .ow, - .kn { - color: #f95757; - } - - .l, - .se, - .m, - .mb, - .mf, - .mh, - .mi, - .il, - .mo { - color: #d588fb; - } - - .ld, - .s, - .sa, - .sb, - .sc, - .dl, - .sd, - .s2, - .sh, - .si, - .sx, - .sr, - .s1, - .ss { - color: #fd992a; - } - - .c, - .ch, - .cm, - .c1, - .cs, - .cp, - .cpf { - color: #929292; - } - - .lnt { - color: #b1b1b1; - } - } -} diff --git a/assets/sass/main.scss b/assets/sass/main.scss deleted file mode 100644 index f7d34fc2..00000000 --- a/assets/sass/main.scss +++ /dev/null @@ -1,42 +0,0 @@ -@use "abstracts/settings"; - -// Global Styles -@use "abstracts/reset"; -@use "abstracts/typography"; -@use "components/global"; -@use "components/header"; -@use "components/footer"; -@use "components/content"; -@use "components/embeds"; - -// Pages -@use "pages/home"; -@use "pages/list"; -@use "pages/single"; -@use "pages/videos"; -@use "pages/etc"; -@use "pages/projects"; -@use "pages/contact"; -@use "pages/fourOhFour"; - -// Miscellaneous -@use "components/syntax"; -@use "components/animation"; - -// Responsive Awesomeness -@media screen and (max-width: settings.$responsive-width) { - @include global.responsive; - @include header.responsive; - @include footer.responsive; - @include content.responsive; - @include embeds.responsive; - - @include home.responsive; - @include list.responsive; - @include single.responsive; - @include videos.responsive; - @include etc.responsive; - @include projects.responsive; - @include contact.responsive; - @include fourOhFour.responsive; -} diff --git a/assets/sass/pages/_contact.scss b/assets/sass/pages/_contact.scss deleted file mode 100644 index ee6881e6..00000000 --- a/assets/sass/pages/_contact.scss +++ /dev/null @@ -1,143 +0,0 @@ -@use "../abstracts/themes"; - -// Contact Styles -div.layout-contact { - max-width: 600px; - padding: 1.5em 0; - - h1 { - margin-bottom: 0.4em; - text-align: center; - } - - code { - background: none !important; - border: 0; - padding: 0; - word-spacing: -0.175em; - white-space: normal; // re-enable "word" wrapping - } - - input[type="text"], - input[type="email"], - select, - textarea { - width: 100%; - padding: 0.8em; - margin: 0.6em 0; - border: 2px solid; - border-radius: 0.3em; - - @include themes.themed( - ( - color: "text", - background-color: "super-duper-light", - border-color: "light", - ) - ); - - &:focus { - outline: none; // disable browsers' outer border - - @include themes.themed( - ( - border-color: "links", - ) - ); - } - } - - textarea { - height: 12em; - min-height: 6em; - margin-bottom: 0; - line-height: 1.5; - - // allow vertical resizing & disable horizontal - resize: vertical; // stylelint-disable-line plugin/no-unsupported-browser-features - } - - div#contact-form-action-row { - display: flex; - align-items: center; - min-height: 3.75em; - - button { - flex-shrink: 0; - padding: 1em 1.25em; - margin-right: 1.5em; - border: 0; - border-radius: 0.3em; - font-size: 1.1em; - cursor: pointer; - user-select: none; - - @include themes.themed( - ( - color: "text", - background-color: "kinda-light", - ) - ); - - &:hover { - @include themes.themed( - ( - color: "super-duper-light", - background-color: "links", - ) - ); - } - - .emoji { - margin-left: 0; - margin-right: 0.4em; - cursor: inherit; - } - } - - span.contact-form-result { - font-weight: 600; - - &#contact-form-result-success { - @include themes.themed( - ( - color: "success", - ) - ); - } - - &#contact-form-result-error { - @include themes.themed( - ( - color: "error", - ) - ); - } - } - } - - // hcaptcha widget - div#contact-form-captcha { - margin: 1em 0; - } - - div#contact-form-md-info { - font-size: 0.825em; - line-height: 1.75; - - a { - // disable fancy underline without `.no-underline` - background: none !important; - padding: 0; - - &:first-of-type { - font-weight: 500; - } - } - } -} - -// Responsive -// stylelint-disable-next-line block-no-empty -@mixin responsive() { -} diff --git a/assets/sass/pages/_etc.scss b/assets/sass/pages/_etc.scss deleted file mode 100644 index 21308ea1..00000000 --- a/assets/sass/pages/_etc.scss +++ /dev/null @@ -1,19 +0,0 @@ -// Video Styles -div.layout-etc { - padding-top: 1.5em; - padding-bottom: 1.5em; - - h1 { - text-align: center; - } -} - -// Responsive -@mixin responsive() { - div.layout-etc { - h1 { - font-size: 1.6em; - padding: 0 0.6em; - } - } -} diff --git a/assets/sass/pages/_fourOhFour.scss b/assets/sass/pages/_fourOhFour.scss deleted file mode 100644 index af7be254..00000000 --- a/assets/sass/pages/_fourOhFour.scss +++ /dev/null @@ -1,26 +0,0 @@ -// 404 Styles -div.layout-404 { - padding-top: 1.5em; - padding-bottom: 1.5em; - text-align: center; - font-size: 0.9em; - - img { - display: block; - margin: 0 auto; - } - - h2 { - margin: 0.75em 0 0.5em 0; - letter-spacing: -0.005em; - } - - p { - margin: 0; - } -} - -// Responsive -// stylelint-disable-next-line block-no-empty -@mixin responsive() { -} diff --git a/assets/sass/pages/_home.scss b/assets/sass/pages/_home.scss deleted file mode 100644 index 786653a3..00000000 --- a/assets/sass/pages/_home.scss +++ /dev/null @@ -1,233 +0,0 @@ -@use "sass:map"; - -@use "../abstracts/themes"; -@use "../abstracts/functions"; - -// Colorful Homepage -$colors-home: ( - boston: ( - light: #fb4d42, - dark: #ff5146, - ), - js-vanilla: ( - light: #f48024, - dark: #e18431, - ), - js-frameworks: ( - light: #1091b3, - dark: #6fcbe3, - ), - jamstack: ( - light: #04a699, - dark: #08bbac, - ), - node: ( - light: #6fbc4e, - dark: #84d95f, - ), - golang: ( - light: #00acd7, - dark: #2ad1fb, - ), - php: ( - light: #8892bf, - dark: #a4afe3, - ), - ruby: ( - light: #d34135, - dark: #f95a4d, - ), - python: ( - light: #fea500, - dark: #ffbb3c, - ), - infosec: ( - light: #00b81a, - dark: #57f06d, - ), - server: ( - light: #0098ec, - dark: #43b9fb, - ), - devops: ( - light: #ff6200, - dark: #f46c16, - ), - frontend: ( - light: #4169e1, - dark: #8ca9ff, - ), - backend: ( - light: #9932cc, - dark: #d588fb, - ), - birthday: ( - light: #e40088, - dark: #fd40b1, - ), - github: ( - light: #8d4eff, - dark: #a379f0, - ), - linkedin: ( - light: #0073b1, - dark: #3b9dd2, - ), - twitter: ( - light: #00acee, - dark: #3bc9ff, - ), - email: ( - light: #de0c0c, - dark: #ff5050, - ), - pgp: ( - light: #757575, - dark: #959595, - ), - sms: ( - light: #6fcc01, - dark: #8edb34, - ), - news-1: ( - light: #ff1b1b, - dark: #f06060, - ), - news-2: ( - light: #f78200, - dark: #fd992a, - ), - news-3: ( - light: #f2b702, - dark: #ffcc2e, - ), - news-4: ( - light: #5ebd3e, - dark: #78df55, - ), - news-5: ( - light: #009cdf, - dark: #29bfff, - ), - news-6: ( - light: #3e49bb, - dark: #7b87ff, - ), - news-7: ( - light: #973999, - dark: #db60dd, - ), -); -$color-serverless: #87cef7; -$icon-wand: 'data:image/svg+xml;charset=utf-8,'; - -// ---------------- - -// Home Styles -div.layout-home { - font-size: 1em; - padding-top: 1.2em; - padding-bottom: 0.6em; - - h1 { - margin: 0 0 0.3em -0.03em; // TODO: why is this indented slightly? - font-size: 1.8em; - font-weight: 500; - letter-spacing: -0.014em; - - .emoji { - margin-left: 0.1em; // a little extra social distancing for the hand - } - - .wave { - margin-left: 2px; - } - } - - h2 { - margin: 0.5em 0 0.5em -0.03em; // TODO: why is this indented slightly? - font-size: 1.35em; - font-weight: 400; - letter-spacing: -0.022em; - line-height: 1.4; - } - - p { - margin: 0.85em 0; - letter-spacing: -0.009em; - line-height: 1.7; - } - - sup { - letter-spacing: normal; - position: relative; - - &#key { - font-size: 0.65em; - word-spacing: -0.3em; - margin-right: 0.1em; - - .emoji { - vertical-align: -0.2em; // magic number to align with text :\ - padding: 0; - } - } - } - - // easter egg emoji cursor - a#birthday { - &:hover { - cursor: url($icon-wand) 0 0, auto; - } - } - - // non-link colors - span { - &#serverless { - color: $color-serverless; - } - - &#shh { - @include themes.themed( - ( - color: "medium-light", - ) - ); - } - } -} - -// Loop through $colors-home (see above) -@each $id, $colors in $colors-home { - @each $theme, $color in $colors { - @at-root html.#{$theme} div.layout-home a##{$id} { - color: $color; - background-image: functions.underline-hack($color, map.get(map.get(themes.$themes, $theme), "background-inner")); - } - } -} - -// Responsive -@mixin responsive() { - div.layout-home { - font-size: 0.975em; - padding-top: 1em; - padding-bottom: 0.2em; - - h1 { - font-size: 1.5em; - letter-spacing: -0.014em; - line-height: 1.7; - } - - h2 { - font-size: 1.185em; - letter-spacing: -0.017em; - line-height: 1.6; - } - - p { - letter-spacing: -0.012em; - } - } -} diff --git a/assets/sass/pages/_list.scss b/assets/sass/pages/_list.scss deleted file mode 100644 index 7d33b90e..00000000 --- a/assets/sass/pages/_list.scss +++ /dev/null @@ -1,62 +0,0 @@ -@use "../abstracts/themes"; - -// Archive/List Styles -div.layout-list { - padding-top: 1.5em; - padding-bottom: 0.25em; - - section.list-section-year { - font-size: 1.05em; - - h2 { - font-size: 2em; - letter-spacing: -0.025em; - margin-top: 0; - margin-bottom: 0.4em; - } - - ul { - list-style-type: none; - margin: 0; - padding: 0; - } - - li { - display: flex; - letter-spacing: -0.008em; - line-height: 1.75; - margin-bottom: 1em; - - div.list-item-date { - width: 5.25em; - flex-shrink: 0; - - @include themes.themed( - ( - color: "medium", - ) - ); - } - - &:last-child { - margin-bottom: 1.6em; - } - } - } -} - -// Responsive -@mixin responsive() { - div.layout-list { - padding-top: 1em; - padding-bottom: 0; - - section.list-section-year { - font-size: 1em; - - h2 { - font-size: 1.75em; - } - } - } -} diff --git a/assets/sass/pages/_projects.scss b/assets/sass/pages/_projects.scss deleted file mode 100644 index 0dfd4f59..00000000 --- a/assets/sass/pages/_projects.scss +++ /dev/null @@ -1,105 +0,0 @@ -@use "../abstracts/themes"; - -// Video Styles -div.layout-projects { - padding-top: 1.5em; - padding-bottom: 0.75em; - - h1 { - margin-bottom: 0.4em; - text-align: center; - } - - div#content > p { - text-align: center; - } - - div#github-cards { - display: flex; - flex-flow: row wrap; - justify-content: space-evenly; - align-items: flex-start; - width: 100%; - - div.github-card { - flex-grow: 1; - width: 416px; // magic number - padding: 1em 1.2em; - margin: 0.6em; - border: 1px solid; - border-radius: 0.5em; - font-size: 0.9em; - - @include themes.themed( - ( - color: "medium-dark", - border-color: "kinda-light", - ) - ); - - a.repo-name { - font-size: 1.2em; - font-weight: 600; - } - - p.repo-description { - margin: 0.2em 0 0.8em 0; - } - - div.repo-meta { - display: flex; - flex-wrap: wrap; - - div.repo-meta-item { - margin-right: 1.5em; - font-size: 0.925em; - - @include themes.themed( - ( - color: "medium", - ) - ); - - a { - background: none !important; - padding: 0; - color: inherit; - - &:hover { - // octicon will inherit this - @include themes.themed( - ( - color: "links", - ) - ); - } - } - - .octicon, - span.repo-language-color { - margin-right: 0.5em; - } - - span.repo-language-color { - display: inline-block; - width: 1.15em; - height: 1.15em; - border-radius: 50%; - position: relative; - top: 0.175em; - } - } - } - } - } -} - -// Responsive -@mixin responsive() { - div.layout-projects { - h1 { - margin-bottom: 0.6em; - font-size: 1.6em; - } - } -} diff --git a/assets/sass/pages/_single.scss b/assets/sass/pages/_single.scss deleted file mode 100644 index 18358a43..00000000 --- a/assets/sass/pages/_single.scss +++ /dev/null @@ -1,106 +0,0 @@ -@use "../abstracts/themes"; - -// Post Styles -div.layout-single { - padding-top: 1em; - padding-bottom: 1em; - - div#meta { - display: flex; - flex-wrap: wrap; - margin-top: 0.3em; - font-size: 0.825em; - line-height: 2.3; - letter-spacing: 0.04em; - - @include themes.themed( - ( - color: "medium", - ) - ); - - a { - color: inherit; - - // disable fancy underline without `.no-underline` - background: none !important; - padding-bottom: 0; - } - - > div { - display: inline-flex; - margin-right: 1.6em; - white-space: nowrap; - - span.meta-icon { - margin-right: 0.4em; - user-select: none; - - .emoji { - margin-right: 0.25em; - vertical-align: -0.22em; - cursor: inherit; - } - } - - &:last-of-type { - margin-right: 0; - } - } - - div#meta-date, - div#meta-edit { - a { - display: inline-flex; - } - } - - div#meta-tags { - white-space: normal; - display: inline-flex; - flex-wrap: wrap; - - span.meta-tag { - text-transform: lowercase; - white-space: nowrap; - margin-right: 0.75em; - - &::before { - content: "#"; // cosmetically hashtagify tags - padding-right: 0.125em; - - @include themes.themed( - ( - color: "light", - ) - ); - } - - &:last-of-type { - margin-right: 0; - } - } - } - } - - h1.title { - margin: 0.3em 0 0.5em -0.03em; // TODO: why is this indented slightly? - font-size: 2.1em; - line-height: 1.3; - font-weight: 700; - letter-spacing: -0.006em; - } -} - -// Responsive -@mixin responsive() { - div.layout-single { - padding-top: 0.8em; - padding-bottom: 0.4em; - - h1.title { - margin-top: 0.4em; - font-size: 1.8em; - } - } -} diff --git a/assets/sass/pages/_videos.scss b/assets/sass/pages/_videos.scss deleted file mode 100644 index 4a218b51..00000000 --- a/assets/sass/pages/_videos.scss +++ /dev/null @@ -1,47 +0,0 @@ -@use "../abstracts/themes"; - -// Video Styles -div.layout-video { - padding: 1.5em 0; - text-align: center; - - p { - font-size: 0.85em; - letter-spacing: -0.005em; - line-height: 1.5; - margin: 1.25em 1em 0.5em 1em; - - @include themes.themed( - ( - color: "medium-light", - ) - ); - - a { - font-weight: bold; - letter-spacing: 0.001em; - } - } - - video { - width: 100%; - height: auto; - } -} - -// Responsive -@mixin responsive() { - div.layout-video { - padding: 1em 0; - - h1 { - font-size: 1.6em; - padding: 0 0.6em; - } - - video { - width: 100%; - height: auto; - } - } -} diff --git a/babel.config.cjs b/babel.config.cjs deleted file mode 100644 index a304e819..00000000 --- a/babel.config.cjs +++ /dev/null @@ -1,18 +0,0 @@ -module.exports = { - presets: [ - [ - "@babel/preset-env", - { - corejs: 3, - useBuiltIns: "entry", - }, - ], - [ - "@babel/preset-react", - { - pragma: "h", - pragmaFrag: "Fragment", - }, - ], - ], -}; diff --git a/components/Container.module.scss b/components/Container.module.scss new file mode 100644 index 00000000..5ba405eb --- /dev/null +++ b/components/Container.module.scss @@ -0,0 +1,5 @@ +.container { + max-width: 865px; + margin: 0 auto; + display: block; +} diff --git a/components/Container.tsx b/components/Container.tsx new file mode 100644 index 00000000..063a603c --- /dev/null +++ b/components/Container.tsx @@ -0,0 +1,30 @@ +import { useRouter } from "next/router"; +import { NextSeo } from "next-seo"; +import * as config from "../lib/config"; + +import styles from "./Container.module.scss"; + +type Props = { + title?: string; + description?: string; + children: unknown; +}; + +export default function Container({ title, description, children }: Props) { + const router = useRouter(); + + return ( + <> + +
{children}
+ + ); +} diff --git a/components/Content.module.scss b/components/Content.module.scss new file mode 100644 index 00000000..eea348a4 --- /dev/null +++ b/components/Content.module.scss @@ -0,0 +1,67 @@ +.content { + font-size: 0.925em; + letter-spacing: -0.004em; + line-height: 1.7; + + b, + strong { + letter-spacing: 0.008em; // not sure why the discrepancy between weights + } + + blockquote { + margin-left: 0; + padding-left: 1.5em; + border-left: 3px solid var(--link); + color: var(--medium-dark); + } + + h2, + h3, + h4 { + margin-top: 1.25em; + margin-bottom: 0.5em; + letter-spacing: 0.001em; + line-height: 1.5; + } + + // special bottom border for H2s + h2 { + padding-bottom: 0.25em; + border-bottom: 1px solid var(--kinda-light); + } + + figure { + margin: 1em auto; + text-align: center; + line-height: 1; + + img { + height: auto; + max-width: 100%; + } + + figcaption { + font-size: 0.95em; + line-height: 1.5; + margin-top: 0.5em; + color: var(--medium); + } + } + + ul, + ol { + margin-left: 1.5em; + padding-left: 0; + + li { + padding-left: 0.25em; + } + } + + hr { + margin: 1.5em auto; + height: 2px; + border: 0; + background-color: var(--light); + } +} diff --git a/components/Content.tsx b/components/Content.tsx new file mode 100644 index 00000000..42c1fa6a --- /dev/null +++ b/components/Content.tsx @@ -0,0 +1,9 @@ +import styles from "./Content.module.scss"; + +type Props = { + children: unknown; +}; + +export default function Content({ children }: Props) { + return
{children}
; +} diff --git a/components/Layout.module.scss b/components/Layout.module.scss new file mode 100644 index 00000000..2bc5175b --- /dev/null +++ b/components/Layout.module.scss @@ -0,0 +1,12 @@ +.main { + width: 100%; + padding: 1.5em; + color: var(--text); + background-color: var(--background-inner); +} + +@media screen and (max-width: 800px) { + .main { + padding: 1.25em; + } +} diff --git a/components/Layout.tsx b/components/Layout.tsx new file mode 100644 index 00000000..6b5716c7 --- /dev/null +++ b/components/Layout.tsx @@ -0,0 +1,14 @@ +import Header from "./page-header/Header"; +import Footer from "./page-footer/Footer"; + +import styles from "./Layout.module.scss"; + +export default function Layout({ children }) { + return ( + <> +
+
{children}
+