@ -13,14 +13,14 @@
|
|||||||
},
|
},
|
||||||
|
|
||||||
// Use 'postCreateCommand' to run commands after the container is created.
|
// 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.
|
// Use 'forwardPorts' to make a list of ports inside the container available locally.
|
||||||
"forwardPorts": [1337],
|
"forwardPorts": [3000],
|
||||||
|
|
||||||
"portsAttributes": {
|
"portsAttributes": {
|
||||||
"1337": {
|
"3000": {
|
||||||
"label": "Webpack",
|
"label": "next dev",
|
||||||
"onAutoForward": "notify"
|
"onAutoForward": "notify"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -32,12 +32,11 @@
|
|||||||
|
|
||||||
// Add the IDs of extensions you want installed when the container is created.
|
// Add the IDs of extensions you want installed when the container is created.
|
||||||
"extensions": [
|
"extensions": [
|
||||||
"editorconfig.editorconfig",
|
|
||||||
"budparr.language-hugo-vscode",
|
|
||||||
"dbaeumer.vscode-eslint",
|
"dbaeumer.vscode-eslint",
|
||||||
"davidanson.vscode-markdownlint",
|
"divlo.vscode-styled-jsx-languageserver",
|
||||||
|
"divlo.vscode-styled-jsx-syntax",
|
||||||
|
"editorconfig.editorconfig",
|
||||||
"esbenp.prettier-vscode",
|
"esbenp.prettier-vscode",
|
||||||
"stylelint.vscode-stylelint",
|
"stylelint.vscode-stylelint"
|
||||||
"ms-vscode.wordcount"
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
16
.env.example
@ -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=
|
|
@ -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
@ -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
@ -1,6 +1,2 @@
|
|||||||
# Set default behavior to automatically normalize line endings.
|
# Set default behavior to automatically normalize line endings.
|
||||||
* text=auto eol=lf
|
* 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
|
|
||||||
|
29
.github/workflows/ci.yml
vendored
@ -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 }}
|
|
61
.github/workflows/post-deploy.yml
vendored
@ -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
|
|
15
.github/workflows/purge-artifacts.yml
vendored
@ -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
@ -1,15 +1,10 @@
|
|||||||
# Hugo artifacts
|
# next.js
|
||||||
public/
|
.next/
|
||||||
builds/
|
out/
|
||||||
_vendor/
|
|
||||||
assets/jsconfig.json
|
|
||||||
hugo_stats.json
|
|
||||||
.hugo_build.lock
|
|
||||||
|
|
||||||
# webpack output
|
# generated at build-time by next-sitemap
|
||||||
static/assets/
|
public/robots.txt
|
||||||
data/manifest.json
|
public/sitemap.xml
|
||||||
webpack_stats.json
|
|
||||||
|
|
||||||
# node/npm/yarn
|
# node/npm/yarn
|
||||||
node_modules/
|
node_modules/
|
||||||
@ -18,12 +13,10 @@ npm-debug.log*
|
|||||||
yarn-debug.log*
|
yarn-debug.log*
|
||||||
yarn-error.log*
|
yarn-error.log*
|
||||||
|
|
||||||
# Lighthouse CI
|
|
||||||
.lighthouseci/
|
|
||||||
|
|
||||||
# here be secrets
|
# here be secrets
|
||||||
*.env*
|
*.env*
|
||||||
!.env.example
|
!.env.example
|
||||||
|
*.pem
|
||||||
|
|
||||||
# macOS/iCloud junk
|
# macOS/iCloud junk
|
||||||
.DS_Store
|
.DS_Store
|
||||||
@ -34,5 +27,11 @@ yarn-error.log*
|
|||||||
*.icloud
|
*.icloud
|
||||||
.nosync
|
.nosync
|
||||||
|
|
||||||
# local Vercel config
|
# local env files
|
||||||
|
.env.local
|
||||||
|
.env.development.local
|
||||||
|
.env.test.local
|
||||||
|
.env.production.local
|
||||||
|
|
||||||
|
# vercel
|
||||||
.vercel
|
.vercel
|
||||||
|
@ -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"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -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
|
|
||||||
}
|
|
34
.percy.yml
@ -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
|
|
@ -1,20 +1,3 @@
|
|||||||
# Hugo/Webpack stuff that doesn't play well with Prettier
|
/.next
|
||||||
assets/**/vendor/
|
/out
|
||||||
assets/jsconfig.json
|
/public
|
||||||
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/
|
|
||||||
|
@ -1,17 +1,11 @@
|
|||||||
{
|
{
|
||||||
"extends": ["stylelint-config-standard-scss", "stylelint-config-sass-guidelines", "stylelint-prettier/recommended"],
|
"extends": ["stylelint-config-standard-scss", "stylelint-prettier/recommended"],
|
||||||
"plugins": ["stylelint-no-unsupported-browser-features", "stylelint-prettier"],
|
"plugins": ["stylelint-scss", "stylelint-prettier"],
|
||||||
"rules": {
|
"rules": {
|
||||||
"color-hex-length": "long",
|
"color-hex-length": "long",
|
||||||
"max-nesting-depth": 6,
|
|
||||||
"no-descending-specificity": null,
|
"no-descending-specificity": null,
|
||||||
"order/order": null,
|
"rule-empty-line-before": null,
|
||||||
"order/properties-alphabetical-order": null,
|
"selector-class-pattern": 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
|
|
||||||
},
|
},
|
||||||
"ignoreFiles": ["public/**", "static/assets/**", "node_modules/**"]
|
"ignoreFiles": ["public/**"]
|
||||||
}
|
}
|
||||||
|
11
.vscode/extensions.json
vendored
@ -1,13 +1,10 @@
|
|||||||
{
|
{
|
||||||
// See https://go.microsoft.com/fwlink/?LinkId=827846
|
|
||||||
// for the documentation about the extensions.json format
|
|
||||||
"recommendations": [
|
"recommendations": [
|
||||||
"editorconfig.editorconfig",
|
|
||||||
"budparr.language-hugo-vscode",
|
|
||||||
"dbaeumer.vscode-eslint",
|
"dbaeumer.vscode-eslint",
|
||||||
"davidanson.vscode-markdownlint",
|
"divlo.vscode-styled-jsx-languageserver",
|
||||||
|
"divlo.vscode-styled-jsx-syntax",
|
||||||
|
"editorconfig.editorconfig",
|
||||||
"esbenp.prettier-vscode",
|
"esbenp.prettier-vscode",
|
||||||
"stylelint.vscode-stylelint",
|
"stylelint.vscode-stylelint"
|
||||||
"ms-vscode.wordcount"
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
21
.vscode/settings.json
vendored
@ -5,29 +5,18 @@
|
|||||||
"files.eol": "\n",
|
"files.eol": "\n",
|
||||||
"files.insertFinalNewline": true,
|
"files.insertFinalNewline": true,
|
||||||
"files.trimTrailingWhitespace": true,
|
"files.trimTrailingWhitespace": true,
|
||||||
|
"search.exclude": {
|
||||||
|
"**/.next": true,
|
||||||
|
"**/node_modules": true
|
||||||
|
},
|
||||||
"css.validate": false,
|
"css.validate": false,
|
||||||
"scss.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.requireConfig": true,
|
||||||
"prettier.configPath": ".prettierrc.json",
|
"prettier.configPath": ".prettierrc.json",
|
||||||
"markdown.preview.lineHeight": 1.75,
|
|
||||||
"stylelint.packageManager": "yarn",
|
"stylelint.packageManager": "yarn",
|
||||||
"stylelint.reportNeedlessDisables": true,
|
"stylelint.reportNeedlessDisables": true,
|
||||||
"stylelint.reportInvalidScopeDisables": true,
|
"stylelint.reportInvalidScopeDisables": true,
|
||||||
"stylelint.validate": [
|
"stylelint.validate": ["css", "scss"],
|
||||||
"css",
|
|
||||||
"scss"
|
|
||||||
],
|
|
||||||
"npm.packageManager": "yarn",
|
"npm.packageManager": "yarn",
|
||||||
"eslint.packageManager": "yarn"
|
"eslint.packageManager": "yarn"
|
||||||
}
|
}
|
||||||
|
24
README.md
@ -1,34 +1,22 @@
|
|||||||
# 🏡 [jarv.is](https://jarv.is/)
|
# 🏡 [jarv.is](https://jarv.is/)
|
||||||
|
|
||||||
[](https://github.com/jakejarvis/jarv.is/actions?query=workflow%3ACI+branch%3Amain)
|
|
||||||
[](https://vercel.com/deployments/jarv.is)
|
[](https://vercel.com/deployments/jarv.is)
|
||||||
[](https://github.com/gohugoio/hugo)
|
|
||||||
[](https://creativecommons.org/licenses/by/4.0/)
|
[](https://creativecommons.org/licenses/by/4.0/)
|
||||||
[](https://github.com/jakejarvis/jarv.is)
|
[](https://github.com/jakejarvis/jarv.is)
|
||||||
[](http://jarvis2i2vp4j4tbxjogsnqdemnte5xhzyi7hziiyzxwge3hzmh57zad.onion/)
|
[](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!
|
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.
|
## 📜 License
|
||||||
|
|
||||||
### ▲ 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
|
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
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).
|
||||||
|
100
api/report.js
@ -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;
|
|
||||||
};
|
|
@ -1,7 +0,0 @@
|
|||||||
---
|
|
||||||
title: "{{ replace .Name "-" " " | title }}"
|
|
||||||
date: {{ dateFormat "2006-01-02 15:04:05-0700" .Date }}
|
|
||||||
description: ""
|
|
||||||
image: ""
|
|
||||||
draft: true
|
|
||||||
---
|
|
@ -1,8 +0,0 @@
|
|||||||
---
|
|
||||||
title: "{{ replace .Name "-" " " | title }}"
|
|
||||||
date: {{ dateFormat "2006-01-02 15:04:05-0700" .Date }}
|
|
||||||
description: ""
|
|
||||||
image: ""
|
|
||||||
layout: etc
|
|
||||||
draft: true
|
|
||||||
---
|
|
@ -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
|
|
||||||
---
|
|
@ -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
|
|
||||||
---
|
|
Before Width: | Height: | Size: 10 KiB |
@ -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 () => {};
|
|
@ -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
|
|
@ -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);
|
|
||||||
});
|
|
@ -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 |
@ -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 |
@ -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 |
@ -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);
|
|
||||||
}
|
|
||||||
});
|
|
@ -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;
|
|
@ -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;
|
|
@ -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;
|
|
@ -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;
|
|
@ -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;
|
|
@ -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"));
|
|
||||||
}
|
|
@ -1,4 +0,0 @@
|
|||||||
import parseEmoji from "./utils/parseEmoji.js";
|
|
||||||
|
|
||||||
// apply to the entire body automatically on load...
|
|
||||||
parseEmoji(document.body);
|
|
@ -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"));
|
|
||||||
}
|
|
@ -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"));
|
|
||||||
}
|
|
@ -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"));
|
|
||||||
}
|
|
@ -1,8 +0,0 @@
|
|||||||
import fetch from "unfetch";
|
|
||||||
|
|
||||||
const getData = (url) =>
|
|
||||||
fetch(url)
|
|
||||||
.then((response) => response.json())
|
|
||||||
.then((data) => data || []);
|
|
||||||
|
|
||||||
export default getData;
|
|
@ -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;
|
|
@ -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";
|
|
||||||
};
|
|
@ -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);
|
|
||||||
}
|
|
@ -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;
|
|
@ -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,
|
|
||||||
),
|
|
||||||
);
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
@ -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() {
|
|
||||||
}
|
|
@ -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() {
|
|
||||||
}
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -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;
|
|
||||||
}
|
|
@ -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() {
|
|
||||||
}
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -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() {
|
|
||||||
}
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,18 +0,0 @@
|
|||||||
module.exports = {
|
|
||||||
presets: [
|
|
||||||
[
|
|
||||||
"@babel/preset-env",
|
|
||||||
{
|
|
||||||
corejs: 3,
|
|
||||||
useBuiltIns: "entry",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
[
|
|
||||||
"@babel/preset-react",
|
|
||||||
{
|
|
||||||
pragma: "h",
|
|
||||||
pragmaFrag: "Fragment",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
],
|
|
||||||
};
|
|
5
components/Container.module.scss
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
.container {
|
||||||
|
max-width: 865px;
|
||||||
|
margin: 0 auto;
|
||||||
|
display: block;
|
||||||
|
}
|
30
components/Container.tsx
Normal 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>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
67
components/Content.module.scss
Normal 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
@ -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>;
|
||||||
|
}
|
12
components/Layout.module.scss
Normal 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
@ -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 />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
23
components/clipboard/CopyButton.module.scss
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
@ -1,12 +1,18 @@
|
|||||||
import { h } from "preact";
|
import { useState, useEffect } from "react";
|
||||||
import { useState, useEffect } from "preact/hooks";
|
|
||||||
import copy from "copy-to-clipboard";
|
import copy from "copy-to-clipboard";
|
||||||
import trimNewlines from "trim-newlines";
|
import trimNewlines from "trim-newlines";
|
||||||
|
|
||||||
// react components:
|
// react components:
|
||||||
import { CopyIcon, CheckIcon } from "@primer/octicons-react";
|
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 [copied, setCopied] = useState(false);
|
||||||
|
|
||||||
const handleCopy = (e) => {
|
const handleCopy = (e) => {
|
||||||
@ -16,7 +22,7 @@ const CopyButton = (props) => {
|
|||||||
e.target.blur();
|
e.target.blur();
|
||||||
|
|
||||||
// trim any surrounding whitespace from target block's content and send it to the clipboard
|
// 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
|
// indicate success
|
||||||
setCopied(didCopy);
|
setCopied(didCopy);
|
||||||
@ -27,25 +33,27 @@ const CopyButton = (props) => {
|
|||||||
if (copied) {
|
if (copied) {
|
||||||
const id = setTimeout(() => {
|
const id = setTimeout(() => {
|
||||||
setCopied(false);
|
setCopied(false);
|
||||||
}, props.timeout || 2000);
|
}, timeout);
|
||||||
|
|
||||||
return () => clearTimeout(id);
|
return () => clearTimeout(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
return () => {};
|
return () => {};
|
||||||
}, [props.timeout, copied]);
|
}, [timeout, copied]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<button
|
<button
|
||||||
class="copy-button"
|
className={styles.copy_button}
|
||||||
title="Copy to clipboard"
|
title="Copy to clipboard"
|
||||||
aria-label="Copy to clipboard"
|
aria-label="Copy to clipboard"
|
||||||
onClick={handleCopy}
|
onClick={handleCopy}
|
||||||
disabled={copied}
|
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>
|
</button>
|
||||||
);
|
);
|
||||||
};
|
}
|
||||||
|
|
||||||
export default CopyButton;
|
|
96
components/contact/ContactForm.module.scss
Normal 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);
|
||||||
|
}
|
@ -1,14 +1,11 @@
|
|||||||
import { h, Fragment } from "preact";
|
import { useState } from "react";
|
||||||
import { useState } from "preact/hooks";
|
|
||||||
import fetch from "unfetch";
|
|
||||||
import { isDark } from "../utils/theme.js";
|
|
||||||
|
|
||||||
// react components:
|
|
||||||
import HCaptcha from "@hcaptcha/react-hcaptcha";
|
import HCaptcha from "@hcaptcha/react-hcaptcha";
|
||||||
import { CheckIcon, XIcon } from "@primer/octicons-react";
|
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:
|
// status/feedback:
|
||||||
const [status, setStatus] = useState({ success: false, message: "" });
|
const [status, setStatus] = useState({ success: false, message: "" });
|
||||||
// keep track of fetch:
|
// 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)
|
// 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"])) {
|
if (!(formData.name && formData.email && formData.message && formData["h-captcha-response"])) {
|
||||||
setSending(false);
|
setSending(false);
|
||||||
setStatus({ success: false, message: "Please make sure that all fields are filled in." });
|
setStatus({ success: false, message: "Please make sure that all fields are filled in." });
|
||||||
|
|
||||||
// remove focus from the submit button
|
|
||||||
document.activeElement.blur();
|
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -83,19 +76,16 @@ const ContactForm = () => {
|
|||||||
// something else went wrong, and it's probably my fault...
|
// something else went wrong, and it's probably my fault...
|
||||||
setStatus({ success: false, message: "Internal server error. Try again later?" });
|
setStatus({ success: false, message: "Internal server error. Try again later?" });
|
||||||
}
|
}
|
||||||
|
|
||||||
// remove focus from the submit button
|
|
||||||
document.activeElement.blur();
|
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<form onSubmit={onSubmit} id="contact-form" action="/api/contact/" method="POST">
|
<form className={styles.form} onSubmit={onSubmit} action="/api/contact/" method="POST">
|
||||||
<input type="text" name="name" placeholder="Name" disabled={status.success} />
|
<input type="text" name="name" placeholder="Name" required disabled={status.success} />
|
||||||
<input type="email" name="email" placeholder="Email" disabled={status.success} />
|
<input type="email" name="email" placeholder="Email" required disabled={status.success} />
|
||||||
<textarea name="message" placeholder="Write something..." 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{" "}
|
Basic{" "}
|
||||||
<a
|
<a
|
||||||
href="https://commonmark.org/help/"
|
href="https://commonmark.org/help/"
|
||||||
@ -112,19 +102,17 @@ const ContactForm = () => {
|
|||||||
](https://jarv.is), and <code>`code`</code>.
|
](https://jarv.is), and <code>`code`</code>.
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="contact-form-captcha">
|
<div className={styles.captcha}>
|
||||||
<HCaptcha
|
<HCaptcha
|
||||||
sitekey={process.env.HCAPTCHA_SITE_KEY}
|
sitekey={process.env.NEXT_PUBLIC_HCAPTCHA_SITE_KEY}
|
||||||
theme={isDark() ? "dark" : "light"}
|
|
||||||
size="normal"
|
size="normal"
|
||||||
reCaptchaCompat={false}
|
|
||||||
onVerify={() => true} // this is allegedly optional but a function undefined error is thrown without it
|
onVerify={() => true} // this is allegedly optional but a function undefined error is thrown without it
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="contact-form-action-row">
|
<div className={styles.action_row}>
|
||||||
<button
|
<button
|
||||||
id="contact-form-btn-submit"
|
className={styles.btn_submit}
|
||||||
title="Send Message"
|
title="Send Message"
|
||||||
aria-label="Send Message"
|
aria-label="Send Message"
|
||||||
disabled={sending}
|
disabled={sending}
|
||||||
@ -134,14 +122,13 @@ const ContactForm = () => {
|
|||||||
<span>Sending...</span>
|
<span>Sending...</span>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
<SendIcon class="emoji" /> <span>Send</span>
|
<SendIcon className={styles.send_icon} /> <span>Send</span>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<span
|
<span
|
||||||
class="contact-form-result"
|
className={status.success ? styles.result_success : styles.result_error}
|
||||||
id={status.success ? "contact-form-result-success" : "contact-form-result-error"}
|
|
||||||
style={{ display: !status.message || sending ? "none" : null }}
|
style={{ display: !status.message || sending ? "none" : null }}
|
||||||
>
|
>
|
||||||
{status.success ? <CheckIcon size={16} /> : <XIcon size={16} />} {status.message}
|
{status.success ? <CheckIcon size={16} /> : <XIcon size={16} />} {status.message}
|
||||||
@ -149,6 +136,4 @@ const ContactForm = () => {
|
|||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
);
|
);
|
||||||
};
|
}
|
||||||
|
|
||||||
export default ContactForm;
|
|
28
components/hits/Hits.tsx
Normal 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>
|
||||||
|
);
|
||||||
|
}
|
59
components/home/ColorLink.tsx
Normal 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>
|
||||||
|
);
|
||||||
|
}
|
107
components/home/Home.module.scss
Normal 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
@ -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&fs=1&showinfo=1&rel=0&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'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
@ -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,
|
||||||
|
};
|
1
components/icons/svg/bot.svg
Normal 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 |
1
components/icons/svg/bulb-off.svg
Normal 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 |
1
components/icons/svg/bulb-on.svg
Normal 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 |
1
components/icons/svg/contact.svg
Normal 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
@ -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
@ -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 |
1
components/icons/svg/floppy.svg
Normal 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 |
1
components/icons/svg/heart.svg
Normal 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 |
1
components/icons/svg/home.svg
Normal 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 |
1
components/icons/svg/laptop.svg
Normal 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 |
1
components/icons/svg/license.svg
Normal 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 |
1
components/icons/svg/lock.svg
Normal 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 |
1
components/icons/svg/mail.svg
Normal 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 |
1
components/icons/svg/nextjs.svg
Normal 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 |
1
components/icons/svg/notes.svg
Normal 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 |
1
components/icons/svg/privacy.svg
Normal 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 |
1
components/icons/svg/projects.svg
Normal 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 |
1
components/icons/svg/send.svg
Normal 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 |
1
components/icons/svg/siren.svg
Normal 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
@ -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 |
1
components/icons/svg/tape.svg
Normal 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 |