Compare commits

...

5 Commits

Author SHA1 Message Date
Moritz Hölting c117db495a change work -> qualifications (add education) 2026-03-18 15:38:07 +01:00
Moritz Hölting 90fea1f2f6 redesign: add toc to ArticleContent 2026-03-18 14:31:42 +01:00
Moritz Hölting df43ac617b redesign: style rss feed 2026-03-18 13:26:17 +01:00
Moritz Hölting 4080f8b3b3 redesign: finish blog & projects 2026-03-18 12:50:34 +01:00
Moritz Hölting e09e36ccb7 redesign: finish start page 2026-03-18 10:56:58 +01:00
33 changed files with 1421 additions and 347 deletions

View File

@ -1,25 +1,19 @@
import { defineConfig } from "astro/config"; import { defineConfig } from "astro/config";
import mdx from "@astrojs/mdx"; import mdx from "@astrojs/mdx";
import sitemap from "@astrojs/sitemap"; import sitemap from "@astrojs/sitemap";
import tailwind from "@tailwindcss/vite";
import solidJs from "@astrojs/solid-js"; import solidJs from "@astrojs/solid-js";
import remarkMath from "remark-math"; import remarkMath from "remark-math";
import rehypeKatex from "rehype-katex"; import rehypeKatex from "rehype-katex";
import matomo from "./src/integrations/matomo"; import matomo from "./src/integrations/matomo";
import tailwindcss from "@tailwindcss/vite"; import tailwindcss from "@tailwindcss/vite";
import icon from "astro-icon";
// https://astro.build/config // https://astro.build/config
export default defineConfig({ export default defineConfig({
site: "https://hoelting.dev", site: "https://hoelting.dev",
trailingSlash: "always", trailingSlash: "always",
integrations: [ integrations: [mdx(), sitemap(), icon(), solidJs(), matomo({
mdx(),
sitemap(),
solidJs(),
matomo({
enabled: import.meta.env.PROD, // only enable in production enabled: import.meta.env.PROD, // only enable in production
url: "https://analytics.hoelting.dev", url: "https://analytics.hoelting.dev",
siteId: 3, siteId: 3,
@ -27,8 +21,7 @@ export default defineConfig({
enableCrossDomainLinking: true, enableCrossDomainLinking: true,
domains: ["*.hoelting.dev", "*.www.hoelting.dev"], domains: ["*.hoelting.dev", "*.www.hoelting.dev"],
respectDoNotTrack: true, respectDoNotTrack: true,
}), })],
],
markdown: { markdown: {
remarkPlugins: [remarkMath], remarkPlugins: [remarkMath],

View File

@ -19,13 +19,20 @@
"@astrojs/rss": "^4.0.17", "@astrojs/rss": "^4.0.17",
"@astrojs/sitemap": "^3.6.0", "@astrojs/sitemap": "^3.6.0",
"@astrojs/solid-js": "^6.0.0", "@astrojs/solid-js": "^6.0.0",
"@iconify-icon/solid": "^3.0.3",
"@iconify-json/devicon": "^1.2.61",
"@iconify-json/material-icon-theme": "^1.2.56",
"@iconify-json/pixel": "^1.2.1",
"@iconify-json/pixelarticons": "^1.2.4",
"@tailwindcss/typography": "^0.5.19", "@tailwindcss/typography": "^0.5.19",
"@tailwindcss/vite": "^4.2.1", "@tailwindcss/vite": "^4.2.1",
"astro": "^6.0.5", "astro": "^6.0.5",
"astro-icon": "^1.1.5",
"astro-og-canvas": "^0.10.1", "astro-og-canvas": "^0.10.1",
"canvaskit-wasm": "^0.40.0", "canvaskit-wasm": "^0.40.0",
"clsx": "^2.1.1", "clsx": "^2.1.1",
"fuse.js": "^7.1.0", "fuse.js": "^7.1.0",
"iconify-icon": "^3.0.2",
"rehype-katex": "^7.0.1", "rehype-katex": "^7.0.1",
"remark-math": "^6.0.0", "remark-math": "^6.0.0",
"sharp": "^0.34.5", "sharp": "^0.34.5",

View File

@ -23,6 +23,21 @@ importers:
'@astrojs/solid-js': '@astrojs/solid-js':
specifier: ^6.0.0 specifier: ^6.0.0
version: 6.0.0(@types/node@24.12.0)(jiti@2.6.1)(lightningcss@1.31.1)(solid-js@1.9.11)(yaml@2.8.2) version: 6.0.0(@types/node@24.12.0)(jiti@2.6.1)(lightningcss@1.31.1)(solid-js@1.9.11)(yaml@2.8.2)
'@iconify-icon/solid':
specifier: ^3.0.3
version: 3.0.3(solid-js@1.9.11)
'@iconify-json/devicon':
specifier: ^1.2.61
version: 1.2.61
'@iconify-json/material-icon-theme':
specifier: ^1.2.56
version: 1.2.56
'@iconify-json/pixel':
specifier: ^1.2.1
version: 1.2.1
'@iconify-json/pixelarticons':
specifier: ^1.2.4
version: 1.2.4
'@tailwindcss/typography': '@tailwindcss/typography':
specifier: ^0.5.19 specifier: ^0.5.19
version: 0.5.19(tailwindcss@4.2.1) version: 0.5.19(tailwindcss@4.2.1)
@ -32,6 +47,9 @@ importers:
astro: astro:
specifier: ^6.0.5 specifier: ^6.0.5
version: 6.0.5(@types/node@24.12.0)(jiti@2.6.1)(lightningcss@1.31.1)(rollup@4.59.0)(typescript@5.9.3)(yaml@2.8.2) version: 6.0.5(@types/node@24.12.0)(jiti@2.6.1)(lightningcss@1.31.1)(rollup@4.59.0)(typescript@5.9.3)(yaml@2.8.2)
astro-icon:
specifier: ^1.1.5
version: 1.1.5
astro-og-canvas: astro-og-canvas:
specifier: ^0.10.1 specifier: ^0.10.1
version: 0.10.1(astro@6.0.5(@types/node@24.12.0)(jiti@2.6.1)(lightningcss@1.31.1)(rollup@4.59.0)(typescript@5.9.3)(yaml@2.8.2)) version: 0.10.1(astro@6.0.5(@types/node@24.12.0)(jiti@2.6.1)(lightningcss@1.31.1)(rollup@4.59.0)(typescript@5.9.3)(yaml@2.8.2))
@ -44,6 +62,9 @@ importers:
fuse.js: fuse.js:
specifier: ^7.1.0 specifier: ^7.1.0
version: 7.1.0 version: 7.1.0
iconify-icon:
specifier: ^3.0.2
version: 3.0.2
rehype-katex: rehype-katex:
specifier: ^7.0.1 specifier: ^7.0.1
version: 7.0.1 version: 7.0.1
@ -71,6 +92,12 @@ importers:
packages: packages:
'@antfu/install-pkg@1.1.0':
resolution: {integrity: sha512-MGQsmw10ZyI+EJo45CdSER4zEb+p31LpDAFp2Z3gkSd1yqVZGi0Ebx++YTEMonJy4oChEMLsxZ64j8FH6sSqtQ==}
'@antfu/utils@8.1.1':
resolution: {integrity: sha512-Mex9nXf9vR6AhcXmMrlz/HVgYYZpVGJ6YlPgwl7UnaFpnshXs6EK/oa5Gpf3CzENMjkvEx2tQtntGnb7UtSTOQ==}
'@astrojs/check@0.9.8': '@astrojs/check@0.9.8':
resolution: {integrity: sha512-LDng8446QLS5ToKjRHd3bgUdirvemVVExV7nRyJfW2wV36xuv7vDxwy5NWN9zqeSEDgg0Tv84sP+T3yEq+Zlkw==} resolution: {integrity: sha512-LDng8446QLS5ToKjRHd3bgUdirvemVVExV7nRyJfW2wV36xuv7vDxwy5NWN9zqeSEDgg0Tv84sP+T3yEq+Zlkw==}
hasBin: true hasBin: true
@ -405,6 +432,32 @@ packages:
cpu: [x64] cpu: [x64]
os: [win32] os: [win32]
'@iconify-icon/solid@3.0.3':
resolution: {integrity: sha512-VUM8/gscfrU0e6dUWjitdsJrNzYoY1bGTp1+gfZIAbvo+tIGcP9bD3WAvFb6pHaCYL/5X6erZUUn8pL0c0xX9Q==}
peerDependencies:
solid-js: '>=1.0.0'
'@iconify-json/devicon@1.2.61':
resolution: {integrity: sha512-GvJZrzyWbChwki9yscm8WaCfIHqFVi4hQbjNxoDS0JLsuAk4uyGOnmlHCJFN+SzAZ9FMU2RtmkdJGjYuw4G96w==}
'@iconify-json/material-icon-theme@1.2.56':
resolution: {integrity: sha512-Kg2MkTL1oQmY6Rs031sMsyW5d9OXnWDJ4rRxIJvrJs03fz5rywUlPyMwlU0W9QJuZifzXTItiXJUIs4MwSJAxA==}
'@iconify-json/pixel@1.2.1':
resolution: {integrity: sha512-XwzURAMyZ/BJKeygh4PloKo9cUvS8GfcePueApzwrePvmwCwYT8SC581AQLxJHJl2FZjGWmUMVQ2FN3b9/cYyw==}
'@iconify-json/pixelarticons@1.2.4':
resolution: {integrity: sha512-nADJKEI3mlxDSmJMLqGvNuvNl1TGa+VbFJIwrtj/8vUA4m15qPNiwxMyw8YxW3gVokuO9MNU462B8uK1hb4rqw==}
'@iconify/tools@4.2.0':
resolution: {integrity: sha512-WRxPva/ipxYkqZd1+CkEAQmd86dQmrwH0vwK89gmp2Kh2WyyVw57XbPng0NehP3x4V1LzLsXUneP1uMfTMZmUA==}
'@iconify/types@2.0.0':
resolution: {integrity: sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg==}
'@iconify/utils@2.3.0':
resolution: {integrity: sha512-GmQ78prtwYW6EtzXRU1rY+KwOKfz32PD7iJh6Iyqw68GiKuoZ2A6pRtzWONz5VQJbp50mEjXh/7NkumtrAgRKA==}
'@img/colour@1.1.0': '@img/colour@1.1.0':
resolution: {integrity: sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ==} resolution: {integrity: sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ==}
engines: {node: '>=18'} engines: {node: '>=18'}
@ -558,6 +611,10 @@ packages:
cpu: [x64] cpu: [x64]
os: [win32] os: [win32]
'@isaacs/fs-minipass@4.0.1':
resolution: {integrity: sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==}
engines: {node: '>=18.0.0'}
'@jridgewell/gen-mapping@0.3.13': '@jridgewell/gen-mapping@0.3.13':
resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==} resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==}
@ -908,6 +965,9 @@ packages:
'@types/unist@3.0.3': '@types/unist@3.0.3':
resolution: {integrity: sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==} resolution: {integrity: sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==}
'@types/yauzl@2.10.3':
resolution: {integrity: sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==}
'@ungap/structured-clone@1.3.0': '@ungap/structured-clone@1.3.0':
resolution: {integrity: sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==} resolution: {integrity: sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==}
@ -990,6 +1050,9 @@ packages:
resolution: {integrity: sha512-LElXdjswlqjWrPpJFg1Fx4wpkOCxj1TDHlSV4PlaRxHGWko024xICaa97ZkMfs6DRKlCguiAI+rbXv5GWwXIkg==} resolution: {integrity: sha512-LElXdjswlqjWrPpJFg1Fx4wpkOCxj1TDHlSV4PlaRxHGWko024xICaa97ZkMfs6DRKlCguiAI+rbXv5GWwXIkg==}
hasBin: true hasBin: true
astro-icon@1.1.5:
resolution: {integrity: sha512-CJYS5nWOw9jz4RpGWmzNQY7D0y2ZZacH7atL2K9DeJXJVaz7/5WrxeyIxO8KASk1jCM96Q4LjRx/F3R+InjJrw==}
astro-og-canvas@0.10.1: astro-og-canvas@0.10.1:
resolution: {integrity: sha512-cUjsWIOkDBi9Bfkgta1Cr45m2UnTX/jW+tK406SVcD1idhqIHrGD+M5qtwGcJ6OanHeWSgyHGmzQuzELGAwPKg==} resolution: {integrity: sha512-cUjsWIOkDBi9Bfkgta1Cr45m2UnTX/jW+tK406SVcD1idhqIHrGD+M5qtwGcJ6OanHeWSgyHGmzQuzELGAwPKg==}
peerDependencies: peerDependencies:
@ -1037,6 +1100,9 @@ packages:
engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7}
hasBin: true hasBin: true
buffer-crc32@0.2.13:
resolution: {integrity: sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==}
caniuse-lite@1.0.30001780: caniuse-lite@1.0.30001780:
resolution: {integrity: sha512-llngX0E7nQci5BPJDqoZSbuZ5Bcs9F5db7EtgfwBerX9XGtkkiO4NwfDDIRzHTTwcYC8vC7bmeUEPGrKlR/TkQ==} resolution: {integrity: sha512-llngX0E7nQci5BPJDqoZSbuZ5Bcs9F5db7EtgfwBerX9XGtkkiO4NwfDDIRzHTTwcYC8vC7bmeUEPGrKlR/TkQ==}
@ -1058,6 +1124,13 @@ packages:
character-reference-invalid@2.0.1: character-reference-invalid@2.0.1:
resolution: {integrity: sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw==} resolution: {integrity: sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw==}
cheerio-select@2.1.0:
resolution: {integrity: sha512-9v9kG0LvzrlcungtnJtpGNxY+fzECQKhK4EGJX2vByejiMX84MFNQw4UxPJl3bFbTMw+Dfs37XaIkCwTZfLh4g==}
cheerio@1.2.0:
resolution: {integrity: sha512-WDrybc/gKFpTYQutKIK6UvfcuxijIZfMfXaYm8NMsPQxSYvf+13fXUJ4rztGGbJcBQ/GF55gvrZ0Bc0bj/mqvg==}
engines: {node: '>=20.18.1'}
chokidar@4.0.3: chokidar@4.0.3:
resolution: {integrity: sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==} resolution: {integrity: sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==}
engines: {node: '>= 14.16.0'} engines: {node: '>= 14.16.0'}
@ -1066,6 +1139,10 @@ packages:
resolution: {integrity: sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw==} resolution: {integrity: sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw==}
engines: {node: '>= 20.19.0'} engines: {node: '>= 20.19.0'}
chownr@3.0.0:
resolution: {integrity: sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==}
engines: {node: '>=18'}
ci-info@4.4.0: ci-info@4.4.0:
resolution: {integrity: sha512-77PSwercCZU2Fc4sX94eF8k8Pxte6JAwL4/ICZLFjJLqegs7kCuAsqqj/70NQF6TvDpgFjkubQB2FW2ZZddvQg==} resolution: {integrity: sha512-77PSwercCZU2Fc4sX94eF8k8Pxte6JAwL4/ICZLFjJLqegs7kCuAsqqj/70NQF6TvDpgFjkubQB2FW2ZZddvQg==}
engines: {node: '>=8'} engines: {node: '>=8'}
@ -1095,6 +1172,10 @@ packages:
resolution: {integrity: sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ==} resolution: {integrity: sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ==}
engines: {node: '>=16'} engines: {node: '>=16'}
commander@7.2.0:
resolution: {integrity: sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==}
engines: {node: '>= 10'}
commander@8.3.0: commander@8.3.0:
resolution: {integrity: sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==} resolution: {integrity: sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==}
engines: {node: '>= 12'} engines: {node: '>= 12'}
@ -1103,6 +1184,12 @@ packages:
resolution: {integrity: sha512-dnN3ibLeoRf2HNC+OlCiNc5d2zxbLJXOtiZUudNFSXZrNSydxcCsSpRzXwfu7BBWCIfHPw+xTayeBvJCP/D8Ng==} resolution: {integrity: sha512-dnN3ibLeoRf2HNC+OlCiNc5d2zxbLJXOtiZUudNFSXZrNSydxcCsSpRzXwfu7BBWCIfHPw+xTayeBvJCP/D8Ng==}
engines: {node: '>= 18'} engines: {node: '>= 18'}
confbox@0.1.8:
resolution: {integrity: sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==}
confbox@0.2.4:
resolution: {integrity: sha512-ysOGlgTFbN2/Y6Cg3Iye8YKulHw+R2fNXHrgSmXISQdMnomY6eNDprVdW9R5xBguEqI954+S6709UyiO7B+6OQ==}
convert-source-map@2.0.0: convert-source-map@2.0.0:
resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==}
@ -1123,6 +1210,10 @@ packages:
resolution: {integrity: sha512-OA0mILzGc1kCOCSJerOeqDxDQ4HOh+G8NbOJFOTgOCzpw7fCBubk0fEyxp8AgOL/jvLgYA/uV0cMbe43ElF1JA==} resolution: {integrity: sha512-OA0mILzGc1kCOCSJerOeqDxDQ4HOh+G8NbOJFOTgOCzpw7fCBubk0fEyxp8AgOL/jvLgYA/uV0cMbe43ElF1JA==}
engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0, npm: '>=7.0.0'} engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0, npm: '>=7.0.0'}
css-tree@2.3.1:
resolution: {integrity: sha512-6Fv1DV/TYw//QF5IzQdqsNDjx/wc8TrMBZsqjL9eW01tWb7R7k/mq+/VXfJCl7SoD5emsJop9cOByJZfs8hYIw==}
engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0}
css-tree@3.2.1: css-tree@3.2.1:
resolution: {integrity: sha512-X7sjQzceUhu1u7Y/ylrRZFU2FS6LRiFVp6rKLPg23y3x3c3DOKAwuXGDp+PAGjh6CSnCjYeAul8pcT8bAl+lSA==} resolution: {integrity: sha512-X7sjQzceUhu1u7Y/ylrRZFU2FS6LRiFVp6rKLPg23y3x3c3DOKAwuXGDp+PAGjh6CSnCjYeAul8pcT8bAl+lSA==}
engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0} engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0}
@ -1212,6 +1303,12 @@ packages:
emoji-regex@8.0.0: emoji-regex@8.0.0:
resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==}
encoding-sniffer@0.2.1:
resolution: {integrity: sha512-5gvq20T6vfpekVtqrYQsSCFZ1wEg5+wW0/QaZMWkFr6BqD3NfKs0rLCx4rrVlSWJeZb5NBJgVLswK/w2MWU+Gw==}
end-of-stream@1.4.5:
resolution: {integrity: sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==}
enhanced-resolve@5.20.1: enhanced-resolve@5.20.1:
resolution: {integrity: sha512-Qohcme7V1inbAfvjItgw0EaxVX5q2rdVEZHRBrEQdRZTssLDGsL8Lwrznl8oQ/6kuTJONLaDcGjkNP247XEhcA==} resolution: {integrity: sha512-Qohcme7V1inbAfvjItgw0EaxVX5q2rdVEZHRBrEQdRZTssLDGsL8Lwrznl8oQ/6kuTJONLaDcGjkNP247XEhcA==}
engines: {node: '>=10.13.0'} engines: {node: '>=10.13.0'}
@ -1277,9 +1374,17 @@ packages:
eventemitter3@5.0.4: eventemitter3@5.0.4:
resolution: {integrity: sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw==} resolution: {integrity: sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw==}
exsolve@1.0.8:
resolution: {integrity: sha512-LmDxfWXwcTArk8fUEnOfSZpHOJ6zOMUJKOtFLFqJLoKJetuQG874Uc7/Kki7zFLzYybmZhp1M7+98pfMqeX8yA==}
extend@3.0.2: extend@3.0.2:
resolution: {integrity: sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==} resolution: {integrity: sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==}
extract-zip@2.0.1:
resolution: {integrity: sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==}
engines: {node: '>= 10.17.0'}
hasBin: true
fast-deep-equal@3.1.3: fast-deep-equal@3.1.3:
resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==}
@ -1293,6 +1398,9 @@ packages:
resolution: {integrity: sha512-BQ30U1mKkvXQXXkAGcuyUA/GA26oEB7NzOtsxCDtyu62sjGw5QraKFhx2Em3WQNjPw9PG6MQ9yuIIgkSDfGu5A==} resolution: {integrity: sha512-BQ30U1mKkvXQXXkAGcuyUA/GA26oEB7NzOtsxCDtyu62sjGw5QraKFhx2Em3WQNjPw9PG6MQ9yuIIgkSDfGu5A==}
hasBin: true hasBin: true
fd-slicer@1.1.0:
resolution: {integrity: sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==}
fdir@6.5.0: fdir@6.5.0:
resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==}
engines: {node: '>=12.0.0'} engines: {node: '>=12.0.0'}
@ -1330,9 +1438,17 @@ packages:
resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==}
engines: {node: 6.* || 8.* || >= 10.*} engines: {node: 6.* || 8.* || >= 10.*}
get-stream@5.2.0:
resolution: {integrity: sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==}
engines: {node: '>=8'}
github-slugger@2.0.0: github-slugger@2.0.0:
resolution: {integrity: sha512-IaOQ9puYtjrkq7Y0Ygl9KDZnrf/aiUJYUpVf89y8kyaxbRG7Y1SrX/jaumrv81vc61+kiMempujsM3Yw7w5qcw==} resolution: {integrity: sha512-IaOQ9puYtjrkq7Y0Ygl9KDZnrf/aiUJYUpVf89y8kyaxbRG7Y1SrX/jaumrv81vc61+kiMempujsM3Yw7w5qcw==}
globals@15.15.0:
resolution: {integrity: sha512-7ACyT3wmyp3I61S4fG682L0VA2RGD9otkqGJIwNUMF1SWUombIIk+af1unuDYgMm082aHYwD+mzJvv9Iu8dsgg==}
engines: {node: '>=18'}
graceful-fs@4.2.11: graceful-fs@4.2.11:
resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==}
@ -1390,9 +1506,19 @@ packages:
html-void-elements@3.0.0: html-void-elements@3.0.0:
resolution: {integrity: sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg==} resolution: {integrity: sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg==}
htmlparser2@10.1.0:
resolution: {integrity: sha512-VTZkM9GWRAtEpveh7MSF6SjjrpNVNNVJfFup7xTY3UpFtm67foy9HDVXneLtFVt4pMz5kZtgNcvCniNFb1hlEQ==}
http-cache-semantics@4.2.0: http-cache-semantics@4.2.0:
resolution: {integrity: sha512-dTxcvPXqPvXBQpq5dUr6mEMJX4oIEFv6bwom3FDwKRDsuIjjJGANqhBuoAn9c1RQJIdAKav33ED65E2ys+87QQ==} resolution: {integrity: sha512-dTxcvPXqPvXBQpq5dUr6mEMJX4oIEFv6bwom3FDwKRDsuIjjJGANqhBuoAn9c1RQJIdAKav33ED65E2ys+87QQ==}
iconify-icon@3.0.2:
resolution: {integrity: sha512-DYPAumiUeUeT/GHT8x2wrAVKn1FqZJqFH0Y5pBefapWRreV1BBvqBVMb0020YQ2njmbR59r/IathL2d2OrDrxA==}
iconv-lite@0.6.3:
resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==}
engines: {node: '>=0.10.0'}
inline-style-parser@0.2.7: inline-style-parser@0.2.7:
resolution: {integrity: sha512-Nb2ctOyNR8DqQoR0OwRG95uNWIC0C1lCgf5Naz5H6Ji72KZ8OcFZLz2P5sNgwlyoJ8Yif11oMuYs5pBQa86csA==} resolution: {integrity: sha512-Nb2ctOyNR8DqQoR0OwRG95uNWIC0C1lCgf5Naz5H6Ji72KZ8OcFZLz2P5sNgwlyoJ8Yif11oMuYs5pBQa86csA==}
@ -1475,6 +1601,9 @@ packages:
resolution: {integrity: sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==} resolution: {integrity: sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==}
engines: {node: '>=6'} engines: {node: '>=6'}
kolorist@1.8.0:
resolution: {integrity: sha512-Y+60/zizpJ3HRH8DCss+q95yr6145JXZo46OTpFvDZWLfRCE4qChOyk1b26nMaNpfHHgxagk9dXT5OP0Tfe+dQ==}
lightningcss-android-arm64@1.31.1: lightningcss-android-arm64@1.31.1:
resolution: {integrity: sha512-HXJF3x8w9nQ4jbXRiNppBCqeZPIAfUo8zE/kOEGbW5NZvGc/K7nMxbhIr+YlFlHW5mpbg/YFPdbnCh1wAXCKFg==} resolution: {integrity: sha512-HXJF3x8w9nQ4jbXRiNppBCqeZPIAfUo8zE/kOEGbW5NZvGc/K7nMxbhIr+YlFlHW5mpbg/YFPdbnCh1wAXCKFg==}
engines: {node: '>= 12.0.0'} engines: {node: '>= 12.0.0'}
@ -1549,6 +1678,10 @@ packages:
resolution: {integrity: sha512-l51N2r93WmGUye3WuFoN5k10zyvrVs0qfKBhyC5ogUQ6Ew6JUSswh78mbSO+IU3nTWsyOArqPCcShdQSadghBQ==} resolution: {integrity: sha512-l51N2r93WmGUye3WuFoN5k10zyvrVs0qfKBhyC5ogUQ6Ew6JUSswh78mbSO+IU3nTWsyOArqPCcShdQSadghBQ==}
engines: {node: '>= 12.0.0'} engines: {node: '>= 12.0.0'}
local-pkg@1.1.2:
resolution: {integrity: sha512-arhlxbFRmoQHl33a0Zkle/YWlmNwoyt6QNZEIJcqNbdrsix5Lvc4HyyI3EnwxTYlZYc32EbYrQ8SzEZ7dqgg9A==}
engines: {node: '>=14'}
longest-streak@3.1.0: longest-streak@3.1.0:
resolution: {integrity: sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==} resolution: {integrity: sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==}
@ -1629,6 +1762,9 @@ packages:
mdn-data@2.0.28: mdn-data@2.0.28:
resolution: {integrity: sha512-aylIc7Z9y4yzHYAJNuESG3hfhC+0Ibp/MAMiaOZgNv4pmEdFyfZhhhny4MNiAfWdBQ1RQ2mfDWmM1x8SvGyp8g==} resolution: {integrity: sha512-aylIc7Z9y4yzHYAJNuESG3hfhC+0Ibp/MAMiaOZgNv4pmEdFyfZhhhny4MNiAfWdBQ1RQ2mfDWmM1x8SvGyp8g==}
mdn-data@2.0.30:
resolution: {integrity: sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA==}
mdn-data@2.27.1: mdn-data@2.27.1:
resolution: {integrity: sha512-9Yubnt3e8A0OKwxYSXyhLymGW4sCufcLG6VdiDdUGVkPhpqLxlvP5vl1983gQjJl3tqbrM731mjaZaP68AgosQ==} resolution: {integrity: sha512-9Yubnt3e8A0OKwxYSXyhLymGW4sCufcLG6VdiDdUGVkPhpqLxlvP5vl1983gQjJl3tqbrM731mjaZaP68AgosQ==}
@ -1744,6 +1880,17 @@ packages:
micromark@4.0.2: micromark@4.0.2:
resolution: {integrity: sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA==} resolution: {integrity: sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA==}
minipass@7.1.3:
resolution: {integrity: sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==}
engines: {node: '>=16 || 14 >=14.17'}
minizlib@3.1.0:
resolution: {integrity: sha512-KZxYo1BUkWD2TVFLr0MQoM8vUUigWD3LlD83a/75BqC+4qE0Hb1Vo5v1FgcfaNXvfXzr+5EhQ6ing/CaBijTlw==}
engines: {node: '>= 18'}
mlly@1.8.1:
resolution: {integrity: sha512-SnL6sNutTwRWWR/vcmCYHSADjiEesp5TGQQ0pXyLhW5IoeibRlF/CbSLailbB3CNqJUk9cVJ9dUDnbD7GrcHBQ==}
mrmime@2.0.1: mrmime@2.0.1:
resolution: {integrity: sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==} resolution: {integrity: sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==}
engines: {node: '>=10'} engines: {node: '>=10'}
@ -1791,6 +1938,9 @@ packages:
ohash@2.0.11: ohash@2.0.11:
resolution: {integrity: sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ==} resolution: {integrity: sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ==}
once@1.4.0:
resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==}
oniguruma-parser@0.12.1: oniguruma-parser@0.12.1:
resolution: {integrity: sha512-8Unqkvk1RYc6yq2WBYRj4hdnsAxVze8i7iPfQr8e4uSP3tRv0rpZcbGUDvxfQQcdwHt/e9PrMvGCsa8OqG9X3w==} resolution: {integrity: sha512-8Unqkvk1RYc6yq2WBYRj4hdnsAxVze8i7iPfQr8e4uSP3tRv0rpZcbGUDvxfQQcdwHt/e9PrMvGCsa8OqG9X3w==}
@ -1818,6 +1968,12 @@ packages:
parse-latin@7.0.0: parse-latin@7.0.0:
resolution: {integrity: sha512-mhHgobPPua5kZ98EF4HWiH167JWBfl4pvAIXXdbaVohtK7a6YBOy56kvhCqduqyo/f3yrHFWmqmiMg/BkBkYYQ==} resolution: {integrity: sha512-mhHgobPPua5kZ98EF4HWiH167JWBfl4pvAIXXdbaVohtK7a6YBOy56kvhCqduqyo/f3yrHFWmqmiMg/BkBkYYQ==}
parse5-htmlparser2-tree-adapter@7.1.0:
resolution: {integrity: sha512-ruw5xyKs6lrpo9x9rCZqZZnIUntICjQAd0Wsmp396Ul9lN/h+ifgVV1x1gZHi8euej6wTfpqX8j+BFQxF0NS/g==}
parse5-parser-stream@7.1.2:
resolution: {integrity: sha512-JyeQc9iwFLn5TbvvqACIF/VXG6abODeB3Fwmv/TGdLk2LfbWkaySGY72at4+Ty7EkPZj854u4CrICqNk2qIbow==}
parse5@7.3.0: parse5@7.3.0:
resolution: {integrity: sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==} resolution: {integrity: sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==}
@ -1828,6 +1984,12 @@ packages:
resolution: {integrity: sha512-qdVgY8KXmVdJZRSS1JdEPOKPdTiEK/pi0RkcT2sw1RhXxohdujUlJFPuS1TSkevZ9vzd3ZlL7ULl1MHGTApKzQ==} resolution: {integrity: sha512-qdVgY8KXmVdJZRSS1JdEPOKPdTiEK/pi0RkcT2sw1RhXxohdujUlJFPuS1TSkevZ9vzd3ZlL7ULl1MHGTApKzQ==}
engines: {node: '>=14.0.0'} engines: {node: '>=14.0.0'}
pathe@2.0.3:
resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==}
pend@1.2.0:
resolution: {integrity: sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==}
piccolore@0.1.3: piccolore@0.1.3:
resolution: {integrity: sha512-o8bTeDWjE086iwKrROaDf31K0qC/BENdm15/uH9usSC/uZjJOKb2YGiVHfLY4GhwsERiPI1jmwI2XrA7ACOxVw==} resolution: {integrity: sha512-o8bTeDWjE086iwKrROaDf31K0qC/BENdm15/uH9usSC/uZjJOKb2YGiVHfLY4GhwsERiPI1jmwI2XrA7ACOxVw==}
@ -1842,6 +2004,12 @@ packages:
resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==} resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==}
engines: {node: '>=12'} engines: {node: '>=12'}
pkg-types@1.3.1:
resolution: {integrity: sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==}
pkg-types@2.3.0:
resolution: {integrity: sha512-SIqCzDRg0s9npO5XQ3tNZioRY1uK06lA41ynBC1YmFTmnY6FjUjVt6s4LoADmwoig1qqD0oK8h1p/8mlMx8Oig==}
postcss-selector-parser@6.0.10: postcss-selector-parser@6.0.10:
resolution: {integrity: sha512-IQ7TZdoaqbT+LCpShg46jnZVlhWD2w6iQYAcYXfHARZ7X1t/UGhhceQDs5X0cGqKvYlHNOuv7Oa1xmb0oQuA3w==} resolution: {integrity: sha512-IQ7TZdoaqbT+LCpShg46jnZVlhWD2w6iQYAcYXfHARZ7X1t/UGhhceQDs5X0cGqKvYlHNOuv7Oa1xmb0oQuA3w==}
engines: {node: '>=4'} engines: {node: '>=4'}
@ -1862,6 +2030,12 @@ packages:
property-information@7.1.0: property-information@7.1.0:
resolution: {integrity: sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ==} resolution: {integrity: sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ==}
pump@3.0.4:
resolution: {integrity: sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA==}
quansync@0.2.11:
resolution: {integrity: sha512-AifT7QEbW9Nri4tAwR5M/uzpBuqfZf+zwaEM/QkzEjj7NBuFD2rBuy0K3dE+8wltbezDV7JMA0WfnCPYRSYbXA==}
radix3@1.1.2: radix3@1.1.2:
resolution: {integrity: sha512-b484I/7b8rDEdSDKckSSBA8knMpcdsXudlE/LNL639wFoHKwLbEkQFZHWEYwDC0wa0FKUcCY+GAF73Z7wxNVFA==} resolution: {integrity: sha512-b484I/7b8rDEdSDKckSSBA8knMpcdsXudlE/LNL639wFoHKwLbEkQFZHWEYwDC0wa0FKUcCY+GAF73Z7wxNVFA==}
@ -1967,6 +2141,9 @@ packages:
engines: {node: '>=18.0.0', npm: '>=8.0.0'} engines: {node: '>=18.0.0', npm: '>=8.0.0'}
hasBin: true hasBin: true
safer-buffer@2.1.2:
resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==}
sax@1.6.0: sax@1.6.0:
resolution: {integrity: sha512-6R3J5M4AcbtLUdZmRv2SygeVaM7IhrLXu9BmnOGmmACak8fiUtOsYNWUS4uK7upbmHIBbLBeFeI//477BKLBzA==} resolution: {integrity: sha512-6R3J5M4AcbtLUdZmRv2SygeVaM7IhrLXu9BmnOGmmACak8fiUtOsYNWUS4uK7upbmHIBbLBeFeI//477BKLBzA==}
engines: {node: '>=11.0.0'} engines: {node: '>=11.0.0'}
@ -2052,6 +2229,11 @@ packages:
style-to-object@1.0.14: style-to-object@1.0.14:
resolution: {integrity: sha512-LIN7rULI0jBscWQYaSswptyderlarFkjQ+t79nzty8tcIAceVomEVlLzH5VP4Cmsv6MtKhs7qaAiwlcp+Mgaxw==} resolution: {integrity: sha512-LIN7rULI0jBscWQYaSswptyderlarFkjQ+t79nzty8tcIAceVomEVlLzH5VP4Cmsv6MtKhs7qaAiwlcp+Mgaxw==}
svgo@3.3.3:
resolution: {integrity: sha512-+wn7I4p7YgJhHs38k2TNjy1vCfPIfLIJWR5MnCStsN8WuuTcBnRKcMHQLMM2ijxGZmDoZwNv8ipl5aTTen62ng==}
engines: {node: '>=14.0.0'}
hasBin: true
svgo@4.0.1: svgo@4.0.1:
resolution: {integrity: sha512-XDpWUOPC6FEibaLzjfe0ucaV0YrOjYotGJO1WpF0Zd+n6ZGEQUsSugaoLq9QkEZtAfQIxT42UChcssDVPP3+/w==} resolution: {integrity: sha512-XDpWUOPC6FEibaLzjfe0ucaV0YrOjYotGJO1WpF0Zd+n6ZGEQUsSugaoLq9QkEZtAfQIxT42UChcssDVPP3+/w==}
engines: {node: '>=16'} engines: {node: '>=16'}
@ -2067,6 +2249,10 @@ packages:
resolution: {integrity: sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==} resolution: {integrity: sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==}
engines: {node: '>=6'} engines: {node: '>=6'}
tar@7.5.11:
resolution: {integrity: sha512-ChjMH33/KetonMTAtpYdgUFr0tbz69Fp2v7zWxQfYZX4g5ZN2nOBXm1R2xyA+lMIKrLKIoKAwFj93jE/avX9cQ==}
engines: {node: '>=18'}
tiny-inflate@1.0.3: tiny-inflate@1.0.3:
resolution: {integrity: sha512-pkY1fj1cKHb2seWDy0B16HeWyczlJA9/WW3u3c4z/NiWDsO3DOU5D7nhTLE9CF0yXv/QZFY7sEJmj24dK+Rrqw==} resolution: {integrity: sha512-pkY1fj1cKHb2seWDy0B16HeWyczlJA9/WW3u3c4z/NiWDsO3DOU5D7nhTLE9CF0yXv/QZFY7sEJmj24dK+Rrqw==}
@ -2124,6 +2310,10 @@ packages:
undici-types@7.16.0: undici-types@7.16.0:
resolution: {integrity: sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==} resolution: {integrity: sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==}
undici@7.24.4:
resolution: {integrity: sha512-BM/JzwwaRXxrLdElV2Uo6cTLEjhSb3WXboncJamZ15NgUURmvlXvxa6xkwIOILIjPNo9i8ku136ZvWV0Uly8+w==}
engines: {node: '>=20.18.1'}
unified@11.0.5: unified@11.0.5:
resolution: {integrity: sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==} resolution: {integrity: sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==}
@ -2393,6 +2583,15 @@ packages:
web-namespaces@2.0.1: web-namespaces@2.0.1:
resolution: {integrity: sha512-bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ13TsorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPG2be8uPrqsQ==} resolution: {integrity: sha512-bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ13TsorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPG2be8uPrqsQ==}
whatwg-encoding@3.1.1:
resolution: {integrity: sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==}
engines: {node: '>=18'}
deprecated: Use @exodus/bytes instead for a more spec-conformant and faster implementation
whatwg-mimetype@4.0.0:
resolution: {integrity: sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==}
engines: {node: '>=18'}
which-pm-runs@1.1.0: which-pm-runs@1.1.0:
resolution: {integrity: sha512-n1brCuqClxfFfq/Rb0ICg9giSZqCS+pLtccdag6C2HyufBrh3fBOiy9nb6ggRMvWOVH5GrdJskj5iGTZNxd7SA==} resolution: {integrity: sha512-n1brCuqClxfFfq/Rb0ICg9giSZqCS+pLtccdag6C2HyufBrh3fBOiy9nb6ggRMvWOVH5GrdJskj5iGTZNxd7SA==}
engines: {node: '>=4'} engines: {node: '>=4'}
@ -2401,6 +2600,9 @@ packages:
resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==}
engines: {node: '>=10'} engines: {node: '>=10'}
wrappy@1.0.2:
resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==}
xxhash-wasm@1.1.0: xxhash-wasm@1.1.0:
resolution: {integrity: sha512-147y/6YNh+tlp6nd/2pWq38i9h6mz/EuQ6njIrmW8D1BS5nCqs0P6DG+m6zTGnNz5I+uhZ0SHxBs9BsPrwcKDA==} resolution: {integrity: sha512-147y/6YNh+tlp6nd/2pWq38i9h6mz/EuQ6njIrmW8D1BS5nCqs0P6DG+m6zTGnNz5I+uhZ0SHxBs9BsPrwcKDA==}
@ -2411,6 +2613,10 @@ packages:
yallist@3.1.1: yallist@3.1.1:
resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==}
yallist@5.0.0:
resolution: {integrity: sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==}
engines: {node: '>=18'}
yaml-language-server@1.20.0: yaml-language-server@1.20.0:
resolution: {integrity: sha512-qhjK/bzSRZ6HtTvgeFvjNPJGWdZ0+x5NREV/9XZWFjIGezew2b4r5JPy66IfOhd5OA7KeFwk1JfmEbnTvev0cA==} resolution: {integrity: sha512-qhjK/bzSRZ6HtTvgeFvjNPJGWdZ0+x5NREV/9XZWFjIGezew2b4r5JPy66IfOhd5OA7KeFwk1JfmEbnTvev0cA==}
hasBin: true hasBin: true
@ -2437,6 +2643,9 @@ packages:
resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==} resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==}
engines: {node: '>=12'} engines: {node: '>=12'}
yauzl@2.10.0:
resolution: {integrity: sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==}
yocto-queue@1.2.2: yocto-queue@1.2.2:
resolution: {integrity: sha512-4LCcse/U2MHZ63HAJVE+v71o7yOdIe4cZ70Wpf8D/IyjDKYQLV5GD46B+hSTjJsvV5PztjvHoU580EftxjDZFQ==} resolution: {integrity: sha512-4LCcse/U2MHZ63HAJVE+v71o7yOdIe4cZ70Wpf8D/IyjDKYQLV5GD46B+hSTjJsvV5PztjvHoU580EftxjDZFQ==}
engines: {node: '>=12.20'} engines: {node: '>=12.20'}
@ -2449,6 +2658,13 @@ packages:
snapshots: snapshots:
'@antfu/install-pkg@1.1.0':
dependencies:
package-manager-detector: 1.6.0
tinyexec: 1.0.4
'@antfu/utils@8.1.1': {}
'@astrojs/check@0.9.8(prettier@3.8.1)(typescript@5.9.3)': '@astrojs/check@0.9.8(prettier@3.8.1)(typescript@5.9.3)':
dependencies: dependencies:
'@astrojs/language-server': 2.16.5(prettier@3.8.1)(typescript@5.9.3) '@astrojs/language-server': 2.16.5(prettier@3.8.1)(typescript@5.9.3)
@ -2819,6 +3035,56 @@ snapshots:
'@esbuild/win32-x64@0.27.4': '@esbuild/win32-x64@0.27.4':
optional: true optional: true
'@iconify-icon/solid@3.0.3(solid-js@1.9.11)':
dependencies:
iconify-icon: 3.0.2
solid-js: 1.9.11
'@iconify-json/devicon@1.2.61':
dependencies:
'@iconify/types': 2.0.0
'@iconify-json/material-icon-theme@1.2.56':
dependencies:
'@iconify/types': 2.0.0
'@iconify-json/pixel@1.2.1':
dependencies:
'@iconify/types': 2.0.0
'@iconify-json/pixelarticons@1.2.4':
dependencies:
'@iconify/types': 2.0.0
'@iconify/tools@4.2.0':
dependencies:
'@iconify/types': 2.0.0
'@iconify/utils': 2.3.0
cheerio: 1.2.0
domhandler: 5.0.3
extract-zip: 2.0.1
local-pkg: 1.1.2
pathe: 2.0.3
svgo: 3.3.3
tar: 7.5.11
transitivePeerDependencies:
- supports-color
'@iconify/types@2.0.0': {}
'@iconify/utils@2.3.0':
dependencies:
'@antfu/install-pkg': 1.1.0
'@antfu/utils': 8.1.1
'@iconify/types': 2.0.0
debug: 4.4.3
globals: 15.15.0
kolorist: 1.8.0
local-pkg: 1.1.2
mlly: 1.8.1
transitivePeerDependencies:
- supports-color
'@img/colour@1.1.0': {} '@img/colour@1.1.0': {}
'@img/sharp-darwin-arm64@0.34.5': '@img/sharp-darwin-arm64@0.34.5':
@ -2915,6 +3181,10 @@ snapshots:
'@img/sharp-win32-x64@0.34.5': '@img/sharp-win32-x64@0.34.5':
optional: true optional: true
'@isaacs/fs-minipass@4.0.1':
dependencies:
minipass: 7.1.3
'@jridgewell/gen-mapping@0.3.13': '@jridgewell/gen-mapping@0.3.13':
dependencies: dependencies:
'@jridgewell/sourcemap-codec': 1.5.5 '@jridgewell/sourcemap-codec': 1.5.5
@ -3223,6 +3493,11 @@ snapshots:
'@types/unist@3.0.3': {} '@types/unist@3.0.3': {}
'@types/yauzl@2.10.3':
dependencies:
'@types/node': 24.12.0
optional: true
'@ungap/structured-clone@1.3.0': {} '@ungap/structured-clone@1.3.0': {}
'@volar/kit@2.4.28(typescript@5.9.3)': '@volar/kit@2.4.28(typescript@5.9.3)':
@ -3315,6 +3590,14 @@ snapshots:
astring@1.9.0: {} astring@1.9.0: {}
astro-icon@1.1.5:
dependencies:
'@iconify/tools': 4.2.0
'@iconify/types': 2.0.0
'@iconify/utils': 2.3.0
transitivePeerDependencies:
- supports-color
astro-og-canvas@0.10.1(astro@6.0.5(@types/node@24.12.0)(jiti@2.6.1)(lightningcss@1.31.1)(rollup@4.59.0)(typescript@5.9.3)(yaml@2.8.2)): astro-og-canvas@0.10.1(astro@6.0.5(@types/node@24.12.0)(jiti@2.6.1)(lightningcss@1.31.1)(rollup@4.59.0)(typescript@5.9.3)(yaml@2.8.2)):
dependencies: dependencies:
astro: 6.0.5(@types/node@24.12.0)(jiti@2.6.1)(lightningcss@1.31.1)(rollup@4.59.0)(typescript@5.9.3)(yaml@2.8.2) astro: 6.0.5(@types/node@24.12.0)(jiti@2.6.1)(lightningcss@1.31.1)(rollup@4.59.0)(typescript@5.9.3)(yaml@2.8.2)
@ -3450,6 +3733,8 @@ snapshots:
node-releases: 2.0.36 node-releases: 2.0.36
update-browserslist-db: 1.2.3(browserslist@4.28.1) update-browserslist-db: 1.2.3(browserslist@4.28.1)
buffer-crc32@0.2.13: {}
caniuse-lite@1.0.30001780: {} caniuse-lite@1.0.30001780: {}
canvaskit-wasm@0.40.0: canvaskit-wasm@0.40.0:
@ -3466,6 +3751,29 @@ snapshots:
character-reference-invalid@2.0.1: {} character-reference-invalid@2.0.1: {}
cheerio-select@2.1.0:
dependencies:
boolbase: 1.0.0
css-select: 5.2.2
css-what: 6.2.2
domelementtype: 2.3.0
domhandler: 5.0.3
domutils: 3.2.2
cheerio@1.2.0:
dependencies:
cheerio-select: 2.1.0
dom-serializer: 2.0.0
domhandler: 5.0.3
domutils: 3.2.2
encoding-sniffer: 0.2.1
htmlparser2: 10.1.0
parse5: 7.3.0
parse5-htmlparser2-tree-adapter: 7.1.0
parse5-parser-stream: 7.1.2
undici: 7.24.4
whatwg-mimetype: 4.0.0
chokidar@4.0.3: chokidar@4.0.3:
dependencies: dependencies:
readdirp: 4.1.2 readdirp: 4.1.2
@ -3474,6 +3782,8 @@ snapshots:
dependencies: dependencies:
readdirp: 5.0.0 readdirp: 5.0.0
chownr@3.0.0: {}
ci-info@4.4.0: {} ci-info@4.4.0: {}
cliui@8.0.1: cliui@8.0.1:
@ -3496,10 +3806,16 @@ snapshots:
commander@11.1.0: {} commander@11.1.0: {}
commander@7.2.0: {}
commander@8.3.0: {} commander@8.3.0: {}
common-ancestor-path@2.0.0: {} common-ancestor-path@2.0.0: {}
confbox@0.1.8: {}
confbox@0.2.4: {}
convert-source-map@2.0.0: {} convert-source-map@2.0.0: {}
cookie-es@1.2.2: {} cookie-es@1.2.2: {}
@ -3523,6 +3839,11 @@ snapshots:
mdn-data: 2.0.28 mdn-data: 2.0.28
source-map-js: 1.2.1 source-map-js: 1.2.1
css-tree@2.3.1:
dependencies:
mdn-data: 2.0.30
source-map-js: 1.2.1
css-tree@3.2.1: css-tree@3.2.1:
dependencies: dependencies:
mdn-data: 2.27.1 mdn-data: 2.27.1
@ -3597,6 +3918,15 @@ snapshots:
emoji-regex@8.0.0: {} emoji-regex@8.0.0: {}
encoding-sniffer@0.2.1:
dependencies:
iconv-lite: 0.6.3
whatwg-encoding: 3.1.1
end-of-stream@1.4.5:
dependencies:
once: 1.4.0
enhanced-resolve@5.20.1: enhanced-resolve@5.20.1:
dependencies: dependencies:
graceful-fs: 4.2.11 graceful-fs: 4.2.11
@ -3694,8 +4024,20 @@ snapshots:
eventemitter3@5.0.4: {} eventemitter3@5.0.4: {}
exsolve@1.0.8: {}
extend@3.0.2: {} extend@3.0.2: {}
extract-zip@2.0.1:
dependencies:
debug: 4.4.3
get-stream: 5.2.0
yauzl: 2.10.0
optionalDependencies:
'@types/yauzl': 2.10.3
transitivePeerDependencies:
- supports-color
fast-deep-equal@3.1.3: {} fast-deep-equal@3.1.3: {}
fast-uri@3.1.0: {} fast-uri@3.1.0: {}
@ -3709,6 +4051,10 @@ snapshots:
fast-xml-builder: 1.1.4 fast-xml-builder: 1.1.4
strnum: 2.2.0 strnum: 2.2.0
fd-slicer@1.1.0:
dependencies:
pend: 1.2.0
fdir@6.5.0(picomatch@4.0.3): fdir@6.5.0(picomatch@4.0.3):
optionalDependencies: optionalDependencies:
picomatch: 4.0.3 picomatch: 4.0.3
@ -3732,8 +4078,14 @@ snapshots:
get-caller-file@2.0.5: {} get-caller-file@2.0.5: {}
get-stream@5.2.0:
dependencies:
pump: 3.0.4
github-slugger@2.0.0: {} github-slugger@2.0.0: {}
globals@15.15.0: {}
graceful-fs@4.2.11: {} graceful-fs@4.2.11: {}
h3@1.15.8: h3@1.15.8:
@ -3895,8 +4247,23 @@ snapshots:
html-void-elements@3.0.0: {} html-void-elements@3.0.0: {}
htmlparser2@10.1.0:
dependencies:
domelementtype: 2.3.0
domhandler: 5.0.3
domutils: 3.2.2
entities: 7.0.1
http-cache-semantics@4.2.0: {} http-cache-semantics@4.2.0: {}
iconify-icon@3.0.2:
dependencies:
'@iconify/types': 2.0.0
iconv-lite@0.6.3:
dependencies:
safer-buffer: 2.1.2
inline-style-parser@0.2.7: {} inline-style-parser@0.2.7: {}
iron-webcrypto@1.2.1: {} iron-webcrypto@1.2.1: {}
@ -3952,6 +4319,8 @@ snapshots:
kleur@4.1.5: {} kleur@4.1.5: {}
kolorist@1.8.0: {}
lightningcss-android-arm64@1.31.1: lightningcss-android-arm64@1.31.1:
optional: true optional: true
@ -4001,6 +4370,12 @@ snapshots:
lightningcss-win32-arm64-msvc: 1.31.1 lightningcss-win32-arm64-msvc: 1.31.1
lightningcss-win32-x64-msvc: 1.31.1 lightningcss-win32-x64-msvc: 1.31.1
local-pkg@1.1.2:
dependencies:
mlly: 1.8.1
pkg-types: 2.3.0
quansync: 0.2.11
longest-streak@3.1.0: {} longest-streak@3.1.0: {}
lru-cache@11.2.7: {} lru-cache@11.2.7: {}
@ -4206,6 +4581,8 @@ snapshots:
mdn-data@2.0.28: {} mdn-data@2.0.28: {}
mdn-data@2.0.30: {}
mdn-data@2.27.1: {} mdn-data@2.27.1: {}
merge-anything@5.1.7: merge-anything@5.1.7:
@ -4486,6 +4863,19 @@ snapshots:
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
minipass@7.1.3: {}
minizlib@3.1.0:
dependencies:
minipass: 7.1.3
mlly@1.8.1:
dependencies:
acorn: 8.16.0
pathe: 2.0.3
pkg-types: 1.3.1
ufo: 1.6.3
mrmime@2.0.1: {} mrmime@2.0.1: {}
ms@2.1.3: {} ms@2.1.3: {}
@ -4522,6 +4912,10 @@ snapshots:
ohash@2.0.11: {} ohash@2.0.11: {}
once@1.4.0:
dependencies:
wrappy: 1.0.2
oniguruma-parser@0.12.1: {} oniguruma-parser@0.12.1: {}
oniguruma-to-es@4.3.5: oniguruma-to-es@4.3.5:
@ -4562,6 +4956,15 @@ snapshots:
unist-util-visit-children: 3.0.0 unist-util-visit-children: 3.0.0
vfile: 6.0.3 vfile: 6.0.3
parse5-htmlparser2-tree-adapter@7.1.0:
dependencies:
domhandler: 5.0.3
parse5: 7.3.0
parse5-parser-stream@7.1.2:
dependencies:
parse5: 7.3.0
parse5@7.3.0: parse5@7.3.0:
dependencies: dependencies:
entities: 6.0.1 entities: 6.0.1
@ -4570,6 +4973,10 @@ snapshots:
path-expression-matcher@1.1.3: {} path-expression-matcher@1.1.3: {}
pathe@2.0.3: {}
pend@1.2.0: {}
piccolore@0.1.3: {} piccolore@0.1.3: {}
picocolors@1.1.1: {} picocolors@1.1.1: {}
@ -4578,6 +4985,18 @@ snapshots:
picomatch@4.0.3: {} picomatch@4.0.3: {}
pkg-types@1.3.1:
dependencies:
confbox: 0.1.8
mlly: 1.8.1
pathe: 2.0.3
pkg-types@2.3.0:
dependencies:
confbox: 0.2.4
exsolve: 1.0.8
pathe: 2.0.3
postcss-selector-parser@6.0.10: postcss-selector-parser@6.0.10:
dependencies: dependencies:
cssesc: 3.0.0 cssesc: 3.0.0
@ -4595,6 +5014,13 @@ snapshots:
property-information@7.1.0: {} property-information@7.1.0: {}
pump@3.0.4:
dependencies:
end-of-stream: 1.4.5
once: 1.4.0
quansync@0.2.11: {}
radix3@1.1.2: {} radix3@1.1.2: {}
readdirp@4.1.2: {} readdirp@4.1.2: {}
@ -4804,6 +5230,8 @@ snapshots:
'@rollup/rollup-win32-x64-msvc': 4.59.0 '@rollup/rollup-win32-x64-msvc': 4.59.0
fsevents: 2.3.3 fsevents: 2.3.3
safer-buffer@2.1.2: {}
sax@1.6.0: {} sax@1.6.0: {}
semver@6.3.1: {} semver@6.3.1: {}
@ -4917,6 +5345,16 @@ snapshots:
dependencies: dependencies:
inline-style-parser: 0.2.7 inline-style-parser: 0.2.7
svgo@3.3.3:
dependencies:
commander: 7.2.0
css-select: 5.2.2
css-tree: 2.3.1
css-what: 6.2.2
csso: 5.0.5
picocolors: 1.1.1
sax: 1.6.0
svgo@4.0.1: svgo@4.0.1:
dependencies: dependencies:
commander: 11.1.0 commander: 11.1.0
@ -4933,6 +5371,14 @@ snapshots:
tapable@2.3.0: {} tapable@2.3.0: {}
tar@7.5.11:
dependencies:
'@isaacs/fs-minipass': 4.0.1
chownr: 3.0.0
minipass: 7.1.3
minizlib: 3.1.0
yallist: 5.0.0
tiny-inflate@1.0.3: {} tiny-inflate@1.0.3: {}
tinyclip@0.1.12: {} tinyclip@0.1.12: {}
@ -4971,6 +5417,8 @@ snapshots:
undici-types@7.16.0: {} undici-types@7.16.0: {}
undici@7.24.4: {}
unified@11.0.5: unified@11.0.5:
dependencies: dependencies:
'@types/unist': 3.0.3 '@types/unist': 3.0.3
@ -5198,6 +5646,12 @@ snapshots:
web-namespaces@2.0.1: {} web-namespaces@2.0.1: {}
whatwg-encoding@3.1.1:
dependencies:
iconv-lite: 0.6.3
whatwg-mimetype@4.0.0: {}
which-pm-runs@1.1.0: {} which-pm-runs@1.1.0: {}
wrap-ansi@7.0.0: wrap-ansi@7.0.0:
@ -5206,12 +5660,16 @@ snapshots:
string-width: 4.2.3 string-width: 4.2.3
strip-ansi: 6.0.1 strip-ansi: 6.0.1
wrappy@1.0.2: {}
xxhash-wasm@1.1.0: {} xxhash-wasm@1.1.0: {}
y18n@5.0.8: {} y18n@5.0.8: {}
yallist@3.1.1: {} yallist@3.1.1: {}
yallist@5.0.0: {}
yaml-language-server@1.20.0: yaml-language-server@1.20.0:
dependencies: dependencies:
'@vscode/l10n': 0.0.18 '@vscode/l10n': 0.0.18
@ -5244,6 +5702,11 @@ snapshots:
y18n: 5.0.8 y18n: 5.0.8
yargs-parser: 21.1.1 yargs-parser: 21.1.1
yauzl@2.10.0:
dependencies:
buffer-crc32: 0.2.13
fd-slicer: 1.1.0
yocto-queue@1.2.2: {} yocto-queue@1.2.2: {}
zod@4.3.6: {} zod@4.3.6: {}

12
public/js/animate.js Normal file
View File

@ -0,0 +1,12 @@
function animate() {
const animateElements = document.querySelectorAll('.animate')
animateElements.forEach((element, index) => {
setTimeout(() => {
element.classList.add('show')
}, index * 150)
});
}
document.addEventListener("DOMContentLoaded", animate)
document.addEventListener("astro:after-swap", animate)

108
public/rss-style.xsl Normal file
View File

@ -0,0 +1,108 @@
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="html" encoding="UTF-8" />
<xsl:template match="/">
<html>
<head>
<style>
@font-face {
font-family: "DepartureMono";
src: url("/fonts/DepartureMono-Regular.woff") format("woff");
font-weight: 400;
font-style: normal;
font-display: swap;
}
@font-face {
font-family: "FiraCode";
src: url("/fonts/FiraCode-Regular.woff") format("woff");
font-weight: 400;
font-style: normal;
font-display: swap;
}
@font-face {
font-family: "FiraCode";
src: url("/fonts/FiraCode-Bold.woff") format("woff");
font-weight: 700;
font-style: normal;
font-display: swap;
}
body {
font-family: "FiraCode", monospace;
background: #1a1a1a;
color: #ccc;
image-rendering: pixelated;
max-width: 768px;
margin-inline: auto;
}
* {
box-sizing: border-box;
}
h1, h2 {
font-family: "DepartureMono", monospace;
}
#backContainer {
margin-block: 1rem;
}
#backButton {
color: #ccc;
text-decoration: none;
padding: 0.5rem 1rem;
background: #555;
border-radius: calc(infinity * 1px);;
}
.item {
border: 2px solid #ccc;
padding: 1rem;
margin-bottom: 1rem;
}
.item > h2 > a {
color: #ccc;
text-decoration: none;
}
.item > .pills {
display: flex;
flex-wrap: wrap;
flex-direction: row;
gap: 1rem;
}
.item > .pills > * {
color: #ccc;
text-decoration: none;
text-transform: capitalize;
background: #555;
padding: 0.5rem 1rem;
border-radius: calc(infinity * 1px);;
}
h1, h2 {
margin: 0 0 1rem 0;
}
</style>
</head>
<body>
<h1><xsl:value-of select="rss/channel/title" /></h1>
<div id="backContainer">
<a id="backButton" href="/">Back to the main page</a>
</div>
<xsl:for-each select="rss/channel/item">
<div class="item">
<h2><a href="{link}"><xsl:value-of select="title" /></a></h2>
<div class="pills">
<a class="type" href="/{entryType}/"><xsl:value-of select="entryType" /></a>
<span class="date"><xsl:value-of select="pubDate" /></span>
</div>
<p><xsl:value-of select="description" /></p>
</div>
</xsl:for-each>
</body>
</html>
</xsl:template>
</xsl:stylesheet>

View File

@ -0,0 +1,59 @@
import type { CollectionEntry } from "astro:content";
import { formatDate } from "@/lib/utils";
import { Icon } from '@iconify-icon/solid';
type Props = {
entry: CollectionEntry<"blog"> | CollectionEntry<"projects">;
pill?: string;
};
export default function ArrowCard({ entry, pill }: Props) {
return (
<a
href={`/${entry.collection}/${entry.id}/`}
class="group p-4 gap-3 flex items-center border rounded-lg hover:bg-black/5 hover:dark:bg-white/10 border-black/15 dark:border-white/20 transition-colors duration-300 ease-in-out"
>
<div class="w-full group-hover:text-black group-hover:dark:text-white blend">
<div class="flex flex-wrap items-center gap-2">
{pill != undefined && (
<div class="text-sm capitalize px-2 py-0.5 rounded-full border border-black/15 dark:border-white/25">
{pill}
</div>
)}
<div class="text-sm font-departure uppercase">
{formatDate(entry.data.date)}
</div>
</div>
<div class="font-semibold mt-3 text-black dark:text-white">
{entry.data.title}
</div>
<div class="text-sm line-clamp-2">{entry.data.summary}</div>
<ul class="flex flex-wrap mt-2 gap-1">
{entry.data.tags.map(
(tag) => (
<li class="text-xs font-departure uppercase py-0.5 px-1 rounded bg-black/5 dark:bg-white/20 text-black/75 dark:text-white/75">
{tag}
</li>
)
)}
</ul>
</div>
<div class="relative overflow-hidden w-4 h-4">
<Icon
icon="pixelarticons:arrow-right"
class="absolute right-0 inset-y-0 text-lg origin-left
scale-x-0 group-hover:scale-x-100
transition-all duration-300 ease-in-out"
/>
<Icon
icon="pixelarticons:chevron-right"
class="absolute right-0 inset-y-0 text-lg
group-hover:translate-x-4
transition-all duration-300 ease-in-out"
/>
</div>
</a>
);
}

View File

@ -0,0 +1,117 @@
---
import { render, type CollectionEntry } from "astro:content";
import Container from "./Container.astro";
type Props = {
entry: CollectionEntry<"projects"> | CollectionEntry<"blog">;
};
const { entry } = Astro.props;
const { Content, headings } = await render(entry);
---
<div class="article-layout">
<!-- Sidebar -->
<aside
class="animate toc font-departure max-w-3xl mx-auto px-5 xl:fixed xl:top-24 xl:right-[max(2rem,calc((100vw-84rem)/2))] xl:h-max xl:w-66"
>
<h3 class="font-semibold text-[0.9rem] opacity-70 mb-2">
On this page
</h3>
<ul class="list-none p-0 m-0">
{
headings.map((h) => (
<li class={`level-${h.depth}`}>
<a href={`#${h.slug}`}>{h.text}</a>
</li>
))
}
</ul>
</aside>
<!-- Main content -->
<Container size="md">
<article class="animate content prose dark:prose-invert">
<Content />
</article>
</Container>
</div>
<style>
.toc li {
margin-bottom: 0.4rem;
}
.toc li.level-2 {
margin-left: 0.5rem;
}
.toc li.level-3 {
margin-left: 1rem;
}
.toc a {
opacity: 0.7;
text-decoration: none;
transition: opacity 0.15s ease;
}
.toc a.active {
opacity: 1;
font-weight: 600;
}
.toc a:hover {
opacity: 1;
}
article.content {
grid-area: content;
}
</style>
<style is:global>
article.content :is(h1, h2, h3, h4, h5, h6) {
font-family: "DepartureMono", monospace;
scroll-margin-top: 6rem;
}
article.content a {
font-family: "DepartureMono", monospace;
text-decoration: none;
}
article.content a:hover {
text-decoration: underline;
}
</style>
<script>
const headings = document.querySelectorAll(
"article.content h1[id], article.content h2[id], article.content h3[id]",
);
const tocLinks = document.querySelectorAll(".toc a");
const observer = new IntersectionObserver(
(entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
const id = entry.target.getAttribute("id");
tocLinks.forEach((link) => {
link.classList.toggle(
"active",
link.getAttribute("href") === `#${id}`,
);
});
}
});
},
{
rootMargin: "0px 0px -80% 0px",
threshold: 0.1,
},
);
headings.forEach((h) => observer.observe(h));
</script>

View File

@ -82,6 +82,7 @@ const { title, description, image = "/open-graph.jpg" } = Astro.props;
<!-- Global Scripts --> <!-- Global Scripts -->
<script is:inline src="/js/theme.js"></script> <script is:inline src="/js/theme.js"></script>
<script is:inline src="/js/scroll.js"></script> <script is:inline src="/js/scroll.js"></script>
<script is:inline src="/js/animate.js"></script>
<ClientRouter /> <ClientRouter />
@ -96,6 +97,10 @@ const { title, description, image = "/open-graph.jpg" } = Astro.props;
); );
</script> </script>
<script>
import "iconify-icon";
</script>
<link <link
rel="stylesheet" rel="stylesheet"
href="https://cdn.jsdelivr.net/npm/katex@0.16.27/dist/katex.css" href="https://cdn.jsdelivr.net/npm/katex@0.16.27/dist/katex.css"

108
src/components/Blog.tsx Normal file
View File

@ -0,0 +1,108 @@
import type { CollectionEntry } from "astro:content";
import { createEffect, createSignal, For, Show } from "solid-js";
import ArrowCard from "@/components/ArrowCard";
import { cn } from "@/lib/utils";
import { Icon } from "@iconify-icon/solid";
type Props = {
tags: string[];
data: CollectionEntry<"blog">[];
};
export default function Blog({ data, tags }: Props) {
const [filter, setFilter] = createSignal(new Set<string>());
const [posts, setPosts] = createSignal<CollectionEntry<"blog">[]>([]);
createEffect(() => {
setPosts(
data.filter((entry) =>
Array.from(filter()).every((value) =>
entry.data.tags.some(
(tag: string) =>
tag.toLowerCase() === String(value).toLowerCase()
)
)
)
);
});
function toggleTag(tag: string) {
setFilter(
(prev) =>
new Set(
prev.has(tag)
? [...prev].filter((t) => t !== tag)
: [...prev, tag]
)
);
}
return (
<div class="grid grid-cols-1 sm:grid-cols-3 gap-6">
<div class="col-span-3 sm:col-span-1">
<div class="sticky top-24">
<div class="text-sm font-semibold font-departure uppercase mb-2 text-black dark:text-white">
Filter
</div>
<ul class="flex flex-wrap sm:flex-col gap-1.5">
<For each={tags}>
{(tag) => (
<li>
<button
onClick={() => toggleTag(tag)}
class={cn(
"w-full px-2 py-1 rounded font-departure",
"whitespace-nowrap overflow-hidden overflow-ellipsis",
"flex gap-2 items-center",
"bg-black/5 dark:bg-white/10",
"hover:bg-black/10 hover:dark:bg-white/15",
"transition-colors duration-300 ease-in-out",
filter().has(tag) &&
"text-black dark:text-white"
)}
>
<div class={cn(
"relative size-5 fill-black/50 dark:fill-white/50",
"transition-colors duration-300 ease-in-out",
filter().has(tag) &&
"fill-black dark:fill-white"
)}>
<Icon icon="pixelarticons:square" />
<Icon icon="pixel:check" class={cn("absolute top-0 right-0",
filter().has(tag)
? "block"
: "hidden"
)} />
</div>
{tag}
</button>
</li>
)}
</For>
</ul>
</div>
</div>
<div class="col-span-3 sm:col-span-2">
<div class="flex flex-col">
<div class="text-sm font-departure uppercase mb-2">
SHOWING {posts().length} OF {data.length} POSTS
</div>
<ul class="flex flex-col gap-3">
{posts().map((post) => (
<li>
<ArrowCard entry={post} />
</li>
))}
<Show when={posts().length == 0}>
<h3 class="mt-5 text-2xl font-departure">
There seem to be no blogs matching the filters
yet...
</h3>
</Show>
</ul>
</div>
</div>
</div>
);
}

View File

@ -3,9 +3,10 @@ import { cn } from "@/lib/utils";
type Props = { type Props = {
size: "sm" | "md" | "lg" | "xl" | "2xl"; size: "sm" | "md" | "lg" | "xl" | "2xl";
class?: string;
}; };
const { size } = Astro.props; const { size, class: className } = Astro.props;
--- ---
<div <div
@ -16,6 +17,7 @@ const { size } = Astro.props;
size === "lg" && "max-w-5xl", size === "lg" && "max-w-5xl",
size === "xl" && "max-w-7xl", size === "xl" && "max-w-7xl",
size === "2xl" && "max-w-screen-2xl", size === "2xl" && "max-w-screen-2xl",
className,
)} )}
> >
<slot /> <slot />

View File

@ -1,5 +1,6 @@
--- ---
import { Image } from "astro:assets"; import { Image } from "astro:assets";
import { Icon } from "astro-icon/components";
import { SITE, SOCIALS } from "@/consts"; import { SITE, SOCIALS } from "@/consts";
import Container from "@/components/Container.astro"; import Container from "@/components/Container.astro";
@ -8,41 +9,32 @@ import pictureOfMe from "@/images/me.jpg";
const CURRENT_YEAR = new Date().getFullYear(); const CURRENT_YEAR = new Date().getFullYear();
--- ---
<footer class="relative bg-white dark:bg-black"> <footer class="relative">
<div class="animate"> <div class="animate bg-zinc-200 dark:bg-zinc-950/50">
<section class="py-5"> <section class="py-5">
<Container size="md"> <Container size="md">
<div class="flex items-center justify-center sm:justify-end"> <div class="flex items-center justify-center sm:justify-end">
<button <button
id="back-to-top" id="back-to-top"
aria-label="Back to top of page" aria-label="Back to top of page"
class="group flex w-fit p-1.5 gap-1.5 text-sm items-center border rounded hover:bg-black/5 hover:dark:bg-white/10 border-black/15 dark:border-white/20 transition-colors duration-300 ease-in-out" class="group flex w-fit p-1.5 gap-1.5 text-sm items-center border rounded hover:bg-black/15 hover:dark:bg-white/10 border-black/15 dark:border-white/20 transition-colors duration-300 ease-in-out"
> >
<svg <div class="relative overflow-hidden w-5 h-4 -mt-1">
xmlns="http://www.w3.org/2000/svg" <Icon
width="20" name="pixelarticons:arrow-up"
height="20" class:list="absolute top-0 inset-x-0 text-lg origin-bottom
viewBox="0 0 24 24" scale-y-0 group-hover:scale-y-100
fill="none" transition-all duration-300 ease-in-out"
stroke-width="2.5" />
stroke-linecap="round" <Icon
stroke-linejoin="round" name="pixelarticons:chevron-up"
class="stroke-current group-hover:stroke-black group-hover:dark:stroke-white rotate-90" class:list="absolute top-0 inset-x-0 text-lg
> group-hover:translate-y-4
<line transition-all duration-300 ease-in-out"
x1="19" />
y1="12" </div>
x2="5"
y2="12"
class="scale-x-0 group-hover:scale-x-100 translate-x-3 group-hover:translate-x-0 transition-all duration-300 ease-in-out"
></line>
<polyline
points="12 19 5 12 12 5"
class="translate-x-1 group-hover:translate-x-0 transition-all duration-300 ease-in-out"
></polyline>
</svg>
<div <div
class="w-full group-hover:text-black group-hover:dark:text-white transition-colors duration-300 ease-in-out" class="w-full font-departure group-hover:text-black group-hover:dark:text-white transition-colors duration-300 ease-in-out"
> >
Back to top Back to top
</div> </div>
@ -65,10 +57,10 @@ const CURRENT_YEAR = new Date().getFullYear();
src={pictureOfMe} src={pictureOfMe}
loading="lazy" loading="lazy"
alt="Me" alt="Me"
class="size-6 fill-current rounded-full" class="size-6 fill-current rounded-lg mr-2"
quality="low" quality="low"
/> />
{SITE.TITLE} {SITE.AUTHOR}
</a> </a>
</div> </div>
<div <div
@ -76,10 +68,7 @@ const CURRENT_YEAR = new Date().getFullYear();
> >
<span class="relative flex h-3 w-3"> <span class="relative flex h-3 w-3">
<span <span
class="absolute inline-flex h-full w-full rounded-full bg-green-300" class="absolute inline-flex h-full w-full rounded-sm bg-green-500"
></span>
<span
class="relative inline-flex rounded-full h-3 w-3 bg-green-500"
></span> ></span>
</span> </span>
All systems normal All systems normal
@ -113,11 +102,12 @@ const CURRENT_YEAR = new Date().getFullYear();
href={SOCIAL.HREF} href={SOCIAL.HREF}
target="_blank" target="_blank"
aria-label={`${SITE.TITLE} on ${SOCIAL.NAME}`} aria-label={`${SITE.TITLE} on ${SOCIAL.NAME}`}
class="group size-10 rounded-full p-2 items-center justify-center text-center hover:bg-black/5 dark:hover:bg-white/20 blend" class="group size-10 rounded-full p-2 flex items-center justify-center hover:bg-black/15 dark:hover:bg-white/20 blend"
> >
<div class="text-2xl -translate-y-1 group-hover:fill-black group-hover:dark:fill-white blend"> <Icon
{SOCIAL.ICON} name={SOCIAL.ICON}
</div> class:list="text-xl text-center group-hover:fill-black group-hover:dark:fill-white blend"
/>
</a> </a>
)) ))
} }

View File

@ -3,6 +3,7 @@ import { Image } from "astro:assets";
import { SITE, LINKS } from "@/consts"; import { SITE, LINKS } from "@/consts";
import { cn } from "@/lib/utils"; import { cn } from "@/lib/utils";
import Container from "@/components/Container.astro"; import Container from "@/components/Container.astro";
import { Icon } from "astro-icon/components";
import pictureOfMe from "@/images/me.jpg"; import pictureOfMe from "@/images/me.jpg";
@ -12,10 +13,10 @@ const subpath = pathname.match(/[^/]+/g);
<header id="header" class="fixed top-0 w-full h-16 z-50"> <header id="header" class="fixed top-0 w-full h-16 z-50">
<Container size="md"> <Container size="md">
<div class="relative h-full w-full">
<div <div
class="absolute left-0 top-1/2 -translate-y-1/2 flex gap-1 font-semibold" class="relative h-full w-full flex flex-row justify-between items-center"
> >
<div class="flex gap-1 font-semibold">
<a <a
href="/" href="/"
class="flex gap-1 text-current hover:text-black dark:hover:text-white transition-colors duration-300 ease-in-out" class="flex gap-1 text-current hover:text-black dark:hover:text-white transition-colors duration-300 ease-in-out"
@ -24,7 +25,7 @@ const subpath = pathname.match(/[^/]+/g);
src={pictureOfMe} src={pictureOfMe}
loading="eager" loading="eager"
alt="Me" alt="Me"
class="size-6 fill-current rounded-full" class="size-6 fill-current rounded-lg mr-2"
quality="low" quality="low"
/> />
<div class="font-departure font-bold"> <div class="font-departure font-bold">
@ -33,9 +34,7 @@ const subpath = pathname.match(/[^/]+/g);
</a> </a>
</div> </div>
<div <div>
class="absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2"
>
<nav <nav
class="hidden md:flex items-center justify-center text-sm gap-1" class="hidden md:flex items-center justify-center text-sm gap-1"
> >
@ -44,11 +43,11 @@ const subpath = pathname.match(/[^/]+/g);
<a <a
href={LINK.HREF} href={LINK.HREF}
class={cn( class={cn(
"h-8 rounded-full px-3 text-current font-departure", "h-8 rounded-xl px-3 text-current font-departure",
"flex items-center justify-center", "flex items-center justify-center",
"transition-colors duration-300 ease-in-out", "transition-colors duration-300 ease-in-out",
pathname === LINK.HREF || pathname === LINK.HREF ||
"/" + subpath?.[0] === LINK.HREF "/" + subpath?.[0] + "/" === LINK.HREF
? "bg-black dark:bg-white text-white dark:text-black" ? "bg-black dark:bg-white text-white dark:text-black"
: "hover:bg-black/5 dark:hover:bg-white/20 hover:text-black dark:hover:text-white", : "hover:bg-black/5 dark:hover:bg-white/20 hover:text-black dark:hover:text-white",
)} )}
@ -60,12 +59,10 @@ const subpath = pathname.match(/[^/]+/g);
</nav> </nav>
</div> </div>
<div <div class="buttons flex gap-1">
class="buttons absolute right-0 top-1/2 -translate-y-1/2 flex gap-1"
>
<a <a
href="/search/" href="/search/"
aria-label={`Search blog posts and projects on ${SITE.TITLE}`} aria-label="Search blog posts and projects"
class={cn( class={cn(
"hidden md:flex", "hidden md:flex",
"size-9 rounded-full p-2 items-center justify-center text-center", "size-9 rounded-full p-2 items-center justify-center text-center",
@ -79,30 +76,28 @@ const subpath = pathname.match(/[^/]+/g);
: "", : "",
)} )}
> >
<span class="size-full text-2xl -translate-y-2" <Icon name="pixelarticons:search" class:list="text-xl" />
>&#xf002</span
>
</a> </a>
<a <a
href="/rss.xml" href="/rss.xml"
target="_blank" target="_blank"
aria-label={`Rss feed for ${SITE.TITLE}`} aria-label="Rss feed"
class={cn( class={cn(
"hidden md:flex", "hidden md:flex",
"size-9 rounded-full p-2 items-center justify-center text-center", "size-9 rounded-full p-2 items-center justify-center text-center",
"bg-transparent hover:bg-black/5 dark:hover:bg-white/20", "bg-transparent hover:bg-black/5 dark:hover:bg-white/20",
"stroke-current hover:stroke-black hover:dark:stroke-white", "stroke-current hover:stroke-black hover:dark:stroke-white",
"border border-black/10 dark:border-white/25", "border border-black/10 dark:border-white/25",
"transition-colors duration-300 ease-in-out", "transition-colors duration-300 ease-in-out active:opacity-1!",
)} )}
> >
<div class="size-full text-2xl -translate-y-2">&#xf428</div> <Icon name="pixelarticons:rss" class:list="text-xl" />
</a> </a>
<button <button
id="header-theme-button" id="header-theme-button"
aria-label={`Toggle light and dark theme`} aria-label="Toggle light and dark theme"
class={cn( class={cn(
"hidden md:flex", "hidden md:flex",
"size-9 rounded-full p-2 items-center justify-center text-center", "size-9 rounded-full p-2 items-center justify-center text-center",
@ -112,14 +107,14 @@ const subpath = pathname.match(/[^/]+/g);
"transition-colors duration-300 ease-in-out", "transition-colors duration-300 ease-in-out",
)} )}
> >
<span <Icon
class="size-full text-2xl -translate-y-2 block dark:hidden" name="pixelarticons:sun"
>&#xf522</span class:list={"text-xl block dark:hidden"}
> />
<span <Icon
class="size-full text-2xl -translate-y-2 hidden dark:block" name="pixelarticons:moon"
>&#xf4ee</span class:list={"text-xl hidden dark:block"}
> />
</button> </button>
<button <button
@ -134,8 +129,15 @@ const subpath = pathname.match(/[^/]+/g);
"transition-colors duration-300 ease-in-out", "transition-colors duration-300 ease-in-out",
)} )}
> >
<div id="drawer-open" class="text-2xl">&#xf0c9</div> <div id="drawer-open">
<div id="drawer-close" class="text-2xl">&#xf467</div> <Icon name="pixelarticons:menu" class:list="text-xl" />
</div>
<div id="drawer-close">
<Icon
name="pixelarticons:chevron-up"
class:list="text-xl"
/>
</div>
</button> </button>
</div> </div>
</div> </div>

109
src/components/Projects.tsx Normal file
View File

@ -0,0 +1,109 @@
import type { CollectionEntry } from "astro:content";
import { createEffect, createSignal, For, Show } from "solid-js";
import ArrowCard from "@/components/ArrowCard";
import { cn } from "@/lib/utils";
import { Icon } from "@iconify-icon/solid";
type Props = {
tags: string[];
data: CollectionEntry<"projects">[];
};
export default function Projects({ data, tags }: Props) {
const [filter, setFilter] = createSignal(new Set<string>());
const [projects, setProjects] = createSignal<CollectionEntry<"projects">[]>(
[]
);
createEffect(() => {
setProjects(
data.filter((entry) =>
Array.from(filter()).every((value) =>
entry.data.tags.some(
(tag: string) =>
tag.toLowerCase() === String(value).toLowerCase()
)
)
)
);
});
function toggleTag(tag: string) {
setFilter(
(prev) =>
new Set(
prev.has(tag)
? [...prev].filter((t) => t !== tag)
: [...prev, tag]
)
);
}
return (
<div class="grid grid-cols-1 sm:grid-cols-3 gap-6">
<div class="col-span-3 sm:col-span-1">
<div class="sticky top-24">
<div class="text-sm font-semibold font-departure uppercase mb-2 text-black dark:text-white">
Filter
</div>
<ul class="flex flex-wrap sm:flex-col gap-1.5">
<For each={tags}>
{(tag) => (
<li>
<button
onClick={() => toggleTag(tag)}
class={cn(
"w-full px-2 py-1 rounded font-departure",
"whitespace-nowrap overflow-hidden overflow-ellipsis",
"flex gap-2 items-center",
"bg-black/5 dark:bg-white/10",
"hover:bg-black/10 hover:dark:bg-white/15",
"transition-colors duration-300 ease-in-out",
filter().has(tag) &&
"text-black dark:text-white"
)}
>
<div class={cn(
"relative size-5 fill-black/50 dark:fill-white/50",
"transition-colors duration-300 ease-in-out",
filter().has(tag) &&
"fill-black dark:fill-white"
)}>
<Icon icon="pixelarticons:square" />
<Icon icon="pixel:check" class={cn("absolute top-0 right-0",
filter().has(tag)
? "block"
: "hidden"
)} />
</div>
{tag}
</button>
</li>
)}
</For>
</ul>
</div>
</div>
<div class="col-span-3 sm:col-span-2">
<div class="flex flex-col">
<div class="text-sm font-departure uppercase mb-2">
SHOWING {projects().length} OF {data.length} PROJECTS
</div>
<ul class="flex flex-col gap-3">
{projects().map((project) => (
<li>
<ArrowCard entry={project} />
</li>
))}
<Show when={projects().length == 0}>
<h3 class="mt-5 text-2xl font-departure">
There seem to be no projects matching the
filters yet...
</h3>
</Show>
</ul>
</div>
</div>
</div>
);
}

View File

@ -1,7 +1,7 @@
import type { CollectionEntry } from "astro:content" import type { CollectionEntry } from "astro:content"
import { createEffect, createSignal } from "solid-js" import { createEffect, createSignal } from "solid-js"
import Fuse from "fuse.js" import Fuse from "fuse.js"
// import ArrowCard from "@components/ArrowCard" import ArrowCard from "@/components/ArrowCard"
type Props = { type Props = {
data: CollectionEntry<"blog">[] data: CollectionEntry<"blog">[]
@ -41,14 +41,13 @@ export default function Search({data}: Props) {
</div> </div>
{(query().length >= 2 && results().length >= 1) && ( {(query().length >= 2 && results().length >= 1) && (
<div class="mt-12"> <div class="mt-12">
<div class="text-sm uppercase mb-2"> <div class="text-sm uppercase mb-2 font-departure">
Found {results().length} results for {`'${query()}'`} Found {results().length} results for {`'${query()}'`}
</div> </div>
<ul class="flex flex-col gap-3"> <ul class="flex flex-col gap-3">
{results().map(result => ( {results().map(result => (
<li> <li>
{/* <ArrowCard entry={result} pill={true} /> */} <ArrowCard entry={result} pill={result.collection} />
<div>{JSON.stringify(result, undefined, 2)}</div>
</li> </li>
))} ))}
</ul> </ul>

View File

@ -0,0 +1,26 @@
---
import { Icon } from "astro-icon/components";
type Props = {
text: string;
icon: string;
href: string;
};
const { text, icon, href } = Astro.props;
---
<a
href={href}
target="_blank"
class="w-fit px-3 py-2 group rounded border flex gap-2 items-center border-neutral-200 dark:border-neutral-700 hover:bg-neutral-100 hover:dark:bg-neutral-800 blend"
>
<div>
<Icon name={icon} class:list="text-xl" />
</div>
<span
class="text-sm capitalize text-neutral-500 dark:text-neutral-400 group-hover:text-black group-hover:dark:text-white blend"
>
{text}
</span>
</a>

View File

@ -7,10 +7,10 @@ export const SITE: Site = {
AUTHOR: "Moritz Hölting", AUTHOR: "Moritz Hölting",
}; };
// Work Page // Qualifications Page
export const WORK: Page = { export const QUALIFICATIONS: Page = {
TITLE: "Work", TITLE: "Work & Education",
DESCRIPTION: "Places I have worked.", DESCRIPTION: "My work and education history.",
}; };
// Blog Page // Blog Page
@ -38,8 +38,8 @@ export const LINKS: Links = [
HREF: "/", HREF: "/",
}, },
{ {
TEXT: "Work", TEXT: "Qualifications",
HREF: "/work/", HREF: "/qualifications/",
}, },
{ {
TEXT: "Blog", TEXT: "Blog",
@ -55,19 +55,19 @@ export const LINKS: Links = [
export const SOCIALS: Socials = [ export const SOCIALS: Socials = [
{ {
NAME: "Email", NAME: "Email",
ICON: "\udb80\uddee", ICON: "pixelarticons:at-sign",
TEXT: "moritz@hoelting.dev", TEXT: "moritz@hoelting.dev",
HREF: "mailto:moritz@hoelting.dev", HREF: "mailto:moritz@hoelting.dev",
}, },
{ {
NAME: "Github", NAME: "Github",
ICON: "\uf09b", ICON: "pixel:github",
TEXT: "moritz-hoelting", TEXT: "moritz-hoelting",
HREF: "https://github.com/moritz-hoelting", HREF: "https://github.com/moritz-hoelting",
}, },
{ {
NAME: "LinkedIn", NAME: "LinkedIn",
ICON: "\udb80\udf3b", ICON: "pixel:linkedin",
TEXT: "moritz-hölting", TEXT: "moritz-hölting",
HREF: "https://www.linkedin.com/in/moritz-h%C3%B6lting/", HREF: "https://www.linkedin.com/in/moritz-h%C3%B6lting/",
}, },

View File

@ -12,6 +12,19 @@ const work = defineCollection({
}), }),
}); });
const education = defineCollection({
loader: glob({ base: "./src/content/education", pattern: "**/*.{md,mdx}" }),
schema: z.object({
institution: z.string(),
degree: z.string(),
field: z.string().optional(),
location: z.string().optional(),
finalGrade: z.union([z.string(), z.number()]).optional(),
dateStart: z.date(),
dateEnd: z.union([z.date(), z.string()]),
}),
});
const blog = defineCollection({ const blog = defineCollection({
loader: glob({ base: "./src/content/blog", pattern: "**/*.{md,mdx}" }), loader: glob({ base: "./src/content/blog", pattern: "**/*.{md,mdx}" }),
schema: z.object({ schema: z.object({
@ -36,4 +49,4 @@ const projects = defineCollection({
}), }),
}); });
export const collections = { work, blog, projects }; export const collections = { work, education, blog, projects };

View File

@ -0,0 +1,6 @@
---
institution: "Brede Gymnasium"
degree: "Abitur"
dateStart: 2014-08-01
dateEnd: 2022-06-30
---

View File

@ -0,0 +1,15 @@
---
institution: "Paderborn University"
degree: "Bachelor of Science"
field: "Computer Science"
dateStart: 2022-10-01
dateEnd: 2026-11-06
---
Bachelor's thesis: [Limitations of the Random Oracle Model](/blog/limitations-of-the-random-oracle-model/) in the "Codes and Cryptography" research group.
Elective modules:
- Einführung in Kryptographie (Introduction to Cryptography)
- 3D Rendering
- Algorithmische Geometrie (Algorithmic Geometry)
- Gestaltung von Nutzungsschnittstellen (User Interface Design)

View File

@ -0,0 +1,7 @@
---
institution: "Paderborn University"
degree: "Master of Science"
field: "Computer Science"
dateStart: 2025-10-01
dateEnd: "expected sep 2027"
---

View File

@ -2,7 +2,7 @@
company: "Diebold Nixdorf" company: "Diebold Nixdorf"
role: "Working Student - Infrastructure & Operations" role: "Working Student - Infrastructure & Operations"
dateStart: 2024-05-01 dateStart: 2024-05-01
dateEnd: 2025-04-30 dateEnd: 2026-10-31
--- ---
At at Diebold Nixdorf, I am responsible for conducting "experiments" with new At at Diebold Nixdorf, I am responsible for conducting "experiments" with new

View File

@ -1,5 +1,6 @@
--- ---
import { type CollectionEntry, getCollection, render } from "astro:content"; import { type CollectionEntry, getCollection } from "astro:content";
import { Icon } from "astro-icon/components";
type Props = { type Props = {
entry: CollectionEntry<"blog"> | CollectionEntry<"projects">; entry: CollectionEntry<"blog"> | CollectionEntry<"projects">;
@ -8,7 +9,6 @@ type Props = {
// Get the requested entry // Get the requested entry
const { entry } = Astro.props; const { entry } = Astro.props;
const { collection } = entry; const { collection } = entry;
const { Content } = await render(entry);
// Get the next and prev entries (modulo to wrap index) // Get the next and prev entries (modulo to wrap index)
const items = (await getCollection(collection)) const items = (await getCollection(collection))
@ -20,9 +20,6 @@ const next = items[(index - 1 + items.length) % items.length];
--- ---
<div> <div>
<article>
<Content />
</article>
<div class="grid grid-cols-1 sm:grid-cols-2 gap-4"> <div class="grid grid-cols-1 sm:grid-cols-2 gap-4">
<a <a
href={`/${prev.collection}/${prev.id}/`} href={`/${prev.collection}/${prev.id}/`}
@ -32,35 +29,26 @@ const next = items[(index - 1 + items.length) % items.length];
class="order-2 w-full h-full group-hover:text-black group-hover:dark:text-white blend" class="order-2 w-full h-full group-hover:text-black group-hover:dark:text-white blend"
> >
<div class="flex flex-wrap gap-2"> <div class="flex flex-wrap gap-2">
<div class="text-sm uppercase">Prev</div> <div class="text-sm font-departure uppercase">Prev</div>
</div> </div>
<div class="font-semibold mt-3 text-black dark:text-white"> <div class="font-semibold mt-3 text-black dark:text-white">
{prev.data.title} {prev.data.title}
</div> </div>
</div> </div>
<svg <div class="relative overflow-hidden w-4 h-4">
xmlns="http://www.w3.org/2000/svg" <Icon
width="20" name="pixelarticons:arrow-left"
height="20" class:list="absolute left-0 inset-y-0 text-lg origin-right
viewBox="0 0 24 24" scale-x-0 group-hover:scale-x-100
fill="none" transition-all duration-300 ease-in-out"
stroke-width="2.5" />
stroke-linecap="round" <Icon
stroke-linejoin="round" name="pixelarticons:chevron-left"
class="order-1 stroke-current group-hover:stroke-black group-hover:dark:stroke-white rotate-180" class:list="absolute left-0 inset-y-0 text-lg
> group-hover:-translate-x-4
<line transition-all duration-300 ease-in-out"
x1="5" />
y1="12" </div>
x2="19"
y2="12"
class="scale-x-0 group-hover:scale-x-100 translate-x-4 group-hover:translate-x-1 transition-all duration-300 ease-in-out"
></line>
<polyline
points="12 5 19 12 12 19"
class="translate-x-0 group-hover:translate-x-1 transition-all duration-300 ease-in-out"
></polyline>
</svg>
</a> </a>
<a <a
href={`/${next.collection}/${next.id}/`} href={`/${next.collection}/${next.id}/`}
@ -69,34 +57,25 @@ const next = items[(index - 1 + items.length) % items.length];
<div <div
class="w-full h-full text-right group-hover:text-black group-hover:dark:text-white blend" class="w-full h-full text-right group-hover:text-black group-hover:dark:text-white blend"
> >
<div class="text-sm uppercase">Next</div> <div class="text-sm font-departure uppercase">Next</div>
<div class="font-semibold mt-3 text-black dark:text-white"> <div class="font-semibold mt-3 text-black dark:text-white">
{next.data.title} {next.data.title}
</div> </div>
</div> </div>
<svg <div class="relative overflow-hidden w-4 h-4">
xmlns="http://www.w3.org/2000/svg" <Icon
width="20" name="pixelarticons:arrow-right"
height="20" class:list="absolute right-0 inset-y-0 text-lg origin-left
viewBox="0 0 24 24" scale-x-0 group-hover:scale-x-100
fill="none" transition-all duration-300 ease-in-out"
stroke-width="2.5" />
stroke-linecap="round" <Icon
stroke-linejoin="round" name="pixelarticons:chevron-right"
class="stroke-current group-hover:stroke-black group-hover:dark:stroke-white" class:list="absolute right-0 inset-y-0 text-lg
> group-hover:translate-x-4
<line transition-all duration-300 ease-in-out"
x1="5" />
y1="12" </div>
x2="19"
y2="12"
class="scale-x-0 group-hover:scale-x-100 translate-x-4 group-hover:translate-x-1 transition-all duration-300 ease-in-out"
></line>
<polyline
points="12 5 19 12 12 19"
class="translate-x-0 group-hover:translate-x-1 transition-all duration-300 ease-in-out"
></polyline>
</svg>
</a> </a>
</div> </div>
</div> </div>

View File

@ -1,5 +1,6 @@
--- ---
import type { CollectionEntry } from "astro:content"; import type { CollectionEntry } from "astro:content";
import { Icon } from "astro-icon/components";
import { formatDate, readingTime } from "@/lib/utils"; import { formatDate, readingTime } from "@/lib/utils";
type Props = { type Props = {
@ -19,50 +20,41 @@ const repoUrl = collection === "projects" ? data.repoUrl : null;
href={`/${collection}/`} href={`/${collection}/`}
class="group w-fit p-1.5 gap-1.5 text-sm flex items-center border rounded hover:bg-black/5 hover:dark:bg-white/10 border-black/15 dark:border-white/20 transition-colors duration-300 ease-in-out" class="group w-fit p-1.5 gap-1.5 text-sm flex items-center border rounded hover:bg-black/5 hover:dark:bg-white/10 border-black/15 dark:border-white/20 transition-colors duration-300 ease-in-out"
> >
<svg <div class="relative overflow-hidden w-4 h-4">
xmlns="http://www.w3.org/2000/svg" <Icon
width="20" name="pixelarticons:arrow-left"
height="20" class:list="absolute left-0 inset-y-0 text-lg origin-right
viewBox="0 0 24 24" scale-x-0 group-hover:scale-x-100
fill="none" transition-all duration-300 ease-in-out"
stroke-width="2.5" />
stroke-linecap="round" <Icon
stroke-linejoin="round" name="pixelarticons:chevron-left"
class="stroke-current group-hover:stroke-black group-hover:dark:stroke-white" class:list="absolute left-0 inset-y-0 text-lg
> group-hover:-translate-x-4
<line transition-all duration-300 ease-in-out"
x1="19" />
y1="12" </div>
x2="5"
y2="12"
class="scale-x-0 group-hover:scale-x-100 translate-x-3 group-hover:translate-x-0 transition-all duration-300 ease-in-out"
></line>
<polyline
points="12 19 5 12 12 5"
class="translate-x-1 group-hover:translate-x-0 transition-all duration-300 ease-in-out"
></polyline>
</svg>
<div <div
class="w-full group-hover:text-black group-hover:dark:text-white transition-colors duration-300 ease-in-out" class="w-full font-departure group-hover:text-black group-hover:dark:text-white transition-colors duration-300 ease-in-out"
> >
Back to {collection} Back to {collection}
</div> </div>
</a> </a>
<div class="flex flex-wrap text-sm uppercase mt-12 gap-3 opacity-75"> <div
class="flex flex-wrap text-sm font-departure uppercase mt-12 gap-3 opacity-75"
>
<div class="flex items-center gap-2"> <div class="flex items-center gap-2">
<svg class="size-5 stroke-current"> <Icon name="pixelarticons:calendar-2" class:list="size-5" />
<use href="/ui.svg#calendar"></use>
</svg>
{formatDate(date)} {formatDate(date)}
</div> </div>
<div class="flex items-center gap-2"> <div class="flex items-center gap-2">
<svg class="size-5 stroke-current"> <Icon name="pixelarticons:book-open" class:list="size-5" />
<use href="/ui.svg#book-open"></use>
</svg>
{readingTime(body ?? "")} {readingTime(body ?? "")}
</div> </div>
</div> </div>
<h1 class="text-3xl font-semibold text-black dark:text-white mt-2"> <h1
class="text-3xl font-semibold font-departure text-black dark:text-white mt-2"
>
{title} {title}
</h1> </h1>
<div class="mt-1"> <div class="mt-1">
@ -77,13 +69,8 @@ const repoUrl = collection === "projects" ? data.repoUrl : null;
target="_blank" target="_blank"
class="group flex gap-2 items-center px-3 py-1.5 truncate rounded text-xs md:text-sm lg:text-base border border-black/25 dark:border-white/25 hover:bg-black/5 hover:dark:bg-white/15 blend" class="group flex gap-2 items-center px-3 py-1.5 truncate rounded text-xs md:text-sm lg:text-base border border-black/25 dark:border-white/25 hover:bg-black/5 hover:dark:bg-white/15 blend"
> >
<svg class="size-4"> <Icon name="pixelarticons:globe" class:list="size-4" />
<use <span class="text-current font-departure group-hover:text-black group-hover:dark:text-white blend">
href="/ui.svg#globe"
class="fill-current group-hover:fill-black group-hover:dark:fill-white blend"
/>
</svg>
<span class="text-current group-hover:text-black group-hover:dark:text-white blend">
See Demo See Demo
</span> </span>
</a> </a>
@ -94,13 +81,8 @@ const repoUrl = collection === "projects" ? data.repoUrl : null;
target="_blank" target="_blank"
class="group flex gap-2 items-center px-3 py-1.5 truncate rounded text-xs md:text-sm lg:text-base border border-black/25 dark:border-white/25 hover:bg-black/5 hover:dark:bg-white/15 blend" class="group flex gap-2 items-center px-3 py-1.5 truncate rounded text-xs md:text-sm lg:text-base border border-black/25 dark:border-white/25 hover:bg-black/5 hover:dark:bg-white/15 blend"
> >
<svg class="size-4"> <Icon name="pixelarticons:link" class:list="size-4" />
<use <span class="text-current font-departure group-hover:text-black group-hover:dark:text-white blend">
href="/ui.svg#link"
class="fill-current group-hover:fill-black group-hover:dark:fill-white blend"
/>
</svg>
<span class="text-current group-hover:text-black group-hover:dark:text-white blend">
See Repository See Repository
</span> </span>
</a> </a>

View File

@ -5,6 +5,7 @@ import TopLayout from "@/layouts/TopLayout.astro";
import BottomLayout from "@/layouts/BottomLayout.astro"; import BottomLayout from "@/layouts/BottomLayout.astro";
import ArticleTopLayout from "@/layouts/ArticleTopLayout.astro"; import ArticleTopLayout from "@/layouts/ArticleTopLayout.astro";
import ArticleBottomLayout from "@/layouts/ArticleBottomLayout.astro"; import ArticleBottomLayout from "@/layouts/ArticleBottomLayout.astro";
import ArticleContent from "@/components/ArticleContent.astro";
// Create the static blog pages // Create the static blog pages
export async function getStaticPaths() { export async function getStaticPaths() {
@ -33,6 +34,7 @@ const { title, summary } = post.data;
<ArticleTopLayout entry={post} /> <ArticleTopLayout entry={post} />
</div> </div>
</TopLayout> </TopLayout>
<ArticleContent entry={post} />
<BottomLayout> <BottomLayout>
<div class="animate"> <div class="animate">
<ArticleBottomLayout entry={post} /> <ArticleBottomLayout entry={post} />

View File

@ -4,6 +4,7 @@ import PageLayout from "@/layouts/PageLayout.astro";
import TopLayout from "@/layouts/TopLayout.astro"; import TopLayout from "@/layouts/TopLayout.astro";
import BottomLayout from "@/layouts/BottomLayout.astro"; import BottomLayout from "@/layouts/BottomLayout.astro";
import { BLOG } from "@/consts"; import { BLOG } from "@/consts";
import Blog from "@/components/Blog";
const posts = (await getCollection("blog")) const posts = (await getCollection("blog"))
.filter((post) => !post.data.draft) .filter((post) => !post.data.draft)
@ -16,15 +17,13 @@ const tags = [...new Set(posts.flatMap((post) => post.data.tags))].sort(
<PageLayout title={BLOG.TITLE} description={BLOG.DESCRIPTION}> <PageLayout title={BLOG.TITLE} description={BLOG.DESCRIPTION}>
<TopLayout> <TopLayout>
<div class="animate page-heading"> <div class="animate font-departure page-heading">
{BLOG.TITLE} {BLOG.TITLE}
</div> </div>
</TopLayout> </TopLayout>
<BottomLayout> <BottomLayout>
<div class="animate"> <div class="animate">
<!-- <Blog client:load tags={tags} data={posts} /> --> <Blog client:load tags={tags} data={posts} />
<div>{JSON.stringify(posts)}</div>
<div>{JSON.stringify(tags)}</div>
</div> </div>
</BottomLayout> </BottomLayout>
</PageLayout> </PageLayout>

View File

@ -1,8 +1,8 @@
--- ---
import { getCollection } from "astro:content"; import { getCollection } from "astro:content";
import PageLayout from "@/layouts/PageLayout.astro"; import PageLayout from "@/layouts/PageLayout.astro";
// import ArrowCard from "@components/ArrowCard"; import ArrowCard from "@/components/ArrowCard";
// import StackCard from "@components/StackCard.astro"; import StackCard from "@/components/StackCard.astro";
import { SITE, SOCIALS } from "@/consts"; import { SITE, SOCIALS } from "@/consts";
const posts = (await getCollection("blog")) const posts = (await getCollection("blog"))
@ -18,22 +18,22 @@ const projects = (await getCollection("projects"))
const stack = [ const stack = [
{ {
text: "Astro", text: "Astro",
icon: "astro", icon: "material-icon-theme:astro",
href: "https://astro.build", href: "https://astro.build",
}, },
{ {
text: "Javascript", text: "Javascript",
icon: "javascript", icon: "devicon:javascript",
href: "https://www.javascript.com", href: "https://www.javascript.com",
}, },
{ {
text: "Typescript", text: "Typescript",
icon: "typescript", icon: "devicon:typescript",
href: "https://www.typescriptlang.org", href: "https://www.typescriptlang.org",
}, },
{ {
text: "Tailwind", text: "Tailwind",
icon: "tailwind", icon: "devicon:tailwindcss",
href: "https://tailwindcss.com", href: "https://tailwindcss.com",
}, },
]; ];
@ -41,7 +41,7 @@ const stack = [
<PageLayout title="Home" description={SITE.DESCRIPTION}> <PageLayout title="Home" description={SITE.DESCRIPTION}>
<!-- HERO --> <!-- HERO -->
<section class="relative h-screen w-full"> <section class="relative h-[75vh] w-full">
<div <div
id="planetcont" id="planetcont"
class="animate absolute inset-0 top-1/4 overflow-hidden" class="animate absolute inset-0 top-1/4 overflow-hidden"
@ -52,7 +52,7 @@ const stack = [
> >
<div <div
id="planet" id="planet"
class="w-full h-full bg-white dark:bg-black rounded-full p-px overflow-hidden flex justify-center" class="w-full h-full bg-zinc-300 dark:bg-zinc-900 rounded-full p-px overflow-hidden flex justify-center"
> >
<div <div
id="blur" id="blur"
@ -90,13 +90,13 @@ const stack = [
> >
<a <a
href="/blog/" href="/blog/"
class="py-2 px-4 rounded truncate text-xs md:text-sm lg:text-base bg-black dark:bg-white text-white dark:text-black hover:opacity-75 blend" class="py-2 px-4 rounded truncate text-xs md:text-sm lg:text-base font-departure bg-zinc-900 dark:bg-zinc-300 text-white dark:text-black hover:opacity-75 blend"
> >
Read my blog Read my blog
</a> </a>
<a <a
href="/work/" href="/work/"
class="py-2 px-4 truncate rounded text-xs md:text-sm lg:text-base border border-black/25 dark:border-white/25 hover:bg-black/5 hover:dark:bg-white/15 blend" class="py-2 px-4 truncate rounded text-xs md:text-sm lg:text-base font-departure border border-black/25 dark:border-white/25 hover:bg-zinc-900/5 hover:dark:bg-white/15 blend"
> >
View my work View my work
</a> </a>
@ -106,7 +106,7 @@ const stack = [
</div> </div>
</section> </section>
<div class="relative bg-white dark:bg-black"> <div class="relative bg-zinc-300 dark:bg-zinc-900">
<div class="mx-auto max-w-screen-sm p-5 space-y-24 pb-16"> <div class="mx-auto max-w-screen-sm p-5 space-y-24 pb-16">
<!-- About Section --> <!-- About Section -->
<section class="animate"> <section class="animate">
@ -116,16 +116,22 @@ const stack = [
a cli-tool. I am motivated by challenging projects where a cli-tool. I am motivated by challenging projects where
I have to research and learn new concepts. I have to research and learn new concepts.
</p> </p>
<p>Now</p> <p class="font-semibold font-departure">Now</p>
<p> <p>
I currently study computer science at I am currently pursuing a Master's degree in Computer
Science at
<a <a
href="https://upb.de" href="https://upb.de"
target="_blank" target="_blank"
rel="noopener noreferrer">Paderborn University</a rel="noopener noreferrer"
> class="font-departure">Paderborn University</a
with economics as my minor subject, while also getting a peek >. During my bachelor's program, I gained insight into
at electrical engineering & maths in my regular subject. electrical engineering and mathematics. Through
extracurricular studies, I have learned about economics.
In the master's program, I can take a broader variety of
courses, including quantum computation, security,
machine learning, and lesser-known fields such as
knowledge representation.
</p> </p>
</article> </article>
</section> </section>
@ -134,7 +140,9 @@ const stack = [
<section class="animate"> <section class="animate">
<div class="space-y-4"> <div class="space-y-4">
<div class="flex justify-between"> <div class="flex justify-between">
<p class="font-semibold text-black dark:text-white"> <p
class="font-semibold font-departure text-black dark:text-white"
>
Recent posts Recent posts
</p> </p>
<a <a
@ -142,7 +150,7 @@ const stack = [
class="w-fit col-span-3 group flex gap-1 items-center underline decoration-[.5px] decoration-black/25 dark:decoration-white/50 hover:decoration-black dark:hover:decoration-white text-black dark:text-white underline-offset-2 blend" class="w-fit col-span-3 group flex gap-1 items-center underline decoration-[.5px] decoration-black/25 dark:decoration-white/50 hover:decoration-black dark:hover:decoration-white text-black dark:text-white underline-offset-2 blend"
> >
<span <span
class="text-black/75 dark:text-white/75 group-hover:text-black group-hover:dark:text-white blend" class="font-departure text-black/75 dark:text-white/75 group-hover:text-black group-hover:dark:text-white blend"
> >
All posts All posts
</span> </span>
@ -152,14 +160,7 @@ const stack = [
{ {
posts.map((post) => ( posts.map((post) => (
<li> <li>
{/* <ArrowCard entry={post} /> */} <ArrowCard entry={post} />
<div>
{JSON.stringify(
post.data,
undefined,
2,
)}
</div>
</li> </li>
)) ))
} }
@ -167,32 +168,13 @@ const stack = [
</div> </div>
</section> </section>
<!-- Tech Stack Section -->
<section class="animate">
<div class="space-y-4">
<p class="font-semibold text-black dark:text-white">
Website build with
</p>
<div class="flex flex-wrap items-center gap-2 mt-5">
{
stack.map((item) => (
// <StackCard
// text={item.text}
// icon={item.icon}
// href={item.href}
// />
<div>{JSON.stringify(item, undefined, 2)}</div>
))
}
</div>
</div>
</section>
<!-- Project Preview Section --> <!-- Project Preview Section -->
<section class="animate"> <section class="animate">
<div class="space-y-4"> <div class="space-y-4">
<div class="flex justify-between"> <div class="flex justify-between">
<p class="font-semibold text-black dark:text-white"> <p
class="font-semibold font-departure text-black dark:text-white"
>
Recent projects Recent projects
</p> </p>
<a <a
@ -200,7 +182,7 @@ const stack = [
class="w-fit col-span-3 group flex gap-1 items-center underline decoration-[.5px] decoration-black/25 dark:decoration-white/50 hover:decoration-black dark:hover:decoration-white text-black dark:text-white underline-offset-2 blend" class="w-fit col-span-3 group flex gap-1 items-center underline decoration-[.5px] decoration-black/25 dark:decoration-white/50 hover:decoration-black dark:hover:decoration-white text-black dark:text-white underline-offset-2 blend"
> >
<span <span
class="text-black/75 dark:text-white/75 group-hover:text-black group-hover:dark:text-white blend" class="font-departure text-black/75 dark:text-white/75 group-hover:text-black group-hover:dark:text-white blend"
> >
All projects All projects
</span> </span>
@ -210,14 +192,7 @@ const stack = [
{ {
projects.map((project) => ( projects.map((project) => (
<li> <li>
{/* <ArrowCard entry={project} /> */} <ArrowCard entry={project} />
<div>
{JSON.stringify(
project.data,
undefined,
2,
)}
</div>
</li> </li>
)) ))
} }
@ -228,10 +203,11 @@ const stack = [
<!-- Contact Section --> <!-- Contact Section -->
<section class="animate"> <section class="animate">
<div> <div>
<p class="font-semibold text-black dark:text-white"> <p
class="font-semibold font-departure text-black dark:text-white"
>
Let's Connect Let's Connect
</p> </p>
<p>Reach out to me via email or on social media.</p>
<div class="grid grid-cols-4 gap-y-2 mt-4 auto-cols-min"> <div class="grid grid-cols-4 gap-y-2 mt-4 auto-cols-min">
{ {
SOCIALS.map((social) => ( SOCIALS.map((social) => (
@ -258,6 +234,28 @@ const stack = [
</div> </div>
</div> </div>
</section> </section>
<!-- Tech Stack Section -->
<section class="animate">
<div class="space-y-4">
<p
class="font-semibold font-departure text-black dark:text-white"
>
Website build with
</p>
<div class="flex flex-wrap items-center gap-2 mt-5">
{
stack.map((item) => (
<StackCard
text={item.text}
icon={item.icon}
href={item.href}
/>
))
}
</div>
</div>
</section>
</div> </div>
</div> </div>
</PageLayout> </PageLayout>

View File

@ -5,6 +5,7 @@ import TopLayout from "@/layouts/TopLayout.astro";
import BottomLayout from "@/layouts/BottomLayout.astro"; import BottomLayout from "@/layouts/BottomLayout.astro";
import ArticleTopLayout from "@/layouts/ArticleTopLayout.astro"; import ArticleTopLayout from "@/layouts/ArticleTopLayout.astro";
import ArticleBottomLayout from "@/layouts/ArticleBottomLayout.astro"; import ArticleBottomLayout from "@/layouts/ArticleBottomLayout.astro";
import ArticleContent from "@/components/ArticleContent.astro";
// Create the static projects pages // Create the static projects pages
export async function getStaticPaths() { export async function getStaticPaths() {
@ -33,6 +34,7 @@ const { title, summary } = project.data;
<ArticleTopLayout entry={project} /> <ArticleTopLayout entry={project} />
</div> </div>
</TopLayout> </TopLayout>
<ArticleContent entry={project} />
<BottomLayout> <BottomLayout>
<div class="animate"> <div class="animate">
<ArticleBottomLayout entry={project} /> <ArticleBottomLayout entry={project} />

View File

@ -3,7 +3,7 @@ import { getCollection } from "astro:content";
import PageLayout from "@/layouts/PageLayout.astro"; import PageLayout from "@/layouts/PageLayout.astro";
import TopLayout from "@/layouts/TopLayout.astro"; import TopLayout from "@/layouts/TopLayout.astro";
import BottomLayout from "@/layouts/BottomLayout.astro"; import BottomLayout from "@/layouts/BottomLayout.astro";
// import Projects from "@components/Projects"; import Projects from "@/components/Projects";
import { PROJECTS } from "@/consts"; import { PROJECTS } from "@/consts";
const projects = (await getCollection("projects")) const projects = (await getCollection("projects"))
@ -17,15 +17,13 @@ const tags = [
<PageLayout title={PROJECTS.TITLE} description={PROJECTS.DESCRIPTION}> <PageLayout title={PROJECTS.TITLE} description={PROJECTS.DESCRIPTION}>
<TopLayout> <TopLayout>
<div class="animate page-heading"> <div class="animate font-departure page-heading">
{PROJECTS.TITLE} {PROJECTS.TITLE}
</div> </div>
</TopLayout> </TopLayout>
<BottomLayout> <BottomLayout>
<div class="animate"> <div class="animate">
<!-- <Projects client:load tags={tags} data={projects} /> --> <Projects client:load tags={tags} data={projects} />
<div>{JSON.stringify(projects)}</div>
<div>{JSON.stringify(tags)}</div>
</div> </div>
</BottomLayout> </BottomLayout>
</PageLayout> </PageLayout>

View File

@ -0,0 +1,121 @@
---
import { getCollection, render } from "astro:content";
import PageLayout from "@/layouts/PageLayout.astro";
import TopLayout from "@/layouts/TopLayout.astro";
import BottomLayout from "@/layouts/BottomLayout.astro";
import { QUALIFICATIONS } from "@/consts";
const workCollection = await getCollection("work");
const educationCollection = await getCollection("education");
workCollection.sort(
(a, b) =>
new Date(b.data.dateStart).getTime() -
new Date(a.data.dateStart).getTime(),
);
educationCollection.sort(
(a, b) =>
new Date(b.data.dateStart).getTime() -
new Date(a.data.dateStart).getTime(),
);
const work = await Promise.all(
workCollection.map(async (item) => {
const { Content } = await render(item);
return { ...item, Content };
}),
);
const education = await Promise.all(
educationCollection.map(async (item) => {
const { Content } = await render(item);
return { ...item, Content };
}),
);
function formatShortDate(input: Date | string) {
if (typeof input === "string") return input;
const month = input.toLocaleDateString("en-US", {
month: "short",
});
const year = new Date(input).getFullYear();
return `${month} ${year}`;
}
---
<PageLayout
title={QUALIFICATIONS.TITLE}
description={QUALIFICATIONS.DESCRIPTION}
>
<!-- Work -->
<TopLayout>
<div class="animate page-heading font-departure">Work</div>
</TopLayout>
<BottomLayout>
<ul class="border-b border-black/10 dark:border-white/25">
{
work.map((entry) => (
<li class="animate border-b border-black/10 dark:border-white/25 mt-4 py-8 first-of-type:mt-0 first-of-type:pt-0 last-of-type:border-none">
<div class="font-departure text-sm uppercase mb-4">
{formatShortDate(entry.data.dateStart)} -{" "}
{formatShortDate(entry.data.dateEnd)}
</div>
<div class="font-departure text-black dark:text-white font-semibold">
{entry.data.company}
</div>
<div class="font-departure text-sm font-semibold">
{entry.data.role}
</div>
{entry.body && (
<article class="prose dark:prose-invert mt-2 pb-8">
<entry.Content />
</article>
)}
</li>
))
}
</ul>
</BottomLayout>
<!-- Education -->
<TopLayout>
<div class="animate page-heading font-departure">Education</div>
</TopLayout>
<BottomLayout>
<ul>
{
education.map((entry) => (
<li class="animate border-b border-black/10 dark:border-white/25 mt-4 py-8 first-of-type:mt-0 first-of-type:pt-0 last-of-type:border-none">
<div class="font-departure text-sm uppercase mb-4">
{formatShortDate(entry.data.dateStart)} -{" "}
{formatShortDate(entry.data.dateEnd)}
</div>
<div class="font-departure text-black dark:text-white font-semibold">
{entry.data.institution}
</div>
<div class="font-departure text-sm font-semibold">
{entry.data.degree}
{entry.data.field && ` - ${entry.data.field}`}
</div>
{(entry.data.finalGrade || entry.data.location) && (
<div class="text-sm">
{entry.data.finalGrade &&
`Grade: ${
entry.data.finalGrade +
(entry.data.location && ", ")
}`}
{entry.data.location}
</div>
)}
{entry.body && (
<article class="prose dark:prose-invert mt-2 pb-8">
<entry.Content />
</article>
)}
</li>
))
}
</ul>
</BottomLayout>
</PageLayout>

View File

@ -27,14 +27,17 @@ export async function GET(context: Context) {
); );
return rss({ return rss({
title: SITE.TITLE, title: `${SITE.TITLE} - ${SITE.AUTHOR}`,
description: SITE.DESCRIPTION, description: SITE.DESCRIPTION,
site: context.site, site: context.site,
stylesheet: "/rss-style.xsl",
items: items.map(({ item, pre }) => ({ items: items.map(({ item, pre }) => ({
title: item.data.title, title: item.data.title,
description: item.data.summary, description: item.data.summary,
author: SITE.AUTHOR,
pubDate: item.data.date, pubDate: item.data.date,
link: `/${pre}/${item.id}/`, link: `/${item.collection}/${item.id}/`,
customData: `<entryType>${item.collection}</entryType>`,
})), })),
}); });
} }

View File

@ -17,7 +17,7 @@ const data = [...posts, ...projects] as CollectionEntry<"blog">[];
<PageLayout title={SEARCH.TITLE} description={SEARCH.DESCRIPTION}> <PageLayout title={SEARCH.TITLE} description={SEARCH.DESCRIPTION}>
<TopLayout> <TopLayout>
<div class="animate page-heading"> <div class="animate page-heading font-departure">
{SEARCH.TITLE} {SEARCH.TITLE}
</div> </div>
</TopLayout> </TopLayout>

View File

@ -1,64 +0,0 @@
---
import { getCollection, render } from "astro:content";
import PageLayout from "@/layouts/PageLayout.astro";
import TopLayout from "@/layouts/TopLayout.astro";
import BottomLayout from "@/layouts/BottomLayout.astro";
import { WORK } from "@/consts";
const collection = await getCollection("work");
collection.sort(
(a, b) =>
new Date(b.data.dateStart).getTime() -
new Date(a.data.dateStart).getTime(),
);
const work = await Promise.all(
collection.map(async (item) => {
const { Content } = await render(item);
return { ...item, Content };
}),
);
function formatWorkDate(input: Date | string) {
if (typeof input === "string") return input;
const month = input.toLocaleDateString("en-US", {
month: "short",
});
const year = new Date(input).getFullYear();
return `${month} ${year}`;
}
---
<PageLayout title={WORK.TITLE} description={WORK.DESCRIPTION}>
<TopLayout>
<div class="animate page-heading">
{WORK.TITLE}
</div>
</TopLayout>
<BottomLayout>
<ul>
{
work.map((entry) => (
<li class="animate border-b border-black/10 dark:border-white/25 mt-4 py-8 first-of-type:mt-0 first-of-type:pt-0 last-of-type:border-none">
<div class="text-sm uppercase mb-4">
{formatWorkDate(entry.data.dateStart)} -{" "}
{formatWorkDate(entry.data.dateEnd)}
</div>
<div class="text-black dark:text-white font-semibold">
{entry.data.company}
</div>
<div class="text-sm font-semibold">
{entry.data.role}
</div>
<article class="prose dark:prose-invert">
<entry.Content />
</article>
</li>
))
}
</ul>
</BottomLayout>
</PageLayout>

View File

@ -10,6 +10,7 @@
font-style: normal; font-style: normal;
font-display: swap; font-display: swap;
} }
@font-face { @font-face {
font-family: "FiraCode"; font-family: "FiraCode";
src: url("/fonts/FiraCode-Regular.woff") format("woff"); src: url("/fonts/FiraCode-Regular.woff") format("woff");
@ -17,6 +18,7 @@
font-style: normal; font-style: normal;
font-display: swap; font-display: swap;
} }
@font-face { @font-face {
font-family: "FiraCode"; font-family: "FiraCode";
src: url("/fonts/FiraCode-Bold.woff") format("woff"); src: url("/fonts/FiraCode-Bold.woff") format("woff");
@ -28,19 +30,19 @@
html { html {
overflow-y: scroll; overflow-y: scroll;
color-scheme: light; color-scheme: light;
background-color: white; background-color: oklch(87.1% 0.006 286.286);
font-family: "FiraCode", monospace; font-family: "FiraCode", monospace;
} }
html.dark { html.dark {
color-scheme: dark; color-scheme: dark;
background-color: black; background-color: oklch(21% 0.006 285.885);
} }
html, html,
body { body {
@apply h-full w-full antialiased; @apply h-full w-full antialiased;
@apply bg-white dark:bg-black; @apply bg-white dark:bg-zinc-900;
@apply text-black/75 dark:text-white/75; @apply text-black/75 dark:text-white/75;
} }
@ -49,7 +51,7 @@
} }
main { main {
@apply flex flex-col flex-1 bg-white dark:bg-black; @apply flex flex-col flex-1 bg-zinc-300 dark:bg-zinc-900;
} }
header { header {
@ -62,7 +64,7 @@
} }
header.scrolled { header.scrolled {
@apply bg-white/75 dark:bg-black/50; @apply bg-zinc-200/75 dark:bg-zinc-950/50;
@apply border-black/10 dark:border-white/25; @apply border-black/10 dark:border-white/25;
@apply backdrop-blur-sm saturate-200; @apply backdrop-blur-sm saturate-200;
} }
@ -79,6 +81,17 @@
@apply transition-all duration-300 ease-in-out; @apply transition-all duration-300 ease-in-out;
} }
.animate {
opacity: 0;
transform: translateY(5px);
transition: opacity 1s ease, transform 1s ease;
}
.animate.show {
opacity: 1;
transform: translateY(0);
}
article img { article img {
padding-top: 20px; padding-top: 20px;
padding-bottom: 20px; padding-bottom: 20px;