Redone the entire blog site in astro

This commit is contained in:
Toastie 2023-12-09 01:22:02 +13:00
parent 9f171d06f1
commit 6efc34c601
No known key found for this signature in database
GPG key ID: FF47E5D4A3C8DC5E
85 changed files with 9788 additions and 8025 deletions

6
.eslintignore Normal file
View file

@ -0,0 +1,6 @@
.husky
.vscode
node_modules
public
dist
.yarn

23
.eslintrc.js Normal file
View file

@ -0,0 +1,23 @@
module.exports = {
env: {
node: true,
es2022: true,
browser: true,
},
extends: ["eslint:recommended", "plugin:astro/recommended"],
parserOptions: {
ecmaVersion: "latest",
sourceType: "module",
},
overrides: [
{
files: ["*.astro"],
parser: "astro-eslint-parser",
parserOptions: {
parser: "@typescript-eslint/parser",
extraFileExtensions: [".astro"],
},
rules: {},
},
],
};

45
.gitignore vendored
View file

@ -1,20 +1,35 @@
# Dependencies
/node_modules
# build output
dist/
.output/
# Production
/build
# Generated files
.docusaurus
.cache-loader
# Misc
.DS_Store
.env.local
.env.development.local
.env.test.local
.env.production.local
# dependencies
node_modules/
# logs
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
# environment variables
.env
.env.production
# macOS-specific files
.DS_Store
# ignore .astro directory
.astro
# ignore Jampack cache files
.jampack/
# yarn
.yarn/*
!.yarn/patches
!.yarn/plugins
!.yarn/releases
!.yarn/sdks
!.yarn/versions
.pnp.*

4
.husky/pre-commit Normal file
View file

@ -0,0 +1,4 @@
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"
npx lint-staged

4
.markdownlint.json Normal file
View file

@ -0,0 +1,4 @@
{
"MD033": false,
"MD013": false
}

2
.npmrc Normal file
View file

@ -0,0 +1,2 @@
# Expose Astro dependencies for `pnpm` users
shamefully-hoist=true

13
.prettierignore Normal file
View file

@ -0,0 +1,13 @@
# Ignore everything
/*
# Except these files & folders
!/src
!/public
!/.github
!tsconfig.json
!astro.config.ts
!package.json
!.prettierrc
!.eslintrc.js
!README.md

20
.prettierrc Normal file
View file

@ -0,0 +1,20 @@
{
"arrowParens": "avoid",
"semi": true,
"tabWidth": 2,
"printWidth": 80,
"singleQuote": false,
"jsxSingleQuote": false,
"trailingComma": "es5",
"bracketSpacing": true,
"endOfLine": "lf",
"plugins": ["prettier-plugin-astro", "prettier-plugin-tailwindcss"],
"overrides": [
{
"files": "*.astro",
"options": {
"parser": "astro"
}
}
]
}

4
.vscode/extensions.json vendored Normal file
View file

@ -0,0 +1,4 @@
{
"recommendations": ["astro-build.astro-vscode"],
"unwantedRecommendations": []
}

11
.vscode/launch.json vendored Normal file
View file

@ -0,0 +1,11 @@
{
"version": "0.2.0",
"configurations": [
{
"command": "./node_modules/.bin/astro dev",
"name": "Development server",
"request": "launch",
"type": "node-terminal"
}
]
}

View file

@ -0,0 +1,159 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" class="theme--agnostic" fill="none" width="1000" height="330">
<style>
.gauge-base {
opacity: 0.1
}
.gauge-arc {
fill: none;
animation-delay: 250ms;
stroke-linecap: round;
transform: rotate(-90deg);
transform-origin: 100px 60px;
animation: load-gauge 1s ease forwards
}
.guage-text {
font-size: 40px;
font-family: monospace;
text-align: center
}
.guage-red {
color: #ff4e42;
fill: #ff4e42;
stroke: #ff4e42
}
.guage-orange {
color: #ffa400;
fill: #ffa400;
stroke: #ffa400
}
.guage-green {
color: #0cce6b;
fill: #0cce6b;
stroke: #0cce6b
}
.theme--agnostic .guage-undefined {
color: #5c5c5c;
fill: #5c5c5c;
stroke: #5c5c5c
}
.theme--light .guage-undefined {
color: #1e1e1e;
fill: #1e1e1e;
stroke: #1e1e1e
}
.theme--dark .guage-undefined {
color: #f5f5f5;
fill: #f5f5f5;
stroke: #f5f5f5
}
.guage-title {
stroke: none;
font-size: 26px;
line-height: 26px;
font-family: Roboto, Halvetica, Arial, sans-serif
}
.metric.guage-title {
font-family: 'Courier New', Courier, monospace
}
.theme--agnostic .guage-title {
color: #737373;
fill: #737373
}
.theme--light .guage-title {
color: #212121;
fill: #212121
}
.theme--dark .guage-title {
color: #f5f5f5;
fill: #f5f5f5
}
@keyframes load-gauge {
from {
stroke-dasharray: 0 352.858
}
}
.lh-gauge--pwa__disc {
fill: #e0e0e0
}
.lh-gauge--pwa__logo {
position: relative;
fill: #b0b0b0
}
.lh-gauge--pwa__invisible {
display: none
}
.lh-gauge--pwa__visible {
display: inline
}
.guage-invisible {
display: none
}
.lh-gauge--pwa__logo--primary-color {
fill: #304ffe
}
.theme--agnostic .lh-gauge--pwa__logo--secondary-color {
fill: #787878
}
.theme--light .lh-gauge--pwa__logo--secondary-color {
fill: #3d3d3d
}
.theme--dark .lh-gauge--pwa__logo--secondary-color {
fill: #d8b6b6
}
.theme--light #svg_2 {
stroke: #00000022
}
.theme--agnostic #svg_2 {
stroke: #616161
}
.theme--light #svg_2 {
stroke: #00000022
}
.theme--dark #svg_2 {
stroke: #f5f5f566
}
</style>
<svg class="guage-div guage-perf guage-green" viewBox="0 0 200 200" width="200" height="200" x="100" y="0">
<circle class="gauge-base" r="56" cx="100" cy="60" stroke-width="8"/>
<circle class="gauge-arc guage-arc-1" r="56" cx="100" cy="60" stroke-width="8" style="stroke-dasharray: 351.858, 351.858;"/>
<text class="guage-text" x="100px" y="60px" alignment-baseline="central" dominant-baseline="central" text-anchor="middle">100</text>
<text class="guage-title" x="100px" y="160px" alignment-baseline="central" dominant-baseline="central" text-anchor="middle">Performance</text>
</svg>,<svg class="guage-div guage-perf guage-green" viewBox="0 0 200 200" width="200" height="200" x="300" y="0">
<circle class="gauge-base" r="56" cx="100" cy="60" stroke-width="8"/>
<circle class="gauge-arc guage-arc-1" r="56" cx="100" cy="60" stroke-width="8" style="stroke-dasharray: 351.858, 351.858;"/>
<text class="guage-text" x="100px" y="60px" alignment-baseline="central" dominant-baseline="central" text-anchor="middle">100</text>
<text class="guage-title" x="100px" y="160px" alignment-baseline="central" dominant-baseline="central" text-anchor="middle">Accessibility</text>
</svg>,<svg class="guage-div guage-perf guage-green" viewBox="0 0 200 200" width="200" height="200" x="500" y="0">
<circle class="gauge-base" r="56" cx="100" cy="60" stroke-width="8"/>
<circle class="gauge-arc guage-arc-1" r="56" cx="100" cy="60" stroke-width="8" style="stroke-dasharray: 351.858, 351.858;"/>
<text class="guage-text" x="100px" y="60px" alignment-baseline="central" dominant-baseline="central" text-anchor="middle">100</text>
<text class="guage-title" x="100px" y="160px" alignment-baseline="central" dominant-baseline="central" text-anchor="middle">Best Practices</text>
</svg>,<svg class="guage-div guage-perf guage-green" viewBox="0 0 200 200" width="200" height="200" x="700" y="0">
<circle class="gauge-base" r="56" cx="100" cy="60" stroke-width="8"/>
<circle class="gauge-arc guage-arc-1" r="56" cx="100" cy="60" stroke-width="8" style="stroke-dasharray: 351.858, 351.858;"/>
<text class="guage-text" x="100px" y="60px" alignment-baseline="central" dominant-baseline="central" text-anchor="middle">100</text>
<text class="guage-title" x="100px" y="160px" alignment-baseline="central" dominant-baseline="central" text-anchor="middle">SEO</text>
</svg>
<svg width="604" height="76" x="200" y="250">
<g>
<rect fill="none" id="canvas_background" height="80" width="604" y="-1" x="-1"/>
<g display="none" overflow="visible" y="0" x="0" height="100%" width="100%" id="canvasGrid">
<rect fill="url(#gridpattern)" stroke-width="0" y="0" x="0" height="100%" width="100%"/>
</g>
</g>
<g>
<rect fill-opacity="0" stroke-width="2" rx="40" id="svg_2" height="72" width="600" y="1" x="0" fill="#000000"/>
<rect stroke="#000" rx="8" id="svg_3" height="14" width="48" y="30" x="35" stroke-opacity="null" stroke-width="0" fill="#ff4e42"/>
<rect stroke="#000" rx="6" id="svg_4" height="14" width="48" y="30" x="220" stroke-opacity="null" stroke-width="0" fill="#ffa400"/>
<rect stroke="#000" rx="6" id="svg_5" height="14" width="48" y="30" x="410" stroke-opacity="null" stroke-width="0" fill="#0cce6b"/>
<text class="metric guage-title" xml:space="preserve" text-anchor="start" font-size="26" id="svg_6" y="45" x="100" stroke-opacity="null" stroke-width="0" stroke="#000">0-49</text>
<text class="metric guage-title" xml:space="preserve" text-anchor="start" font-size="26" id="svg_7" y="45" x="280" stroke-opacity="null" stroke-width="0" stroke="#000">50-89</text>
<text class="metric guage-title" xml:space="preserve" text-anchor="start" font-size="26" id="svg_8" y="45" x="470" stroke-opacity="null" stroke-width="0" stroke="#000">90-100</text>
</g>
</svg>
</svg>

After

Width:  |  Height:  |  Size: 6.1 KiB

21
LICENSE Normal file
View file

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2023 Sat Naing
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

40
astro.config.ts Normal file
View file

@ -0,0 +1,40 @@
import { defineConfig } from "astro/config";
import tailwind from "@astrojs/tailwind";
import react from "@astrojs/react";
import remarkToc from "remark-toc";
import remarkCollapse from "remark-collapse";
import sitemap from "@astrojs/sitemap";
import { SITE } from "./src/config";
// https://astro.build/config
export default defineConfig({
site: SITE.website,
integrations: [
tailwind({
applyBaseStyles: false,
}),
react(),
sitemap(),
],
markdown: {
remarkPlugins: [
remarkToc,
[
remarkCollapse,
{
test: "Table of contents",
},
],
],
shikiConfig: {
theme: "one-dark-pro",
wrap: true,
},
},
vite: {
optimizeDeps: {
exclude: ["@resvg/resvg-js"],
},
},
scopedStyleStrategy: "where",
});

View file

@ -1,3 +0,0 @@
module.exports = {
presets: [require.resolve('@docusaurus/core/lib/babel/preset')],
};

View file

@ -1,5 +0,0 @@
toastie_t0ast:
name: toastie_t0ast
title: Head gremlin
url: https://www.toastiet0ast.com
image_url: https://toastielab.dev/toastie_t0ast.png

View file

@ -1,12 +0,0 @@
---
slug: welcome
title: Welcome to my blog
authors: [toastie_t0ast]
tags: [welcome]
---
Welcome to my blog here you can find what I get up to
Thanks for reading!
EmotionChild

View file

@ -1,114 +0,0 @@
// @ts-check
// Note: type annotations allow type checking and IDEs autocompletion
const lightCodeTheme = require('prism-react-renderer/themes/github');
const darkCodeTheme = require('prism-react-renderer/themes/dracula');
/** @type {import('@docusaurus/types').Config} */
const config = {
title: 'Toasties Blog',
tagline: 'The chaotic world of Toastie_t0ast',
favicon: 'img/favicon.ico',
// Set the production url of your site here
url: 'https://blog.toastiet0ast.com',
// Set the /<baseUrl>/ pathname under which your site is served
// For GitHub pages deployment, it is often '/<projectName>/'
baseUrl: '/',
// GitHub pages deployment config.
// If you aren't using GitHub pages, you don't need these.
organizationName: 'Emotions-stuff', // Usually your GitHub org/user name.
projectName: 'Emotion-Blog', // Usually your repo name.
onBrokenLinks: 'throw',
onBrokenMarkdownLinks: 'warn',
// Even if you don't use internalization, you can use this field to set useful
// metadata like html lang. For example, if your site is Chinese, you may want
// to replace "en" with "zh-Hans".
i18n: {
defaultLocale: 'en',
locales: ['en'],
},
presets: [
[
'classic',
/** @type {import('@docusaurus/preset-classic').Options} */
({
docs: false,
blog: {
routeBasePath: '/',
showReadingTime: true,
readingTime: ({content, frontMatter, defaultReadingTime}) =>
frontMatter.hide_reading_time ? undefined : defaultReadingTime({content}),
blogTitle: 'Toasties Blog',
blogDescription: 'Here you can find blog posts about what Toastie has gotten up to',
postsPerPage: 'ALL',
feedOptions: {
type: 'all',
copyright: `Copyright © ${new Date().getFullYear()} toastie_t0ast.`,
},
},
theme: {
customCss: require.resolve('./src/css/custom.css'),
},
}),
],
],
themeConfig:
/** @type {import('@docusaurus/preset-classic').ThemeConfig} */
({
// Replace with your project's social card
navbar: {
title: 'Toasties Blog',
logo: {
alt: 'My Site Logo',
src: 'img/favicon.png',
},
items: [
{
href: 'https://github.com/Emotions-stuff/Emotion-Blog',
label: 'GitHub',
position: 'right',
},
],
},
footer: {
style: 'dark',
links: [
{
title: 'Community',
items: [
{
label: 'Discord',
href: 'https://discord.com/invite/aAsGMFPfdu',
},
{
label: 'Twitter',
href: 'https://twitter.com/Computergeex5',
},
],
},
{
title: 'More',
items: [
{
label: 'GitHub',
href: 'https://github.com/Emotions-stuff/Emotion-Blog',
},
],
},
],
copyright: `Copyright © ${new Date().getFullYear()} ToastieChild`,
},
prism: {
theme: lightCodeTheme,
darkTheme: darkCodeTheme,
},
}),
};
module.exports = config;

View file

@ -1,44 +1,62 @@
{
"name": "emotion-blog",
"version": "0.0.0",
"private": true,
"name": "toasite-blog",
"version": "3.0.0",
"scripts": {
"docusaurus": "docusaurus",
"start": "docusaurus start",
"build": "docusaurus build",
"swizzle": "docusaurus swizzle",
"deploy": "docusaurus deploy",
"clear": "docusaurus clear",
"serve": "docusaurus serve",
"write-translations": "docusaurus write-translations",
"write-heading-ids": "docusaurus write-heading-ids"
"dev": "astro dev",
"start": "astro dev",
"build": "astro build && jampack ./dist",
"preview": "astro preview",
"sync": "astro sync",
"astro": "astro",
"format:check": "prettier --plugin-search-dir=. --check .",
"format": "prettier --plugin-search-dir=. --write .",
"cz": "cz",
"prepare": "husky install",
"lint": "eslint ."
},
"dependencies": {
"@docusaurus/core": "2.4.1",
"@docusaurus/preset-classic": "2.4.1",
"@docusaurus/types": "^2.4.1",
"@mdx-js/react": "^1.6.22",
"clsx": "^1.2.1",
"prism-react-renderer": "^1.3.5",
"react": "^17.0.2",
"react-dom": "^17.0.2"
"@astrojs/check": "^0.2.0",
"@astrojs/rss": "^3.0.0",
"@resvg/resvg-js": "^2.4.1",
"astro": "^3.1.3",
"fuse.js": "^6.6.2",
"github-slugger": "^2.0.0",
"remark-collapse": "^0.1.2",
"remark-toc": "^9.0.0",
"satori": "^0.10.8",
"tailwindcss": "^3.3.3",
"typescript": "^5.2.2"
},
"devDependencies": {
"@docusaurus/module-type-aliases": "2.4.1"
"@astrojs/react": "^3.0.2",
"@astrojs/sitemap": "^3.0.0",
"@astrojs/tailwind": "^5.0.0",
"@divriots/jampack": "^0.20.2",
"@tailwindcss/typography": "^0.5.10",
"@types/github-slugger": "^1.3.0",
"@types/react": "^18.2.22",
"@typescript-eslint/parser": "^6.7.3",
"astro-eslint-parser": "^0.15.0",
"commitizen": "^4.3.0",
"cz-conventional-changelog": "^3.3.0",
"eslint": "^8.50.0",
"eslint-plugin-astro": "^0.29.0",
"husky": "^8.0.3",
"lint-staged": "^14.0.1",
"prettier": "^3.0.3",
"prettier-plugin-astro": "^0.12.0",
"prettier-plugin-tailwindcss": "^0.5.4",
"react": "^18.2.0",
"react-dom": "^18.2.0"
},
"browserslist": {
"production": [
">0.5%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
"config": {
"commitizen": {
"path": "./node_modules/cz-conventional-changelog"
}
},
"lint-staged": {
"*.{js,jsx,ts,tsx,md,mdx,json}": [
"prettier --plugin-search-dir=. --write"
]
},
"engines": {
"node": ">=16.14"
}
}
}

6647
pnpm-lock.yaml Normal file

File diff suppressed because it is too large Load diff

361
public/assets/dev.svg Normal file
View file

@ -0,0 +1,361 @@
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg xmlns="http://www.w3.org/2000/svg" width="865.76" height="682.89" viewBox="0 0 865.76 682.89">
<defs>
<style xmlns="http://www.w3.org/1999/xhtml">*, body, html { -webkit-font-smoothing: antialiased; }
img, svg { max-width: 100%; }
</style>
</defs>
<path d="M391.82,532.2c-15.44,2.82-87.85,18.09-73.28,55a33.24,33.24,0,0,0,9.74,13.13c18.18,15.25,83.33,52.58,272.06,32.22,10.69-1.15,21.42-1.86,32.17-2.06,49.73-.92,206-9.34,202-78.54,0,0-2.07-38.74-95.7-26.87l-71.21-4.43s-160.55-12.38-268.7,10.11C396.57,531.29,394.2,531.77,391.82,532.2Z" fill="#787878" data-primary="true"/>
<path d="M391.82,532.2c-15.44,2.82-87.85,18.09-73.28,55a33.24,33.24,0,0,0,9.74,13.13c18.18,15.25,83.33,52.58,272.06,32.22,10.69-1.15,21.42-1.86,32.17-2.06,49.73-.92,206-9.34,202-78.54,0,0-2.07-38.74-95.7-26.87l-71.21-4.43s-160.55-12.38-268.7,10.11C396.57,531.29,394.2,531.77,391.82,532.2Z" fill="#fff" opacity="0.7"/>
<path d="M503.08,522.3C179.26,552.79,133.91,359.63,133.91,359.63c-24.79-67.13-3.45-111,27.66-152.68a303.36,303.36,0,0,1,117.77-94.5c74.9-34.06,126.36-41,126.36-41S622.58,15.68,735.64,183.7c0,0,108,135.55,37.54,221.14,0,0-35.14,47.34-127.63,82.89l-69.3,20.46A387.7,387.7,0,0,1,503.08,522.3Z" fill="#787878" data-primary="true"/>
<path d="M503.08,522.3C179.26,552.79,133.91,359.63,133.91,359.63c-24.79-67.13-3.45-111,27.66-152.68a303.36,303.36,0,0,1,117.77-94.5c74.9-34.06,126.36-41,126.36-41S622.58,15.68,735.64,183.7c0,0,108,135.55,37.54,221.14,0,0-35.14,47.34-127.63,82.89l-69.3,20.46A387.7,387.7,0,0,1,503.08,522.3Z" fill="#fff" opacity="0.7"/>
<rect x="104.67" y="206.46" width="463.2" height="348.88" fill="#fff"/>
<rect x="108.43" y="206.46" width="459.44" height="35.42" fill="#e6e6e6"/>
<rect x="128.82" y="259.06" width="104.13" height="104.13" fill="#787878" opacity="0.29" data-primary="true"/>
<rect x="713.86" y="369.62" width="5.37" height="37.57" fill="#999"/>
<polygon points="664.89 442.18 664.89 554.44 672.53 554.44 676.93 436.58 664.89 442.18" fill="#ccc"/>
<polygon points="711.71 420.08 711.71 537.08 719.36 537.08 723.52 414.71 711.71 420.08" fill="#ccc"/>
<polygon points="668.23 434.1 733.18 405.05 703.86 399.96 670.01 385.44 668.23 434.1" fill="#ccc"/>
<path d="M656.14,446.25l77-35.83v-5.37L668.23,434.1S660.68,442.36,656.14,446.25Z" fill="#b3b3b3"/>
<path d="M693.46,271.94H734a4.55,4.55,0,0,1,4.55,4.55v67.37a0,0,0,0,1,0,0H693.46a0,0,0,0,1,0,0V271.94A0,0,0,0,1,693.46,271.94Z" fill="#999"/>
<rect x="241.54" y="44.36" width="325.8" height="139.55" fill="#787878" data-primary="true"/>
<rect x="263.01" y="83.01" width="100.91" height="65.48" fill="#fff" opacity="0.3"/>
<g opacity="0.3">
<path d="M297.36,131.59a1.07,1.07,0,0,1-.76-.32l-14.79-14.76a1.08,1.08,0,0,1,0-1.5l14.79-15.56a1.07,1.07,0,0,1,1.56,1.47l-14.07,14.81,14.05,14a1.07,1.07,0,0,1,0,1.52A1.09,1.09,0,0,1,297.36,131.59Z" fill="#fff"/>
</g>
<g opacity="0.3">
<path d="M328.73,132.66a1.06,1.06,0,0,1-.76-.31,1.07,1.07,0,0,1,0-1.52l14-14L328,102a1.08,1.08,0,1,1,1.56-1.48l14.78,15.56a1.06,1.06,0,0,1,0,1.5l-14.78,14.77A1.07,1.07,0,0,1,328.73,132.66Z" fill="#fff"/>
</g>
<g opacity="0.3">
<path d="M305.56,131.59a1.08,1.08,0,0,1-1-1.56l14.34-28.18a1.08,1.08,0,1,1,1.92,1L306.51,131A1.07,1.07,0,0,1,305.56,131.59Z" fill="#fff"/>
</g>
<path d="M524.39,119.51H454.62a1.08,1.08,0,0,1,0-2.15h69.77a1.08,1.08,0,1,1,0,2.15Z" fill="#fff"/>
<path d="M540.5,132.39H454.62a1.08,1.08,0,0,1,0-2.15H540.5a1.08,1.08,0,0,1,0,2.15Z" fill="#fff"/>
<rect x="460.52" y="153.86" width="65.48" height="16.1" rx="7.5" fill="#fff" opacity="0.3"/>
<path d="M567.33,44.36V183.91H241.54s54.75-59.1,144.51-74c4.1-.68,8.24-1.12,12.38-1.4C426.41,106.6,557.79,95.18,567.33,44.36Z" fill="#fff" opacity="0.3"/>
<rect x="31.14" y="128.09" width="187.86" height="213.62" fill="#787878" data-primary="true"/>
<rect x="31.14" y="128.09" width="187.86" height="34.35" fill="#fff" opacity="0.3"/>
<rect x="46.17" y="173.18" width="57.97" height="57.97" fill="#282728" data-secondary="true"/>
<circle cx="164.78" cy="145.27" r="3.76" fill="#fff" opacity="0.3"/>
<circle cx="184.11" cy="145.27" r="3.76" fill="#fff" opacity="0.3"/>
<circle cx="203.43" cy="145.27" r="3.76" fill="#fff" opacity="0.3"/>
<path d="M170.69,192.5H117a1.07,1.07,0,1,1,0-2.14h53.67a1.07,1.07,0,0,1,0,2.14Z" fill="#fff"/>
<path d="M186.25,205.38h-68.7a1.07,1.07,0,0,1,0-2.14h68.7a1.07,1.07,0,1,1,0,2.14Z" fill="#fff"/>
<path d="M203.43,218.27H117.55a1.08,1.08,0,0,1,0-2.15h85.88a1.08,1.08,0,0,1,0,2.15Z" fill="#fff"/>
<path d="M168,287H84.28a1.08,1.08,0,1,1,0-2.15H168a1.08,1.08,0,0,1,0,2.15Z" fill="#fff"/>
<path d="M194.84,299.85H57.44a1.08,1.08,0,1,1,0-2.15h137.4a1.08,1.08,0,1,1,0,2.15Z" fill="#fff"/>
<path d="M168.54,312.73H83.74a1.08,1.08,0,1,1,0-2.15h84.8a1.08,1.08,0,1,1,0,2.15Z" fill="#fff"/>
<rect x="83.74" y="248.32" width="78.36" height="16.1" fill="#fff" opacity="0.3"/>
<rect x="256.57" y="259.06" width="66.55" height="17.18" fill="#787878" opacity="0.29" data-primary="true"/>
<path d="M308.78,293.79H256.57a1.08,1.08,0,1,1,0-2.15h52.21a1.08,1.08,0,1,1,0,2.15Z" fill="#e6e6e6"/>
<path d="M325.8,306.67H256.57a1.07,1.07,0,1,1,0-2.14H325.8a1.07,1.07,0,1,1,0,2.14Z" fill="#e6e6e6"/>
<path d="M339.76,319.55H256.57a1.07,1.07,0,1,1,0-2.14h83.19a1.07,1.07,0,0,1,0,2.14Z" fill="#e6e6e6"/>
<path d="M379.48,332.44H256.57a1.08,1.08,0,1,1,0-2.15H379.48a1.08,1.08,0,0,1,0,2.15Z" fill="#e6e6e6"/>
<rect x="256.57" y="348.15" width="154.58" height="15.03" fill="#787878" opacity="0.29" data-primary="true"/>
<path d="M252.45,400.29h-122a1.08,1.08,0,0,1,0-2.15h122a1.08,1.08,0,1,1,0,2.15Z" fill="#e6e6e6"/>
<path d="M353.18,400.29H268.91a1.08,1.08,0,0,1,0-2.15h84.27a1.08,1.08,0,0,1,0,2.15Z" fill="#e6e6e6"/>
<path d="M417.59,400.29H388.06a1.08,1.08,0,0,1,0-2.15h29.53a1.08,1.08,0,0,1,0,2.15Z" fill="#e6e6e6"/>
<rect x="256.57" y="396.53" width="5.37" height="5.37" fill="#787878" opacity="0.29" data-primary="true"/>
<rect x="360.69" y="396.53" width="5.37" height="5.37" fill="#787878" opacity="0.29" data-primary="true"/>
<rect x="373.57" y="396.53" width="5.37" height="5.37" fill="#ccc"/>
<path d="M223.29,429.16H131a1.08,1.08,0,0,1,0-2.15h92.32a1.08,1.08,0,0,1,0,2.15Z" fill="#e6e6e6"/>
<path d="M289.84,455.37H129.9a1.08,1.08,0,1,1,0-2.15H289.84a1.08,1.08,0,1,1,0,2.15Z" fill="#e6e6e6"/>
<path d="M325.27,429.16H255a1.08,1.08,0,1,1,0-2.15h70.31a1.08,1.08,0,0,1,0,2.15Z" fill="#e6e6e6"/>
<path d="M349.42,455.37h-36a1.08,1.08,0,0,1,0-2.15h36a1.08,1.08,0,0,1,0,2.15Z" fill="#e6e6e6"/>
<rect x="227.58" y="425.4" width="5.37" height="5.37" fill="#787878" opacity="0.29" data-primary="true"/>
<rect x="240.46" y="425.4" width="5.37" height="5.37" fill="#ccc"/>
<rect x="290.92" y="451.61" width="5.37" height="5.37" fill="#787878" opacity="0.29" data-primary="true"/>
<rect x="303.8" y="451.61" width="5.37" height="5.37" fill="#ccc"/>
<path d="M355.32,512.93H298.43a1.08,1.08,0,0,1,0-2.15h56.89a1.08,1.08,0,1,1,0,2.15Z" fill="#e6e6e6"/>
<path d="M416,512.93H388.06a1.08,1.08,0,0,1,0-2.15H416a1.08,1.08,0,1,1,0,2.15Z" fill="#e6e6e6"/>
<rect x="361.77" y="509.17" width="5.37" height="5.37" fill="#ccc"/>
<rect x="374.65" y="509.17" width="5.37" height="5.37" fill="#787878" opacity="0.29" data-primary="true"/>
<path d="M416,455.37H375.72a1.08,1.08,0,0,1,0-2.15H416a1.08,1.08,0,1,1,0,2.15Z" fill="#e6e6e6"/>
<rect x="353.18" y="451.61" width="5.37" height="5.37" fill="#ccc"/>
<rect x="366.06" y="451.61" width="5.37" height="5.37" fill="#787878" opacity="0.29" data-primary="true"/>
<path d="M205,485H131a1.08,1.08,0,0,1,0-2.15H205a1.08,1.08,0,0,1,0,2.15Z" fill="#e6e6e6"/>
<path d="M349.42,485h-52.6a1.08,1.08,0,0,1,0-2.15h52.6a1.08,1.08,0,0,1,0,2.15Z" fill="#e6e6e6"/>
<path d="M416,485H363.38a1.08,1.08,0,1,1,0-2.15H416a1.08,1.08,0,1,1,0,2.15Z" fill="#e6e6e6"/>
<rect x="207.19" y="481.26" width="5.37" height="5.37" fill="#ccc"/>
<rect x="220.07" y="481.26" width="5.37" height="5.37" fill="#787878" opacity="0.29" data-primary="true"/>
<rect x="231.88" y="481.26" width="5.37" height="5.37" fill="#ccc"/>
<path d="M256.57,512.93H131a1.08,1.08,0,0,1,0-2.15h125.6a1.08,1.08,0,0,1,0,2.15Z" fill="#e6e6e6"/>
<rect x="258.71" y="509.17" width="5.37" height="5.37" fill="#ccc"/>
<rect x="271.59" y="509.17" width="5.37" height="5.37" fill="#787878" opacity="0.29" data-primary="true"/>
<rect x="283.4" y="509.17" width="5.37" height="5.37" fill="#ccc"/>
<rect x="244.76" y="481.26" width="5.37" height="5.37" fill="#787878" opacity="0.29" data-primary="true"/>
<rect x="259.79" y="481.26" width="5.37" height="5.37" fill="#ccc"/>
<rect x="271.59" y="481.26" width="5.37" height="5.37" fill="#787878" opacity="0.29" data-primary="true"/>
<rect x="284.48" y="481.26" width="5.37" height="5.37" fill="#ccc"/>
<path d="M417.59,429.16H358a1.08,1.08,0,1,1,0-2.15h59.58a1.08,1.08,0,0,1,0,2.15Z" fill="#e6e6e6"/>
<rect x="330.63" y="425.4" width="5.37" height="5.37" fill="#787878" opacity="0.29" data-primary="true"/>
<rect x="343.52" y="425.4" width="5.37" height="5.37" fill="#ccc"/>
<rect x="51.53" y="436.18" width="103.05" height="64.41" fill="#787878" data-primary="true"/>
<g opacity="0.3">
<path d="M88.5,485.36a1.06,1.06,0,0,1-.74-.3l-15.5-14.83a1.06,1.06,0,0,1,0-1.54l15.49-15a1.07,1.07,0,0,1,1.52,0,1.08,1.08,0,0,1,0,1.52l-14.7,14.25,14.69,14.06a1.07,1.07,0,0,1,0,1.52A1.1,1.1,0,0,1,88.5,485.36Z" fill="#fff"/>
</g>
<g opacity="0.3">
<path d="M119.16,485.36a1.07,1.07,0,0,1-.74-1.84l14.69-14.26L118.42,455.2a1.07,1.07,0,0,1,1.48-1.55l15.5,14.83a1.07,1.07,0,0,1,.33.77,1.08,1.08,0,0,1-.32.78l-15.5,15A1.08,1.08,0,0,1,119.16,485.36Z" fill="#fff"/>
</g>
<g opacity="0.3">
<path d="M96.62,483.41a1.11,1.11,0,0,1-.5-.12,1.07,1.07,0,0,1-.45-1.45l14-26.83a1.08,1.08,0,1,1,1.91,1l-14,26.83A1.06,1.06,0,0,1,96.62,483.41Z" fill="#fff"/>
</g>
<rect x="434.76" y="367.48" width="11.81" height="208.25" fill="#999"/>
<rect x="441.2" y="367.48" width="5.37" height="208.25" opacity="0.1"/>
<rect x="471.26" y="368.01" width="11.81" height="172.29" fill="#999"/>
<rect x="477.7" y="368.01" width="5.37" height="172.29" opacity="0.1"/>
<rect x="728.89" y="367.48" width="11.81" height="208.25" fill="#999"/>
<rect x="735.33" y="367.48" width="5.37" height="208.25" opacity="0.1"/>
<rect x="758.95" y="354.06" width="11.81" height="186.25" fill="#999"/>
<rect x="765.39" y="354.06" width="5.37" height="186.25" opacity="0.1"/>
<path d="M688.1,271.94h40.53a4.55,4.55,0,0,1,4.55,4.55v67.37a0,0,0,0,1,0,0H688.1a0,0,0,0,1,0,0V271.94A0,0,0,0,1,688.1,271.94Z" fill="#b3b3b3"/>
<polygon points="421.88 364.26 477.27 336.37 786.88 336.37 750.36 364.26 421.88 364.26" fill="#ccc"/>
<path d="M542.11,559.63l-32.5,25.42S496,597.2,507.76,604.71c0,0,17.17,10.74,31.13-7.51l19.37-31.64Z" fill="#787878" data-primary="true"/>
<path d="M505.61,596.12c8,8.68,20.58,6.87,28.45-1,3.7-3.79,7-8.33,10.52-12.3,3.08-3.62,7.51-8.79,10.65-12.28-2.8,3.74-7.06,9.09-10,12.81-3.41,4.12-6.73,8.65-10.42,12.54-8.21,8.11-21.45,9.88-29.19.26Z" opacity="0.2"/>
<path d="M512.32,583.74c6.45-.09,13.31,2.42,17.35,7.63a15.61,15.61,0,0,1,2.79,5.84c-.26-.47-.51-1-.74-1.43a8.51,8.51,0,0,0-.81-1.37c-4-6.39-11.44-9.4-18.59-10.67Z" opacity="0.2"/>
<path d="M519.56,580c4.83-.65,11.72.93,12.9,6.4-2.62-4.61-8.1-5.41-12.9-6.4Z" opacity="0.2"/>
<path d="M523.86,575.73c4.82-.65,11.72.93,12.89,6.39-2.61-4.6-8.1-5.4-12.89-6.39Z" opacity="0.2"/>
<path d="M532.45,569.29c4.82-.65,11.72.93,12.89,6.39-2.61-4.61-8.1-5.4-12.89-6.39Z" opacity="0.2"/>
<path d="M550.16,544.06l-8,15.57s-3.32,4,1.25,6.48a8.52,8.52,0,0,0,4.06,1h7.9a3.61,3.61,0,0,0,2.94-1.51L568,551.93S554.41,546.7,550.16,544.06Z" fill="#f9b499"/>
<polygon points="548.32 510.23 551.84 520.86 557.18 505.66 548.32 510.23" fill="#f9b499"/>
<path d="M710.77,332.4c0,8.24-5.5,8.24-5.5,8.24l-15,5.37c-6.68,2.23-9.44,1.89-10.5,1.46a1.62,1.62,0,0,0-1.36.1.24.24,0,0,1-.08.06,11.71,11.71,0,0,1-3.82,1.75h0c-2.89.58-2.48-2.31-2.48-2.31a12.77,12.77,0,0,0,.2-1.54,9.91,9.91,0,0,0-5.8-9.37,26.59,26.59,0,0,0-4.77-1.68,6.38,6.38,0,0,0-3.07,0l-8.33,1.91H608.39L570,335.27a98.24,98.24,0,0,1,12.58-65.48,86.2,86.2,0,0,1,8.82-12.47c.72-.84,1.14-1.27,1.14-1.27l2.37-3.43s32,2.14,32.37,1.07,22.15-14,22.15-14l30.76,13a10.63,10.63,0,0,1,3.59,2.59c6.62,6.85,11.81,23.17,11.81,23.17l14,46.16A30.89,30.89,0,0,1,710.77,332.4Z" fill="#787878" data-primary="true"/>
<path d="M675.8,305s-30.74,5-53.75.22c-.59-.12-1.17-.27-1.75-.43A88.92,88.92,0,0,0,592.56,302l-22.06-.18-1.09,7.3h36.87s12,.39,21.7,3.61c0,0,9.66,3.22,29,1.07l21.82-2.66Z" fill="#282728" data-secondary="true"/>
<path d="M683.8,255.21c-20.39,2.6-56.89,14.58-56.89,14.58-8.59-6.44-35.49-12.47-35.49-12.47.72-.84,1.14-1.27,1.14-1.27l2.37-3.43s32,2.14,32.37,1.07,22.15-14,22.15-14l30.76,13A10.63,10.63,0,0,1,683.8,255.21Z" opacity="0.2"/>
<path d="M620.1,254.32a12.38,12.38,0,0,1-1.24.26c-7.26,1.28-14.75-1.87-20.74-8a43,43,0,0,1-10.73-19.86c-4.59-18.58,2.63-36.33,16.12-39.66s28.13,9,32.72,27.59S633.6,251,620.1,254.32Z" fill="#f9b499"/>
<ellipse cx="639.26" cy="215.05" rx="1.61" ry="3.22" fill="none" stroke="red" stroke-miterlimit="10" stroke-width="0.75"/>
<path d="M651.6,210.75s17.18,9.45-10.73,18.14a15.44,15.44,0,0,1-.54-3.65,15.8,15.8,0,0,1,.54-4.54l-8.59-4.36s-4.67.85-7.17-4.52c0,0-8.93,0-7.86-6.44,0,0-6.44,4.3-8.59-1.07,0,0-9.69,6.88-19.33-4.62,0,0-4.28,7.84-3,21,0,0-6.34,5.08-9.93.38a6.63,6.63,0,0,1-1.28-3.77,5.58,5.58,0,0,1,3.22-5.49s-7.77-2.89-7.56-9.28a10.2,10.2,0,0,1,1.41-4.67s1.61-4,7.63-2.31h0a19.17,19.17,0,0,1,3.1,1.24s-8.21-17.26,3.4-28.49c0,0,19.14-19.82,26.66,4.87,0,0,6.55-10.14,17-7.62h0a15.76,15.76,0,0,1,2.25.72s7.51,2,6.44,14.1c0,0,9.66-8.28,18.25,1.38,0,0,6.44,7.89,0,16.28C657,198.05,660.19,205.38,651.6,210.75Z" fill="#282728" data-secondary="true"/>
<path d="M590.41,197.6s-3.22-10.46,6.44-16.91c0,0,6.93-4.51,16.49,1.46a23.89,23.89,0,0,1,2.73,2.07,16.44,16.44,0,0,0,10.59,4.11s11-.47,12.6,12c0,0-12.35-10.72-21.47-5.54,0,0-4.83-15.22-17.72-10.93C600.07,183.91,592.56,186.06,590.41,197.6Z" opacity="0.2"/>
<path d="M579.79,195.56c-5.23.93-9,7-9,7a10.2,10.2,0,0,1,1.41-4.67S573.77,193.84,579.79,195.56Z" opacity="0.2"/>
<path d="M651.6,210.75s17.18,9.45-10.73,18.14a7.4,7.4,0,0,1-.54-3.65,7.26,7.26,0,0,1,.54-2.07l4.29-6.83h0c3.22-1.93,3.22-6.66,3.22-6.66a10.45,10.45,0,0,0,4.63-3.63,8.74,8.74,0,0,0,1.09-8.24c-2.23-5.68-8.94-4.09-8.94-4.09,2.15-16-7.52-10.85-7.52-10.85-1-14.52-7.22-17.15-7.61-17.3a15.76,15.76,0,0,1,2.25.72s7.51,2,6.44,14.1c0,0,9.66-8.28,18.25,1.38,0,0,6.44,7.89,0,16.28C657,198.05,660.19,205.38,651.6,210.75Z" opacity="0.2"/>
<path d="M659.11,241.88l-26.83,22.77c-4.3,3.45-6.44.85-6.44.85l-1.19-1.87-5.79-9c20.4-4.65,18.67-31.41,18.67-31.41h3.34a17.63,17.63,0,0,0,2.14,11.2c3.91-1.57,9-1.42,11.4-1.23a14.8,14.8,0,0,1,2.89.49C665.93,236,659.11,241.88,659.11,241.88Z" fill="#f9b499"/>
<path d="M659.11,241.88l-26.83,22.77c-4.3,3.45-6.44.85-6.44.85l-1.19-1.87c11.69-5.09,23-18.83,23-18.83,4.06-6-2.64-9.53-4.26-10.28a.16.16,0,0,1,0-.29c3.86-1.42,8.68-1.27,11-1.09a14.8,14.8,0,0,1,2.89.49C665.93,236,659.11,241.88,659.11,241.88Z" fill="#f7a48b"/>
<path d="M618.86,254.58l3.07,4.81s18.66-10.53,15-26.36C637,233,635.5,251.18,618.86,254.58Z" fill="#f7a48b"/>
<path d="M599,253.69a55.57,55.57,0,0,1,18.79,6.51" fill="none" stroke="red" stroke-miterlimit="10" stroke-width="0.75"/>
<path d="M710.77,332.4c0,8.24-5.5,8.24-5.5,8.24l-15,5.37c-6.68,2.23-9.44,1.89-10.5,1.46a1.62,1.62,0,0,0-1.36.1c.36-.24,1.68-1.46-.08-5l-5.63-8.42a1.13,1.13,0,0,1,.39-1.6,1.07,1.07,0,0,1,.55-.14,1.12,1.12,0,0,1,.91.46l7.14,9.93s1.07,4.29,15-2.15C696.69,340.64,708.75,336,710.77,332.4Z" opacity="0.2"/>
<path d="M674.48,349.38h0c-2.89.58-2.48-2.31-2.48-2.31a12.77,12.77,0,0,0,.2-1.54,9.91,9.91,0,0,0-5.8-9.37,26.59,26.59,0,0,0-4.77-1.68s8.22-3.51,12.51,7.22C674.14,341.71,675.46,346.35,674.48,349.38Z" opacity="0.2"/>
<path d="M677.36,323.46s-14,5.89-18.8,11l-8.33,1.91H608.39L570,335.27a98.24,98.24,0,0,1,12.58-65.48c.3,0,8.89,2.15,17.47,32.21,0,0,8.53,31.81,22,30.4h23.5s21.08.73,21.08-13.23V297.7s.65-8.42,4.09-3.13L681.66,317S683.8,321.32,677.36,323.46Z" opacity="0.2"/>
<path d="M680.58,258c-8.42,6.71-12.77,17.28-12.88,27.91-.1-1.33-.27-2.68-.25-4,0-9.45,4.89-19.05,13.13-23.89Z" opacity="0.2"/>
<path d="M640.87,324h0a.54.54,0,0,1-.52-.55l1.07-32.21c0-21.29,5.35-36.51,5.4-36.66a.54.54,0,0,1,.69-.32.53.53,0,0,1,.32.68c0,.15-5.33,15.2-5.33,36.32l-1.08,32.22A.54.54,0,0,1,640.87,324Z" fill="#282728" data-secondary="true"/>
<path d="M614,327.22h0a.54.54,0,0,1-.52-.55l1.08-31.13a208.17,208.17,0,0,1,2.69-33.9.53.53,0,0,1,.62-.43.54.54,0,0,1,.43.63,208.45,208.45,0,0,0-2.67,33.71l-1.07,31.15A.55.55,0,0,1,614,327.22Z" fill="#282728" data-secondary="true"/>
<g opacity="0.2">
<path d="M640.87,324h0a.54.54,0,0,1-.52-.55l1.07-32.21c0-21.29,5.35-36.51,5.4-36.66a.54.54,0,0,1,.69-.32.53.53,0,0,1,.32.68c0,.15-5.33,15.2-5.33,36.32l-1.08,32.22A.54.54,0,0,1,640.87,324Z"/>
<path d="M614,327.22h0a.54.54,0,0,1-.52-.55l1.08-31.13a208.17,208.17,0,0,1,2.69-33.9.53.53,0,0,1,.62-.43.54.54,0,0,1,.43.63,208.45,208.45,0,0,0-2.67,33.71l-1.07,31.15A.55.55,0,0,1,614,327.22Z"/>
</g>
<path d="M706.34,371.77c0,.17,0,8.76-2.14,25.76l-.34,2.43a58.67,58.67,0,0,0-.52,7.27c-.06,3.84-2.56,11.15-21.3,8.21.49-.83.94-1.65,1.36-2.49l.35-.68c1.57-3,3.69-7.58,4.35-11.52,0,0-59-5.36-78.37-12.88,0,0-28.3-11.81-38.84-8.05l-8.39,7s-7.51-2.15,3.22-15Z" fill="#787878" data-primary="true"/>
<path d="M688.1,400.75s13.8.92,13.29,7a4,4,0,0,1-1.61,2.85c-1.84,1.41-6.25,3.29-16.15,1.89A63,63,0,0,0,688.1,400.75Z" opacity="0.2"/>
<path d="M564.14,385.44s-6.33-4,6.75-5.62Z" opacity="0.2"/>
<path d="M688.1,400.75a53.84,53.84,0,0,1-4.35,11.52l-.35.68c-.23.43-.44.81-.63,1.15s-.47.89-.73,1.34c-13.3,23.42-41.56,44.44-53.06,52.37a12.19,12.19,0,0,0-5.27,9.22c-2.38,36.84-19.34,68.64-19.34,68.64-3.61,5.17-9,7.66-15,8.46-19.1,2.58-45.12-11.68-45.12-11.68l14-39.72c11.81-44,23.61-56.89,23.61-56.89l26.54-55.12,1.37-2.85C629.06,395.39,688.1,400.75,688.1,400.75Z" fill="#282728" data-secondary="true"/>
<path d="M688.1,400.75a53.84,53.84,0,0,1-4.35,11.52l-.35.68c-.23.43-.44.81-.63,1.15s-.47.89-.73,1.34c-13.3,23.42-41.56,44.44-53.06,52.37a12.19,12.19,0,0,0-5.27,9.22c-2.38,36.84-19.34,68.64-19.34,68.64-3.61,5.17-9,7.66-15,8.46,20-25.72,31.27-83,31.27-83-.89-13.33,38.47-47.86,38.47-47.86,15-15-4.29-19.33-4.29-19.33-17.27-2.72-39.92-10.84-46.46-13.25l1.37-2.85C629.06,395.39,688.1,400.75,688.1,400.75Z" opacity="0.2"/>
<path d="M589.37,430.17l-33.31,13.52,13,26s-5.54,16.19-11.4,35.76c0,0-13.42,9.12-27.37,8.05,0,0-11.81-30.06-18.25-61.19,0,0-8.59-18.25,6.44-30.06L562,387.05l8.93-7.23s6.64-4.83,38.84,8.05Z" fill="#282728" data-secondary="true"/>
<path d="M547.62,532.88,526.35,549.3a27.56,27.56,0,0,1-6.81,3.93c-3.21,1.25-7.85,4-6.84,9.17a8.45,8.45,0,0,0,5.07,6.09c2.55,1.06,6.59,1.78,12.53.37l11.81-9.23,7.47-14.54-5.33-2.64Z" fill="#787878" data-primary="true"/>
<path d="M547.62,532.88,526.35,549.3a27.56,27.56,0,0,1-6.81,3.93c-3.21,1.25-7.85,4-6.84,9.17a8.45,8.45,0,0,0,5.07,6.09c2.55,1.06,6.59,1.78,12.53.37l11.81-9.23,7.47-14.54-5.33-2.64Z" opacity="0.2"/>
<path d="M589.37,430.17l-33.31,13.52,13,26a370.36,370.36,0,0,1-11.89,36s-12.93,8.88-26.88,7.81c39.72-3.22,20.78-67.22,20.78-67.22-4.38-9.78,2.19-12.65,6.18-13.48a53.87,53.87,0,0,0,7.5-2.12l10.06-3.7a29.52,29.52,0,0,0,16.91-15.53,27.2,27.2,0,0,0,2.23-8.31,14.25,14.25,0,0,0-10.3-15c-8.42-2.48-16.86-1.84-21.7-1.1l8.93-7.23s6.64-4.83,38.84,8.05Z" opacity="0.2"/>
<path d="M512.56,561c8,4.49,17.84,3.92,26,.25,1.19-.51,2.33-1.14,3.54-1.66-1.08.73-2.18,1.45-3.3,2.13-8,4.53-18.78,5.1-26.25-.72Z" opacity="0.2"/>
<path d="M524.65,550.52c4.14,1.84,12.18,6.84,12.1,11.9-.72-2.81-3.3-4.59-5.35-6.46s-4.5-3.57-6.75-5.44Z" opacity="0.2"/>
<path d="M530.3,546.25c3.1.44,5.52,3.24,6.45,6.1-2.23-2.05-4-4.27-6.45-6.1Z" opacity="0.2"/>
<path d="M534.32,543.14a9.55,9.55,0,0,1,6.62,6.16c-2.17-2.19-4.35-4-6.62-6.16Z" opacity="0.2"/>
<path d="M538.89,539.61a11.81,11.81,0,0,1,5.36,6.61,29.31,29.31,0,0,1-5.36-6.61Z" opacity="0.2"/>
<rect x="421.88" y="364.26" width="328.48" height="7.51" fill="#b3b3b3"/>
<polygon points="750.36 364.26 750.36 371.77 786.86 342.79 786.88 336.37 750.36 364.26" fill="#999"/>
<path d="M507.76,344.93h98.07l-7.33-63.74a5.61,5.61,0,0,0-5.57-5h-90a3,3,0,0,0-2.93,3.31Z" fill="#787878" data-primary="true"/>
<path d="M605.83,344.93H507.76L500,279.54a3,3,0,0,1,2.95-3.31h90a5.61,5.61,0,0,1,5.56,5Z" fill="#fff" opacity="0.3"/>
<polygon points="583.53 276.23 507.76 341.71 506.12 329.04 567.52 276.23 583.53 276.23" fill="#fff" opacity="0.3"/>
<path d="M517.07,344.93l79.55-67.31a6,6,0,0,1,1.88,3.57l.38,3.34-71.09,60.4Z" fill="#fff" opacity="0.3"/>
<rect x="507.76" y="344.93" width="94.46" height="6.44" fill="#787878" data-primary="true"/>
<rect x="602.22" y="344.93" width="29.49" height="6.44" fill="#787878" data-primary="true"/>
<rect x="602.22" y="344.93" width="29.49" height="6.44" opacity="0.2"/>
<polygon points="419.73 353.52 466.38 353.52 499.97 333.94 459.85 333.94 419.73 353.52" fill="#fff"/>
<rect x="419.73" y="353.52" width="46.65" height="4.65" fill="#e6e6e6"/>
<polygon points="499.97 333.94 499.97 339.8 466.38 358.17 466.38 353.52 499.97 333.94" fill="#ccc"/>
<polygon points="499.97 333.94 499.97 339.8 466.38 358.17 466.38 353.52 499.97 333.94" opacity="0.1"/>
<path d="M658.56,334.46s-13.47,1.87-20.95,12.08c0,0-10.05,9.15-.18,7.53,0,0,.47,4.68,8.39,1.53,0,0,1.37,3.31,10-1.53,0,0,8.64-4.84,16.16-7C672,347.08,675.17,334.71,658.56,334.46Z" fill="#f9b499"/>
<path d="M646,343.86a40.12,40.12,0,0,1-8.55,10.21A40.49,40.49,0,0,1,646,343.86Z" fill="#f7a48b"/>
<path d="M645.82,355.6a24.61,24.61,0,0,1,6.85-7.82,24.71,24.71,0,0,1-6.85,7.82Z" fill="#f7a48b"/>
<ellipse cx="638.72" cy="215.58" rx="6.44" ry="8.05" fill="#f9b499"/>
<path d="M640.87,228.89s12.2-4.93,24.24-3.72a26.56,26.56,0,0,1,17.33,9.17c4.85,5.6,11.54,15.1,4.38,18.3a8.59,8.59,0,0,1-7.29-.33c-5-2.49-17.91-6.91-47.79,12.65l27.37-23.08s6.49-6.48-3.5-8.53a13.52,13.52,0,0,0-2.62-.25,46.27,46.27,0,0,0-10,1.27S640.87,230.93,640.87,228.89Z" fill="#787878" data-primary="true"/>
<path d="M640.87,228.89s12.2-4.93,24.24-3.72a26.56,26.56,0,0,1,17.33,9.17c4.85,5.6,11.54,15.1,4.38,18.3a8.59,8.59,0,0,1-7.29-.33c-5-2.49-17.91-6.91-47.79,12.65l27.37-23.08s6.49-6.48-3.5-8.53a13.52,13.52,0,0,0-2.62-.25,46.27,46.27,0,0,0-10,1.27S640.87,230.93,640.87,228.89Z" fill="#fff" opacity="0.3"/>
<path d="M674.14,234.37c-5.73,6.95-13.48,12.06-21.25,16.49-1.15.58-2.28,1.2-3.44,1.76,8.36-5.92,17-11.41,24.69-18.25Z" fill="#fff" opacity="0.3"/>
<path d="M683.8,238.66C679,244,671.85,246.84,664.89,248c6.47-2.57,13.26-5.24,18.91-9.35Z" fill="#fff" opacity="0.3"/>
<path d="M625.84,265.5c-4.44-2.67-21.36-6.8-27.08-8.15a23.81,23.81,0,0,0-3.37-.5c-4.81-.45-3.9-3.16-3.9-3.16,0-4.29,6.63-7.09,6.63-7.09,6,6.11,13.48,9.26,20.74,8Z" fill="#787878" data-primary="true"/>
<path d="M625.84,265.5c-4.44-2.67-21.36-6.8-27.08-8.15a23.81,23.81,0,0,0-3.37-.5c-4.81-.45-3.9-3.16-3.9-3.16,0-4.29,6.63-7.09,6.63-7.09,6,6.11,13.48,9.26,20.74,8Z" fill="#fff" opacity="0.3"/>
<circle cx="551.23" cy="311.12" r="8.05" fill="#fff"/>
</svg>

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

BIN
public/assets/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

10
public/assets/logo.svg Normal file

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 7.2 KiB

BIN
public/astropaper-og.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 145 KiB

BIN
public/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

76
public/toggle-theme.js Normal file
View file

@ -0,0 +1,76 @@
const primaryColorScheme = ""; // "light" | "dark"
// Get theme data from local storage
const currentTheme = localStorage.getItem("theme");
function getPreferTheme() {
// return theme value in local storage if it is set
if (currentTheme) return currentTheme;
// return primary color scheme if it is set
if (primaryColorScheme) return primaryColorScheme;
// return user device's prefer color scheme
return window.matchMedia("(prefers-color-scheme: dark)").matches
? "dark"
: "light";
}
let themeValue = getPreferTheme();
function setPreference() {
localStorage.setItem("theme", themeValue);
reflectPreference();
}
function reflectPreference() {
document.firstElementChild.setAttribute("data-theme", themeValue);
document.querySelector("#theme-btn")?.setAttribute("aria-label", themeValue);
// Get a reference to the body element
const body = document.body;
// Check if the body element exists before using getComputedStyle
if (body) {
// Get the computed styles for the body element
const computedStyles = window.getComputedStyle(body);
// Get the background color property
const bgColor = computedStyles.backgroundColor;
// Set the background color in <meta theme-color ... />
document
.querySelector("meta[name='theme-color']")
?.setAttribute("content", bgColor);
}
}
// set early so no page flashes / CSS is made aware
reflectPreference();
window.onload = () => {
function setThemeFeature() {
// set on load so screen readers can get the latest value on the button
reflectPreference();
// now this script can find and listen for clicks on the control
document.querySelector("#theme-btn")?.addEventListener("click", () => {
themeValue = themeValue === "light" ? "dark" : "light";
setPreference();
});
}
setThemeFeature();
// Runs on view transitions navigation
document.addEventListener("astro:after-swap", setThemeFeature);
};
// sync with system changes
window
.matchMedia("(prefers-color-scheme: dark)")
.addEventListener("change", ({ matches: isDark }) => {
themeValue = isDark ? "dark" : "light";
setPreference();
});

1
remark-collapse.d.ts vendored Normal file
View file

@ -0,0 +1 @@
declare module 'remark-collapse';

View file

@ -1,33 +0,0 @@
/**
* Creating a sidebar enables you to:
- create an ordered group of docs
- render a sidebar for each doc of that group
- provide next/previous navigation
The sidebars can be generated from the filesystem, or explicitly defined here.
Create as many sidebars as you want.
*/
// @ts-check
/** @type {import('@docusaurus/plugin-content-docs').SidebarsConfig} */
const sidebars = {
// By default, Docusaurus generates a sidebar from the docs folder structure
tutorialSidebar: [{type: 'autogenerated', dirName: '.'}],
// But you can create a sidebar manually
/*
tutorialSidebar: [
'intro',
'hello',
{
type: 'category',
label: 'Tutorial',
items: ['tutorial-basics/create-a-document'],
},
],
*/
};
module.exports = sidebars;

Binary file not shown.

After

Width:  |  Height:  |  Size: 124 KiB

213
src/assets/socialIcons.ts Normal file
View file

@ -0,0 +1,213 @@
import type { SocialIcons } from "../types";
const socialIcons: SocialIcons = {
Github: `<svg
xmlns="http://www.w3.org/2000/svg"
class="icon-tabler"
stroke-linecap="round"
stroke-linejoin="round"
>
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
<path
d="M9 19c-4.3 1.4 -4.3 -2.5 -6 -3m12 5v-3.5c0 -1 .1 -1.4 -.5 -2c2.8 -.3 5.5 -1.4 5.5 -6a4.6 4.6 0 0 0 -1.3 -3.2a4.2 4.2 0 0 0 -.1 -3.2s-1.1 -.3 -3.5 1.3a12.3 12.3 0 0 0 -6.2 0c-2.4 -1.6 -3.5 -1.3 -3.5 -1.3a4.2 4.2 0 0 0 -.1 3.2a4.6 4.6 0 0 0 -1.3 3.2c0 4.6 2.7 5.7 5.5 6c-.6 .6 -.6 1.2 -.5 2v3.5"
></path>
</svg>`,
Facebook: `<svg
xmlns="http://www.w3.org/2000/svg"
class="icon-tabler"
stroke-linecap="round"
stroke-linejoin="round"
>
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
<path
d="M7 10v4h3v7h4v-7h3l1 -4h-4v-2a1 1 0 0 1 1 -1h3v-4h-3a5 5 0 0 0 -5 5v2h-3"
></path>
</svg>`,
Instagram: `<svg
xmlns="http://www.w3.org/2000/svg"
class="icon-tabler"
stroke-linecap="round"
stroke-linejoin="round"
>
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
<rect x="4" y="4" width="16" height="16" rx="4"></rect>
<circle cx="12" cy="12" r="3"></circle>
<line x1="16.5" y1="7.5" x2="16.5" y2="7.501"></line>
</svg>`,
LinkedIn: `<svg
xmlns="http://www.w3.org/2000/svg"
class="icon-tabler"
stroke-linecap="round"
stroke-linejoin="round"
>
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
<rect x="4" y="4" width="16" height="16" rx="2"></rect>
<line x1="8" y1="11" x2="8" y2="16"></line>
<line x1="8" y1="8" x2="8" y2="8.01"></line>
<line x1="12" y1="16" x2="12" y2="11"></line>
<path d="M16 16v-3a2 2 0 0 0 -4 0"></path>
</svg>`,
Mail: `<svg
xmlns="http://www.w3.org/2000/svg"
class="icon-tabler"
stroke-linecap="round"
stroke-linejoin="round"
>
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
<rect x="3" y="5" width="18" height="14" rx="2"></rect>
<polyline points="3 7 12 13 21 7"></polyline>
</svg>`,
Twitter: `<svg
xmlns="http://www.w3.org/2000/svg"
class="icon-tabler"
stroke-linecap="round"
stroke-linejoin="round"
>
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
<path d="M22 4.01c-1 .49 -1.98 .689 -3 .99c-1.121 -1.265 -2.783 -1.335 -4.38 -.737s-2.643 2.06 -2.62 3.737v1c-3.245 .083 -6.135 -1.395 -8 -4c0 0 -4.182 7.433 4 11c-1.872 1.247 -3.739 2.088 -6 2c3.308 1.803 6.913 2.423 10.034 1.517c3.58 -1.04 6.522 -3.723 7.651 -7.742a13.84 13.84 0 0 0 .497 -3.753c-.002 -.249 1.51 -2.772 1.818 -4.013z"></path>
</svg>`,
Twitch: `<svg
xmlns="http://www.w3.org/2000/svg"
class="icon-tabler"
stroke-linecap="round"
stroke-linejoin="round"
>
<path d="M21 2H3v16h5v4l4-4h5l4-4V2zm-10 9V7m5 4V7"></path>
</svg>`,
YouTube: `<svg
xmlns="http://www.w3.org/2000/svg"
class="icon-tabler"
stroke-linecap="round"
stroke-linejoin="round"
>
<path d="M22.54 6.42a2.78 2.78 0 0 0-1.94-2C18.88 4 12 4 12 4s-6.88 0-8.6.46a2.78 2.78 0 0 0-1.94 2A29 29 0 0 0 1 11.75a29 29 0 0 0 .46 5.33A2.78 2.78 0 0 0 3.4 19c1.72.46 8.6.46 8.6.46s6.88 0 8.6-.46a2.78 2.78 0 0 0 1.94-2 29 29 0 0 0 .46-5.25 29 29 0 0 0-.46-5.33z"></path>
<polygon points="9.75 15.02 15.5 11.75 9.75 8.48 9.75 15.02"></polygon>
</svg>`,
WhatsApp: `<svg
xmlns="http://www.w3.org/2000/svg"
class="icon-tabler"
stroke-linecap="round"
stroke-linejoin="round"
>
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
<path d="M3 21l1.65 -3.8a9 9 0 1 1 3.4 2.9l-5.05 .9"></path>
<path d="M9 10a0.5 .5 0 0 0 1 0v-1a0.5 .5 0 0 0 -1 0v1a5 5 0 0 0 5 5h1a0.5 .5 0 0 0 0 -1h-1a0.5 .5 0 0 0 0 1"></path>
</svg>`,
Snapchat: `<svg
xmlns="http://www.w3.org/2000/svg"
class="icon-tabler"
stroke-linecap="round"
stroke-linejoin="round"
>
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
<path d="M16.882 7.842a4.882 4.882 0 0 0 -9.764 0c0 4.273 -.213 6.409 -4.118 8.118c2 .882 2 .882 3 3c3 0 4 2 6 2s3 -2 6 -2c1 -2.118 1 -2.118 3 -3c-3.906 -1.709 -4.118 -3.845 -4.118 -8.118zm-13.882 8.119c4 -2.118 4 -4.118 1 -7.118m17 7.118c-4 -2.118 -4 -4.118 -1 -7.118"></path>
</svg>`,
Pinterest: `<svg
xmlns="http://www.w3.org/2000/svg"
class="icon-tabler"
stroke-linecap="round"
stroke-linejoin="round"
>
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
<line x1="8" y1="20" x2="12" y2="11"></line>
<path d="M10.7 14c.437 1.263 1.43 2 2.55 2c2.071 0 3.75 -1.554 3.75 -4a5 5 0 1 0 -9.7 1.7"></path>
<circle cx="12" cy="12" r="9"></circle>
</svg>`,
TikTok: `<svg
xmlns="http://www.w3.org/2000/svg"
class="icon-tabler"
stroke-linecap="round"
stroke-linejoin="round"
>
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
<path d="M9 12a4 4 0 1 0 4 4v-12a5 5 0 0 0 5 5"></path>
</svg>`,
CodePen: `<svg
xmlns="http://www.w3.org/2000/svg"
class="icon-tabler"
stroke-linecap="round"
stroke-linejoin="round"
>
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
<path d="M3 15l9 6l9 -6l-9 -6l-9 6"></path>
<path d="M3 9l9 6l9 -6l-9 -6l-9 6"></path>
<line x1="3" y1="9" x2="3" y2="15"></line>
<line x1="21" y1="9" x2="21" y2="15"></line>
<line x1="12" y1="3" x2="12" y2="9"></line>
<line x1="12" y1="15" x2="12" y2="21"></line>
</svg>`,
Discord: `<svg
xmlns="http://www.w3.org/2000/svg"
class="icon-tabler"
stroke-linecap="round"
stroke-linejoin="round"
>
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
<circle cx="9" cy="12" r="1"></circle>
<circle cx="15" cy="12" r="1"></circle>
<path d="M7.5 7.5c3.5 -1 5.5 -1 9 0"></path>
<path d="M7 16.5c3.5 1 6.5 1 10 0"></path>
<path d="M15.5 17c0 1 1.5 3 2 3c1.5 0 2.833 -1.667 3.5 -3c.667 -1.667 .5 -5.833 -1.5 -11.5c-1.457 -1.015 -3 -1.34 -4.5 -1.5l-1 2.5"></path>
<path d="M8.5 17c0 1 -1.356 3 -1.832 3c-1.429 0 -2.698 -1.667 -3.333 -3c-.635 -1.667 -.476 -5.833 1.428 -11.5c1.388 -1.015 2.782 -1.34 4.237 -1.5l1 2.5"></path>
</svg>`,
GitLab: `<svg
xmlns="http://www.w3.org/2000/svg"
class="icon-tabler"
stroke-linecap="round"
stroke-linejoin="round"
>
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
<path d="M21 14l-9 7l-9 -7l3 -11l3 7h6l3 -7z"></path>
</svg>`,
Reddit: `<svg
xmlns="http://www.w3.org/2000/svg"
class="icon-tabler"
stroke-linecap="round"
stroke-linejoin="round"
>
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
<path d="M12 8c2.648 0 5.028 .826 6.675 2.14a2.5 2.5 0 0 1 2.326 4.36c0 3.59 -4.03 6.5 -9 6.5c-4.875 0 -8.845 -2.8 -9 -6.294l-1 -.206a2.5 2.5 0 0 1 2.326 -4.36c1.646 -1.313 4.026 -2.14 6.674 -2.14z"></path>
<path d="M12 8l1 -5l6 1"></path>
<circle cx="19" cy="4" r="1"></circle>
<circle cx="9" cy="13" r=".5" fill="currentColor"></circle>
<circle cx="15" cy="13" r=".5" fill="currentColor"></circle>
<path d="M10 17c.667 .333 1.333 .5 2 .5s1.333 -.167 2 -.5"></path>
</svg>`,
Skype: `<svg
xmlns="http://www.w3.org/2000/svg"
class="icon-tabler"
stroke-linecap="round"
stroke-linejoin="round"
>
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
<path d="M12 3a9 9 0 0 1 8.603 11.65a4.5 4.5 0 0 1 -5.953 5.953a9 9 0 0 1 -11.253 -11.253a4.5 4.5 0 0 1 5.953 -5.954a8.987 8.987 0 0 1 2.65 -.396z"></path>
<path d="M8 14.5c.5 2 2.358 2.5 4 2.5c2.905 0 4 -1.187 4 -2.5c0 -1.503 -1.927 -2.5 -4 -2.5s-4 -.997 -4 -2.5c0 -1.313 1.095 -2.5 4 -2.5c1.642 0 3.5 .5 4 2.5"></path>
</svg>`,
Steam: `<svg
xmlns="http://www.w3.org/2000/svg"
class="icon-tabler"
stroke-linecap="round"
stroke-linejoin="round"
>
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
<path d="M16.5 5a4.5 4.5 0 1 1 -.653 8.953l-4.347 3.009l0 .038a3 3 0 0 1 -2.824 2.995l-.176 .005a3 3 0 0 1 -2.94 -2.402l-2.56 -1.098v-3.5l3.51 1.755a2.989 2.989 0 0 1 2.834 -.635l2.727 -3.818a4.5 4.5 0 0 1 4.429 -5.302z"></path>
<circle fill="currentColor" cx="16.5" cy="9.5" r="1"></circle>
</svg>`,
Telegram: `<svg
xmlns="http://www.w3.org/2000/svg"
class="icon-tabler"
stroke-linecap="round"
stroke-linejoin="round"
>
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
<path d="M15 10l-4 4l6 6l4 -16l-18 7l4 2l2 6l3 -4"></path>
</svg>`,
Mastodon: `<svg class="icon-tabler" viewBox="-10 -5 1034 1034" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1">
<path fill="currentColor"
d="M499 112q-93 1 -166 11q-81 11 -128 33l-14 8q-16 10 -32 25q-22 21 -38 47q-21 33 -32 73q-14 47 -14 103v37q0 77 1 119q3 113 18 188q19 95 62 154q50 67 134 89q109 29 210 24q46 -3 88 -12q30 -7 55 -17l19 -8l-4 -75l-22 6q-28 6 -57 10q-41 6 -78 4q-53 -1 -80 -7
q-43 -8 -67 -30q-29 -25 -35 -72q-2 -14 -2 -29l25 6q31 6 65 10q48 7 93 9q42 2 92 -2q32 -2 88 -9t107 -30q49 -23 81.5 -54.5t38.5 -63.5q9 -45 13 -109q4 -46 5 -97v-41q0 -56 -14 -103q-11 -40 -32 -73q-16 -26 -38 -47q-15 -15 -32 -25q-12 -8 -14 -8
q-46 -22 -127 -33q-74 -10 -166 -11h-3zM367 267q73 0 109 56l24 39l24 -39q36 -56 109 -56q63 0 101 43t38 117v239h-95v-232q0 -74 -61 -74q-69 0 -69 88v127h-94v-127q0 -88 -69 -88q-61 0 -61 74v232h-95v-239q0 -74 38 -117t101 -43z" />
</svg>`,
};
export default socialIcons;

View file

@ -0,0 +1,60 @@
---
// Remove current url path and remove trailing slash if exists
const currentUrlPath = Astro.url.pathname.replace(/\/+$/, "");
// Get url array from path
// eg: /tags/tailwindcss => ['tags', 'tailwindcss']
const breadcrumbList = currentUrlPath.split("/").slice(1);
// if breadcrumb is Home > Posts > 1 <etc>
// replace Posts with Posts (page number)
breadcrumbList[0] === "posts" &&
breadcrumbList.splice(0, 2, `Posts (page ${breadcrumbList[1] || 1})`);
---
<nav class="breadcrumb" aria-label="breadcrumb">
<ul>
<li>
<a href="/">Home</a>
<span aria-hidden="true">&nbsp;&gt;&nbsp;</span>
</li>
{
breadcrumbList.map((breadcrumb, index) =>
index + 1 === breadcrumbList.length ? (
<li>
<span
class={`${index > 0 ? "lowercase" : "capitalize"}`}
aria-current="page"
>
{/* make the last part lowercase in Home > Tags > some-tag */}
{breadcrumb}
</span>
</li>
) : (
<li>
<a href={`/${breadcrumb}`}>{breadcrumb}</a>
<span aria-hidden="true">&nbsp;&gt;&nbsp;</span>
</li>
)
)
}
</ul>
</nav>
<style>
.breadcrumb {
@apply mx-auto mb-1 mt-8 w-full max-w-3xl px-4;
}
.breadcrumb ul li {
@apply inline;
}
.breadcrumb ul li a {
@apply capitalize opacity-70;
}
.breadcrumb ul li span {
@apply opacity-70;
}
.breadcrumb ul li:not(:last-child) a {
@apply hover:opacity-100;
}
</style>

35
src/components/Card.tsx Normal file
View file

@ -0,0 +1,35 @@
import { slugifyStr } from "@utils/slugify";
import Datetime from "./Datetime";
import type { CollectionEntry } from "astro:content";
export interface Props {
href?: string;
frontmatter: CollectionEntry<"blog">["data"];
secHeading?: boolean;
}
export default function Card({ href, frontmatter, secHeading = true }: Props) {
const { title, pubDatetime, description } = frontmatter;
const headerProps = {
style: { viewTransitionName: slugifyStr(title) },
className: "text-lg font-medium decoration-dashed hover:underline",
};
return (
<li className="my-6">
<a
href={href}
className="inline-block text-lg font-medium text-skin-accent decoration-dashed underline-offset-4 focus-visible:no-underline focus-visible:underline-offset-0"
>
{secHeading ? (
<h2 {...headerProps}>{title}</h2>
) : (
<h3 {...headerProps}>{title}</h3>
)}
</a>
<Datetime datetime={pubDatetime} />
<p>{description}</p>
</li>
);
}

View file

@ -0,0 +1,52 @@
import { LOCALE } from "@config";
export interface Props {
datetime: string | Date;
size?: "sm" | "lg";
className?: string;
}
export default function Datetime({ datetime, size = "sm", className }: Props) {
return (
<div className={`flex items-center space-x-2 opacity-80 ${className}`}>
<svg
xmlns="http://www.w3.org/2000/svg"
className={`${
size === "sm" ? "scale-90" : "scale-100"
} inline-block h-6 w-6 fill-skin-base`}
aria-hidden="true"
>
<path d="M7 11h2v2H7zm0 4h2v2H7zm4-4h2v2h-2zm0 4h2v2h-2zm4-4h2v2h-2zm0 4h2v2h-2z"></path>
<path d="M5 22h14c1.103 0 2-.897 2-2V6c0-1.103-.897-2-2-2h-2V2h-2v2H9V2H7v2H5c-1.103 0-2 .897-2 2v14c0 1.103.897 2 2 2zM19 8l.001 12H5V8h14z"></path>
</svg>
<span className="sr-only">Posted on:</span>
<span className={`italic ${size === "sm" ? "text-sm" : "text-base"}`}>
<FormattedDatetime datetime={datetime} />
</span>
</div>
);
}
const FormattedDatetime = ({ datetime }: { datetime: string | Date }) => {
const myDatetime = new Date(datetime);
const date = myDatetime.toLocaleDateString(LOCALE, {
year: "numeric",
month: "long",
day: "numeric",
});
const time = myDatetime.toLocaleTimeString(LOCALE, {
hour: "2-digit",
minute: "2-digit",
});
return (
<>
{date}
<span aria-hidden="true"> | </span>
<span className="sr-only">&nbsp;at&nbsp;</span>
{time}
</>
);
};

View file

@ -0,0 +1,43 @@
---
import Hr from "./Hr.astro";
import Socials from "./Socials.astro";
const currentYear = new Date().getFullYear();
export interface Props {
noMarginTop?: boolean;
}
const { noMarginTop = false } = Astro.props;
---
<footer class={`${noMarginTop ? "" : "mt-auto"}`}>
<Hr noPadding />
<div class="footer-wrapper">
<Socials centered />
<div class="copyright-wrapper">
<span>Copyright &#169 {currentYear}, toastie_t0ast</span>
</div>
</div>
</footer>
<style>
footer {
@apply w-full;
}
.footer-wrapper {
@apply flex flex-col items-center justify-between py-6 sm:flex-row-reverse sm:py-4;
}
.link-button {
@apply my-1 p-2 hover:rotate-6;
}
.link-button svg {
@apply scale-125;
}
.copyright-wrapper {
@apply my-2 flex flex-col items-center whitespace-nowrap sm:flex-row;
}
.separator {
@apply hidden sm:inline;
}
</style>

214
src/components/Header.astro Normal file
View file

@ -0,0 +1,214 @@
---
import { LOGO_IMAGE, SITE } from "@config";
import Hr from "./Hr.astro";
import LinkButton from "./LinkButton.astro";
export interface Props {
activeNav?: "posts" | "tags" | "about" | "search";
}
const { activeNav } = Astro.props;
---
<header>
<a id="skip-to-content" href="#main-content">Skip to content</a>
<div class="nav-container">
<div class="top-nav-wrap">
<a href="/" class="logo whitespace-nowrap">
{
LOGO_IMAGE.enable ? (
<img
src={`/assets/${LOGO_IMAGE.svg ? "logo.svg" : "logo.png"}`}
alt={SITE.title}
width={LOGO_IMAGE.width}
height={LOGO_IMAGE.height}
/>
) : (
SITE.title
)
}
</a>
<nav id="nav-menu">
<button
class="hamburger-menu focus-outline"
aria-label="Open Menu"
aria-expanded="false"
aria-controls="menu-items"
>
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="1.5"
stroke-linecap="round"
stroke-linejoin="round"
class="menu-icon"
>
<line x1="7" y1="12" x2="21" y2="12" class="line"></line>
<line x1="3" y1="6" x2="21" y2="6" class="line"></line>
<line x1="12" y1="18" x2="21" y2="18" class="line"></line>
<line x1="18" y1="6" x2="6" y2="18" class="close"></line>
<line x1="6" y1="6" x2="18" y2="18" class="close"></line>
</svg>
</button>
<ul id="menu-items" class="display-none sm:flex">
<li>
<a href="/posts" class={activeNav === "posts" ? "active" : ""}>
Posts
</a>
</li>
<li>
<a href="/tags" class={activeNav === "tags" ? "active" : ""}>
Tags
</a>
</li>
<li>
<a href="/about" class={activeNav === "about" ? "active" : ""}>
About
</a>
</li>
<li>
<LinkButton
href="/search"
className={`focus-outline p-3 sm:p-1 ${
activeNav === "search" ? "active" : ""
} flex`}
ariaLabel="search"
title="Search"
>
<svg
xmlns="http://www.w3.org/2000/svg"
class="scale-125 sm:scale-100"
><path
d="M19.023 16.977a35.13 35.13 0 0 1-1.367-1.384c-.372-.378-.596-.653-.596-.653l-2.8-1.337A6.962 6.962 0 0 0 16 9c0-3.859-3.14-7-7-7S2 5.141 2 9s3.14 7 7 7c1.763 0 3.37-.66 4.603-1.739l1.337 2.8s.275.224.653.596c.387.363.896.854 1.384 1.367l1.358 1.392.604.646 2.121-2.121-.646-.604c-.379-.372-.885-.866-1.391-1.36zM9 14c-2.757 0-5-2.243-5-5s2.243-5 5-5 5 2.243 5 5-2.243 5-5 5z"
></path>
</svg>
</LinkButton>
</li>
<li>
{
SITE.lightAndDarkMode && (
<button
id="theme-btn"
class="focus-outline"
title="Toggles light & dark"
aria-label="auto"
aria-live="polite"
>
<svg xmlns="http://www.w3.org/2000/svg" id="moon-svg">
<path d="M20.742 13.045a8.088 8.088 0 0 1-2.077.271c-2.135 0-4.14-.83-5.646-2.336a8.025 8.025 0 0 1-2.064-7.723A1 1 0 0 0 9.73 2.034a10.014 10.014 0 0 0-4.489 2.582c-3.898 3.898-3.898 10.243 0 14.143a9.937 9.937 0 0 0 7.072 2.93 9.93 9.93 0 0 0 7.07-2.929 10.007 10.007 0 0 0 2.583-4.491 1.001 1.001 0 0 0-1.224-1.224zm-2.772 4.301a7.947 7.947 0 0 1-5.656 2.343 7.953 7.953 0 0 1-5.658-2.344c-3.118-3.119-3.118-8.195 0-11.314a7.923 7.923 0 0 1 2.06-1.483 10.027 10.027 0 0 0 2.89 7.848 9.972 9.972 0 0 0 7.848 2.891 8.036 8.036 0 0 1-1.484 2.059z" />
</svg>
<svg xmlns="http://www.w3.org/2000/svg" id="sun-svg">
<path d="M6.993 12c0 2.761 2.246 5.007 5.007 5.007s5.007-2.246 5.007-5.007S14.761 6.993 12 6.993 6.993 9.239 6.993 12zM12 8.993c1.658 0 3.007 1.349 3.007 3.007S13.658 15.007 12 15.007 8.993 13.658 8.993 12 10.342 8.993 12 8.993zM10.998 19h2v3h-2zm0-17h2v3h-2zm-9 9h3v2h-3zm17 0h3v2h-3zM4.219 18.363l2.12-2.122 1.415 1.414-2.12 2.122zM16.24 6.344l2.122-2.122 1.414 1.414-2.122 2.122zM6.342 7.759 4.22 5.637l1.415-1.414 2.12 2.122zm13.434 10.605-1.414 1.414-2.122-2.122 1.414-1.414z" />
</svg>
</button>
)
}
</li>
</ul>
</nav>
</div>
</div>
<Hr />
</header>
<style>
#skip-to-content {
@apply absolute -top-full left-16 z-50 bg-skin-accent px-3 py-2 text-skin-inverted transition-all focus:top-4;
}
.nav-container {
@apply mx-auto flex max-w-3xl flex-col items-center justify-between sm:flex-row;
}
.top-nav-wrap {
@apply relative flex w-full items-start justify-between p-4 sm:items-center sm:py-8;
}
.logo {
@apply absolute py-1 text-xl font-semibold sm:static sm:text-2xl;
}
.hamburger-menu {
@apply self-end p-2 sm:hidden;
}
.hamburger-menu svg {
@apply h-6 w-6 scale-125 fill-skin-base;
}
nav {
@apply flex w-full flex-col items-center sm:ml-2 sm:flex-row sm:justify-end sm:space-x-4 sm:py-0;
}
nav ul {
@apply mt-4 grid w-44 grid-cols-2 grid-rows-4 gap-x-2 gap-y-2 sm:ml-0 sm:mt-0 sm:w-auto sm:gap-x-5 sm:gap-y-0;
}
nav ul li {
@apply col-span-2 flex items-center justify-center;
}
nav ul li a {
@apply w-full px-4 py-3 text-center font-medium hover:text-skin-accent sm:my-0 sm:px-2 sm:py-1;
}
nav ul li:nth-last-child(2) a {
@apply w-auto;
}
nav ul li:nth-last-child(1),
nav ul li:nth-last-child(2) {
@apply col-span-1;
}
nav a.active {
@apply underline decoration-wavy decoration-2 underline-offset-4;
}
nav a.active svg {
@apply fill-skin-accent;
}
nav button {
@apply p-1;
}
nav button svg {
@apply h-6 w-6 fill-skin-base hover:fill-skin-accent;
}
#theme-btn {
@apply p-3 sm:p-1;
}
#theme-btn svg {
@apply scale-125 hover:rotate-12 sm:scale-100;
}
.menu-icon line {
@apply transition-opacity duration-75 ease-in-out;
}
.menu-icon .close {
opacity: 0;
}
.menu-icon.is-active .line {
@apply opacity-0;
}
.menu-icon.is-active .close {
@apply opacity-100;
}
</style>
<script>
function toggleNav() {
// Toggle menu
const menuBtn = document.querySelector(".hamburger-menu");
const menuIcon = document.querySelector(".menu-icon");
const menuItems = document.querySelector("#menu-items");
menuBtn?.addEventListener("click", () => {
const menuExpanded = menuBtn.getAttribute("aria-expanded") === "true";
menuIcon?.classList.toggle("is-active");
menuBtn.setAttribute("aria-expanded", menuExpanded ? "false" : "true");
menuBtn.setAttribute(
"aria-label",
menuExpanded ? "Open Menu" : "Close Menu"
);
menuItems?.classList.toggle("display-none");
});
}
toggleNav();
// Runs on view transitions navigation
document.addEventListener("astro:after-swap", toggleNav);
</script>

View file

@ -1,64 +0,0 @@
import React from 'react';
import clsx from 'clsx';
import styles from './styles.module.css';
const FeatureList = [
{
title: 'Easy to Use',
Svg: require('@site/static/img/undraw_docusaurus_mountain.svg').default,
description: (
<>
Docusaurus was designed from the ground up to be easily installed and
used to get your website up and running quickly.
</>
),
},
{
title: 'Focus on What Matters',
Svg: require('@site/static/img/undraw_docusaurus_tree.svg').default,
description: (
<>
Docusaurus lets you focus on your docs, and we&apos;ll do the chores. Go
ahead and move your docs into the <code>docs</code> directory.
</>
),
},
{
title: 'Powered by React',
Svg: require('@site/static/img/undraw_docusaurus_react.svg').default,
description: (
<>
Extend or customize your website layout by reusing React. Docusaurus can
be extended while reusing the same header and footer.
</>
),
},
];
function Feature({Svg, title, description}) {
return (
<div className={clsx('col col--4')}>
<div className="text--center">
<Svg className={styles.featureSvg} role="img" />
</div>
<div className="text--center padding-horiz--md">
<h3>{title}</h3>
<p>{description}</p>
</div>
</div>
);
}
export default function HomepageFeatures() {
return (
<section className={styles.features}>
<div className="container">
<div className="row">
{FeatureList.map((props, idx) => (
<Feature key={idx} {...props} />
))}
</div>
</div>
</section>
);
}

View file

@ -1,11 +0,0 @@
.features {
display: flex;
align-items: center;
padding: 2rem 0;
width: 100%;
}
.featureSvg {
height: 200px;
width: 200px;
}

12
src/components/Hr.astro Normal file
View file

@ -0,0 +1,12 @@
---
export interface Props {
noPadding?: boolean;
ariaHidden?: boolean;
}
const { noPadding = false, ariaHidden = true } = Astro.props;
---
<div class={`max-w-3xl mx-auto ${noPadding ? "px-0" : "px-4"}`}>
<hr class="border-skin-line" aria-hidden={ariaHidden} />
</div>

View file

@ -0,0 +1,28 @@
---
export interface Props {
href: string;
className?: string;
ariaLabel?: string;
title?: string;
disabled?: boolean;
}
const { href, className, ariaLabel, title, disabled = false } = Astro.props;
---
<a
href={disabled ? "#" : href}
tabindex={disabled ? "-1" : "0"}
class={`group inline-block ${className}`}
aria-label={ariaLabel}
title={title}
aria-disabled={disabled}
>
<slot />
</a>
<style>
a {
@apply hover:text-skin-accent;
}
</style>

122
src/components/Search.tsx Normal file
View file

@ -0,0 +1,122 @@
import Fuse from "fuse.js";
import { useEffect, useRef, useState, useMemo } from "react";
import Card from "@components/Card";
import slugify from "@utils/slugify";
import type { CollectionEntry } from "astro:content";
export type SearchItem = {
title: string;
description: string;
data: CollectionEntry<"blog">["data"];
};
interface Props {
searchList: SearchItem[];
}
interface SearchResult {
item: SearchItem;
refIndex: number;
}
export default function SearchBar({ searchList }: Props) {
const inputRef = useRef<HTMLInputElement>(null);
const [inputVal, setInputVal] = useState("");
const [searchResults, setSearchResults] = useState<SearchResult[] | null>(
null
);
const handleChange = (e: React.FormEvent<HTMLInputElement>) => {
setInputVal(e.currentTarget.value);
};
const fuse = useMemo(
() =>
new Fuse(searchList, {
keys: ["title", "description"],
includeMatches: true,
minMatchCharLength: 2,
threshold: 0.5,
}),
[searchList]
);
useEffect(() => {
// if URL has search query,
// insert that search query in input field
const searchUrl = new URLSearchParams(window.location.search);
const searchStr = searchUrl.get("q");
if (searchStr) setInputVal(searchStr);
// put focus cursor at the end of the string
setTimeout(function () {
inputRef.current!.selectionStart = inputRef.current!.selectionEnd =
searchStr?.length || 0;
}, 50);
}, []);
useEffect(() => {
// Add search result only if
// input value is more than one character
let inputResult = inputVal.length > 1 ? fuse.search(inputVal) : [];
setSearchResults(inputResult);
// Update search string in URL
if (inputVal.length > 0) {
const searchParams = new URLSearchParams(window.location.search);
searchParams.set("q", inputVal);
const newRelativePathQuery =
window.location.pathname + "?" + searchParams.toString();
history.replaceState(history.state, "", newRelativePathQuery);
} else {
history.replaceState(history.state, "", window.location.pathname);
}
}, [inputVal]);
return (
<>
<label className="relative block">
<span className="absolute inset-y-0 left-0 flex items-center pl-2 opacity-75">
<svg xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
<path d="M19.023 16.977a35.13 35.13 0 0 1-1.367-1.384c-.372-.378-.596-.653-.596-.653l-2.8-1.337A6.962 6.962 0 0 0 16 9c0-3.859-3.14-7-7-7S2 5.141 2 9s3.14 7 7 7c1.763 0 3.37-.66 4.603-1.739l1.337 2.8s.275.224.653.596c.387.363.896.854 1.384 1.367l1.358 1.392.604.646 2.121-2.121-.646-.604c-.379-.372-.885-.866-1.391-1.36zM9 14c-2.757 0-5-2.243-5-5s2.243-5 5-5 5 2.243 5 5-2.243 5-5 5z"></path>
</svg>
</span>
<input
className="block w-full rounded border border-skin-fill
border-opacity-40 bg-skin-fill py-3 pl-10
pr-3 placeholder:italic placeholder:text-opacity-75
focus:border-skin-accent focus:outline-none"
placeholder="Search for anything..."
type="text"
name="search"
value={inputVal}
onChange={handleChange}
autoComplete="off"
autoFocus
ref={inputRef}
/>
</label>
{inputVal.length > 1 && (
<div className="mt-8">
Found {searchResults?.length}
{searchResults?.length && searchResults?.length === 1
? " result"
: " results"}{" "}
for '{inputVal}'
</div>
)}
<ul>
{searchResults &&
searchResults.map(({ item, refIndex }) => (
<Card
href={`/posts/${slugify(item.data)}`}
frontmatter={item.data}
key={`${refIndex}-${slugify(item.data)}`}
/>
))}
</ul>
</>
);
}

View file

@ -0,0 +1,34 @@
---
import { SOCIALS } from "@config";
import LinkButton from "./LinkButton.astro";
import socialIcons from "@assets/socialIcons";
export interface Props {
centered?: boolean;
}
const { centered = false } = Astro.props;
---
<div class={`social-icons ${centered ? "flex" : ""}`}>
{
SOCIALS.filter(social => social.active).map(social => (
<LinkButton
href={social.href}
className="link-button"
title={social.linkTitle}
>
<Fragment set:html={socialIcons[social.name]} />
</LinkButton>
))
}
</div>
<style>
.social-icons {
@apply flex-wrap justify-center gap-1;
}
.link-button {
@apply p-2 hover:rotate-6 sm:p-1;
}
</style>

38
src/components/Tag.astro Normal file
View file

@ -0,0 +1,38 @@
---
export interface Props {
name: string;
size?: "sm" | "lg";
}
const { name, size = "sm" } = Astro.props;
---
<li
class={`inline-block ${
size === "sm" ? "my-1 underline-offset-4" : "my-3 mx-1 underline-offset-8"
}`}
>
<a
href={`/tags/${name.toLowerCase()}`}
transition:name={name.toLowerCase()}
class={`${size === "sm" ? "text-sm" : "text-lg"} pr-2 group`}
>
<svg
xmlns="http://www.w3.org/2000/svg"
class={`${size === "sm" ? " scale-75" : "scale-110"}`}
><path
d="M16.018 3.815 15.232 8h-4.966l.716-3.815-1.964-.37L8.232 8H4v2h3.857l-.751 4H3v2h3.731l-.714 3.805 1.965.369L8.766 16h4.966l-.714 3.805 1.965.369.783-4.174H20v-2h-3.859l.751-4H21V8h-3.733l.716-3.815-1.965-.37zM14.106 14H9.141l.751-4h4.966l-.752 4z"
></path>
</svg>
&nbsp;<span>{name.toLowerCase()}</span>
</a>
</li>
<style>
a {
@apply relative underline decoration-dashed hover:-top-0.5 hover:text-skin-accent focus-visible:p-1;
}
a svg {
@apply -mr-5 h-6 w-6 scale-95 text-skin-base opacity-80 group-hover:fill-skin-accent;
}
</style>

53
src/config.ts Normal file
View file

@ -0,0 +1,53 @@
import type { Site, SocialObjects } from "./types";
export const SITE: Site = {
website: "https://blog.toastiet0ast.com/", // replace this with your deployed domain
author: "Toastie",
desc: "The ramblins of a random software developer.",
title: "Toastie's blog",
ogImage: "",
lightAndDarkMode: true,
postPerPage: 3,
};
export const LOCALE = ["en-EN"]; // set to [] to use the environment default
export const LOGO_IMAGE = {
enable: false,
svg: true,
width: 216,
height: 46,
};
export const SOCIALS: SocialObjects = [
{
name: "Mail",
href: "mailto:toastie@toastiet0ast.com",
linkTitle: `Send an email to me`,
active: true,
},
{
name: "Twitch",
href: "https://twitch.tv/toastie_t0ast",
linkTitle: `My channel on Twitch`,
active: true,
},
{
name: "YouTube",
href: "https://youtube.com/@toastie_t0ast",
linkTitle: `My YouTube channel`,
active: true,
},
{
name: "Discord",
href: "https://discord.gg/aAsGMFPfdu",
linkTitle: `My Discord server`,
active: true,
},
{
name: "Mastodon",
href: "https://valkyriecoms.com/@toastie",
linkTitle: `My profile on Valkyriecoms`,
active: true,
},
];

BIN
src/content/blog/image.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 118 KiB

View file

@ -0,0 +1,41 @@
---
author: Toastie
pubDatetime: 2023-12-09
title: New projects
description: The projects I have started.
tags:
- project-update
featured: true
---
Hi there, it has been a while.
I am back to let you all know how life has been treating me so far as well as share the projects I have been working on.
### First up is Valkyriecoms,
Valkyriecoms is a social site I have been working on since December last year and just got things fully up, below is a screenshot of the site.
![](https://cdn.discordapp.com/attachments/1138770664342441984/1182117160609386650/image.png)
### Next up is Dragon's child hosting
Dragon's child hosting is a small server hosting service I started under the Dragon's child studios name right now we are just starting out on our journey.
Below is a screenshot of our panel (there is a new one which will replace this in the works)
![](https://cdn.discordapp.com/attachments/881396607218753607/1182651077384994816/dashboard.png)
### Last up is Toastielab
Toastielab is a small git platform that I operate wit the help of Dragon's child studios which is where I host all my projects (even the one which this site is located at)
I am going to include a screenshot of Toastielab below (our icons a little bit bugged right now so it is currently showing the default Forgejo icons)
![](https://cdn.discordapp.com/attachments/881396607218753607/1182653249480835163/image.png)
### Here is a list of links for the projects listed above
- https://valkyriecoms.com
- https://dragonschildhosting.net
- https://toastielab.dev

View file

@ -1,8 +1,9 @@
---
slug: emotionchild-update
pubDatetime: 2023-06-21
title: Update on things
authors: [toastie_t0ast]
tags: [Update]
description: Where I have been among other things.
tags:
- update
---
Hey guys it has been a while and I have not streamed in a little while and I want to update you on why this has happened, First of all I have been having somputer issues as well as some IRL stuff taking over a lot, Secondly I have started some major projects, one with a friend and another to support me first project. These projects are as follows [Valkyriecoms](https://valkyriecoms.com), [Toastielab](https://toastielab.dev) and last but not least a small group stared by me and a good friend who you may know by Elearu called [Dragon's Child Studios](https://dragonschildstudios.com).
@ -11,4 +12,4 @@ As you can see I have been quite busy but I hope I will be able to return to str
Thanks for taking the time to read this,
Toastie_t0ast (EmotionChild)
Toastie_t0ast

26
src/content/config.ts Normal file
View file

@ -0,0 +1,26 @@
import { SITE } from "@config";
import { defineCollection, z } from "astro:content";
const blog = defineCollection({
type: "content",
schema: ({ image }) =>
z.object({
author: z.string().default(SITE.author),
pubDatetime: z.date(),
title: z.string(),
postSlug: z.string().optional(),
featured: z.boolean().optional(),
draft: z.boolean().optional(),
tags: z.array(z.string()).default(["others"]),
ogImage: image()
.refine(img => img.width >= 1200 && img.height >= 630, {
message: "OpenGraph image must be at least 1200 X 630 pixels!",
})
.or(z.string())
.optional(),
description: z.string(),
canonicalURL: z.string().optional(),
}),
});
export const collections = { blog };

View file

@ -1,30 +0,0 @@
/**
* Any CSS included here will be global. The classic template
* bundles Infima by default. Infima is a CSS framework designed to
* work well for content-centric websites.
*/
/* You can override the default Infima variables here. */
:root {
--ifm-color-primary: #2e8555;
--ifm-color-primary-dark: #29784c;
--ifm-color-primary-darker: #277148;
--ifm-color-primary-darkest: #205d3b;
--ifm-color-primary-light: #33925d;
--ifm-color-primary-lighter: #359962;
--ifm-color-primary-lightest: #3cad6e;
--ifm-code-font-size: 95%;
--docusaurus-highlighted-code-line-bg: rgba(0, 0, 0, 0.1);
}
/* For readability concerns, you should choose a lighter palette in dark mode. */
[data-theme='dark'] {
--ifm-color-primary: #25c2a0;
--ifm-color-primary-dark: #21af90;
--ifm-color-primary-darker: #1fa588;
--ifm-color-primary-darkest: #1a8870;
--ifm-color-primary-light: #29d5b0;
--ifm-color-primary-lighter: #32d8b4;
--ifm-color-primary-lightest: #4fddbf;
--docusaurus-highlighted-code-line-bg: rgba(0, 0, 0, 0.3);
}

2
src/env.d.ts vendored Normal file
View file

@ -0,0 +1,2 @@
/// <reference path="../.astro/types.d.ts" />
/// <reference types="astro/client" />

View file

@ -0,0 +1,28 @@
---
import { SITE } from "@config";
import Breadcrumbs from "@components/Breadcrumbs.astro";
import Footer from "@components/Footer.astro";
import Header from "@components/Header.astro";
import Layout from "./Layout.astro";
export interface Props {
frontmatter: {
title: string;
description?: string;
};
}
const { frontmatter } = Astro.props;
---
<Layout title={`${frontmatter.title} | ${SITE.title}`}>
<Header activeNav="about" />
<Breadcrumbs />
<main id="main-content">
<section id="about" class="prose mb-28 max-w-3xl prose-img:border-0">
<h1 class="text-2xl tracking-wider sm:text-3xl">{frontmatter.title}</h1>
<slot />
</section>
</main>
<Footer />
</Layout>

88
src/layouts/Layout.astro Normal file
View file

@ -0,0 +1,88 @@
---
import { SITE } from "@config";
import "@styles/base.css";
import { ViewTransitions } from "astro:transitions";
const googleSiteVerification = import.meta.env.PUBLIC_GOOGLE_SITE_VERIFICATION;
export interface Props {
title?: string;
author?: string;
description?: string;
ogImage?: string;
canonicalURL?: string;
}
const {
title = SITE.title,
author = SITE.author,
description = SITE.desc,
ogImage = SITE.ogImage,
canonicalURL = new URL(Astro.url.pathname, Astro.site).href,
} = Astro.props;
const socialImageURL = new URL(
ogImage ?? SITE.ogImage ?? "og.png",
Astro.url.origin
).href;
---
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width" />
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<link rel="canonical" href={canonicalURL} />
<meta name="generator" content={Astro.generator} />
<!-- General Meta Tags -->
<title>{title}</title>
<meta name="title" content={title} />
<meta name="description" content={description} />
<meta name="author" content={author} />
<link rel="sitemap" href="/sitemap-index.xml" />
<!-- Open Graph / Facebook -->
<meta property="og:title" content={title} />
<meta property="og:description" content={description} />
<meta property="og:url" content={canonicalURL} />
<meta property="og:image" content={socialImageURL} />
<!-- Twitter -->
<meta property="twitter:card" content="summary_large_image" />
<meta property="twitter:url" content={canonicalURL} />
<meta property="twitter:title" content={title} />
<meta property="twitter:description" content={description} />
<meta property="twitter:image" content={socialImageURL} />
<!-- Google Font -->
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link
href="https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:ital,wght@0,400;0,500;0,600;0,700;1,400;1,600&display=swap"
rel="stylesheet"
/>
<meta name="theme-color" content="" />
{
// If PUBLIC_GOOGLE_SITE_VERIFICATION is set in the environment variable,
// include google-site-verification tag in the heading
// Learn more: https://support.google.com/webmasters/answer/9008080#meta_tag_verification&zippy=%2Chtml-tag
googleSiteVerification && (
<meta
name="google-site-verification"
content={googleSiteVerification}
/>
)
}
<ViewTransitions />
<script is:inline src="/toggle-theme.js"></script>
</head>
<body>
<slot />
</body>
</html>

48
src/layouts/Main.astro Normal file
View file

@ -0,0 +1,48 @@
---
import Breadcrumbs from "@components/Breadcrumbs.astro";
interface StringTitleProp {
pageTitle: string;
pageDesc?: string;
}
interface ArrayTitleProp {
pageTitle: [string, string];
titleTransition: string;
pageDesc?: string;
}
export type Props = StringTitleProp | ArrayTitleProp;
const { props } = Astro;
---
<Breadcrumbs />
<main id="main-content">
{
"titleTransition" in props ? (
<h1>
{props.pageTitle[0]}
<span transition:name={props.titleTransition}>
{props.pageTitle[1]}
</span>
</h1>
) : (
<h1>{props.pageTitle}</h1>
)
}
<p>{props.pageDesc}</p>
<slot />
</main>
<style>
#main-content {
@apply mx-auto w-full max-w-3xl px-4 pb-12;
}
#main-content h1 {
@apply text-2xl font-semibold sm:text-3xl;
}
#main-content p {
@apply mb-6 mt-2 italic;
}
</style>

View file

@ -0,0 +1,72 @@
---
import Layout from "@layouts/Layout.astro";
import Header from "@components/Header.astro";
import Footer from "@components/Footer.astro";
import Tag from "@components/Tag.astro";
import Datetime from "@components/Datetime";
import type { CollectionEntry } from "astro:content";
import { slugifyStr } from "@utils/slugify";
export interface Props {
post: CollectionEntry<"blog">;
}
const { post } = Astro.props;
const { title, author, description, ogImage, canonicalURL, pubDatetime, tags } =
post.data;
const { Content } = await post.render();
const ogImageUrl = typeof ogImage === "string" ? ogImage : ogImage?.src;
const ogUrl = new URL(
ogImageUrl ?? `/posts/${slugifyStr(title)}.png`,
Astro.url.origin
).href;
---
<Layout
title={title}
author={author}
description={description}
ogImage={ogUrl}
canonicalURL={canonicalURL}
>
<Header />
<div class="mx-auto flex w-full max-w-3xl justify-start px-2">
<button
class="focus-outline mb-2 mt-8 flex hover:opacity-75"
onclick="history.back()"
>
<svg xmlns="http://www.w3.org/2000/svg"
><path
d="M13.293 6.293 7.586 12l5.707 5.707 1.414-1.414L10.414 12l4.293-4.293z"
></path>
</svg><span>Go back</span>
</button>
</div>
<main id="main-content">
<h1 transition:name={slugifyStr(title)} class="post-title">{title}</h1>
<Datetime datetime={pubDatetime} size="lg" className="my-2" />
<article id="article" role="article" class="prose mx-auto mt-8 max-w-3xl">
<Content />
</article>
<ul class="tags-container">
{tags.map(tag => <Tag name={slugifyStr(tag)} />)}
</ul>
</main>
<Footer />
</Layout>
<style>
main {
@apply mx-auto w-full max-w-3xl px-4 pb-12;
}
.post-title {
@apply text-2xl font-semibold text-skin-accent;
}
.tags-container {
@apply my-8;
}
</style>

77
src/layouts/Posts.astro Normal file
View file

@ -0,0 +1,77 @@
---
import { SITE } from "@config";
import Layout from "@layouts/Layout.astro";
import Main from "@layouts/Main.astro";
import Header from "@components/Header.astro";
import Footer from "@components/Footer.astro";
import Card from "@components/Card";
import LinkButton from "@components/LinkButton.astro";
import slugify from "@utils/slugify";
import type { CollectionEntry } from "astro:content";
export interface Props {
pageNum: number;
totalPages: number;
posts: CollectionEntry<"blog">[];
}
const { pageNum, totalPages, posts } = Astro.props;
const prev = pageNum > 1 ? "" : "disabled";
const next = pageNum < totalPages ? "" : "disabled";
---
<Layout title={`Posts | ${SITE.title}`}>
<Header activeNav="posts" />
<Main pageTitle="Posts" pageDesc="All the articles I've posted.">
<ul>
{
posts.map(({ data }) => (
<Card href={`/posts/${slugify(data)}`} frontmatter={data} />
))
}
</ul>
</Main>
{
totalPages > 1 && (
<nav class="pagination-wrapper" aria-label="Pagination">
<LinkButton
disabled={prev === "disabled"}
href={`/posts${pageNum - 1 !== 1 ? "/" + (pageNum - 1) : ""}`}
className={`mr-4 select-none ${prev}`}
ariaLabel="Previous"
>
<svg xmlns="http://www.w3.org/2000/svg" class={`${prev}-svg`}>
<path d="M12.707 17.293 8.414 13H18v-2H8.414l4.293-4.293-1.414-1.414L4.586 12l6.707 6.707z" />
</svg>
Prev
</LinkButton>
<LinkButton
disabled={next === "disabled"}
href={`/posts/${pageNum + 1}`}
className={`ml-4 select-none ${next}`}
ariaLabel="Next"
>
Next
<svg xmlns="http://www.w3.org/2000/svg" class={`${next}-svg`}>
<path d="m11.293 17.293 1.414 1.414L19.414 12l-6.707-6.707-1.414 1.414L15.586 11H6v2h9.586z" />
</svg>
</LinkButton>
</nav>
)
}
<Footer noMarginTop={totalPages > 1} />
</Layout>
<style>
.pagination-wrapper {
@apply mb-8 mt-auto flex justify-center;
}
.disabled {
@apply pointer-events-none select-none opacity-50 hover:text-skin-base group-hover:fill-skin-base;
}
.disabled-svg {
@apply group-hover:!fill-skin-base;
}
</style>

42
src/pages/404.astro Normal file
View file

@ -0,0 +1,42 @@
---
import { SITE } from "@config";
import Layout from "@layouts/Layout.astro";
import Header from "@components/Header.astro";
import Footer from "@components/Footer.astro";
import LinkButton from "@components/LinkButton.astro";
---
<Layout title={`404 Not Found | ${SITE.title}`}>
<Header />
<main id="main-content">
<div class="not-found-wrapper">
<h1 aria-label="404 Not Found">404</h1>
<span aria-hidden="true">¯\_(ツ)_/¯</span>
<p>Page Not Found</p>
<LinkButton
href="/"
className="my-6 underline decoration-dashed underline-offset-8 text-lg"
>
Go back home
</LinkButton>
</div>
</main>
<Footer />
</Layout>
<style>
#main-content {
@apply mx-auto flex max-w-3xl flex-1 items-center justify-center;
}
.not-found-wrapper {
@apply mb-14 flex flex-col items-center justify-center;
}
.not-found-wrapper h1 {
@apply text-9xl font-bold text-skin-accent;
}
.not-found-wrapper p {
@apply mt-4 text-2xl sm:text-3xl;
}
</style>

6
src/pages/about.md Normal file
View file

@ -0,0 +1,6 @@
---
layout: ../layouts/AboutLayout.astro
title: "About"
---
Hi there I am Toastie a software developer from New Zealand I mostly write Discord bots and some other things, I hope you like what I post here.

152
src/pages/index.astro Normal file
View file

@ -0,0 +1,152 @@
---
import { getCollection } from "astro:content";
import Layout from "@layouts/Layout.astro";
import Header from "@components/Header.astro";
import Footer from "@components/Footer.astro";
import LinkButton from "@components/LinkButton.astro";
import Hr from "@components/Hr.astro";
import Card from "@components/Card";
import Socials from "@components/Socials.astro";
import getSortedPosts from "@utils/getSortedPosts";
import slugify from "@utils/slugify";
import { SOCIALS } from "@config";
const posts = await getCollection("blog");
const sortedPosts = getSortedPosts(posts);
const featuredPosts = sortedPosts.filter(({ data }) => data.featured);
const socialCount = SOCIALS.filter(social => social.active).length;
---
<Layout>
<Header />
<main id="main-content">
<section id="hero">
<h1 class="mr-2">Welcome!
</h1>
<a
target="_blank"
href="/rss.xml"
class="rss-link"
aria-label="rss feed"
title="RSS Feed"
>
<svg xmlns="http://www.w3.org/2000/svg" class="rss-icon"
><path
d="M19 20.001C19 11.729 12.271 5 4 5v2c7.168 0 13 5.832 13 13.001h2z"
></path><path
d="M12 20.001h2C14 14.486 9.514 10 4 10v2c4.411 0 8 3.589 8 8.001z"
></path><circle cx="6" cy="18" r="2"></circle>
</svg>
</a>
<p>
Hi there, it looks like you have stumbled upon my blog,
please go and get something to drink or something to eat
while you read the contents of this blog.
</p>
{
// only display if at least one social link is enabled
socialCount > 0 && (
<div class="social-wrapper">
<div class="social-links">Social Links:</div>
<Socials />
</div>
)
}
</section>
<Hr />
{
featuredPosts.length > 0 && (
<>
<section id="featured">
<h2>Featured</h2>
<ul>
{featuredPosts.map(({ data }) => (
<Card
href={`/posts/${slugify(data)}`}
frontmatter={data}
secHeading={false}
/>
))}
</ul>
</section>
<Hr />
</>
)
}
<section id="recent-posts">
<h2>Recent Posts</h2>
<ul>
{
sortedPosts
.filter(({ data }) => !data.featured)
.map(
({ data }, index) =>
index < 4 && (
<Card
href={`/posts/${slugify(data)}`}
frontmatter={data}
secHeading={false}
/>
)
)
}
</ul>
<div class="all-posts-btn-wrapper">
<LinkButton href="/posts">
All Posts
<svg xmlns="http://www.w3.org/2000/svg"
><path
d="m11.293 17.293 1.414 1.414L19.414 12l-6.707-6.707-1.414 1.414L15.586 11H6v2h9.586z"
></path>
</svg>
</LinkButton>
</div>
</section>
</main>
<Footer />
</Layout>
<style>
/* ===== Hero Section ===== */
#hero {
@apply pb-6 pt-8;
}
#hero h1 {
@apply my-4 inline-block text-3xl font-bold sm:my-8 sm:text-5xl;
}
#hero .rss-link {
@apply mb-6;
}
#hero .rss-icon {
@apply mb-2 h-6 w-6 scale-110 fill-skin-accent sm:mb-3 sm:scale-125;
}
#hero p {
@apply my-2;
}
.social-wrapper {
@apply mt-4 flex flex-col sm:flex-row sm:items-center;
}
.social-links {
@apply mb-1 mr-2 whitespace-nowrap sm:mb-0;
}
/* ===== Featured & Recent Posts Sections ===== */
#featured,
#recent-posts {
@apply pb-6 pt-12;
}
#featured h2,
#recent-posts h2 {
@apply text-2xl font-semibold tracking-wide;
}
.all-posts-btn-wrapper {
@apply my-8 text-center;
}
</style>

View file

@ -1,23 +0,0 @@
/**
* CSS files with the .module.css suffix will be treated as CSS modules
* and scoped locally.
*/
.heroBanner {
padding: 4rem 0;
text-align: center;
position: relative;
overflow: hidden;
}
@media screen and (max-width: 996px) {
.heroBanner {
padding: 2rem;
}
}
.buttons {
display: flex;
align-items: center;
justify-content: center;
}

View file

@ -1,7 +0,0 @@
---
title: Markdown page example
---
# Markdown page example
You don't need React to write simple standalone pages.

7
src/pages/og.png.ts Normal file
View file

@ -0,0 +1,7 @@
import type { APIRoute } from "astro";
import { generateOgImageForSite } from "@utils/generateOgImages";
export const GET: APIRoute = async () =>
new Response(await generateOgImageForSite(), {
headers: { "Content-Type": "image/png" },
});

View file

@ -0,0 +1,58 @@
---
import { type CollectionEntry, getCollection } from "astro:content";
import Posts from "@layouts/Posts.astro";
import PostDetails from "@layouts/PostDetails.astro";
import getSortedPosts from "@utils/getSortedPosts";
import getPageNumbers from "@utils/getPageNumbers";
import slugify from "@utils/slugify";
import { SITE } from "@config";
export interface Props {
post: CollectionEntry<"blog">;
}
export async function getStaticPaths() {
const posts = await getCollection("blog", ({ data }) => !data.draft);
const postResult = posts.map(post => ({
params: { slug: slugify(post.data) },
props: { post },
}));
const pagePaths = getPageNumbers(posts.length).map(pageNum => ({
params: { slug: String(pageNum) },
}));
return [...postResult, ...pagePaths];
}
const { slug } = Astro.params;
const { post } = Astro.props;
const posts = await getCollection("blog");
const sortedPosts = getSortedPosts(posts);
const totalPages = getPageNumbers(sortedPosts.length);
const currentPage =
slug && !isNaN(Number(slug)) && totalPages.includes(Number(slug))
? Number(slug)
: 0;
const lastPost = currentPage * SITE.postPerPage;
const startPost = lastPost - SITE.postPerPage;
const paginatedPosts = sortedPosts.slice(startPost, lastPost);
---
{
post ? (
<PostDetails post={post} />
) : (
<Posts
posts={paginatedPosts}
pageNum={currentPage}
totalPages={totalPages.length}
/>
)
}

View file

@ -0,0 +1,20 @@
import type { APIRoute } from "astro";
import { getCollection, type CollectionEntry } from "astro:content";
import { generateOgImageForPost } from "@utils/generateOgImages";
import { slugifyStr } from "@utils/slugify";
export async function getStaticPaths() {
const posts = await getCollection("blog").then(p =>
p.filter(({ data }) => !data.draft && !data.ogImage)
);
return posts.map(post => ({
params: { slug: slugifyStr(post.data.title) },
props: post,
}));
}
export const GET: APIRoute = async ({ props }) =>
new Response(await generateOgImageForPost(props as CollectionEntry<"blog">), {
headers: { "Content-Type": "image/png" },
});

View file

@ -0,0 +1,18 @@
---
import { SITE } from "@config";
import Posts from "@layouts/Posts.astro";
import getSortedPosts from "@utils/getSortedPosts";
import getPageNumbers from "@utils/getPageNumbers";
import { getCollection } from "astro:content";
const posts = await getCollection("blog");
const sortedPosts = getSortedPosts(posts);
const totalPages = getPageNumbers(sortedPosts.length);
const paginatedPosts = sortedPosts.slice(0, SITE.postPerPage);
---
<Posts posts={paginatedPosts} pageNum={1} totalPages={totalPages.length} />

17
src/pages/robots.txt.ts Normal file
View file

@ -0,0 +1,17 @@
import type { APIRoute } from "astro";
import { SITE } from "@config";
const robots = `
User-agent: Googlebot
Disallow: /nogooglebot/
User-agent: *
Allow: /
Sitemap: ${new URL("sitemap-index.xml", SITE.website).href}
`.trim();
export const GET: APIRoute = () =>
new Response(robots, {
headers: { "Content-Type": "text/plain" },
});

21
src/pages/rss.xml.ts Normal file
View file

@ -0,0 +1,21 @@
import rss from "@astrojs/rss";
import { getCollection } from "astro:content";
import getSortedPosts from "@utils/getSortedPosts";
import slugify from "@utils/slugify";
import { SITE } from "@config";
export async function GET() {
const posts = await getCollection("blog");
const sortedPosts = getSortedPosts(posts);
return rss({
title: SITE.title,
description: SITE.desc,
site: SITE.website,
items: sortedPosts.map(({ data }) => ({
link: `posts/${slugify(data)}`,
title: data.title,
description: data.description,
pubDate: new Date(data.pubDatetime),
})),
});
}

27
src/pages/search.astro Normal file
View file

@ -0,0 +1,27 @@
---
import { getCollection } from "astro:content";
import { SITE } from "@config";
import Layout from "@layouts/Layout.astro";
import Main from "@layouts/Main.astro";
import Header from "@components/Header.astro";
import Footer from "@components/Footer.astro";
import SearchBar from "@components/Search";
// Retrieve all articles
const posts = await getCollection("blog", ({ data }) => !data.draft);
// List of items to search in
const searchList = posts.map(({ data }) => ({
title: data.title,
description: data.description,
data,
}));
---
<Layout title={`Search | ${SITE.title}`}>
<Header activeNav="search" />
<Main pageTitle="Search" pageDesc="Search any article ...">
<SearchBar client:load searchList={searchList} />
</Main>
<Footer />
</Layout>

View file

@ -0,0 +1,58 @@
---
import { type CollectionEntry, getCollection } from "astro:content";
import Layout from "@layouts/Layout.astro";
import Main from "@layouts/Main.astro";
import Header from "@components/Header.astro";
import Footer from "@components/Footer.astro";
import Card from "@components/Card";
import getUniqueTags from "@utils/getUniqueTags";
import getPostsByTag from "@utils/getPostsByTag";
import slugify from "@utils/slugify";
import { SITE } from "@config";
import getSortedPosts from "@utils/getSortedPosts";
export interface Props {
post: CollectionEntry<"blog">;
tag: string;
}
export async function getStaticPaths() {
const posts = await getCollection("blog");
const tags = getUniqueTags(posts);
return tags.map(tag => {
return {
params: { tag },
props: { tag },
};
});
}
const { tag } = Astro.props;
const posts = await getCollection("blog", ({ data }) => !data.draft);
const tagPosts = getPostsByTag(posts, tag);
const sortTagsPost = getSortedPosts(tagPosts);
---
<Layout title={`Tag:${tag} | ${SITE.title}`}>
<Header activeNav="tags" />
<Main
pageTitle={[`Tag:`, `${tag}`]}
titleTransition={tag}
pageDesc={`All the articles with the tag "${tag}".`}
>
<h1 slot="title" transition:name={tag}>{`Tag:${tag}`}</h1>
<ul>
{
sortTagsPost.map(({ data }) => (
<Card href={`/posts/${slugify(data)}`} frontmatter={data} />
))
}
</ul>
</Main>
<Footer />
</Layout>

View file

@ -0,0 +1,24 @@
---
import { getCollection } from "astro:content";
import Header from "@components/Header.astro";
import Footer from "@components/Footer.astro";
import Layout from "@layouts/Layout.astro";
import Main from "@layouts/Main.astro";
import Tag from "@components/Tag.astro";
import getUniqueTags from "@utils/getUniqueTags";
import { SITE } from "@config";
const posts = await getCollection("blog");
let tags = getUniqueTags(posts);
---
<Layout title={`Tags | ${SITE.title}`}>
<Header activeNav="tags" />
<Main pageTitle="Tags" pageDesc="All the tags used in posts.">
<ul>
{tags.map(tag => <Tag name={tag} size="lg" />)}
</ul>
</Main>
<Footer />
</Layout>

135
src/styles/base.css Normal file
View file

@ -0,0 +1,135 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
@layer base {
:root,
html[data-theme="light"] {
--color-fill: 251, 254, 251;
--color-text-base: 40, 39, 40;
--color-accent: 0, 108, 172;
--color-card: 230, 230, 230;
--color-card-muted: 205, 205, 205;
--color-border: 236, 233, 233;
}
html[data-theme="dark"] {
--color-fill: 33, 39, 55;
--color-text-base: 234, 237, 243;
--color-accent: 255, 107, 1;
--color-card: 52, 63, 96;
--color-card-muted: 138, 51, 2;
--color-border: 171, 75, 8;
}
#sun-svg,
html[data-theme="dark"] #moon-svg {
display: none;
}
#moon-svg,
html[data-theme="dark"] #sun-svg {
display: block;
}
body {
@apply flex min-h-[100svh] flex-col bg-skin-fill font-mono text-skin-base
selection:bg-skin-accent selection:bg-opacity-70 selection:text-skin-inverted;
}
section,
footer {
@apply mx-auto max-w-3xl px-4;
}
a {
@apply outline-2 outline-offset-1 outline-skin-fill
focus-visible:no-underline focus-visible:outline-dashed;
}
svg {
@apply inline-block h-6 w-6 fill-skin-base group-hover:fill-skin-accent;
}
svg.icon-tabler {
@apply inline-block h-6 w-6 scale-125 fill-transparent
stroke-current stroke-2 opacity-90 group-hover:fill-transparent
sm:scale-110;
}
.prose {
@apply prose-headings:!mb-3 prose-headings:!text-skin-base
prose-h3:italic prose-p:!text-skin-base
prose-a:!text-skin-base prose-a:!decoration-dashed prose-a:underline-offset-8
hover:prose-a:text-skin-accent prose-blockquote:!border-l-skin-accent
prose-blockquote:border-opacity-50 prose-blockquote:opacity-80
prose-figcaption:!text-skin-base prose-figcaption:opacity-70
prose-strong:!text-skin-base
prose-code:rounded prose-code:bg-skin-card
prose-code:bg-opacity-75 prose-code:p-1 prose-code:!text-skin-base
prose-code:before:!content-[''] prose-code:after:!content-['']
prose-pre:!text-skin-base prose-ol:!text-skin-base
prose-ul:overflow-x-clip prose-ul:!text-skin-base prose-li:marker:!text-skin-accent
prose-table:text-skin-base prose-th:border
prose-th:border-skin-line prose-td:border
prose-td:border-skin-line prose-img:mx-auto
prose-img:!mt-2 prose-img:border-2
prose-img:border-skin-line prose-hr:!border-skin-line;
}
.prose a {
@apply break-words hover:!text-skin-accent;
}
.prose thead th:first-child,
tbody td:first-child,
tfoot td:first-child {
padding-left: 0.5714286em;
}
.prose h2#table-of-contents {
@apply mb-2;
}
.prose details {
@apply inline-block cursor-pointer select-none text-skin-base;
}
.prose summary {
@apply focus-outline;
}
.prose h2#table-of-contents + p {
@apply hidden;
}
/* ===== scrollbar ===== */
html {
overflow-y: scroll;
}
/* width */
::-webkit-scrollbar {
@apply w-3;
}
/* Track */
::-webkit-scrollbar-track {
@apply bg-skin-fill;
}
/* Handle */
::-webkit-scrollbar-thumb {
@apply bg-skin-card;
}
/* Handle on hover */
::-webkit-scrollbar-thumb:hover {
@apply bg-skin-card-muted;
}
code,
blockquote {
word-wrap: break-word;
}
pre > code {
white-space: pre;
}
}
@layer components {
.display-none {
@apply hidden;
}
.focus-outline {
@apply outline-2 outline-offset-1 outline-skin-fill focus-visible:no-underline focus-visible:outline-dashed;
}
}

42
src/types.ts Normal file
View file

@ -0,0 +1,42 @@
export type Site = {
website: string;
author: string;
desc: string;
title: string;
ogImage?: string;
lightAndDarkMode: boolean;
postPerPage: number;
};
export type SocialObjects = {
name: SocialMedia;
href: string;
active: boolean;
linkTitle: string;
}[];
export type SocialIcons = {
[social in SocialMedia]: string;
};
export type SocialMedia =
| "Github"
| "Facebook"
| "Instagram"
| "LinkedIn"
| "Mail"
| "Twitter"
| "Twitch"
| "YouTube"
| "WhatsApp"
| "Snapchat"
| "Pinterest"
| "TikTok"
| "CodePen"
| "Discord"
| "GitLab"
| "Reddit"
| "Skype"
| "Steam"
| "Telegram"
| "Mastodon";

View file

@ -0,0 +1,59 @@
import satori, { type SatoriOptions } from "satori";
import { Resvg } from "@resvg/resvg-js";
import { type CollectionEntry } from "astro:content";
import postOgImage from "./og-templates/post";
import siteOgImage from "./og-templates/site";
const fetchFonts = async () => {
// Regular Font
const fontFileRegular = await fetch(
"https://www.1001fonts.com/download/font/ibm-plex-mono.regular.ttf"
);
const fontRegular: ArrayBuffer = await fontFileRegular.arrayBuffer();
// Bold Font
const fontFileBold = await fetch(
"https://www.1001fonts.com/download/font/ibm-plex-mono.bold.ttf"
);
const fontBold: ArrayBuffer = await fontFileBold.arrayBuffer();
return { fontRegular, fontBold };
};
const { fontRegular, fontBold } = await fetchFonts();
const options: SatoriOptions = {
width: 1200,
height: 630,
embedFont: true,
fonts: [
{
name: "IBM Plex Mono",
data: fontRegular,
weight: 400,
style: "normal",
},
{
name: "IBM Plex Mono",
data: fontBold,
weight: 600,
style: "normal",
},
],
};
function svgBufferToPngBuffer(svg: string) {
const resvg = new Resvg(svg);
const pngData = resvg.render();
return pngData.asPng();
}
export async function generateOgImageForPost(post: CollectionEntry<"blog">) {
const svg = await satori(postOgImage(post), options);
return svgBufferToPngBuffer(svg);
}
export async function generateOgImageForSite() {
const svg = await satori(siteOgImage(), options);
return svgBufferToPngBuffer(svg);
}

View file

@ -0,0 +1,14 @@
import { SITE } from "@config";
const getPageNumbers = (numberOfPosts: number) => {
const numberOfPages = numberOfPosts / Number(SITE.postPerPage);
let pageNumbers: number[] = [];
for (let i = 1; i <= Math.ceil(numberOfPages); i++) {
pageNumbers = [...pageNumbers, i];
}
return pageNumbers;
};
export default getPageNumbers;

View file

@ -0,0 +1,7 @@
import { slugifyAll } from "./slugify";
import type { CollectionEntry } from "astro:content";
const getPostsByTag = (posts: CollectionEntry<"blog">[], tag: string) =>
posts.filter(post => slugifyAll(post.data.tags).includes(tag));
export default getPostsByTag;

View file

@ -0,0 +1,12 @@
import type { CollectionEntry } from "astro:content";
const getSortedPosts = (posts: CollectionEntry<"blog">[]) =>
posts
.filter(({ data }) => !data.draft)
.sort(
(a, b) =>
Math.floor(new Date(b.data.pubDatetime).getTime() / 1000) -
Math.floor(new Date(a.data.pubDatetime).getTime() / 1000)
);
export default getSortedPosts;

View file

@ -0,0 +1,17 @@
import { slugifyStr } from "./slugify";
import type { CollectionEntry } from "astro:content";
const getUniqueTags = (posts: CollectionEntry<"blog">[]) => {
const filteredPosts = posts.filter(({ data }) => !data.draft);
const tags: string[] = filteredPosts
.flatMap(post => post.data.tags)
.map(tag => slugifyStr(tag))
.filter(
(value: string, index: number, self: string[]) =>
self.indexOf(value) === index
)
.sort((tagA: string, tagB: string) => tagA.localeCompare(tagB));
return tags;
};
export default getUniqueTags;

View file

@ -0,0 +1,96 @@
import { SITE } from "@config";
import type { CollectionEntry } from "astro:content";
export default (post: CollectionEntry<"blog">) => {
return (
<div
style={{
background: "#fefbfb",
width: "100%",
height: "100%",
display: "flex",
alignItems: "center",
justifyContent: "center",
}}
>
<div
style={{
position: "absolute",
top: "-1px",
right: "-1px",
border: "4px solid #000",
background: "#ecebeb",
opacity: "0.9",
borderRadius: "4px",
display: "flex",
justifyContent: "center",
margin: "2.5rem",
width: "88%",
height: "80%",
}}
/>
<div
style={{
border: "4px solid #000",
background: "#fefbfb",
borderRadius: "4px",
display: "flex",
justifyContent: "center",
margin: "2rem",
width: "88%",
height: "80%",
}}
>
<div
style={{
display: "flex",
flexDirection: "column",
justifyContent: "space-between",
margin: "20px",
width: "90%",
height: "90%",
}}
>
<p
style={{
fontSize: 72,
fontWeight: "bold",
maxHeight: "84%",
overflow: "hidden",
}}
>
{post.data.title}
</p>
<div
style={{
display: "flex",
justifyContent: "space-between",
width: "100%",
marginBottom: "8px",
fontSize: 28,
}}
>
<span>
by{" "}
<span
style={{
color: "transparent",
}}
>
"
</span>
<span style={{ overflow: "hidden", fontWeight: "bold" }}>
{post.data.author}
</span>
</span>
<span style={{ overflow: "hidden", fontWeight: "bold" }}>
{SITE.title}
</span>
</div>
</div>
</div>
</div>
);
};

View file

@ -0,0 +1,87 @@
import { SITE } from "@config";
export default () => {
return (
<div
style={{
background: "#fefbfb",
width: "100%",
height: "100%",
display: "flex",
alignItems: "center",
justifyContent: "center",
}}
>
<div
style={{
position: "absolute",
top: "-1px",
right: "-1px",
border: "4px solid #000",
background: "#ecebeb",
opacity: "0.9",
borderRadius: "4px",
display: "flex",
justifyContent: "center",
margin: "2.5rem",
width: "88%",
height: "80%",
}}
/>
<div
style={{
border: "4px solid #000",
background: "#fefbfb",
borderRadius: "4px",
display: "flex",
justifyContent: "center",
margin: "2rem",
width: "88%",
height: "80%",
}}
>
<div
style={{
display: "flex",
flexDirection: "column",
justifyContent: "space-between",
margin: "20px",
width: "90%",
height: "90%",
}}
>
<div
style={{
display: "flex",
flexDirection: "column",
justifyContent: "center",
alignItems: "center",
height: "90%",
maxHeight: "90%",
overflow: "hidden",
textAlign: "center",
}}
>
<p style={{ fontSize: 72, fontWeight: "bold" }}>{SITE.title}</p>
<p style={{ fontSize: 28 }}>{SITE.desc}</p>
</div>
<div
style={{
display: "flex",
justifyContent: "flex-end",
width: "100%",
marginBottom: "8px",
fontSize: 28,
}}
>
<span style={{ overflow: "hidden", fontWeight: "bold" }}>
{new URL(SITE.website).hostname}
</span>
</div>
</div>
</div>
</div>
);
};

11
src/utils/slugify.ts Normal file
View file

@ -0,0 +1,11 @@
import { slug as slugger } from "github-slugger";
import type { CollectionEntry } from "astro:content";
export const slugifyStr = (str: string) => slugger(str);
const slugify = (post: CollectionEntry<"blog">["data"]) =>
post.postSlug ? slugger(post.postSlug) : slugger(post.title);
export const slugifyAll = (arr: string[]) => arr.map(str => slugifyStr(str));
export default slugify;

View file

Binary file not shown.

Before

Width:  |  Height:  |  Size: 219 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 219 KiB

65
tailwind.config.cjs Normal file
View file

@ -0,0 +1,65 @@
function withOpacity(variableName) {
return ({ opacityValue }) => {
if (opacityValue !== undefined) {
return `rgba(var(${variableName}), ${opacityValue})`;
}
return `rgb(var(${variableName}))`;
};
}
/** @type {import('tailwindcss').Config} */
module.exports = {
content: ["./src/**/*.{astro,html,js,jsx,md,mdx,svelte,ts,tsx,vue}"],
theme: {
// Remove the following screen breakpoint or add other breakpoints
// if one breakpoint is not enough for you
screens: {
sm: "640px",
},
// Uncomment the following extend
// if existing Tailwind color palette will be used
// extend: {
textColor: {
skin: {
base: withOpacity("--color-text-base"),
accent: withOpacity("--color-accent"),
inverted: withOpacity("--color-fill"),
},
},
backgroundColor: {
skin: {
fill: withOpacity("--color-fill"),
accent: withOpacity("--color-accent"),
inverted: withOpacity("--color-text-base"),
card: withOpacity("--color-card"),
"card-muted": withOpacity("--color-card-muted"),
},
},
outlineColor: {
skin: {
fill: withOpacity("--color-accent"),
},
},
borderColor: {
skin: {
line: withOpacity("--color-border"),
fill: withOpacity("--color-text-base"),
accent: withOpacity("--color-accent"),
},
},
fill: {
skin: {
base: withOpacity("--color-text-base"),
accent: withOpacity("--color-accent"),
},
transparent: "transparent",
},
fontFamily: {
mono: ["IBM Plex Mono", "monospace"],
},
// },
},
plugins: [require("@tailwindcss/typography")],
};

17
tsconfig.json Normal file
View file

@ -0,0 +1,17 @@
{
"extends": "astro/tsconfigs/strict",
"compilerOptions": {
"baseUrl": "src",
"jsx": "react-jsx",
"paths": {
"@assets/*": ["assets/*"],
"@config": ["config.ts"],
"@components/*": ["components/*"],
"@content/*": ["content/*"],
"@layouts/*": ["layouts/*"],
"@pages/*": ["pages/*"],
"@styles/*": ["styles/*"],
"@utils/*": ["utils/*"]
}
}
}

7669
yarn.lock

File diff suppressed because it is too large Load diff