1
mirror of https://github.com/jakejarvis/jarv.is.git synced 2025-04-25 17:55:23 -04:00

v5: Revenge of the JavaScript 🦸 (#711)

Hugo ➡️ Next.js
This commit is contained in:
Jake Jarvis 2021-12-30 08:18:41 -05:00 committed by GitHub
parent b7505fa260
commit 9979e1bf3f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
577 changed files with 8019 additions and 11864 deletions

View File

@ -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"
]
}

View File

@ -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=

View File

@ -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/**"],
};

8
.eslintrc.js Normal file
View File

@ -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",
},
};

4
.gitattributes vendored
View File

@ -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

View File

@ -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 }}

View File

@ -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

View File

@ -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

29
.gitignore vendored
View File

@ -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

View File

@ -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"
}
}
}

View File

@ -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
}

View File

@ -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

View File

@ -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

View File

@ -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/**"]
}

View File

@ -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"
]
}

21
.vscode/settings.json vendored
View File

@ -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"
}

View File

@ -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).

View File

@ -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;
};

View File

@ -1,7 +0,0 @@
---
title: "{{ replace .Name "-" " " | title }}"
date: {{ dateFormat "2006-01-02 15:04:05-0700" .Date }}
description: ""
image: ""
draft: true
---

View File

@ -1,8 +0,0 @@
---
title: "{{ replace .Name "-" " " | title }}"
date: {{ dateFormat "2006-01-02 15:04:05-0700" .Date }}
description: ""
image: ""
layout: etc
draft: true
---

View File

@ -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
---

View File

@ -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
---

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

View File

@ -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 () => {};

View File

@ -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

View File

@ -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. `<h2 id="...">`)
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(<Anchor id={heading.getAttribute("id")} title={heading.textContent.trim()} />, heading, linkTarget);
});

View File

@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 22 35"><path d="M22 11.06c0 6.44-5 7.44-5 13.44 0 3.1-3.12 3.36-5.5 3.36-2.05 0-6.59-.78-6.59-3.36 0-6-4.91-7-4.91-13.44C0 5.03 5.29.14 11.08.14 16.88.14 22 5.03 22 11.06z" fill="#CCCBCB"/><path d="M15.17 32.5c0 .83-2.24 2.5-4.17 2.5-1.93 0-4.17-1.67-4.17-2.5 0-.83 2.24-.5 4.17-.5 1.93 0 4.17-.33 4.17.5z" fill="#CCD6DD"/><path d="M15.7 10.3a1 1 0 0 0-1.4 0L11 13.58l-3.3-3.3a1 1 0 1 0-1.4 1.42l3.7 3.7V26a1 1 0 1 0 2 0V15.41l3.7-3.7a1 1 0 0 0 0-1.42z" fill="#7D7A72"/><path d="M17 31a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2v-6h12v6z" fill="#99AAB5"/><path d="M5 32a1 1 0 0 1-.16-1.99l12-2a1 1 0 1 1 .33 1.97l-12 2A.93.93 0 0 1 5 32zm0-4a1 1 0 0 1-.16-1.99l12-2a1 1 0 1 1 .33 1.97l-12 2A.93.93 0 0 1 5 28z" fill="#CCD6DD"/></svg>

Before

Width:  |  Height:  |  Size: 774 B

View File

@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 22 35"><path d="M22 11.06c0 6.44-5 7.44-5 13.44 0 3.1-3.12 3.36-5.5 3.36-2.05 0-6.59-.78-6.59-3.36 0-6-4.91-7-4.91-13.44C0 5.03 5.29.14 11.08.14 16.88.14 22 5.03 22 11.06z" fill="#FFD983"/><path d="M15.17 32.5c0 .83-2.24 2.5-4.17 2.5-1.93 0-4.17-1.67-4.17-2.5 0-.83 2.24-.5 4.17-.5 1.93 0 4.17-.33 4.17.5z" fill="#B9C9D9"/><path d="M15.7 10.3a1 1 0 0 0-1.4 0L11 13.58l-3.3-3.3a1 1 0 1 0-1.4 1.42l3.7 3.7V26a1 1 0 1 0 2 0V15.41l3.7-3.7a1 1 0 0 0 0-1.42z" fill="#FFCC4D"/><path d="M17 31a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2v-6h12v6z" fill="#99AAB5"/><path d="M5 32a1 1 0 0 1-.16-1.99l12-2a1 1 0 1 1 .33 1.97l-12 2A.93.93 0 0 1 5 32zm0-4a1 1 0 0 1-.16-1.99l12-2a1 1 0 1 1 .33 1.97l-12 2A.93.93 0 0 1 5 28z" fill="#CCD6DD"/></svg>

Before

Width:  |  Height:  |  Size: 774 B

View File

@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#D99E82" d="M36 32a4 4 0 0 1-4 4H4a4 4 0 0 1-4-4v-9c0-2.209.791-3 3-3h30c2.209 0 3 .791 3 3v9z"></path><path fill="#662113" d="M25 20a7 7 0 1 1-14 0h14z"></path><path fill="#C1694F" d="M4 36h28a4 4 0 0 0 4-4H0a4 4 0 0 0 4 4z"></path><path fill="#DD2E44" d="M27.435 8.511 19.572.648a2.23 2.23 0 0 0-3.145 0L8.564 8.511C7.7 9.377 7.993 10 9.216 10H14v6a2 2 0 0 0 2 2h4a2 2 0 0 0 2-2v-6h4.784c1.223 0 1.516-.623.651-1.489z"></path></svg>

Before

Width:  |  Height:  |  Size: 507 B

View File

@ -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(<CopyButton content={codeElement.textContent} timeout={2000} />, highlightDiv);
}
});

View File

@ -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
<a
class="anchorjs-link"
href={`#${props.id}`}
title={`Jump to "${props.title}"`}
aria-label={`Jump to "${props.title}"`}
style={{
// if this is a touchscreen, always show the "#" icon instead waiting for hover
// NOTE: this is notoriously unreliable; see https://github.com/Modernizr/Modernizr/pull/2432
opacity: isTouchDevice() ? 1 : null,
}}
/>
);
};
export default Anchor;

View File

@ -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 <Loading boxes={3} width={20} />;
}
// we have data!
return (
<span title={`${hits.toLocaleString("en-US")} ${hits === 1 ? "view" : "views"}`}>
{hits.toLocaleString("en-US")}
</span>
);
};
export default Counter;

View File

@ -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(
<div
style={{
// width of each box correlates with number of boxes (with a little padding)
width: `${props.width / (boxes + 1)}px`,
height: "100%",
display: "inline-block",
// see assets/sass/components/_animation.scss:
animation: "loading 1.5s infinite ease-in-out both",
"animation-delay": `${i * animationTiming}s`,
}}
/>
);
}
return (
<div
class="loading"
style={{
width: `${props.width}px`,
height: `${props.width / 2}px`,
display: "inline-block",
"text-align": "center",
...props.style,
}}
>
{divs}
</div>
);
};
export default Loading;

View File

@ -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) => (
<div class="github-card">
<a class="repo-name" href={props.url} target="_blank" rel="noopener noreferrer">
{props.name}
</a>
{props.description && (
<p
class="repo-description"
// eslint-disable-next-line react/no-danger
dangerouslySetInnerHTML={{ __html: parseEmoji(props.description) }}
/>
)}
<div class="repo-meta">
{props.language && (
<div class="repo-meta-item">
<span class="repo-language-color" style={{ "background-color": props.language.color }} />
<span>{props.language.name}</span>
</div>
)}
{props.stars > 0 && (
<div class="repo-meta-item">
<a
href={`${props.url}/stargazers`}
title={`${props.stars.toLocaleString("en-US")} ${props.stars === 1 ? "star" : "stars"}`}
target="_blank"
rel="noopener noreferrer"
>
<StarIcon size={16} />
<span>{props.stars.toLocaleString("en-US")}</span>
</a>
</div>
)}
{props.forks > 0 && (
<div class="repo-meta-item">
<a
href={`${props.url}/network/members`}
title={`${props.forks.toLocaleString("en-US")} ${props.forks === 1 ? "fork" : "forks"}`}
target="_blank"
rel="noopener noreferrer"
>
<RepoForkedIcon size={16} />
<span>{props.forks.toLocaleString("en-US")}</span>
</a>
</div>
)}
<div
class="repo-meta-item"
title={intlFormat(
new Date(props.updatedAt),
{
year: "numeric",
month: "short",
day: "numeric",
hour: "numeric",
minute: "numeric",
timeZoneName: "short",
},
{
locale: "en-US",
}
)}
>
<span>Updated {formatDistanceToNowStrict(new Date(props.updatedAt), { addSuffix: true })}</span>
</div>
</div>
</div>
);
export default RepositoryCard;

View File

@ -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 <Loading boxes={3} width={40} style={{ margin: "0.7em auto" }} />;
}
// we have data!
return (
<>
{repos.map((repo) => (
// eslint-disable-next-line react/jsx-key
<RepositoryCard {...repo} />
))}
</>
);
};
export default RepositoryGrid;

View File

@ -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(<ContactForm />, document.querySelector(".layout-contact #contact-form-wrapper"));
}

View File

@ -1,4 +0,0 @@
import parseEmoji from "./utils/parseEmoji.js";
// apply to the entire body automatically on load...
parseEmoji(document.body);

View File

@ -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 <link rel="canonical"> 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(<Counter slug={slug} />, document.querySelector(".layout-single #meta-hits-counter"));
}

View File

@ -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(<RepositoryGrid />, document.querySelector(".layout-projects #github-cards"));
}

View File

@ -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(<ThemeToggle />, document.querySelector(".theme-toggle"));
}

View File

@ -1,8 +0,0 @@
import fetch from "unfetch";
const getData = (url) =>
fetch(url)
.then((response) => response.json())
.then((data) => data || []);
export default getData;

View File

@ -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;

View File

@ -1,25 +0,0 @@
// class names (`<html class="...">`) 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 `<html class="...">` 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 `<html class="...">` and `color-scheme` CSS property
export const updateDOM = (dark) => {
const root = document.documentElement;
// set `<html class="...">`
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";
};

View File

@ -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);
}

View File

@ -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;

View File

@ -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,
),
);

View File

@ -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;
}
}

View File

@ -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() {
}

View File

@ -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 <a> with <span>, so cover both
> a,
> span {
margin: 0 0.5em;
}
}
}
// Responsive
// stylelint-disable-next-line block-no-empty
@mixin responsive() {
}

View File

@ -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;
}
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}
}
}
}
}
}
}

View File

@ -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;
}
}
}

View File

@ -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;
}

View File

@ -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() {
}

View File

@ -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;
}
}
}

View File

@ -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() {
}

View File

@ -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,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36" width="20" height="20"><g fill="none"><path fill="#292F33" d="M2.651 6.073l26.275 26.276c.391.391 2.888-2.107 2.497-2.497L5.148 3.576c-.39-.391-2.888 2.107-2.497 2.497z"/><path fill="#66757F" d="M29.442 31.23L3.146 4.934l.883-.883 26.296 26.296z"/><path fill="#E1E8ED" d="M33.546 33.483l-.412.412-.671.671a.967.967 0 01-.255.169.988.988 0 01-1.159-.169l-2.102-2.102.495-.495.883-.883 1.119-1.119 2.102 2.102a.999.999 0 010 1.414zM4.029 4.79l-.883.883-.495.495L.442 3.96a.988.988 0 01-.169-1.159.967.967 0 01.169-.255l.671-.671.412-.412a.999.999 0 011.414 0l2.208 2.208L4.029 4.79z"/><path fill="#F5F8FA" d="M30.325 30.497l2.809 2.809-.671.671a.967.967 0 01-.255.169l-2.767-2.767.884-.882zM3.146 5.084L.273 2.211a.967.967 0 01.169-.255l.671-.671 2.916 2.916-.883.883z"/><path fill="#FFAC33" d="M27.897 10.219l1.542.571.6 2.2a.667.667 0 001.287 0l.6-2.2 1.542-.571a.665.665 0 000-1.25l-1.534-.568-.605-2.415a.667.667 0 00-1.293 0l-.605 2.415-1.534.568a.665.665 0 000 1.25m-16.936 9.628l2.61.966.966 2.61a1.103 1.103 0 002.07 0l.966-2.61 2.609-.966a1.103 1.103 0 000-2.07l-2.609-.966-.966-2.61a1.105 1.105 0 00-2.07 0l-.966 2.61-2.61.966a1.104 1.104 0 000 2.07M23.13 4.36l1.383.512.512 1.382a.585.585 0 001.096 0l.512-1.382 1.382-.512a.584.584 0 000-1.096l-1.382-.512-.512-1.382a.585.585 0 00-1.096 0l-.512 1.382-1.383.512a.585.585 0 000 1.096"/></g></svg>';
// ----------------
// 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;
}
}
}

View File

@ -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;
}
}
}
}

View File

@ -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;
}
}
}

View File

@ -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;
}
}
}

View File

@ -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;
}
}
}

View File

@ -1,18 +0,0 @@
module.exports = {
presets: [
[
"@babel/preset-env",
{
corejs: 3,
useBuiltIns: "entry",
},
],
[
"@babel/preset-react",
{
pragma: "h",
pragmaFrag: "Fragment",
},
],
],
};

View File

@ -0,0 +1,5 @@
.container {
max-width: 865px;
margin: 0 auto;
display: block;
}

30
components/Container.tsx Normal file
View File

@ -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 (
<>
<NextSeo
title={title}
description={description}
canonical={`${config.baseURL}${router.asPath}`}
openGraph={{
title: title,
url: `${config.baseURL}${router.asPath}`,
}}
/>
<div className={styles.container}>{children}</div>
</>
);
}

View File

@ -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);
}
}

9
components/Content.tsx Normal file
View File

@ -0,0 +1,9 @@
import styles from "./Content.module.scss";
type Props = {
children: unknown;
};
export default function Content({ children }: Props) {
return <div className={styles.content}>{children}</div>;
}

View File

@ -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;
}
}

14
components/Layout.tsx Normal file
View File

@ -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 (
<>
<Header />
<main className={styles.main}>{children}</main>
<Footer />
</>
);
}

View File

@ -0,0 +1,23 @@
.copy_button {
position: absolute;
top: 0;
right: 0;
padding: 0.75em;
color: var(--text);
background-color: var(--background-inner);
border: 1px solid var(--kinda-light);
cursor: pointer;
&:hover {
color: var(--link);
}
.octicon {
width: 16px;
height: 16px;
}
.octicon-check {
color: var(--success);
}
}

View File

@ -1,12 +1,18 @@
import { h } from "preact";
import { useState, useEffect } from "preact/hooks";
import { useState, useEffect } from "react";
import copy from "copy-to-clipboard";
import trimNewlines from "trim-newlines";
// react components:
import { CopyIcon, CheckIcon } from "@primer/octicons-react";
const CopyButton = (props) => {
import styles from "./CopyButton.module.scss";
type Props = {
content: string;
timeout?: number;
};
export default function CopyButton({ content, timeout = 2000 }: Props) {
const [copied, setCopied] = useState(false);
const handleCopy = (e) => {
@ -16,7 +22,7 @@ const CopyButton = (props) => {
e.target.blur();
// trim any surrounding whitespace from target block's content and send it to the clipboard
const didCopy = copy(trimNewlines(props.content));
const didCopy = copy(trimNewlines(content));
// indicate success
setCopied(didCopy);
@ -27,25 +33,27 @@ const CopyButton = (props) => {
if (copied) {
const id = setTimeout(() => {
setCopied(false);
}, props.timeout || 2000);
}, timeout);
return () => clearTimeout(id);
}
return () => {};
}, [props.timeout, copied]);
}, [timeout, copied]);
return (
<button
class="copy-button"
className={styles.copy_button}
title="Copy to clipboard"
aria-label="Copy to clipboard"
onClick={handleCopy}
disabled={copied}
>
{copied ? <CheckIcon size={16} /> : <CopyIcon size={16} />}
{copied ? (
<CheckIcon size={16} className={`${styles.octicon} ${styles["octicon-check"]}`} />
) : (
<CopyIcon size={16} className={styles.octicon} />
)}
</button>
);
};
export default CopyButton;
}

View File

@ -0,0 +1,96 @@
.form {
input[type="text"],
input[type="email"],
select,
textarea {
width: 100%;
padding: 0.8em;
margin: 0.6em 0;
border: 2px solid;
border-radius: 0.3em;
color: var(--text);
background-color: var(--super-duper-light);
border-color: var(--light);
&:focus {
outline: none; // disable browsers' outer border
border-color: var(--link);
}
}
textarea {
height: 12em;
min-height: 6em;
margin-bottom: 0;
line-height: 1.5;
// allow vertical resizing & disable horizontal
resize: vertical;
}
}
.markdown_tip {
font-size: 0.825em;
line-height: 1.75;
a {
background: none;
padding-bottom: 0;
&:first-of-type {
font-weight: 500;
}
}
}
.captcha {
margin: 1em 0;
}
.action_row {
display: flex;
align-items: center;
min-height: 3.75em;
}
.btn_submit {
flex-shrink: 0;
height: 3.25em;
padding: 1em 1.25em;
margin-right: 1.5em;
border: 0;
border-radius: 0.3em;
font-size: 1.1em;
cursor: pointer;
user-select: none;
color: var(--text);
background-color: var(--kinda-light);
&:hover {
color: var(--super-duper-light);
background-color: var(--link);
}
.send_icon {
height: 1.2em;
width: 1.2em;
vertical-align: -0.22em;
border: 0;
display: inline-block;
margin-right: 0.4em;
cursor: inherit;
}
}
.result_success,
.result_error {
font-weight: 600;
}
.result_success {
color: var(--success);
}
.result_error {
color: var(--error);
}

View File

@ -1,14 +1,11 @@
import { h, Fragment } from "preact";
import { useState } from "preact/hooks";
import fetch from "unfetch";
import { isDark } from "../utils/theme.js";
// react components:
import { useState } from "react";
import HCaptcha from "@hcaptcha/react-hcaptcha";
import { CheckIcon, XIcon } from "@primer/octicons-react";
import SendIcon from "../assets/send.svg";
import { SendIcon } from "../icons";
const ContactForm = () => {
import styles from "./ContactForm.module.scss";
export default function ContactForm() {
// status/feedback:
const [status, setStatus] = useState({ success: false, message: "" });
// keep track of fetch:
@ -30,14 +27,10 @@ const ContactForm = () => {
};
// some client-side validation to save requests (these are also checked on the server to be safe)
// TODO: change border color of the specific empty/missing field(s) to red
if (!(formData.name && formData.email && formData.message && formData["h-captcha-response"])) {
setSending(false);
setStatus({ success: false, message: "Please make sure that all fields are filled in." });
// remove focus from the submit button
document.activeElement.blur();
return;
}
@ -83,19 +76,16 @@ const ContactForm = () => {
// something else went wrong, and it's probably my fault...
setStatus({ success: false, message: "Internal server error. Try again later?" });
}
// remove focus from the submit button
document.activeElement.blur();
});
};
return (
<form onSubmit={onSubmit} id="contact-form" action="/api/contact/" method="POST">
<input type="text" name="name" placeholder="Name" disabled={status.success} />
<input type="email" name="email" placeholder="Email" disabled={status.success} />
<textarea name="message" placeholder="Write something..." disabled={status.success} />
<form className={styles.form} onSubmit={onSubmit} action="/api/contact/" method="POST">
<input type="text" name="name" placeholder="Name" required disabled={status.success} />
<input type="email" name="email" placeholder="Email" required disabled={status.success} />
<textarea name="message" placeholder="Write something..." required disabled={status.success} />
<div id="contact-form-md-info">
<div className={styles.markdown_tip}>
Basic{" "}
<a
href="https://commonmark.org/help/"
@ -112,19 +102,17 @@ const ContactForm = () => {
](https://jarv.is), and <code>`code`</code>.
</div>
<div id="contact-form-captcha">
<div className={styles.captcha}>
<HCaptcha
sitekey={process.env.HCAPTCHA_SITE_KEY}
theme={isDark() ? "dark" : "light"}
sitekey={process.env.NEXT_PUBLIC_HCAPTCHA_SITE_KEY}
size="normal"
reCaptchaCompat={false}
onVerify={() => true} // this is allegedly optional but a function undefined error is thrown without it
/>
</div>
<div id="contact-form-action-row">
<div className={styles.action_row}>
<button
id="contact-form-btn-submit"
className={styles.btn_submit}
title="Send Message"
aria-label="Send Message"
disabled={sending}
@ -134,14 +122,13 @@ const ContactForm = () => {
<span>Sending...</span>
) : (
<>
<SendIcon class="emoji" /> <span>Send</span>
<SendIcon className={styles.send_icon} /> <span>Send</span>
</>
)}
</button>
<span
class="contact-form-result"
id={status.success ? "contact-form-result-success" : "contact-form-result-error"}
className={status.success ? styles.result_success : styles.result_error}
style={{ display: !status.message || sending ? "none" : null }}
>
{status.success ? <CheckIcon size={16} /> : <XIcon size={16} />} {status.message}
@ -149,6 +136,4 @@ const ContactForm = () => {
</div>
</form>
);
};
export default ContactForm;
}

28
components/hits/Hits.tsx Normal file
View File

@ -0,0 +1,28 @@
import useSWR from "swr";
import { fetcher } from "../../lib/fetcher";
import Loading from "../loading/Loading";
export default function Counter({ slug }) {
// start fetching repos from API immediately
const { data, error } = useSWR(`/api/hits/?slug=${encodeURIComponent(slug)}`, fetcher, {
// avoid double (or more) counting views
revalidateOnFocus: false,
});
// show spinning loading indicator if data isn't fetched yet
if (!data) {
return <Loading boxes={3} width={20} />;
}
// fail secretly
if (error) {
return;
}
// we have data!
return (
<span title={`${data.hits.toLocaleString("en-US")} ${data.hits === 1 ? "view" : "views"}`}>
{data.hits.toLocaleString("en-US")}
</span>
);
}

View File

@ -0,0 +1,59 @@
import hexRgb from "hex-rgb";
import isAbsoluteUrl from "is-absolute-url";
import Link from "next/link";
type Props = {
children: unknown;
href: string;
lightColor: string;
darkColor: string;
title?: string;
className?: string;
external?: boolean;
};
export default function ColorLink({
children,
href,
lightColor,
darkColor,
title,
className,
external = false,
}: Props) {
external = external || isAbsoluteUrl(href);
// hacky hack to form a unique CSS var based on the light hex code, since they need to be set "globally"
const varName = `Home__${lightColor.replace("#", "")}`;
const alpha = 0.4;
return (
<Link href={href} passHref={true} prefetch={false}>
{/* eslint-disable-next-line react/jsx-no-target-blank */}
<a
className={className}
title={title}
target={external ? "_blank" : undefined}
rel={external ? "noopener noreferrer" : undefined}
>
{children}
<style jsx global>{`
:root {
--${varName}: ${lightColor};
--${varName}_alpha: ${hexRgb(lightColor, { alpha: alpha, format: "css" })};
}
[data-theme="dark"] {
--${varName}: ${darkColor};
--${varName}_alpha: ${hexRgb(darkColor, { alpha: alpha, format: "css" })};
}
`}</style>
<style jsx>{`
a {
color: var(--${varName});
background-image: linear-gradient(var(--${varName}_alpha), var(--${varName}_alpha));
}
`}</style>
</a>
</Link>
);
}

View File

@ -0,0 +1,107 @@
.home {
font-size: 1em;
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;
.wave {
display: inline-block;
margin-left: 2px;
animation: wave 5s infinite;
animation-delay: 1s;
transform-origin: 65% 80%;
will-change: transform;
}
}
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;
&:last-of-type {
margin-bottom: 0;
}
}
sup {
letter-spacing: normal;
position: relative;
margin-left: 0.2em;
a {
background: none;
padding-bottom: 0;
}
}
.pgp {
font-size: 0.875em;
word-spacing: -0.3em;
margin-right: 0.075em;
}
.light {
color: var(--medium-light);
}
.birthday:hover {
cursor: url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns=%27http://www.w3.org/2000/svg%27 viewBox=%270 0 36 36%27 width=%2720%27 height=%2720%27%3E%3Cg fill=%27none%27%3E%3Cpath fill=%27%23292F33%27 d=%27m2.651 6.073 26.275 26.276c.391.391 2.888-2.107 2.497-2.497L5.148 3.576c-.39-.391-2.888 2.107-2.497 2.497z%27/%3E%3Cpath fill=%27%2366757F%27 d=%27M29.442 31.23 3.146 4.934l.883-.883 26.296 26.296z%27/%3E%3Cpath fill=%27%23E1E8ED%27 d=%27m33.546 33.483-.412.412-.671.671a.967.967 0 0 1-.255.169.988.988 0 0 1-1.159-.169l-2.102-2.102.495-.495.883-.883 1.119-1.119 2.102 2.102a.999.999 0 0 1 0 1.414zM4.029 4.79l-.883.883-.495.495L.442 3.96a.988.988 0 0 1-.169-1.159.967.967 0 0 1 .169-.255l.671-.671.412-.412a.999.999 0 0 1 1.414 0l2.208 2.208L4.029 4.79z%27/%3E%3Cpath fill=%27%23F5F8FA%27 d=%27m30.325 30.497 2.809 2.809-.671.671a.967.967 0 0 1-.255.169l-2.767-2.767.884-.882zM3.146 5.084.273 2.211a.967.967 0 0 1 .169-.255l.671-.671 2.916 2.916-.883.883z%27/%3E%3Cpath fill=%27%23FFAC33%27 d=%27m27.897 10.219 1.542.571.6 2.2a.667.667 0 0 0 1.287 0l.6-2.2 1.542-.571a.665.665 0 0 0 0-1.25l-1.534-.568-.605-2.415a.667.667 0 0 0-1.293 0l-.605 2.415-1.534.568a.665.665 0 0 0 0 1.25m-16.936 9.628 2.61.966.966 2.61a1.103 1.103 0 0 0 2.07 0l.966-2.61 2.609-.966a1.103 1.103 0 0 0 0-2.07l-2.609-.966-.966-2.61a1.105 1.105 0 0 0-2.07 0l-.966 2.61-2.61.966a1.104 1.104 0 0 0 0 2.07M23.13 4.36l1.383.512.512 1.382a.585.585 0 0 0 1.096 0l.512-1.382 1.382-.512a.584.584 0 0 0 0-1.096l-1.382-.512-.512-1.382a.585.585 0 0 0-1.096 0l-.512 1.382-1.383.512a.585.585 0 0 0 0 1.096%27/%3E%3C/g%3E%3C/svg%3E")
0 0,
auto;
}
}
@media screen and (max-width: 800px) {
.home {
font-size: 0.975em;
h1 {
font-size: 1.6em;
}
h2 {
font-size: 1.2em;
}
}
}
@keyframes wave {
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);
}
// pause for 3.5 out of 5 seconds
100% {
transform: rotate(0deg);
}
}

247
components/home/Home.tsx Normal file
View File

@ -0,0 +1,247 @@
import Link from "./ColorLink";
import { WaveIcon, LockIcon } from "../icons";
import styles from "./Home.module.scss";
export default function Home() {
return (
<div className={styles.home}>
<h1>
Hi there! I'm Jake.{" "}
<span className={styles.wave}>
<WaveIcon />
</span>
</h1>
<h2>
I'm a frontend web developer based in{" "}
<Link
href="https://www.youtube-nocookie.com/embed/rLwbzGyC6t4?hl=en&amp;fs=1&amp;showinfo=1&amp;rel=0&amp;iv_load_policy=3"
title='"Boston Accent Trailer - Late Night with Seth Meyers" on YouTube'
lightColor="#fb4d42"
darkColor="#ff5146"
>
Boston
</Link>
.
</h2>
<p>
I specialize in{" "}
<Link
href="https://stackoverflow.blog/2018/01/11/brutal-lifecycle-javascript-frameworks/"
title='"The Brutal Lifecycle of JavaScript Frameworks" by Ian Allen'
lightColor="#1091b3"
darkColor="#6fcbe3"
>
modern JS frameworks
</Link>{" "}
and{" "}
<Link
href="http://vanilla-js.com/"
title="The best JS framework in the world by Eric Wastl"
lightColor="#f48024"
darkColor="#e18431"
>
vanilla JavaScript
</Link>{" "}
to make nifty{" "}
<Link href="https://jamstack.wtf/" title="WTF is JAMstack?" lightColor="#04a699" darkColor="#08bbac">
JAMstack sites
</Link>{" "}
with dynamic{" "}
<Link href="https://nodejs.org/en/" title="Node.js Official Website" lightColor="#6fbc4e" darkColor="#84d95f">
Node.js
</Link>{" "}
services. But I'm fluent in non-buzzwords like{" "}
<Link
href="https://stitcher.io/blog/php-in-2020"
title='"PHP in 2020" by Brent Roose'
lightColor="#8892bf"
darkColor="#a4afe3"
>
PHP
</Link>
,{" "}
<Link
href="https://www.ruby-lang.org/en/"
title="Ruby Official Website"
lightColor="#d34135"
darkColor="#f95a4d"
>
Ruby
</Link>
, and{" "}
<Link href="https://golang.org/" title="Golang Official Website" lightColor="#00acd7" darkColor="#2ad1fb">
Go
</Link>{" "}
too.
</p>
<p>
Whenever possible, I also apply my experience in{" "}
<Link
href="https://github.com/jakejarvis/awesome-shodan-queries"
title="jakejarvis/awesome-shodan-queries on GitHub"
lightColor="#00b81a"
darkColor="#57f06d"
>
application security
</Link>
,{" "}
<Link
href="https://www.cloudflare.com/learning/serverless/what-is-serverless/"
title='"What is serverless computing?" on Cloudflare'
lightColor="#0098ec"
darkColor="#43b9fb"
>
serverless stacks
</Link>
, and{" "}
<Link href="https://xkcd.com/1319/" title='"Automation" on xkcd' lightColor="#ff6200" darkColor="#f46c16">
DevOps automation
</Link>
.
</p>
<p>
I fell in love with{" "}
<Link
href="/previously/"
title="My Terrible, Horrible, No Good, Very Bad First Websites"
lightColor="#4169e1"
darkColor="#8ca9ff"
>
frontend web design
</Link>{" "}
and{" "}
<Link
href="/notes/my-first-code/"
title="Jake's Bulletin Board, circa 2003"
lightColor="#9932cc"
darkColor="#d588fb"
>
backend programming
</Link>{" "}
back when my only source of income was{" "}
<Link
className={styles.birthday}
href="/birthday/"
title="🎉 Cranky Birthday Boy on VHS Tape 📼"
lightColor="#e40088"
darkColor="#fd40b1"
>
the Tooth Fairy
</Link>
. <span className={styles.light}>I've improved a bit since then, I think...</span>
</p>
<p>
Over the years, some of my side projects{" "}
<Link
href="https://tuftsdaily.com/news/2012/04/06/student-designs-iphone-joeytracker-app/"
title='"Student designs iPhone JoeyTracker app" on The Tufts Daily'
lightColor="#ff1b1b"
darkColor="#f06060"
>
have
</Link>{" "}
<Link
href="/leo/"
title="Powncer segment on The Lab with Leo Laporte (G4techTV)"
lightColor="#f78200"
darkColor="#fd992a"
>
been
</Link>{" "}
<Link
href="https://www.google.com/books/edition/The_Facebook_Effect/RRUkLhyGZVgC?hl=en&gbpv=1&dq=%22jake%20jarvis%22&pg=PA226&printsec=frontcover&bsq=%22jake%20jarvis%22"
title='"The Facebook Effect" by David Kirkpatrick (Google Books)'
lightColor="#f2b702"
darkColor="#ffcc2e"
>
featured
</Link>{" "}
<Link
href="https://money.cnn.com/2007/06/01/technology/facebookplatform.fortune/index.htm"
title='"The new Facebook is on a roll" on CNN Money'
lightColor="#5ebd3e"
darkColor="#78df55"
>
by
</Link>{" "}
<Link
href="https://www.wired.com/2007/04/our-web-servers/"
title='"Middio: A YouTube Scraper for Major Label Music Videos" on Wired'
lightColor="#009cdf"
darkColor="#29bfff"
>
various
</Link>{" "}
<Link
href="https://gigaom.com/2009/10/06/fresh-faces-in-tech-10-kid-entrepreneurs-to-watch/6/"
title='"Fresh Faces in Tech: 10 Kid Entrepreneurs to Watch" on Gigaom'
lightColor="#3e49bb"
darkColor="#7b87ff"
>
media
</Link>{" "}
<Link
href="https://adage.com/article/small-agency-diary/client-ceo-s-son/116723/"
title='"Your Next Client? The CEO&#39;s Son" on Advertising Age'
lightColor="#973999"
darkColor="#db60dd"
>
outlets
</Link>
.
</p>
<p>
You can find more of my work on{" "}
<Link
href="https://github.com/jakejarvis"
title="Jake Jarvis on GitHub"
lightColor="#8d4eff"
darkColor="#a379f0"
>
GitHub
</Link>{" "}
and{" "}
<Link
href="https://www.linkedin.com/in/jakejarvis/"
title="Jake Jarvis on LinkedIn"
lightColor="#0073b1"
darkColor="#3b9dd2"
>
LinkedIn
</Link>
. I'm always available to connect over{" "}
<Link href="/contact/" title="Send an email" lightColor="#de0c0c" darkColor="#ff5050">
email
</Link>{" "}
<sup className="monospace">
<Link href="/pubkey.asc" title="My Public Key" lightColor="#757575" darkColor="#959595" external={true}>
<span className={styles.pgp}>
<LockIcon alt="PGP Key" /> 2B0C 9CF2 51E6 9A39
</span>
</Link>
</sup>
,{" "}
<Link
href="https://twitter.com/jakejarvis"
title="Jake Jarvis on Twitter"
lightColor="#00acee"
darkColor="#3bc9ff"
>
Twitter
</Link>
, or{" "}
<Link href="sms:+1-617-917-3737" title="Send SMS to +1 (617) 917-3737" lightColor="#6fcc01" darkColor="#8edb34">
SMS
</Link>{" "}
as well!
</p>
</div>
);
}

51
components/icons/index.ts Normal file
View File

@ -0,0 +1,51 @@
import BotIcon from "./svg/bot.svg";
import BulbOffIcon from "./svg/bulb-off.svg";
import BulbOnIcon from "./svg/bulb-on.svg";
import ContactIcon from "./svg/contact.svg";
import DateIcon from "./svg/date.svg";
import EditIcon from "./svg/edit.svg";
import FloppyIcon from "./svg/floppy.svg";
import HeartIcon from "./svg/heart.svg";
import HomeIcon from "./svg/home.svg";
import LaptopIcon from "./svg/laptop.svg";
import LicenseIcon from "./svg/license.svg";
import LockIcon from "./svg/lock.svg";
import MailIcon from "./svg/mail.svg";
import NextjsIcon from "./svg/nextjs.svg";
import NotesIcon from "./svg/notes.svg";
import PrivacyIcon from "./svg/privacy.svg";
import ProjectsIcon from "./svg/projects.svg";
import SendIcon from "./svg/send.svg";
import SirenIcon from "./svg/siren.svg";
import TagIcon from "./svg/tag.svg";
import TapeIcon from "./svg/tape.svg";
import ViewsIcon from "./svg/views.svg";
import WandIcon from "./svg/wand.svg";
import WaveIcon from "./svg/wave.svg";
export {
BotIcon,
BulbOffIcon,
BulbOnIcon,
ContactIcon,
DateIcon,
EditIcon,
FloppyIcon,
HeartIcon,
HomeIcon,
LaptopIcon,
LicenseIcon,
LockIcon,
MailIcon,
NextjsIcon,
NotesIcon,
PrivacyIcon,
ProjectsIcon,
SendIcon,
SirenIcon,
TagIcon,
TapeIcon,
ViewsIcon,
WandIcon,
WaveIcon,
};

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><ellipse fill="#F4900C" cx="33.5" cy="14.5" rx="2.5" ry="3.5"/><ellipse fill="#F4900C" cx="2.5" cy="14.5" rx="2.5" ry="3.5"/><path fill="#FFAC33" d="M34 19c0 .553-.447 1-1 1h-3c-.553 0-1-.447-1-1v-9c0-.552.447-1 1-1h3c.553 0 1 .448 1 1v9zM7 19c0 .553-.448 1-1 1H3c-.552 0-1-.447-1-1v-9c0-.552.448-1 1-1h3c.552 0 1 .448 1 1v9z"/><path fill="#FFCC4D" d="M28 5c0 2.761-4.478 4-10 4C12.477 9 8 7.761 8 5s4.477-5 10-5c5.522 0 10 2.239 10 5z"/><path fill="#F4900C" d="M25 4.083C25 5.694 21.865 7 18 7c-3.866 0-7-1.306-7-2.917 0-1.611 3.134-2.917 7-2.917 3.865 0 7 1.306 7 2.917z"/><path fill="#269" d="M30 5.5C30 6.881 28.881 7 27.5 7h-19C7.119 7 6 6.881 6 5.5S7.119 3 8.5 3h19C28.881 3 30 4.119 30 5.5z"/><path fill="#55ACEE" d="M30 6H6c-1.104 0-2 .896-2 2v26h28V8c0-1.104-.896-2-2-2z"/><path fill="#3B88C3" d="M35 33v-1c0-1.104-.896-2-2-2H22.071l-3.364 3.364c-.391.391-1.023.391-1.414 0L13.929 30H3c-1.104 0-2 .896-2 2v1c0 1.104-.104 2 1 2h32c1.104 0 1-.896 1-2z"/><circle fill="#FFF" cx="24.5" cy="14.5" r="4.5"/><circle fill="#DD2E44" cx="24.5" cy="14.5" r="2.721"/><circle fill="#FFF" cx="11.5" cy="14.5" r="4.5"/><path fill="#F5F8FA" d="M29 25.5c0 1.381-1.119 2.5-2.5 2.5h-17C8.119 28 7 26.881 7 25.5S8.119 23 9.5 23h17c1.381 0 2.5 1.119 2.5 2.5z"/><path fill="#CCD6DD" d="M17 23h2v5h-2zm-5 0h2v5h-2zm10 0h2v5h-2zM7 25.5c0 1.21.859 2.218 2 2.45v-4.9c-1.141.232-2 1.24-2 2.45zm20-2.45v4.899c1.141-.232 2-1.24 2-2.45s-.859-2.217-2-2.449z"/><circle fill="#DD2E44" cx="11.5" cy="14.5" r="2.721"/></svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><g fill="none"><path d="m30 11.376c0 6.6229714-5.2272727 7.6515429-5.2272727 13.824 0 3.1865143-3.2649546 3.4549714-5.75 3.4549714-2.1463182 0-6.8853637-.8012571-6.8853637-3.4570285 0-6.1693715-5.1373636-7.1979429-5.1373636-13.8219429 0-6.20331429 5.5252273-11.232 11.5867727-11.232 6.0636364 0 11.4132273 5.02868571 11.4132273 11.232z" fill="#cccbcb"/><path d="m22.8564091 33.4285714c0 .8516572-2.3355455 2.5714286-4.3564091 2.5714286s-4.3564091-1.7197714-4.3564091-2.5714286c0-.8516571 2.3345-.5142857 4.3564091-.5142857 2.0208636 0 4.3564091-.3373714 4.3564091.5142857z" fill="#ccd6dd"/><path d="m23.4209545 10.5870857c-.4087727-.4021714-1.0695-.4021714-1.4782727 0l-3.4426818 3.3870857-3.4426818-3.3870857c-.4087727-.4021714-1.0695-.4021714-1.4782727 0-.4087728.4021714-.4087728 1.0522286 0 1.4544l3.8755 3.8129143v10.8884571c0 .5688.4683636 1.0285715 1.0454545 1.0285715s1.0454545-.4597715 1.0454545-1.0285715v-10.8884571l3.8755-3.8129143c.4087728-.4021714.4087728-1.0522286 0-1.4544z" fill="#7d7a72"/><path d="m24.7727273 31.8857143c0 1.1355428-.9367273 2.0571428-2.0909091 2.0571428h-8.3636364c-1.1541818 0-2.0909091-.9216-2.0909091-2.0571428v-6.1714286h12.5454546z" fill="#99aab5"/><path d="m12.2262273 32.9142857c-.5018182 0-.9450909-.3569143-1.0297728-.8598857-.0951363-.5595429.289591-1.0902857.8593637-1.1828571l12.5454545-2.0571429c.5687273-.1008 1.1081818.2849143 1.2022728.8454857.0951363.5595429-.289591 1.0902857-.8593637 1.1828572l-12.5454545 2.0571428c-.0575.0102857-.1160455.0144-.1725.0144zm0-4.1142857c-.5018182 0-.9450909-.3569143-1.0297728-.8598857-.0951363-.5595429.289591-1.0902857.8593637-1.1828572l12.5454545-2.0571428c.5687273-.0997714 1.1081818.2849143 1.2022728.8454857.0951363.5595429-.289591 1.0902857-.8593637 1.1828571l-12.5454545 2.0571429c-.0575.0102857-.1160455.0144-.1725.0144z" fill="#ccd6dd"/></g></svg>

After

Width:  |  Height:  |  Size: 1.9 KiB

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><g fill="none"><path d="m30 11.376c0 6.6229714-5.2272727 7.6515429-5.2272727 13.824 0 3.1865143-3.2649546 3.4549714-5.75 3.4549714-2.1463182 0-6.8853637-.8012571-6.8853637-3.4570285 0-6.1693715-5.1373636-7.1979429-5.1373636-13.8219429 0-6.20331429 5.5252273-11.232 11.5867727-11.232 6.0636364 0 11.4132273 5.02868571 11.4132273 11.232z" fill="#ffd983"/><path d="m22.8564091 33.4285714c0 .8516572-2.3355455 2.5714286-4.3564091 2.5714286s-4.3564091-1.7197714-4.3564091-2.5714286c0-.8516571 2.3345-.5142857 4.3564091-.5142857 2.0208636 0 4.3564091-.3373714 4.3564091.5142857z" fill="#b9c9d9"/><path d="m23.4209545 10.5870857c-.4087727-.4021714-1.0695-.4021714-1.4782727 0l-3.4426818 3.3870857-3.4426818-3.3870857c-.4087727-.4021714-1.0695-.4021714-1.4782727 0-.4087728.4021714-.4087728 1.0522286 0 1.4544l3.8755 3.8129143v10.8884571c0 .5688.4683636 1.0285715 1.0454545 1.0285715s1.0454545-.4597715 1.0454545-1.0285715v-10.8884571l3.8755-3.8129143c.4087728-.4021714.4087728-1.0522286 0-1.4544z" fill="#ffcc4d"/><path d="m24.7727273 31.8857143c0 1.1355428-.9367273 2.0571428-2.0909091 2.0571428h-8.3636364c-1.1541818 0-2.0909091-.9216-2.0909091-2.0571428v-6.1714286h12.5454546z" fill="#99aab5"/><path d="m12.2262273 32.9142857c-.5018182 0-.9450909-.3569143-1.0297728-.8598857-.0951363-.5595429.289591-1.0902857.8593637-1.1828571l12.5454545-2.0571429c.5687273-.1008 1.1081818.2849143 1.2022728.8454857.0951363.5595429-.289591 1.0902857-.8593637 1.1828572l-12.5454545 2.0571428c-.0575.0102857-.1160455.0144-.1725.0144zm0-4.1142857c-.5018182 0-.9450909-.3569143-1.0297728-.8598857-.0951363-.5595429.289591-1.0902857.8593637-1.1828572l12.5454545-2.0571428c.5687273-.0997714 1.1081818.2849143 1.2022728.8454857.0951363.5595429-.289591 1.0902857-.8593637 1.1828571l-12.5454545 2.0571429c-.0575.0102857-.1160455.0144-.1725.0144z" fill="#ccd6dd"/></g></svg>

After

Width:  |  Height:  |  Size: 1.9 KiB

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#C1694F" d="M22 33c0 2.209-8 2.209-8 0V23c0-2.209 1.791-4 4-4s4 1.791 4 4v10z"/><path fill="#292F33" d="M10 3c-4.418 0-8 3.582-8 8v12h16V11c0-4.418-3.582-8-8-8z"/><path fill="#CCD6DD" d="M20 18.761C20 19.997 18.935 21 17.707 21H2.293C1.064 21 0 19.997 0 18.761V8.239C0 7.003 1.064 6 2.293 6h15.414C18.935 6 20 7.003 20 8.239v10.522z"/><path fill="#99AAB5" d="M20 8.239C20 7.003 18.935 6 17.707 6H2.293C1.064 6 0 7.003 0 8.239v1.419l4.879 4.904-4.78 4.806c.112.376.316.716.596.983l4.972-4.998 2.407 2.419c1.052 1.06 2.768 1.06 3.821 0l2.426-2.432 4.984 5.011c.28-.268.483-.608.596-.983l-4.792-4.818L20 9.646V8.239z"/><path fill="#E1E8ED" d="M17.707 6H2.293C1.127 6 .121 6.906.02 8.055l8.408 8.397c.869.874 2.277.84 3.145-.035l8.41-8.346C19.889 6.914 18.877 6 17.707 6z"/><path fill="#99AAB5" d="M26 3H10c4.418 0 8 3.582 8 8v12h16V11c0-4.418-3.582-8-8-8z"/><path fill="#DD2E44" d="M26 1h-4c-1.104 0-2 .896-2 2v12c0 1.104.896 2 2 2s2-.896 2-2V7h2c1.104 0 2-.896 2-2V3c0-1.104-.896-2-2-2z"/></svg>

After

Width:  |  Height:  |  Size: 1.0 KiB

1
components/icons/svg/date.svg Executable file
View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path d="m36 32c0 2.209-1.791 4-4 4h-28c-2.209 0-4-1.791-4-4v-23c0-2.209 1.791-4 4-4h28c2.209 0 4 1.791 4 4z" fill="#e0e7ec"/><path d="m23.657 19.12h-5.787c-1.22 0-1.673-.791-1.673-1.56 0-.791.429-1.56 1.673-1.56h8.184c1.154 0 1.628 1.04 1.628 1.628 0 .452-.249.927-.52 1.492l-5.607 11.395c-.633 1.266-.882 1.717-1.899 1.717-1.244 0-1.877-.949-1.877-1.605 0-.271.068-.474.226-.791zm-12.768-.12h-.5c-1.085 0-1.538-.731-1.538-1.5 0-.792.565-1.5 1.538-1.5h2.015c.972 0 1.515.701 1.515 1.605v12.865c0 1.13-.558 1.763-1.53 1.763s-1.5-.633-1.5-1.763z" fill="#66757f"/><path d="m34 0h-3.277c.172.295.277.634.277 1 0 1.104-.896 2-2 2s-2-.896-2-2c0-.366.105-.705.277-1h-18.554c.172.295.277.634.277 1 0 1.104-.896 2-2 2s-2-.896-2-2c0-.366.105-.705.277-1h-3.277c-1.104 0-2 .896-2 2v11h36v-11c0-1.104-.896-2-2-2z" fill="#dd2f45"/><path d="m13.182 4.604c0-.5.32-.78.75-.78.429 0 .749.28.749.78v5.017h1.779c.51 0 .73.38.72.72-.02.33-.28.659-.72.659h-2.498c-.49 0-.78-.319-.78-.819zm-6.91 0c0-.5.32-.78.75-.78s.75.28.75.78v3.488c0 .92.589 1.649 1.539 1.649.909 0 1.529-.769 1.529-1.649v-3.488c0-.5.319-.78.749-.78s.75.28.75.78v3.568c0 1.679-1.38 2.949-3.028 2.949-1.669 0-3.039-1.25-3.039-2.949zm-.782 4.397c0 1.679-1.069 2.119-1.979 2.119-.689 0-1.839-.27-1.839-1.14 0-.269.23-.609.56-.609.4 0 .75.37 1.199.37.56 0 .56-.52.56-.84v-4.297c0-.5.32-.78.749-.78.431 0 .75.28.75.78z" fill="#f5f8fa"/><path d="m32 10c0 .552.447 1 1 1s1-.448 1-1-.447-1-1-1-1 .448-1 1m0-3c0 .552.447 1 1 1s1-.448 1-1-.447-1-1-1-1 .448-1 1m-3 3c0 .552.447 1 1 1s1-.448 1-1-.447-1-1-1-1 .448-1 1m0-3c0 .552.447 1 1 1s1-.448 1-1-.447-1-1-1-1 .448-1 1m-3 3c0 .552.447 1 1 1s1-.448 1-1-.447-1-1-1-1 .448-1 1m0-3c0 .552.447 1 1 1s1-.448 1-1-.447-1-1-1-1 .448-1 1m-3 0c0 .552.447 1 1 1s1-.448 1-1-.447-1-1-1-1 .448-1 1m0 3c0 .552.447 1 1 1s1-.448 1-1-.447-1-1-1-1 .448-1 1" fill="#f4abba"/></svg>

After

Width:  |  Height:  |  Size: 1.9 KiB

1
components/icons/svg/edit.svg Executable file
View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path d="m35.222 33.598c-.647-2.101-1.705-6.059-2.325-7.566-.501-1.216-.969-2.438-1.544-3.014-.575-.575-1.553-.53-2.143.058 0 0-2.469 1.675-3.354 2.783-1.108.882-2.785 3.357-2.785 3.357-.59.59-.635 1.567-.06 2.143.576.575 1.798 1.043 3.015 1.544 1.506.62 5.465 1.676 7.566 2.325.359.11 1.74-1.271 1.63-1.63z" fill="#d99e82"/><path d="m13.643 5.308c1.151 1.151 1.151 3.016 0 4.167l-4.167 4.168c-1.151 1.15-3.018 1.15-4.167 0l-4.168-4.168c-1.15-1.151-1.15-3.016 0-4.167l4.167-4.167c1.15-1.151 3.016-1.151 4.167 0z" fill="#ea596e"/><path d="m31.353 23.018-4.17 4.17-4.163 4.165-15.628-15.627 8.335-8.334z" fill="#ffcc4d"/><path d="m32.078 34.763s2.709 1.489 3.441.757-.765-3.435-.765-3.435-2.566.048-2.676 2.678z" fill="#292f33"/><path d="m2.183 10.517 8.335-8.335 5.208 5.209-8.334 8.335z" fill="#ccd6dd"/><path d="m3.225 11.558 8.334-8.334 1.042 1.042-8.334 8.334zm2.083 2.086 8.335-8.335 1.042 1.042-8.335 8.334z" fill="#99aab5"/></svg>

After

Width:  |  Height:  |  Size: 997 B

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#31373D" d="M4 36s-4 0-4-4V4s0-4 4-4h26c1 0 2 1 2 1l3 3s1 1 1 2v26s0 4-4 4H4z"/><path fill="#55ACEE" d="M5 19v-1s0-2 2-2h21c2 0 2 2 2 2v1H5z"/><path fill="#E1E8ED" d="M5 32.021V19h25v13s0 2-2 2H7c-2 0-2-1.979-2-1.979zM10 3s0-1 1-1h18c1.048 0 1 1 1 1v10s0 1-1 1H11s-1 0-1-1V3zm12 10h5V3h-5v10z"/></svg>

After

Width:  |  Height:  |  Size: 374 B

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#DD2E44" d="M35.885 11.833c0-5.45-4.418-9.868-9.867-9.868-3.308 0-6.227 1.633-8.018 4.129-1.791-2.496-4.71-4.129-8.017-4.129-5.45 0-9.868 4.417-9.868 9.868 0 .772.098 1.52.266 2.241C1.751 22.587 11.216 31.568 18 34.034c6.783-2.466 16.249-11.447 17.617-19.959.17-.721.268-1.469.268-2.242z"/></svg>

After

Width:  |  Height:  |  Size: 369 B

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path d="m36 33.5c0 .828-.672 1.5-1.5 1.5h-33c-.828 0-1.5-.672-1.5-1.5s.672-1.5 1.5-1.5h33c.828 0 1.5.672 1.5 1.5z" fill="#5c913b"/><path d="m12.344 14.702h-2c-.276 0-.5-.224-.5-.5v-7c0-.276.224-.5.5-.5h2c.276 0 .5.224.5.5v7c0 .276-.224.5-.5.5z" fill="#a0041e"/><path d="m5.942 32c-.137-4.657-.506-8-.942-8-.435 0-.804 3.343-.941 8z" fill="#ffcc4d"/><path d="m10 18.731c0 5.575-2.238 7.269-5 7.269-2.761 0-5-1.694-5-7.269 0-5.577 4-13.731 5-13.731s5 8.154 5 13.731z" fill="#77b255"/><path d="m8 16 13-13 13 13v16h-26z" fill="#ffe8b6"/><path d="m21 16h1v16h-1z" fill="#ffcc4d"/><path d="m34 17c-.256 0-.512-.098-.707-.293l-12.293-12.293-12.293 12.293c-.391.391-1.023.391-1.414 0s-.391-1.023 0-1.414l13-13c.391-.391 1.023-.391 1.414 0l13 13c.391.391.391 1.023 0 1.414-.195.195-.451.293-.707.293z" fill="#66757f"/><path d="m21 17c-.256 0-.512-.098-.707-.293-.391-.391-.391-1.023 0-1.414l6.5-6.5c.391-.391 1.023-.391 1.414 0s.391 1.023 0 1.414l-6.5 6.5c-.195.195-.451.293-.707.293z" fill="#66757f"/><path d="m13 26h4v6h-4z" fill="#c1694f"/><path d="m13 17h4v4h-4zm12.5 0h4v4h-4zm0 9h4v4h-4z" fill="#55acee"/><path d="m10.625 29.991c0 1.613-.858 2.103-1.917 2.103-1.058 0-1.917-.49-1.917-2.103s1.533-3.973 1.917-3.973 1.917 2.359 1.917 3.973zm25.25 0c0 1.613-.858 2.103-1.917 2.103-1.058 0-1.917-.49-1.917-2.103s1.533-3.973 1.917-3.973 1.917 2.359 1.917 3.973z" fill="#77b255"/></svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#CCD6DD" d="M34 29.096c-.417-.963-.896-2.008-2-2.008h-1c1.104 0 2-.899 2-2.008V8.008C33 6.899 32.104 6 31 6H5c-1.104 0-2 .899-2 2.008V25.08c0 1.109.896 2.008 2 2.008H4c-1.104 0-1.667 1.004-2 2.008l-2 4.895C0 35.101.896 36 2 36h32c1.104 0 2-.899 2-2.008l-2-4.896z"/><path fill="#9AAAB4" d="M.008 34.075l.006.057.17.692C.5 35.516 1.192 36 2 36h32c1.076 0 1.947-.855 1.992-1.925H.008z"/><path fill="#5DADEC" d="M31 24.075c0 .555-.447 1.004-1 1.004H6c-.552 0-1-.449-1-1.004V9.013c0-.555.448-1.004 1-1.004h24c.553 0 1 .45 1 1.004v15.062z"/><path fill="#AEBBC1" d="M32.906 31.042l-.76-2.175c-.239-.46-.635-.837-1.188-.837H5.11c-.552 0-.906.408-1.156 1.036l-.688 1.977c-.219.596.448 1.004 1 1.004h7.578s.937-.047 1.103-.608c.192-.648.415-1.624.463-1.796.074-.264.388-.531.856-.531h8.578c.5 0 .746.253.811.566.042.204.312 1.141.438 1.782.111.571 1.221.586 1.221.586h6.594c.551 0 1.217-.471.998-1.004z"/><path fill="#9AAAB4" d="M22.375 33.113h-7.781c-.375 0-.538-.343-.484-.675.054-.331.359-1.793.383-1.963.023-.171.274-.375.524-.375h7.015c.297 0 .49.163.55.489.059.327.302 1.641.321 1.941.019.301-.169.583-.528.583z"/></svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#FFD983" d="M32 0H10C7.791 0 6 1.791 6 4v24H4c-2.209 0-4 1.791-4 4s1.791 4 4 4h24c2.209 0 4-1.791 4-4V8c2.209 0 4-1.791 4-4s-1.791-4-4-4z"/><path fill="#E39F3D" d="M8 10h24V8H10L8 7z"/><path fill="#FFE8B6" d="M10 0C7.791 0 6 1.791 6 4v24.555C5.41 28.211 4.732 28 4 28c-2.209 0-4 1.791-4 4s1.791 4 4 4 4-1.791 4-4V7.445C8.59 7.789 9.268 8 10 8c2.209 0 4-1.791 4-4s-1.791-4-4-4z"/><path fill="#C1694F" d="M12 4c0 1.104-.896 2-2 2s-2-.896-2-2 .896-2 2-2 2 .896 2 2M6 32c0 1.104-.896 2-2 2s-2-.896-2-2 .896-2 2-2 2 .896 2 2m24-17c0 .552-.447 1-1 1H11c-.552 0-1-.448-1-1s.448-1 1-1h18c.553 0 1 .448 1 1m0 4c0 .553-.447 1-1 1H11c-.552 0-1-.447-1-1s.448-1 1-1h18c.553 0 1 .447 1 1m0 4c0 .553-.447 1-1 1H11c-.552 0-1-.447-1-1s.448-1 1-1h18c.553 0 1 .447 1 1m0 4c0 .553-.447 1-1 1H11c-.552 0-1-.447-1-1 0-.553.448-1 1-1h18c.553 0 1 .447 1 1"/></svg>

After

Width:  |  Height:  |  Size: 913 B

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#AAB8C2" d="M13 3C7.477 3 3 7.477 3 13v10h4V13c0-3.313 2.687-6 6-6s6 2.687 6 6v10h4V13c0-5.523-4.477-10-10-10z"/><path fill="#FFAC33" d="M26 32c0 2.209-1.791 4-4 4H4c-2.209 0-4-1.791-4-4V20c0-2.209 1.791-4 4-4h18c2.209 0 4 1.791 4 4v12z"/><path fill="#C1694F" d="M35 9c0-4.971-4.029-9-9-9s-9 4.029-9 9c0 3.917 2.507 7.24 6 8.477V33.5c0 1.381 1.119 2.5 2.5 2.5 1.213 0 2.223-.864 2.45-2.01.018.001.032.01.05.01.553 0 1-.447 1-1v-1c0-.553-.447-1-1-1v-1c.553 0 1-.447 1-1v-2c0-.553-.447-1-1-1v-2.277c.596-.347 1-.984 1-1.723v-4.523c3.493-1.236 6-4.559 6-8.477zm-9-7c1.104 0 2 .896 2 2s-.896 2-2 2-2-.896-2-2 .896-2 2-2z"/></svg>

After

Width:  |  Height:  |  Size: 698 B

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#CCD6DD" d="M36 27c0 2.209-1.791 4-4 4H4c-2.209 0-4-1.791-4-4V9c0-2.209 1.791-4 4-4h28c2.209 0 4 1.791 4 4v18z"/><path fill="#99AAB5" d="M11.95 17.636L.637 28.949c-.027.028-.037.063-.06.091.34.57.814 1.043 1.384 1.384.029-.023.063-.033.09-.06L13.365 19.05c.39-.391.39-1.023 0-1.414-.392-.391-1.024-.391-1.415 0M35.423 29.04c-.021-.028-.033-.063-.06-.09L24.051 17.636c-.392-.391-1.024-.391-1.415 0-.391.392-.391 1.024 0 1.414l11.313 11.314c.026.026.062.037.09.06.571-.34 1.044-.814 1.384-1.384"/><path fill="#99AAB5" d="M32 5H4C1.791 5 0 6.791 0 9v1.03l14.528 14.496c1.894 1.893 4.988 1.893 6.884 0L36 10.009V9c0-2.209-1.791-4-4-4z"/><path fill="#E1E8ED" d="M32 5H4C2.412 5 1.051 5.934.405 7.275l14.766 14.767c1.562 1.562 4.096 1.562 5.657 0L35.595 7.275C34.949 5.934 33.589 5 32 5z"/></svg>

After

Width:  |  Height:  |  Size: 863 B

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="m11.5725 0c-.1763 0-.3098.0013-.3584.0067-.0516.0053-.2159.021-.3636.0328-3.4088.3073-6.6017 2.1463-8.624 4.9728-1.1261 1.5717-1.8463 3.3543-2.1183 5.2427-.0962.659-.108.8537-.108 1.7474s.012 1.0884.108 1.7476c.652 4.506 3.8591 8.2919 8.2087 9.6945.7789.2511 1.6.4223 2.5337.5255.3636.04 1.9354.04 2.299 0 1.6117-.1783 2.9772-.577 4.3237-1.2643.2065-.1056.2464-.1337.2183-.1573-.0188-.0139-.8987-1.1938-1.9543-2.62l-1.919-2.592-2.4047-3.5583c-1.3231-1.9564-2.4117-3.556-2.4211-3.556-.0094-.0026-.0187 1.5787-.0235 3.509-.0067 3.3802-.0093 3.5162-.0516 3.596-.061.115-.108.1618-.2064.2134-.075.0374-.1408.0445-.495.0445h-.406l-.1078-.068a.4383.4383 0 0 1 -.1572-.1712l-.0493-.1056.0053-4.703.0067-4.7054.0726-.0915c.0376-.0493.1174-.1125.1736-.143.0962-.047.1338-.0517.5396-.0517.4787 0 .5584.0187.6827.1547.0353.0377 1.3373 1.9987 2.895 4.3608a10760.433 10760.433 0 0 0 4.7344 7.1706l1.9002 2.8782.096-.0633c.8518-.5536 1.7525-1.3418 2.4657-2.1627 1.5179-1.7429 2.4963-3.868 2.8247-6.134.0961-.6591.1078-.854.1078-1.7475 0-.8937-.012-1.0884-.1078-1.7476-.6522-4.506-3.8592-8.2919-8.2087-9.6945-.7672-.2487-1.5836-.42-2.4985-.5232-.169-.0176-1.0835-.0366-1.6123-.037zm4.0685 7.217c.3473 0 .4082.0053.4857.047.1127.0562.204.1642.237.2767.0186.061.0234 1.3653.0186 4.3044l-.0067 4.2175-.7436-1.14-.7461-1.14v-3.066c0-1.982.0093-3.0963.0234-3.1502.0375-.1313.1196-.2346.2323-.2955.0961-.0494.1313-.054.4997-.054z"/></svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path d="m27.815 4h1.996v2.391h-1.996z" fill="#66757f"/><path d="m29 4h-20c-2.209 0-4 1.791-4 4v24c0 2.209 1.791 4 4 4h20c2.209 0 4-1.791 4-4v-24c0-2.209-1.791-4-4-4z" fill="#55acee"/><path d="m27 4h-20c-2.209 0-4 1.791-4 4v24c0 2.209 1.791 4 4 4h20c2.209 0 4-1.791 4-4v-24c0-2.209-1.791-4-4-4z" fill="#ccd6dd"/><path d="m28 15c0 .553-.447 1-1 1h-20c-.552 0-1-.447-1-1 0-.552.448-1 1-1h20c.553 0 1 .448 1 1zm0 5c0 .553-.447 1-1 1h-20c-.552 0-1-.447-1-1s.448-1 1-1h20c.553 0 1 .447 1 1zm0 5c0 .553-.447 1-1 1h-20c-.552 0-1-.447-1-1s.448-1 1-1h20c.553 0 1 .447 1 1zm-8 5c0 .553-.447 1-1 1h-12c-.552 0-1-.447-1-1s.448-1 1-1h12c.553 0 1 .447 1 1z" fill="#99aab5"/><path d="m7.836 8.731c-.702 0-1.271-.666-1.271-1.489 0-.822.569-1.489 1.271-1.489.701 0 1.27.667 1.27 1.489s-.569 1.489-1.27 1.489z" fill="#292f33"/><path d="m8.543 7.083c-.055-.48-.374-.792-.729-1.017-.485-.307-1-1.008-1-1.877 0-1.104.671-2.095 1.5-2.095s1.5.905 1.5 1.905h1.996c-.021-2-1.575-3.821-3.496-3.821-1.934 0-3.5 1.819-3.5 4.005 0 1.853 1.045 3.371 2.569 3.926.76.276 1.224-.447 1.16-1.026z" fill="#66757f"/><path d="m13.836 8.731c-.702 0-1.271-.666-1.271-1.489 0-.822.569-1.489 1.271-1.489.701 0 1.27.667 1.27 1.489s-.569 1.489-1.27 1.489z" fill="#292f33"/><path d="m14.543 7.083c-.055-.48-.374-.792-.729-1.017-.485-.307-1-1.008-1-1.877 0-1.104.671-2.095 1.5-2.095s1.5.905 1.5 1.905h1.996c-.02-2-1.575-3.821-3.496-3.821-1.934 0-3.5 1.819-3.5 4.005 0 1.853 1.045 3.371 2.569 3.926.76.276 1.224-.447 1.16-1.026z" fill="#66757f"/><path d="m19.836 8.731c-.702 0-1.271-.666-1.271-1.489 0-.822.569-1.489 1.271-1.489.701 0 1.271.667 1.271 1.489-.001.822-.57 1.489-1.271 1.489z" fill="#292f33"/><path d="m20.543 7.083c-.055-.48-.374-.792-.728-1.017-.485-.307-1-1.008-1-1.877 0-1.104.671-2.095 1.5-2.095s1.5.905 1.5 1.905h1.996c-.02-2-1.575-3.821-3.496-3.821-1.934 0-3.5 1.819-3.5 4.005 0 1.853 1.045 3.371 2.569 3.926.759.276 1.223-.447 1.159-1.026z" fill="#66757f"/><path d="m25.836 8.731c-.702 0-1.271-.666-1.271-1.489 0-.822.569-1.489 1.271-1.489.701 0 1.271.667 1.271 1.489-.001.822-.57 1.489-1.271 1.489z" fill="#292f33"/><path d="m26.543 7.083c-.055-.48-.374-.792-.728-1.017-.485-.307-1-1.008-1-1.877 0-1.104.671-2.095 1.5-2.095s1.5.905 1.5 1.905h1.996c-.02-2-1.575-3.821-3.496-3.821-1.934 0-3.5 1.819-3.5 4.005 0 1.853 1.045 3.371 2.569 3.926.759.276 1.223-.447 1.159-1.026z" fill="#66757f"/></svg>

After

Width:  |  Height:  |  Size: 2.4 KiB

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path d="m33 36v-1c0-3.313-2.687-6-6-6h-18c-3.313 0-6 2.687-6 6v1zm-6.25-15.565c1.188.208 2.619.129 2.416.917-.479 1.854-2.604 1.167-2.979 1.188-.375.02.563-2.105.563-2.105z" fill="#66757f"/><path d="m27.062 20.645c1.875.25 2.541.416 1.166.958-.772.305-2.243 4.803-3.331 4.118-1.087-.685 2.165-5.076 2.165-5.076z" fill="#292f33"/><path d="m9.255 20.435c-1.188.208-2.619.129-2.416.917.479 1.854 2.604 1.167 2.979 1.188.375.02-.563-2.105-.563-2.105z" fill="#66757f"/><path d="m8.943 20.645c-1.875.25-2.541.416-1.166.958.772.305 2.243 4.803 3.331 4.118s-2.165-5.076-2.165-5.076z" fill="#292f33"/><path d="m21.771 4.017c-1.958-.634-6.566-.461-7.718 1.037-2.995.058-6.508 2.764-6.969 6.335-.456 3.534.56 5.175.922 7.833.409 3.011 2.102 3.974 3.456 4.377 1.947 2.572 4.017 2.462 7.492 2.462 6.787 0 10.019-4.541 10.305-12.253.172-4.665-2.565-8.198-7.488-9.791z" fill="#ffac33"/><path d="m25.652 14.137c-.657-.909-1.497-1.641-3.34-1.901.691.317 1.353 1.411 1.44 2.016.086.605.173 1.094-.374.49-2.192-2.423-4.579-1.469-6.944-2.949-1.652-1.034-2.155-2.177-2.155-2.177s-.202 1.526-2.707 3.081c-.726.451-1.593 1.455-2.073 2.937-.346 1.066-.238 2.016-.238 3.64 0 4.74 3.906 8.726 8.726 8.726s8.726-4.02 8.726-8.726c-.004-2.948-.312-4.1-1.061-5.137z" fill="#ffdc5d"/><path d="m18.934 21.565h-1.922c-.265 0-.481-.215-.481-.481v-.174c0-.265.215-.482.481-.482h1.922c.265 0 .482.216.482.482v.174c0 .266-.216.481-.482.481" fill="#c1694f"/><path clip-rule="evenodd" d="m7.657 14.788c.148.147.888.591 1.036 1.034s.445 2.954 1.333 3.693c.916.762 4.37.478 5.032.149 1.48-.738 1.662-2.798 1.924-3.842.148-.591 1.036-.591 1.036-.591s.888 0 1.036.591c.262 1.044.444 3.104 1.924 3.841.662.33 4.116.614 5.034-.147.887-.739 1.183-3.25 1.331-3.694.146-.443.888-.886 1.035-1.034.148-.148.148-.739 0-.887-.296-.295-3.788-.559-7.548-.148-.75.082-1.035.295-2.812.295-1.776 0-2.062-.214-2.812-.295-3.759-.411-7.252-.148-7.548.148-.149.148-.149.74-.001.887z" fill="#292f33" fill-rule="evenodd"/><path d="m7.858 8.395s1.359-8.901 5.932-8.372c3.512.406 4.89.825 7.833.097 1.947-.482 4.065 1.136 5.342 4.379.816 2.068 1.224 4.041 1.224 4.041s3.938-.385 4.165 1.732c.228 2.117-4.354 4.716-15.889 4.716-6.465-.001-13.135-2.358-13.452-4.331s4.845-2.262 4.845-2.262z" fill="#66757f"/><path d="m8.125 7.15s-.27 1.104-.406 1.871c-.136.768.226 1.296 2.705 1.824 3.287.7 10.679.692 15.058-.383 1.759-.432 2.886-.72 2.751-1.583-.167-1.068-.196-1.066-.541-2.208 0 0-1.477.502-3.427.96-2.66.624-9.964.911-13.481.144-1.874-.41-2.659-.625-2.659-.625zm-.136 13.953c-.354.145 2.921 1.378 7.48 1.458 4.771.084 6.234.39 5.146 1.459-1.146 1.125-.852 2.894-.771 3.418s2.047 1.916 2.208 2.56c.161.645-1.229 5.961-1.229 5.961l-8.729-.252c-2.565-8.844-2.883-8.501-4.105-13.604-.241-1.008 0-1 0-1z" fill="#292f33"/><path d="m6.989 21.144c-.354.146 2.921 1.378 7.48 1.458 4.771.084 6.234.39 5.146 1.459-1.146 1.125-.664 2.894-.583 3.418s1.859 1.916 2.021 2.561c.16.644-1.231 5.96-1.231 5.96l-8.729-.252c-2.565-8.844-2.883-8.501-4.105-13.604-.24-1.008.001-1 .001-1z" fill="#66757f"/><path d="m28.052 21.103c.354.145-2.921 1.378-7.479 1.458-4.771.084-6.234.39-5.146 1.459 1.146 1.125 2.976 2.892 2.896 3.416-.081.524-4.172 1.918-4.333 2.562-.161.645 1.229 5.961 1.229 5.961l8.729-.252c2.565-8.844 2.883-8.501 4.104-13.604.241-1.008 0-1 0-1z" fill="#292f33"/><path d="m28.958 21.103c.354.145-2.921 1.378-7.479 1.458-4.771.084-6.234.39-5.146 1.459 1.146 1.125 2.977 2.892 2.896 3.416s-4.172 1.918-4.333 2.562c-.161.645 1.229 5.961 1.229 5.961l8.657.01c2.565-8.844 2.955-8.763 4.177-13.866.24-1.008-.001-1-.001-1z" fill="#66757f"/></svg>

After

Width:  |  Height:  |  Size: 3.5 KiB

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#77B255" d="M35 36v-5c0-3.314-2.686-6-6-6H13c-3.313 0-6 2.686-6 6v5h28z"/><path fill="#FFDC5D" d="M16.64 25.106c0 .894 2.36 1.993 4.36 1.993s4.359-1.099 4.359-1.992V21.29h-8.72v3.816z"/><path fill="#F9CA55" d="M16.632 22.973c1.216 1.374 2.724 1.746 4.364 1.746 1.639 0 3.146-.373 4.363-1.746v-3.491h-8.728v3.491z"/><path fill="#FFDC5D" d="M14.444 12.936c0 1.448-.734 2.622-1.639 2.622s-1.639-1.174-1.639-2.622.734-2.623 1.639-2.623c.905-.001 1.639 1.174 1.639 2.623m16.389 0c0 1.448-.733 2.622-1.639 2.622-.905 0-1.639-1.174-1.639-2.622s.733-2.623 1.639-2.623c.906-.001 1.639 1.174 1.639 2.623"/><path fill="#FFDC5D" d="M12.477 13.96c0-5.589 3.816-10.121 8.523-10.121s8.522 4.532 8.522 10.121S25.707 24.081 21 24.081c-4.706-.001-8.523-4.532-8.523-10.121"/><path fill="#C1694F" d="M21 20.802c-2.754 0-3.6-.705-3.741-.848-.256-.256-.256-.671 0-.927.248-.248.646-.255.902-.023.052.037.721.487 2.839.487 2.2 0 2.836-.485 2.842-.49.256-.255.657-.243.913.015.256.256.242.683-.014.938-.141.143-.987.848-3.741.848"/><path fill="#FFAC33" d="M21 0c5.648 0 9.178 4.648 9.178 8.121 0 3.473-.706 4.863-1.412 3.473l-1.412-2.778s-4.235 0-5.647-1.39c0 0 2.118 4.168-2.118 0 0 0 .706 2.779-3.53-.694 0 0-2.118 1.389-2.824 4.862-.196.964-1.412 0-1.412-3.473C11.822 4.648 14.646 0 21 0"/><path fill="#662113" d="M17 14c-.55 0-1-.45-1-1v-1c0-.55.45-1 1-1s1 .45 1 1v1c0 .55-.45 1-1 1m8 0c-.55 0-1-.45-1-1v-1c0-.55.45-1 1-1s1 .45 1 1v1c0 .55-.45 1-1 1"/><path fill="#C1694F" d="M21.75 16.75h-1.5c-.413 0-.75-.337-.75-.75s.337-.75.75-.75h1.5c.413 0 .75.337.75.75s-.337.75-.75.75"/><path fill="#E1E8ED" d="M33 35c0 .553-.447 1-1 1H22c-.553 0-1-.447-1-1 0-.553.447-1 1-1h10c.553 0 1 .447 1 1z"/><path fill="#E1E8ED" d="M20.24 22H3.759c-1.524 0-3.478.771-2.478 3.531l3.072 8.475C4.354 34.006 4.75 36 7 36h20l-4-11.24c-.438-1.322-1.235-2.76-2.76-2.76z"/><path fill="#99AAB5" d="M19.24 22H2.759c-1.524 0-3.478.771-2.478 3.531l3.072 8.475C3.354 34.006 3.75 36 6 36h20l-4-11.24c-.438-1.322-1.235-2.76-2.76-2.76z"/><path fill="#E1E8ED" d="M14.019 29.283c.524 1.572.099 3.13-.949 3.479-1.048.35-2.322-.641-2.846-2.213s-.099-3.13.949-3.479c1.048-.349 2.323.641 2.846 2.213zM19 24.75H3c-.414 0-.75-.336-.75-.75s.336-.75.75-.75h16c.414 0 .75.336.75.75s-.336.75-.75.75z"/></svg>

After

Width:  |  Height:  |  Size: 2.3 KiB

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path d="m36 32a4 4 0 0 1 -4 4h-28a4 4 0 0 1 -4-4v-9c0-2.209.791-3 3-3h30c2.209 0 3 .791 3 3z" fill="#d99e82"/><path d="m25 20a7 7 0 1 1 -14 0z" fill="#662113"/><path d="m4 36h28a4 4 0 0 0 4-4h-36a4 4 0 0 0 4 4z" fill="#c1694f"/><path d="m27.435 8.511-7.863-7.863a2.23 2.23 0 0 0 -3.145 0l-7.863 7.863c-.864.866-.571 1.489.652 1.489h4.784v6a2 2 0 0 0 2 2h4a2 2 0 0 0 2-2v-6h4.784c1.223 0 1.516-.623.651-1.489z" fill="#dd2e44"/></svg>

After

Width:  |  Height:  |  Size: 494 B

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path d="m34.16 28.812-2.916-26.134c-.17-1.525-1.459-2.678-2.993-2.678h-20.587c-1.545 0-2.839 1.168-2.997 2.704l-2.67 26.108z" fill="#dd2e44"/><circle cx="18.069" cy="14" fill="#be1931" r="9.366"/><path d="m35.521 29.18h-35.042l-.479 4.82c0 2 2 2 2 2h32s2 0 2-2z" fill="#99aab5"/><path d="m35.594 29.912-.073-.732c-.141-.738-.77-1.18-1.521-1.18h-32c-.751 0-1.38.442-1.521 1.18l-.073.732z" fill="#ccd6dd"/><path d="m29.647 13.63-7.668-1.248 4.539-6.308c.107-.148.091-.354-.039-.484-.131-.129-.336-.146-.484-.039l-6.309 4.538-1.247-7.667c-.029-.181-.187-.314-.37-.314s-.341.133-.37.314l-1.248 7.667-6.308-4.538c-.149-.107-.353-.09-.484.039-.13.131-.146.335-.039.484l4.538 6.308-7.668 1.248c-.181.029-.314.186-.314.37s.133.341.314.37l7.668 1.248-4.538 6.308c-.107.149-.091.354.039.484.131.129.335.146.484.039l6.308-4.538 1.248 7.667c.029.182.187.314.37.314s.341-.134.37-.314l1.247-7.667 6.308 4.538c.148.106.354.09.484-.039.13-.131.146-.335.039-.484l-4.538-6.308 7.668-1.248c.182-.029.314-.187.314-.37s-.132-.341-.314-.37z" fill="#ec9dad"/></svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

1
components/icons/svg/tag.svg Executable file
View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path d="m32.017 20.181-14.672-14.435c-.658-.659-1.522-.746-2.385-.746h-10.077c-1.854 0-2.883 1.029-2.883 2.883v10.082c0 .861.089 1.723.746 2.38l14.554 14.672c1.311 1.31 3.378 1.31 4.688 0l10.059-10.088c1.31-1.312 1.28-3.438-.03-4.748zm-23.596-8.76c-.585.585-1.533.585-2.118 0s-.586-1.533 0-2.118c.585-.586 1.533-.585 2.118 0 .585.586.586 1.533 0 2.118z" fill="#ffd983"/><path d="m9.952 7.772c-1.43-1.431-3.749-1.431-5.179 0-1.431 1.43-1.431 3.749 0 5.18 1.43 1.43 3.749 1.43 5.18 0 1.43-1.431 1.429-3.749-.001-5.18zm-1.53 3.65c-.585.585-1.534.585-2.119 0s-.586-1.534 0-2.119c.585-.587 1.534-.585 2.119 0s.586 1.533 0 2.119z" fill="#d99e82"/><path d="m8.507 10.501c-.391.391-1.023.391-1.415 0-.391-.391-.39-1.023 0-1.414l8.485-8.485c.391-.391 1.023-.391 1.415 0 .391.391.39 1.023 0 1.414z" fill="#c1694f"/></svg>

After

Width:  |  Height:  |  Size: 873 B

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 36 36"><path fill="#31373D" d="M36 25s0 4-4 4H4s-4 0-4-4V10c0-4 4-4 4-4h28s4 0 4 4v15z"/><path fill="#CCD6DD" d="M32 12h-6s-1 0-1 1v8s0 1 1 1h6c1 0 2-2 2-5s-1-5-2-5z"/><path d="M29.894 12H26s-1 0-1 1v1c1.656 0 3 1.343 3 3s-1.344 3-3 3v1s0 1 1 1h3.895C31.193 20.73 32 18.96 32 17c0-1.959-.808-3.729-2.106-5z"/><path fill="#66757F" d="M28 17c0-1.657-1.344-3-3-3v6c1.656 0 3-1.343 3-3z"/><path fill="#CCD6DD" d="M4 12h6s1 0 1 1v8s0 1-1 1H4c-1 0-2-2-2-5s1-5 2-5z"/><path d="M11 20c-1.657 0-3-1.343-3-3s1.343-3 3-3v-1c0-1-1-1-1-1H6.106C4.808 13.271 4 15.04 4 17s.808 3.729 2.106 5H10c1 0 1-1 1-1v-1z"/><path fill="#66757F" d="M8 17c0 1.657 1.343 3 3 3v-6c-1.657 0-3 1.343-3 3z"/><path fill="#88C9F9" d="M13 14s0-1 1-1h8s1 0 1 1v6s0 1-1 1h-8s-1 0-1-1v-6z"/><path d="M34 26c0 .553-.447 1-1 1H3c-.552 0-1-.447-1-1 0-.553.448-1 1-1h30c.553 0 1 .447 1 1z"/></svg>

After

Width:  |  Height:  |  Size: 907 B

Some files were not shown because too many files have changed in this diff Show More