mirror of
https://github.com/anotherhadi/blog.git
synced 2026-05-20 13:32:33 +02:00
Compare commits
27 Commits
e3f0fc5735
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 25fb5a4bf0 | |||
| d257a0f26e | |||
| 3dfbdcf970 | |||
| 930c3bf3bb | |||
| a055640fa8 | |||
| 9c0bbc4b77 | |||
| 7968c662f6 | |||
| 99890dd1ef | |||
| db42928299 | |||
| 73b668b204 | |||
| c314445219 | |||
| b4b755b608 | |||
| 3e60ae5a35 | |||
| 4f64ccf706 | |||
| d6d410a2fa | |||
| a74f6b91d4 | |||
| 35ac328d5e | |||
| 5ad26be352 | |||
| fac0a2fff6 | |||
| d2424e0a17 | |||
| 3b17b01d86 | |||
| 0e83788a15 | |||
| eea8c3e9be | |||
| 761e8a20cb | |||
| 1025d5bfa1 | |||
| f00515e4c3 | |||
| 5472ac3449 |
+2
-2
@@ -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()]
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -8,13 +8,14 @@
|
|||||||
"@astrojs/mdx": "5.0.4",
|
"@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": "6.1.9",
|
"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": {
|
||||||
@@ -42,6 +43,8 @@
|
|||||||
|
|
||||||
"@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/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/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=="],
|
||||||
@@ -256,6 +259,12 @@
|
|||||||
|
|
||||||
"@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=="],
|
||||||
@@ -308,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=="],
|
||||||
@@ -412,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=="],
|
||||||
@@ -458,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=="],
|
||||||
@@ -560,6 +579,8 @@
|
|||||||
|
|
||||||
"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-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=="],
|
"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=="],
|
||||||
@@ -596,12 +617,12 @@
|
|||||||
|
|
||||||
"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.2", "", { "dependencies": { "@babel/parser": "^7.29.0", "@babel/types": "^7.29.0", "source-map-js": "^1.2.1" } }, "sha512-E3ZJh4J3S9KfwdjZhe2afj6R9lGIN5Pher1pF39UGrXRqq/VDaGVIGN13BjHd2u8B61hArAGOnso7nBOouW3TQ=="],
|
"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=="],
|
||||||
@@ -838,6 +859,8 @@
|
|||||||
|
|
||||||
"sax": ["sax@1.4.3", "", {}, "sha512-yqYn1JhPczigF94DMS+shiDMjDowYO6y9+wB/4WgO0Y19jWYk0lQ4tuG5KI7kj4FTp1wxPj5IFfcrz/s1c3jjQ=="],
|
"sax": ["sax@1.4.3", "", {}, "sha512-yqYn1JhPczigF94DMS+shiDMjDowYO6y9+wB/4WgO0Y19jWYk0lQ4tuG5KI7kj4FTp1wxPj5IFfcrz/s1c3jjQ=="],
|
||||||
|
|
||||||
|
"scule": ["scule@1.3.0", "", {}, "sha512-6FtHJEvt+pVMIB9IBY+IcCJ6Z5f1iQnytgyfKMhDKgmzYG+TeH/wx1y3l27rshSbLiSanrR9ffZDrEsmjlQF2g=="],
|
||||||
|
|
||||||
"semver": ["semver@7.7.4", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA=="],
|
"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=="],
|
||||||
@@ -870,6 +893,10 @@
|
|||||||
|
|
||||||
"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=="],
|
||||||
|
|
||||||
|
"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=="],
|
"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=="],
|
||||||
@@ -938,7 +965,7 @@
|
|||||||
|
|
||||||
"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.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=="],
|
"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=="],
|
||||||
|
|
||||||
@@ -996,6 +1023,8 @@
|
|||||||
|
|
||||||
"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=="],
|
||||||
|
|
||||||
|
"zimmerframe": ["zimmerframe@1.1.4", "", {}, "sha512-B58NGBEoc8Y9MWWCQGl/gq9xBCe4IiKM0a2x7GZdQKOW5Exr8S1W24J6OgM1njK8xCRGvAJIL/MxXHf6SkmQKQ=="],
|
||||||
|
|
||||||
"zod": ["zod@4.3.6", "", {}, "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg=="],
|
"zod": ["zod@4.3.6", "", {}, "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg=="],
|
||||||
|
|
||||||
"zwitch": ["zwitch@2.0.4", "", {}, "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A=="],
|
"zwitch": ["zwitch@2.0.4", "", {}, "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A=="],
|
||||||
@@ -1026,8 +1055,6 @@
|
|||||||
|
|
||||||
"anymatch/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="],
|
"anymatch/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="],
|
||||||
|
|
||||||
"astro/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=="],
|
|
||||||
|
|
||||||
"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=="],
|
||||||
|
|
||||||
"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=="],
|
||||||
@@ -1058,6 +1085,8 @@
|
|||||||
|
|
||||||
"sharp/semver": ["semver@7.7.3", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q=="],
|
"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=="],
|
"svgo/sax": ["sax@1.6.0", "", {}, "sha512-6R3J5M4AcbtLUdZmRv2SygeVaM7IhrLXu9BmnOGmmACak8fiUtOsYNWUS4uK7upbmHIBbLBeFeI//477BKLBzA=="],
|
||||||
|
|
||||||
"tinyglobby/picomatch": ["picomatch@4.0.3", "", {}, "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q=="],
|
"tinyglobby/picomatch": ["picomatch@4.0.3", "", {}, "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q=="],
|
||||||
@@ -1068,10 +1097,6 @@
|
|||||||
|
|
||||||
"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=="],
|
|
||||||
|
|
||||||
"vite/picomatch": ["picomatch@4.0.3", "", {}, "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q=="],
|
|
||||||
|
|
||||||
"volar-service-typescript/semver": ["semver@7.7.3", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q=="],
|
"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=="],
|
||||||
@@ -1085,57 +1110,5 @@
|
|||||||
"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=="],
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -53,6 +53,10 @@
|
|||||||
url = "https://registry.npmjs.org/@astrojs/sitemap/-/sitemap-3.7.2.tgz";
|
url = "https://registry.npmjs.org/@astrojs/sitemap/-/sitemap-3.7.2.tgz";
|
||||||
hash = "sha512-PqkzkcZTb5ICiyIR8VoKbIAP/laNRXi5tw616N1Ckk+40oNB8Can1AzVV56lrbC5GKSZFCyJYUVYqVivMisvpA==";
|
hash = "sha512-PqkzkcZTb5ICiyIR8VoKbIAP/laNRXi5tw616N1Ckk+40oNB8Can1AzVV56lrbC5GKSZFCyJYUVYqVivMisvpA==";
|
||||||
};
|
};
|
||||||
|
"@astrojs/svelte@8.1.0" = fetchurl {
|
||||||
|
url = "https://registry.npmjs.org/@astrojs/svelte/-/svelte-8.1.0.tgz";
|
||||||
|
hash = "sha512-yZrHRFOxDJeo2hr9rGAMou6/6OL3agEaUCvWNWrea8YhZultsERTYZthfKNC58onAtZs76xNklOYV+G2Dp10kw==";
|
||||||
|
};
|
||||||
"@astrojs/telemetry@3.3.1" = fetchurl {
|
"@astrojs/telemetry@3.3.1" = fetchurl {
|
||||||
url = "https://registry.npmjs.org/@astrojs/telemetry/-/telemetry-3.3.1.tgz";
|
url = "https://registry.npmjs.org/@astrojs/telemetry/-/telemetry-3.3.1.tgz";
|
||||||
hash = "sha512-7fcIxXS9J4ls5tr8b3ww9rbAIz2+HrhNJYZdkAhhB4za/I5IZ/60g+Bs8q7zwG0tOIZfNB4JWhVJ1Qkl/OrNCw==";
|
hash = "sha512-7fcIxXS9J4ls5tr8b3ww9rbAIz2+HrhNJYZdkAhhB4za/I5IZ/60g+Bs8q7zwG0tOIZfNB4JWhVJ1Qkl/OrNCw==";
|
||||||
@@ -133,210 +137,106 @@
|
|||||||
url = "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.0.tgz";
|
url = "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.0.tgz";
|
||||||
hash = "sha512-N10dEJNSsUx41Z6pZsXU8FjPjpBEplgH24sfkmITrBED1/U2Esum9F3lfLrMjKHHjmi557zQn7kR9R+XWXu5Rg==";
|
hash = "sha512-N10dEJNSsUx41Z6pZsXU8FjPjpBEplgH24sfkmITrBED1/U2Esum9F3lfLrMjKHHjmi557zQn7kR9R+XWXu5Rg==";
|
||||||
};
|
};
|
||||||
"@esbuild/aix-ppc64@0.25.12" = fetchurl {
|
|
||||||
url = "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz";
|
|
||||||
hash = "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==";
|
|
||||||
};
|
|
||||||
"@esbuild/aix-ppc64@0.27.4" = fetchurl {
|
"@esbuild/aix-ppc64@0.27.4" = fetchurl {
|
||||||
url = "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.4.tgz";
|
url = "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.4.tgz";
|
||||||
hash = "sha512-cQPwL2mp2nSmHHJlCyoXgHGhbEPMrEEU5xhkcy3Hs/O7nGZqEpZ2sUtLaL9MORLtDfRvVl2/3PAuEkYZH0Ty8Q==";
|
hash = "sha512-cQPwL2mp2nSmHHJlCyoXgHGhbEPMrEEU5xhkcy3Hs/O7nGZqEpZ2sUtLaL9MORLtDfRvVl2/3PAuEkYZH0Ty8Q==";
|
||||||
};
|
};
|
||||||
"@esbuild/android-arm64@0.25.12" = fetchurl {
|
|
||||||
url = "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.12.tgz";
|
|
||||||
hash = "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==";
|
|
||||||
};
|
|
||||||
"@esbuild/android-arm64@0.27.4" = fetchurl {
|
"@esbuild/android-arm64@0.27.4" = fetchurl {
|
||||||
url = "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.4.tgz";
|
url = "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.4.tgz";
|
||||||
hash = "sha512-gdLscB7v75wRfu7QSm/zg6Rx29VLdy9eTr2t44sfTW7CxwAtQghZ4ZnqHk3/ogz7xao0QAgrkradbBzcqFPasw==";
|
hash = "sha512-gdLscB7v75wRfu7QSm/zg6Rx29VLdy9eTr2t44sfTW7CxwAtQghZ4ZnqHk3/ogz7xao0QAgrkradbBzcqFPasw==";
|
||||||
};
|
};
|
||||||
"@esbuild/android-arm@0.25.12" = fetchurl {
|
|
||||||
url = "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.12.tgz";
|
|
||||||
hash = "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==";
|
|
||||||
};
|
|
||||||
"@esbuild/android-arm@0.27.4" = fetchurl {
|
"@esbuild/android-arm@0.27.4" = fetchurl {
|
||||||
url = "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.4.tgz";
|
url = "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.4.tgz";
|
||||||
hash = "sha512-X9bUgvxiC8CHAGKYufLIHGXPJWnr0OCdR0anD2e21vdvgCI8lIfqFbnoeOz7lBjdrAGUhqLZLcQo6MLhTO2DKQ==";
|
hash = "sha512-X9bUgvxiC8CHAGKYufLIHGXPJWnr0OCdR0anD2e21vdvgCI8lIfqFbnoeOz7lBjdrAGUhqLZLcQo6MLhTO2DKQ==";
|
||||||
};
|
};
|
||||||
"@esbuild/android-x64@0.25.12" = fetchurl {
|
|
||||||
url = "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.12.tgz";
|
|
||||||
hash = "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==";
|
|
||||||
};
|
|
||||||
"@esbuild/android-x64@0.27.4" = fetchurl {
|
"@esbuild/android-x64@0.27.4" = fetchurl {
|
||||||
url = "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.4.tgz";
|
url = "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.4.tgz";
|
||||||
hash = "sha512-PzPFnBNVF292sfpfhiyiXCGSn9HZg5BcAz+ivBuSsl6Rk4ga1oEXAamhOXRFyMcjwr2DVtm40G65N3GLeH1Lvw==";
|
hash = "sha512-PzPFnBNVF292sfpfhiyiXCGSn9HZg5BcAz+ivBuSsl6Rk4ga1oEXAamhOXRFyMcjwr2DVtm40G65N3GLeH1Lvw==";
|
||||||
};
|
};
|
||||||
"@esbuild/darwin-arm64@0.25.12" = fetchurl {
|
|
||||||
url = "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.12.tgz";
|
|
||||||
hash = "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==";
|
|
||||||
};
|
|
||||||
"@esbuild/darwin-arm64@0.27.4" = fetchurl {
|
"@esbuild/darwin-arm64@0.27.4" = fetchurl {
|
||||||
url = "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.4.tgz";
|
url = "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.4.tgz";
|
||||||
hash = "sha512-b7xaGIwdJlht8ZFCvMkpDN6uiSmnxxK56N2GDTMYPr2/gzvfdQN8rTfBsvVKmIVY/X7EM+/hJKEIbbHs9oA4tQ==";
|
hash = "sha512-b7xaGIwdJlht8ZFCvMkpDN6uiSmnxxK56N2GDTMYPr2/gzvfdQN8rTfBsvVKmIVY/X7EM+/hJKEIbbHs9oA4tQ==";
|
||||||
};
|
};
|
||||||
"@esbuild/darwin-x64@0.25.12" = fetchurl {
|
|
||||||
url = "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.12.tgz";
|
|
||||||
hash = "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==";
|
|
||||||
};
|
|
||||||
"@esbuild/darwin-x64@0.27.4" = fetchurl {
|
"@esbuild/darwin-x64@0.27.4" = fetchurl {
|
||||||
url = "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.4.tgz";
|
url = "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.4.tgz";
|
||||||
hash = "sha512-sR+OiKLwd15nmCdqpXMnuJ9W2kpy0KigzqScqHI3Hqwr7IXxBp3Yva+yJwoqh7rE8V77tdoheRYataNKL4QrPw==";
|
hash = "sha512-sR+OiKLwd15nmCdqpXMnuJ9W2kpy0KigzqScqHI3Hqwr7IXxBp3Yva+yJwoqh7rE8V77tdoheRYataNKL4QrPw==";
|
||||||
};
|
};
|
||||||
"@esbuild/freebsd-arm64@0.25.12" = fetchurl {
|
|
||||||
url = "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.12.tgz";
|
|
||||||
hash = "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==";
|
|
||||||
};
|
|
||||||
"@esbuild/freebsd-arm64@0.27.4" = fetchurl {
|
"@esbuild/freebsd-arm64@0.27.4" = fetchurl {
|
||||||
url = "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.4.tgz";
|
url = "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.4.tgz";
|
||||||
hash = "sha512-jnfpKe+p79tCnm4GVav68A7tUFeKQwQyLgESwEAUzyxk/TJr4QdGog9sqWNcUbr/bZt/O/HXouspuQDd9JxFSw==";
|
hash = "sha512-jnfpKe+p79tCnm4GVav68A7tUFeKQwQyLgESwEAUzyxk/TJr4QdGog9sqWNcUbr/bZt/O/HXouspuQDd9JxFSw==";
|
||||||
};
|
};
|
||||||
"@esbuild/freebsd-x64@0.25.12" = fetchurl {
|
|
||||||
url = "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.12.tgz";
|
|
||||||
hash = "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==";
|
|
||||||
};
|
|
||||||
"@esbuild/freebsd-x64@0.27.4" = fetchurl {
|
"@esbuild/freebsd-x64@0.27.4" = fetchurl {
|
||||||
url = "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.4.tgz";
|
url = "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.4.tgz";
|
||||||
hash = "sha512-2kb4ceA/CpfUrIcTUl1wrP/9ad9Atrp5J94Lq69w7UwOMolPIGrfLSvAKJp0RTvkPPyn6CIWrNy13kyLikZRZQ==";
|
hash = "sha512-2kb4ceA/CpfUrIcTUl1wrP/9ad9Atrp5J94Lq69w7UwOMolPIGrfLSvAKJp0RTvkPPyn6CIWrNy13kyLikZRZQ==";
|
||||||
};
|
};
|
||||||
"@esbuild/linux-arm64@0.25.12" = fetchurl {
|
|
||||||
url = "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.12.tgz";
|
|
||||||
hash = "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==";
|
|
||||||
};
|
|
||||||
"@esbuild/linux-arm64@0.27.4" = fetchurl {
|
"@esbuild/linux-arm64@0.27.4" = fetchurl {
|
||||||
url = "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.4.tgz";
|
url = "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.4.tgz";
|
||||||
hash = "sha512-7nQOttdzVGth1iz57kxg9uCz57dxQLHWxopL6mYuYthohPKEK0vU0C3O21CcBK6KDlkYVcnDXY099HcCDXd9dA==";
|
hash = "sha512-7nQOttdzVGth1iz57kxg9uCz57dxQLHWxopL6mYuYthohPKEK0vU0C3O21CcBK6KDlkYVcnDXY099HcCDXd9dA==";
|
||||||
};
|
};
|
||||||
"@esbuild/linux-arm@0.25.12" = fetchurl {
|
|
||||||
url = "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.12.tgz";
|
|
||||||
hash = "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==";
|
|
||||||
};
|
|
||||||
"@esbuild/linux-arm@0.27.4" = fetchurl {
|
"@esbuild/linux-arm@0.27.4" = fetchurl {
|
||||||
url = "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.4.tgz";
|
url = "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.4.tgz";
|
||||||
hash = "sha512-aBYgcIxX/wd5n2ys0yESGeYMGF+pv6g0DhZr3G1ZG4jMfruU9Tl1i2Z+Wnj9/KjGz1lTLCcorqE2viePZqj4Eg==";
|
hash = "sha512-aBYgcIxX/wd5n2ys0yESGeYMGF+pv6g0DhZr3G1ZG4jMfruU9Tl1i2Z+Wnj9/KjGz1lTLCcorqE2viePZqj4Eg==";
|
||||||
};
|
};
|
||||||
"@esbuild/linux-ia32@0.25.12" = fetchurl {
|
|
||||||
url = "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.12.tgz";
|
|
||||||
hash = "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==";
|
|
||||||
};
|
|
||||||
"@esbuild/linux-ia32@0.27.4" = fetchurl {
|
"@esbuild/linux-ia32@0.27.4" = fetchurl {
|
||||||
url = "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.4.tgz";
|
url = "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.4.tgz";
|
||||||
hash = "sha512-oPtixtAIzgvzYcKBQM/qZ3R+9TEUd1aNJQu0HhGyqtx6oS7qTpvjheIWBbes4+qu1bNlo2V4cbkISr8q6gRBFA==";
|
hash = "sha512-oPtixtAIzgvzYcKBQM/qZ3R+9TEUd1aNJQu0HhGyqtx6oS7qTpvjheIWBbes4+qu1bNlo2V4cbkISr8q6gRBFA==";
|
||||||
};
|
};
|
||||||
"@esbuild/linux-loong64@0.25.12" = fetchurl {
|
|
||||||
url = "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.12.tgz";
|
|
||||||
hash = "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==";
|
|
||||||
};
|
|
||||||
"@esbuild/linux-loong64@0.27.4" = fetchurl {
|
"@esbuild/linux-loong64@0.27.4" = fetchurl {
|
||||||
url = "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.4.tgz";
|
url = "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.4.tgz";
|
||||||
hash = "sha512-8mL/vh8qeCoRcFH2nM8wm5uJP+ZcVYGGayMavi8GmRJjuI3g1v6Z7Ni0JJKAJW+m0EtUuARb6Lmp4hMjzCBWzA==";
|
hash = "sha512-8mL/vh8qeCoRcFH2nM8wm5uJP+ZcVYGGayMavi8GmRJjuI3g1v6Z7Ni0JJKAJW+m0EtUuARb6Lmp4hMjzCBWzA==";
|
||||||
};
|
};
|
||||||
"@esbuild/linux-mips64el@0.25.12" = fetchurl {
|
|
||||||
url = "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.12.tgz";
|
|
||||||
hash = "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==";
|
|
||||||
};
|
|
||||||
"@esbuild/linux-mips64el@0.27.4" = fetchurl {
|
"@esbuild/linux-mips64el@0.27.4" = fetchurl {
|
||||||
url = "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.4.tgz";
|
url = "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.4.tgz";
|
||||||
hash = "sha512-1RdrWFFiiLIW7LQq9Q2NES+HiD4NyT8Itj9AUeCl0IVCA459WnPhREKgwrpaIfTOe+/2rdntisegiPWn/r/aAw==";
|
hash = "sha512-1RdrWFFiiLIW7LQq9Q2NES+HiD4NyT8Itj9AUeCl0IVCA459WnPhREKgwrpaIfTOe+/2rdntisegiPWn/r/aAw==";
|
||||||
};
|
};
|
||||||
"@esbuild/linux-ppc64@0.25.12" = fetchurl {
|
|
||||||
url = "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.12.tgz";
|
|
||||||
hash = "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==";
|
|
||||||
};
|
|
||||||
"@esbuild/linux-ppc64@0.27.4" = fetchurl {
|
"@esbuild/linux-ppc64@0.27.4" = fetchurl {
|
||||||
url = "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.4.tgz";
|
url = "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.4.tgz";
|
||||||
hash = "sha512-tLCwNG47l3sd9lpfyx9LAGEGItCUeRCWeAx6x2Jmbav65nAwoPXfewtAdtbtit/pJFLUWOhpv0FpS6GQAmPrHA==";
|
hash = "sha512-tLCwNG47l3sd9lpfyx9LAGEGItCUeRCWeAx6x2Jmbav65nAwoPXfewtAdtbtit/pJFLUWOhpv0FpS6GQAmPrHA==";
|
||||||
};
|
};
|
||||||
"@esbuild/linux-riscv64@0.25.12" = fetchurl {
|
|
||||||
url = "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.12.tgz";
|
|
||||||
hash = "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==";
|
|
||||||
};
|
|
||||||
"@esbuild/linux-riscv64@0.27.4" = fetchurl {
|
"@esbuild/linux-riscv64@0.27.4" = fetchurl {
|
||||||
url = "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.4.tgz";
|
url = "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.4.tgz";
|
||||||
hash = "sha512-BnASypppbUWyqjd1KIpU4AUBiIhVr6YlHx/cnPgqEkNoVOhHg+YiSVxM1RLfiy4t9cAulbRGTNCKOcqHrEQLIw==";
|
hash = "sha512-BnASypppbUWyqjd1KIpU4AUBiIhVr6YlHx/cnPgqEkNoVOhHg+YiSVxM1RLfiy4t9cAulbRGTNCKOcqHrEQLIw==";
|
||||||
};
|
};
|
||||||
"@esbuild/linux-s390x@0.25.12" = fetchurl {
|
|
||||||
url = "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.12.tgz";
|
|
||||||
hash = "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==";
|
|
||||||
};
|
|
||||||
"@esbuild/linux-s390x@0.27.4" = fetchurl {
|
"@esbuild/linux-s390x@0.27.4" = fetchurl {
|
||||||
url = "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.4.tgz";
|
url = "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.4.tgz";
|
||||||
hash = "sha512-+eUqgb/Z7vxVLezG8bVB9SfBie89gMueS+I0xYh2tJdw3vqA/0ImZJ2ROeWwVJN59ihBeZ7Tu92dF/5dy5FttA==";
|
hash = "sha512-+eUqgb/Z7vxVLezG8bVB9SfBie89gMueS+I0xYh2tJdw3vqA/0ImZJ2ROeWwVJN59ihBeZ7Tu92dF/5dy5FttA==";
|
||||||
};
|
};
|
||||||
"@esbuild/linux-x64@0.25.12" = fetchurl {
|
|
||||||
url = "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.12.tgz";
|
|
||||||
hash = "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==";
|
|
||||||
};
|
|
||||||
"@esbuild/linux-x64@0.27.4" = fetchurl {
|
"@esbuild/linux-x64@0.27.4" = fetchurl {
|
||||||
url = "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.4.tgz";
|
url = "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.4.tgz";
|
||||||
hash = "sha512-S5qOXrKV8BQEzJPVxAwnryi2+Iq5pB40gTEIT69BQONqR7JH1EPIcQ/Uiv9mCnn05jff9umq/5nqzxlqTOg9NA==";
|
hash = "sha512-S5qOXrKV8BQEzJPVxAwnryi2+Iq5pB40gTEIT69BQONqR7JH1EPIcQ/Uiv9mCnn05jff9umq/5nqzxlqTOg9NA==";
|
||||||
};
|
};
|
||||||
"@esbuild/netbsd-arm64@0.25.12" = fetchurl {
|
|
||||||
url = "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.12.tgz";
|
|
||||||
hash = "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==";
|
|
||||||
};
|
|
||||||
"@esbuild/netbsd-arm64@0.27.4" = fetchurl {
|
"@esbuild/netbsd-arm64@0.27.4" = fetchurl {
|
||||||
url = "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.4.tgz";
|
url = "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.4.tgz";
|
||||||
hash = "sha512-xHT8X4sb0GS8qTqiwzHqpY00C95DPAq7nAwX35Ie/s+LO9830hrMd3oX0ZMKLvy7vsonee73x0lmcdOVXFzd6Q==";
|
hash = "sha512-xHT8X4sb0GS8qTqiwzHqpY00C95DPAq7nAwX35Ie/s+LO9830hrMd3oX0ZMKLvy7vsonee73x0lmcdOVXFzd6Q==";
|
||||||
};
|
};
|
||||||
"@esbuild/netbsd-x64@0.25.12" = fetchurl {
|
|
||||||
url = "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.12.tgz";
|
|
||||||
hash = "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==";
|
|
||||||
};
|
|
||||||
"@esbuild/netbsd-x64@0.27.4" = fetchurl {
|
"@esbuild/netbsd-x64@0.27.4" = fetchurl {
|
||||||
url = "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.4.tgz";
|
url = "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.4.tgz";
|
||||||
hash = "sha512-RugOvOdXfdyi5Tyv40kgQnI0byv66BFgAqjdgtAKqHoZTbTF2QqfQrFwa7cHEORJf6X2ht+l9ABLMP0dnKYsgg==";
|
hash = "sha512-RugOvOdXfdyi5Tyv40kgQnI0byv66BFgAqjdgtAKqHoZTbTF2QqfQrFwa7cHEORJf6X2ht+l9ABLMP0dnKYsgg==";
|
||||||
};
|
};
|
||||||
"@esbuild/openbsd-arm64@0.25.12" = fetchurl {
|
|
||||||
url = "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.12.tgz";
|
|
||||||
hash = "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==";
|
|
||||||
};
|
|
||||||
"@esbuild/openbsd-arm64@0.27.4" = fetchurl {
|
"@esbuild/openbsd-arm64@0.27.4" = fetchurl {
|
||||||
url = "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.4.tgz";
|
url = "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.4.tgz";
|
||||||
hash = "sha512-2MyL3IAaTX+1/qP0O1SwskwcwCoOI4kV2IBX1xYnDDqthmq5ArrW94qSIKCAuRraMgPOmG0RDTA74mzYNQA9ow==";
|
hash = "sha512-2MyL3IAaTX+1/qP0O1SwskwcwCoOI4kV2IBX1xYnDDqthmq5ArrW94qSIKCAuRraMgPOmG0RDTA74mzYNQA9ow==";
|
||||||
};
|
};
|
||||||
"@esbuild/openbsd-x64@0.25.12" = fetchurl {
|
|
||||||
url = "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.12.tgz";
|
|
||||||
hash = "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==";
|
|
||||||
};
|
|
||||||
"@esbuild/openbsd-x64@0.27.4" = fetchurl {
|
"@esbuild/openbsd-x64@0.27.4" = fetchurl {
|
||||||
url = "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.4.tgz";
|
url = "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.4.tgz";
|
||||||
hash = "sha512-u8fg/jQ5aQDfsnIV6+KwLOf1CmJnfu1ShpwqdwC0uA7ZPwFws55Ngc12vBdeUdnuWoQYx/SOQLGDcdlfXhYmXQ==";
|
hash = "sha512-u8fg/jQ5aQDfsnIV6+KwLOf1CmJnfu1ShpwqdwC0uA7ZPwFws55Ngc12vBdeUdnuWoQYx/SOQLGDcdlfXhYmXQ==";
|
||||||
};
|
};
|
||||||
"@esbuild/openharmony-arm64@0.25.12" = fetchurl {
|
|
||||||
url = "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.12.tgz";
|
|
||||||
hash = "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==";
|
|
||||||
};
|
|
||||||
"@esbuild/openharmony-arm64@0.27.4" = fetchurl {
|
"@esbuild/openharmony-arm64@0.27.4" = fetchurl {
|
||||||
url = "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.4.tgz";
|
url = "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.4.tgz";
|
||||||
hash = "sha512-JkTZrl6VbyO8lDQO3yv26nNr2RM2yZzNrNHEsj9bm6dOwwu9OYN28CjzZkH57bh4w0I2F7IodpQvUAEd1mbWXg==";
|
hash = "sha512-JkTZrl6VbyO8lDQO3yv26nNr2RM2yZzNrNHEsj9bm6dOwwu9OYN28CjzZkH57bh4w0I2F7IodpQvUAEd1mbWXg==";
|
||||||
};
|
};
|
||||||
"@esbuild/sunos-x64@0.25.12" = fetchurl {
|
|
||||||
url = "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.12.tgz";
|
|
||||||
hash = "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==";
|
|
||||||
};
|
|
||||||
"@esbuild/sunos-x64@0.27.4" = fetchurl {
|
"@esbuild/sunos-x64@0.27.4" = fetchurl {
|
||||||
url = "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.4.tgz";
|
url = "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.4.tgz";
|
||||||
hash = "sha512-/gOzgaewZJfeJTlsWhvUEmUG4tWEY2Spp5M20INYRg2ZKl9QPO3QEEgPeRtLjEWSW8FilRNacPOg8R1uaYkA6g==";
|
hash = "sha512-/gOzgaewZJfeJTlsWhvUEmUG4tWEY2Spp5M20INYRg2ZKl9QPO3QEEgPeRtLjEWSW8FilRNacPOg8R1uaYkA6g==";
|
||||||
};
|
};
|
||||||
"@esbuild/win32-arm64@0.25.12" = fetchurl {
|
|
||||||
url = "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.12.tgz";
|
|
||||||
hash = "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==";
|
|
||||||
};
|
|
||||||
"@esbuild/win32-arm64@0.27.4" = fetchurl {
|
"@esbuild/win32-arm64@0.27.4" = fetchurl {
|
||||||
url = "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.4.tgz";
|
url = "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.4.tgz";
|
||||||
hash = "sha512-Z9SExBg2y32smoDQdf1HRwHRt6vAHLXcxD2uGgO/v2jK7Y718Ix4ndsbNMU/+1Qiem9OiOdaqitioZwxivhXYg==";
|
hash = "sha512-Z9SExBg2y32smoDQdf1HRwHRt6vAHLXcxD2uGgO/v2jK7Y718Ix4ndsbNMU/+1Qiem9OiOdaqitioZwxivhXYg==";
|
||||||
};
|
};
|
||||||
"@esbuild/win32-ia32@0.25.12" = fetchurl {
|
|
||||||
url = "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.12.tgz";
|
|
||||||
hash = "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==";
|
|
||||||
};
|
|
||||||
"@esbuild/win32-ia32@0.27.4" = fetchurl {
|
"@esbuild/win32-ia32@0.27.4" = fetchurl {
|
||||||
url = "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.4.tgz";
|
url = "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.4.tgz";
|
||||||
hash = "sha512-DAyGLS0Jz5G5iixEbMHi5KdiApqHBWMGzTtMiJ72ZOLhbu/bzxgAe8Ue8CTS3n3HbIUHQz/L51yMdGMeoxXNJw==";
|
hash = "sha512-DAyGLS0Jz5G5iixEbMHi5KdiApqHBWMGzTtMiJ72ZOLhbu/bzxgAe8Ue8CTS3n3HbIUHQz/L51yMdGMeoxXNJw==";
|
||||||
};
|
};
|
||||||
"@esbuild/win32-x64@0.25.12" = fetchurl {
|
|
||||||
url = "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.12.tgz";
|
|
||||||
hash = "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==";
|
|
||||||
};
|
|
||||||
"@esbuild/win32-x64@0.27.4" = fetchurl {
|
"@esbuild/win32-x64@0.27.4" = fetchurl {
|
||||||
url = "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.4.tgz";
|
url = "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.4.tgz";
|
||||||
hash = "sha512-+knoa0BDoeXgkNvvV1vvbZX4+hizelrkwmGJBdT17t8FNPwG2lKemmuMZlmaNQ3ws3DKKCxpb4zRZEIp3UxFCg==";
|
hash = "sha512-+knoa0BDoeXgkNvvV1vvbZX4+hizelrkwmGJBdT17t8FNPwG2lKemmuMZlmaNQ3ws3DKKCxpb4zRZEIp3UxFCg==";
|
||||||
@@ -601,6 +501,18 @@
|
|||||||
url = "https://registry.npmjs.org/@shikijs/vscode-textmate/-/vscode-textmate-10.0.2.tgz";
|
url = "https://registry.npmjs.org/@shikijs/vscode-textmate/-/vscode-textmate-10.0.2.tgz";
|
||||||
hash = "sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg==";
|
hash = "sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg==";
|
||||||
};
|
};
|
||||||
|
"@sveltejs/acorn-typescript@1.0.9" = fetchurl {
|
||||||
|
url = "https://registry.npmjs.org/@sveltejs/acorn-typescript/-/acorn-typescript-1.0.9.tgz";
|
||||||
|
hash = "sha512-lVJX6qEgs/4DOcRTpo56tmKzVPtoWAaVbL4hfO7t7NVwl9AAXzQR6cihesW1BmNMPl+bK6dreu2sOKBP2Q9CIA==";
|
||||||
|
};
|
||||||
|
"@sveltejs/vite-plugin-svelte-inspector@5.0.2" = fetchurl {
|
||||||
|
url = "https://registry.npmjs.org/@sveltejs/vite-plugin-svelte-inspector/-/vite-plugin-svelte-inspector-5.0.2.tgz";
|
||||||
|
hash = "sha512-TZzRTcEtZffICSAoZGkPSl6Etsj2torOVrx6Uw0KpXxrec9Gg6jFWQ60Q3+LmNGfZSxHRCZL7vXVZIWmuV50Ig==";
|
||||||
|
};
|
||||||
|
"@sveltejs/vite-plugin-svelte@6.2.4" = fetchurl {
|
||||||
|
url = "https://registry.npmjs.org/@sveltejs/vite-plugin-svelte/-/vite-plugin-svelte-6.2.4.tgz";
|
||||||
|
hash = "sha512-ou/d51QSdTyN26D7h6dSpusAKaZkAiGM55/AKYi+9AGZw7q85hElbjK3kEyzXHhLSnRISHOYzVge6x0jRZ7DXA==";
|
||||||
|
};
|
||||||
"@tailwindcss/node@4.2.4" = fetchurl {
|
"@tailwindcss/node@4.2.4" = fetchurl {
|
||||||
url = "https://registry.npmjs.org/@tailwindcss/node/-/node-4.2.4.tgz";
|
url = "https://registry.npmjs.org/@tailwindcss/node/-/node-4.2.4.tgz";
|
||||||
hash = "sha512-Ai7+yQPxz3ddrDQzFfBKdHEVBg0w3Zl83jnjuwxnZOsnH9pGn93QHQtpU0p/8rYWxvbFZHneni6p1BSLK4DkGA==";
|
hash = "sha512-Ai7+yQPxz3ddrDQzFfBKdHEVBg0w3Zl83jnjuwxnZOsnH9pGn93QHQtpU0p/8rYWxvbFZHneni6p1BSLK4DkGA==";
|
||||||
@@ -713,6 +625,10 @@
|
|||||||
url = "https://registry.npmjs.org/@types/sax/-/sax-1.2.7.tgz";
|
url = "https://registry.npmjs.org/@types/sax/-/sax-1.2.7.tgz";
|
||||||
hash = "sha512-rO73L89PJxeYM3s3pPPjiPgVVcymqU490g0YO5n5By0k2Erzj6tay/4lr1CHAAU4JyOWd1rpQ8bCf6cZfHU96A==";
|
hash = "sha512-rO73L89PJxeYM3s3pPPjiPgVVcymqU490g0YO5n5By0k2Erzj6tay/4lr1CHAAU4JyOWd1rpQ8bCf6cZfHU96A==";
|
||||||
};
|
};
|
||||||
|
"@types/trusted-types@2.0.7" = fetchurl {
|
||||||
|
url = "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz";
|
||||||
|
hash = "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==";
|
||||||
|
};
|
||||||
"@types/unist@2.0.11" = fetchurl {
|
"@types/unist@2.0.11" = fetchurl {
|
||||||
url = "https://registry.npmjs.org/@types/unist/-/unist-2.0.11.tgz";
|
url = "https://registry.npmjs.org/@types/unist/-/unist-2.0.11.tgz";
|
||||||
hash = "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==";
|
hash = "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==";
|
||||||
@@ -797,6 +713,10 @@
|
|||||||
url = "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz";
|
url = "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz";
|
||||||
hash = "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==";
|
hash = "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==";
|
||||||
};
|
};
|
||||||
|
"aria-query@5.3.1" = fetchurl {
|
||||||
|
url = "https://registry.npmjs.org/aria-query/-/aria-query-5.3.1.tgz";
|
||||||
|
hash = "sha512-Z/ZeOgVl7bcSYZ/u/rh0fOpvEpq//LZmdbkXyc7syVzjPAhfOa9ebsdTSjEBDU4vs5nC98Kfduj1uFo0qyET3g==";
|
||||||
|
};
|
||||||
"aria-query@5.3.2" = fetchurl {
|
"aria-query@5.3.2" = fetchurl {
|
||||||
url = "https://registry.npmjs.org/aria-query/-/aria-query-5.3.2.tgz";
|
url = "https://registry.npmjs.org/aria-query/-/aria-query-5.3.2.tgz";
|
||||||
hash = "sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==";
|
hash = "sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==";
|
||||||
@@ -937,6 +857,14 @@
|
|||||||
url = "https://registry.npmjs.org/decode-named-character-reference/-/decode-named-character-reference-1.2.0.tgz";
|
url = "https://registry.npmjs.org/decode-named-character-reference/-/decode-named-character-reference-1.2.0.tgz";
|
||||||
hash = "sha512-c6fcElNV6ShtZXmsgNgFFV5tVX2PaV4g+MOAkb8eXHvn6sryJBrZa9r0zV6+dtTyoCKxtDy5tyQ5ZwQuidtd+Q==";
|
hash = "sha512-c6fcElNV6ShtZXmsgNgFFV5tVX2PaV4g+MOAkb8eXHvn6sryJBrZa9r0zV6+dtTyoCKxtDy5tyQ5ZwQuidtd+Q==";
|
||||||
};
|
};
|
||||||
|
"dedent-js@1.0.1" = fetchurl {
|
||||||
|
url = "https://registry.npmjs.org/dedent-js/-/dedent-js-1.0.1.tgz";
|
||||||
|
hash = "sha512-OUepMozQULMLUmhxS95Vudo0jb0UchLimi3+pQ2plj61Fcy8axbP9hbiD4Sz6DPqn6XG3kfmziVfQ1rSys5AJQ==";
|
||||||
|
};
|
||||||
|
"deepmerge@4.3.1" = fetchurl {
|
||||||
|
url = "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz";
|
||||||
|
hash = "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==";
|
||||||
|
};
|
||||||
"defu@6.1.4" = fetchurl {
|
"defu@6.1.4" = fetchurl {
|
||||||
url = "https://registry.npmjs.org/defu/-/defu-6.1.4.tgz";
|
url = "https://registry.npmjs.org/defu/-/defu-6.1.4.tgz";
|
||||||
hash = "sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==";
|
hash = "sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==";
|
||||||
@@ -1021,10 +949,6 @@
|
|||||||
url = "https://registry.npmjs.org/esast-util-from-js/-/esast-util-from-js-2.0.1.tgz";
|
url = "https://registry.npmjs.org/esast-util-from-js/-/esast-util-from-js-2.0.1.tgz";
|
||||||
hash = "sha512-8Ja+rNJ0Lt56Pcf3TAmpBZjmx8ZcK5Ts4cAzIOjsjevg9oSXJnl6SUQ2EevU8tv3h6ZLWmoKL5H4fgWvdvfETw==";
|
hash = "sha512-8Ja+rNJ0Lt56Pcf3TAmpBZjmx8ZcK5Ts4cAzIOjsjevg9oSXJnl6SUQ2EevU8tv3h6ZLWmoKL5H4fgWvdvfETw==";
|
||||||
};
|
};
|
||||||
"esbuild@0.25.12" = fetchurl {
|
|
||||||
url = "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz";
|
|
||||||
hash = "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==";
|
|
||||||
};
|
|
||||||
"esbuild@0.27.4" = fetchurl {
|
"esbuild@0.27.4" = fetchurl {
|
||||||
url = "https://registry.npmjs.org/esbuild/-/esbuild-0.27.4.tgz";
|
url = "https://registry.npmjs.org/esbuild/-/esbuild-0.27.4.tgz";
|
||||||
hash = "sha512-Rq4vbHnYkK5fws5NF7MYTU68FPRE1ajX7heQ/8QXXWqNgqqJ/GkmmyxIzUnf2Sr/bakf8l54716CcMGHYhMrrQ==";
|
hash = "sha512-Rq4vbHnYkK5fws5NF7MYTU68FPRE1ajX7heQ/8QXXWqNgqqJ/GkmmyxIzUnf2Sr/bakf8l54716CcMGHYhMrrQ==";
|
||||||
@@ -1037,6 +961,14 @@
|
|||||||
url = "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz";
|
url = "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz";
|
||||||
hash = "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==";
|
hash = "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==";
|
||||||
};
|
};
|
||||||
|
"esm-env@1.2.2" = fetchurl {
|
||||||
|
url = "https://registry.npmjs.org/esm-env/-/esm-env-1.2.2.tgz";
|
||||||
|
hash = "sha512-Epxrv+Nr/CaL4ZcFGPJIYLWFom+YeV1DqMLHJoEd9SYRxNbaFruBwfEX/kkHUJf55j2+TUbmDcmuilbP1TmXHA==";
|
||||||
|
};
|
||||||
|
"esrap@2.2.5" = fetchurl {
|
||||||
|
url = "https://registry.npmjs.org/esrap/-/esrap-2.2.5.tgz";
|
||||||
|
hash = "sha512-/yLB1538mag+dn0wsePTe8C0rDIjUOaJpMs2McodSzmM2msWcZsBSdRtg6HOBt0A/r82BN+Md3pgwSc/uWt2Ig==";
|
||||||
|
};
|
||||||
"estree-util-attach-comments@3.0.0" = fetchurl {
|
"estree-util-attach-comments@3.0.0" = fetchurl {
|
||||||
url = "https://registry.npmjs.org/estree-util-attach-comments/-/estree-util-attach-comments-3.0.0.tgz";
|
url = "https://registry.npmjs.org/estree-util-attach-comments/-/estree-util-attach-comments-3.0.0.tgz";
|
||||||
hash = "sha512-cKUwm/HUcTDsYh/9FgnuFqpfquUbwIqwKM26BVCGDPVgvaCl/nDCCjUfiLlx6lsEZ3Z4RFxNbOQ60pkaEwFxGw==";
|
hash = "sha512-cKUwm/HUcTDsYh/9FgnuFqpfquUbwIqwKM26BVCGDPVgvaCl/nDCCjUfiLlx6lsEZ3Z4RFxNbOQ60pkaEwFxGw==";
|
||||||
@@ -1249,6 +1181,10 @@
|
|||||||
url = "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz";
|
url = "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz";
|
||||||
hash = "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==";
|
hash = "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==";
|
||||||
};
|
};
|
||||||
|
"is-reference@3.0.3" = fetchurl {
|
||||||
|
url = "https://registry.npmjs.org/is-reference/-/is-reference-3.0.3.tgz";
|
||||||
|
hash = "sha512-ixkJoqQvAP88E6wLydLGGqCJsrFUnqoH6HnaczB8XmDH1oaWU+xxdptvikTgaEhtZ53Ky6YXiBuUI2WXLMCwjw==";
|
||||||
|
};
|
||||||
"is-wsl@3.1.1" = fetchurl {
|
"is-wsl@3.1.1" = fetchurl {
|
||||||
url = "https://registry.npmjs.org/is-wsl/-/is-wsl-3.1.1.tgz";
|
url = "https://registry.npmjs.org/is-wsl/-/is-wsl-3.1.1.tgz";
|
||||||
hash = "sha512-e6rvdUCiQCAuumZslxRJWR/Doq4VpPR82kqclvcS0efgt430SlGIk05vdCN58+VrzgtIcfNODjozVielycD4Sw==";
|
hash = "sha512-e6rvdUCiQCAuumZslxRJWR/Doq4VpPR82kqclvcS0efgt430SlGIk05vdCN58+VrzgtIcfNODjozVielycD4Sw==";
|
||||||
@@ -1325,6 +1261,10 @@
|
|||||||
url = "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz";
|
url = "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz";
|
||||||
hash = "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==";
|
hash = "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==";
|
||||||
};
|
};
|
||||||
|
"locate-character@3.0.0" = fetchurl {
|
||||||
|
url = "https://registry.npmjs.org/locate-character/-/locate-character-3.0.0.tgz";
|
||||||
|
hash = "sha512-SW13ws7BjaeJ6p7Q6CO2nchbYEc3X3J6WrmTTDto7yMPqVSZTUyY5Tjbid+Ab8gLnATtygYtiDIJGQRRn2ZOiA==";
|
||||||
|
};
|
||||||
"longest-streak@3.1.0" = fetchurl {
|
"longest-streak@3.1.0" = fetchurl {
|
||||||
url = "https://registry.npmjs.org/longest-streak/-/longest-streak-3.1.0.tgz";
|
url = "https://registry.npmjs.org/longest-streak/-/longest-streak-3.1.0.tgz";
|
||||||
hash = "sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==";
|
hash = "sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==";
|
||||||
@@ -1333,10 +1273,6 @@
|
|||||||
url = "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.7.tgz";
|
url = "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.7.tgz";
|
||||||
hash = "sha512-aY/R+aEsRelme17KGQa/1ZSIpLpNYYrhcrepKTZgE+W3WM16YMCaPwOHLHsmopZHELU0Ojin1lPVxKR0MihncA==";
|
hash = "sha512-aY/R+aEsRelme17KGQa/1ZSIpLpNYYrhcrepKTZgE+W3WM16YMCaPwOHLHsmopZHELU0Ojin1lPVxKR0MihncA==";
|
||||||
};
|
};
|
||||||
"lucide-astro@0.556.0" = fetchurl {
|
|
||||||
url = "https://registry.npmjs.org/lucide-astro/-/lucide-astro-0.556.0.tgz";
|
|
||||||
hash = "sha512-ugMjPb45AMfkLCaduNSbyy5NQEKvB1TxVVMmUS4S6L807PMESnX0Qp+DIKHjbyjJmPXOyLRbrzvR3YikTK7brg==";
|
|
||||||
};
|
|
||||||
"magic-string@0.30.21" = fetchurl {
|
"magic-string@0.30.21" = fetchurl {
|
||||||
url = "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz";
|
url = "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz";
|
||||||
hash = "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==";
|
hash = "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==";
|
||||||
@@ -1833,6 +1769,10 @@
|
|||||||
url = "https://registry.npmjs.org/sax/-/sax-1.6.0.tgz";
|
url = "https://registry.npmjs.org/sax/-/sax-1.6.0.tgz";
|
||||||
hash = "sha512-6R3J5M4AcbtLUdZmRv2SygeVaM7IhrLXu9BmnOGmmACak8fiUtOsYNWUS4uK7upbmHIBbLBeFeI//477BKLBzA==";
|
hash = "sha512-6R3J5M4AcbtLUdZmRv2SygeVaM7IhrLXu9BmnOGmmACak8fiUtOsYNWUS4uK7upbmHIBbLBeFeI//477BKLBzA==";
|
||||||
};
|
};
|
||||||
|
"scule@1.3.0" = fetchurl {
|
||||||
|
url = "https://registry.npmjs.org/scule/-/scule-1.3.0.tgz";
|
||||||
|
hash = "sha512-6FtHJEvt+pVMIB9IBY+IcCJ6Z5f1iQnytgyfKMhDKgmzYG+TeH/wx1y3l27rshSbLiSanrR9ffZDrEsmjlQF2g==";
|
||||||
|
};
|
||||||
"semver@7.7.3" = fetchurl {
|
"semver@7.7.3" = fetchurl {
|
||||||
url = "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz";
|
url = "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz";
|
||||||
hash = "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==";
|
hash = "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==";
|
||||||
@@ -1901,6 +1841,14 @@
|
|||||||
url = "https://registry.npmjs.org/style-to-object/-/style-to-object-1.0.14.tgz";
|
url = "https://registry.npmjs.org/style-to-object/-/style-to-object-1.0.14.tgz";
|
||||||
hash = "sha512-LIN7rULI0jBscWQYaSswptyderlarFkjQ+t79nzty8tcIAceVomEVlLzH5VP4Cmsv6MtKhs7qaAiwlcp+Mgaxw==";
|
hash = "sha512-LIN7rULI0jBscWQYaSswptyderlarFkjQ+t79nzty8tcIAceVomEVlLzH5VP4Cmsv6MtKhs7qaAiwlcp+Mgaxw==";
|
||||||
};
|
};
|
||||||
|
"svelte2tsx@0.7.53" = fetchurl {
|
||||||
|
url = "https://registry.npmjs.org/svelte2tsx/-/svelte2tsx-0.7.53.tgz";
|
||||||
|
hash = "sha512-ljVSwmnYRDHRm8+7ICP6QoAN7U7vgOFfPBLN6T745YWNYqRRSzHxlrzUVqMjYls2Un8MzJissfziy/38e6Deeg==";
|
||||||
|
};
|
||||||
|
"svelte@5.55.5" = fetchurl {
|
||||||
|
url = "https://registry.npmjs.org/svelte/-/svelte-5.55.5.tgz";
|
||||||
|
hash = "sha512-2uCs/LZ9us+AktdzYJM8OcxQ8qnPS1kpaO7syGT/MgO+6Qr1Ybl+TqPq+97u7PHqmmMlye5ZkoyXONy5mjjAbw==";
|
||||||
|
};
|
||||||
"svgo@4.0.1" = fetchurl {
|
"svgo@4.0.1" = fetchurl {
|
||||||
url = "https://registry.npmjs.org/svgo/-/svgo-4.0.1.tgz";
|
url = "https://registry.npmjs.org/svgo/-/svgo-4.0.1.tgz";
|
||||||
hash = "sha512-XDpWUOPC6FEibaLzjfe0ucaV0YrOjYotGJO1WpF0Zd+n6ZGEQUsSugaoLq9QkEZtAfQIxT42UChcssDVPP3+/w==";
|
hash = "sha512-XDpWUOPC6FEibaLzjfe0ucaV0YrOjYotGJO1WpF0Zd+n6ZGEQUsSugaoLq9QkEZtAfQIxT42UChcssDVPP3+/w==";
|
||||||
@@ -2045,10 +1993,6 @@
|
|||||||
url = "https://registry.npmjs.org/vfile/-/vfile-6.0.3.tgz";
|
url = "https://registry.npmjs.org/vfile/-/vfile-6.0.3.tgz";
|
||||||
hash = "sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==";
|
hash = "sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==";
|
||||||
};
|
};
|
||||||
"vite@6.4.1" = fetchurl {
|
|
||||||
url = "https://registry.npmjs.org/vite/-/vite-6.4.1.tgz";
|
|
||||||
hash = "sha512-+Oxm7q9hDoLMyJOYfUYBuHQo+dkAloi33apOPP56pzj+vsdJDzr+j1NISE5pyaAuKL4A3UD34qd0lx5+kfKp2g==";
|
|
||||||
};
|
|
||||||
"vite@7.3.2" = fetchurl {
|
"vite@7.3.2" = fetchurl {
|
||||||
url = "https://registry.npmjs.org/vite/-/vite-7.3.2.tgz";
|
url = "https://registry.npmjs.org/vite/-/vite-7.3.2.tgz";
|
||||||
hash = "sha512-Bby3NOsna2jsjfLVOHKes8sGwgl4TT0E6vvpYgnAYDIF/tie7MRaFthmKuHx1NSXjiTueXH3do80FMQgvEktRg==";
|
hash = "sha512-Bby3NOsna2jsjfLVOHKes8sGwgl4TT0E6vvpYgnAYDIF/tie7MRaFthmKuHx1NSXjiTueXH3do80FMQgvEktRg==";
|
||||||
@@ -2173,6 +2117,10 @@
|
|||||||
url = "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.2.2.tgz";
|
url = "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.2.2.tgz";
|
||||||
hash = "sha512-4LCcse/U2MHZ63HAJVE+v71o7yOdIe4cZ70Wpf8D/IyjDKYQLV5GD46B+hSTjJsvV5PztjvHoU580EftxjDZFQ==";
|
hash = "sha512-4LCcse/U2MHZ63HAJVE+v71o7yOdIe4cZ70Wpf8D/IyjDKYQLV5GD46B+hSTjJsvV5PztjvHoU580EftxjDZFQ==";
|
||||||
};
|
};
|
||||||
|
"zimmerframe@1.1.4" = fetchurl {
|
||||||
|
url = "https://registry.npmjs.org/zimmerframe/-/zimmerframe-1.1.4.tgz";
|
||||||
|
hash = "sha512-B58NGBEoc8Y9MWWCQGl/gq9xBCe4IiKM0a2x7GZdQKOW5Exr8S1W24J6OgM1njK8xCRGvAJIL/MxXHf6SkmQKQ==";
|
||||||
|
};
|
||||||
"zod@4.3.6" = fetchurl {
|
"zod@4.3.6" = fetchurl {
|
||||||
url = "https://registry.npmjs.org/zod/-/zod-4.3.6.tgz";
|
url = "https://registry.npmjs.org/zod/-/zod-4.3.6.tgz";
|
||||||
hash = "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==";
|
hash = "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==";
|
||||||
|
|||||||
+3
-2
@@ -15,13 +15,14 @@
|
|||||||
"@astrojs/mdx": "5.0.4",
|
"@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": "6.1.9",
|
"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": {
|
||||||
|
|||||||
+79
-36
@@ -3,57 +3,85 @@
|
|||||||
(function oneko() {
|
(function oneko() {
|
||||||
if (window.matchMedia("(prefers-reduced-motion: reduce)").matches) return;
|
if (window.matchMedia("(prefers-reduced-motion: reduce)").matches) return;
|
||||||
|
|
||||||
const SIZE = 32;
|
const SIZE = 32;
|
||||||
const SPEED = 10;
|
const SPEED = 10;
|
||||||
|
|
||||||
const spriteSets = {
|
const spriteSets = {
|
||||||
idle: [[-3, -3]],
|
idle: [[-3, -3]],
|
||||||
alert: [[-7, -3]],
|
alert: [[-7, -3]],
|
||||||
scratchSelf: [[-5, 0], [-6, 0], [-7, 0]],
|
scratchSelf: [
|
||||||
scratchWallE:[[-2, -2], [-2, -3]],
|
[-5, 0],
|
||||||
scratchWallW:[[-4, 0], [-4, -1]],
|
[-6, 0],
|
||||||
tired: [[-3, -2]],
|
[-7, 0],
|
||||||
sleeping: [[-2, 0], [-2, -1]],
|
],
|
||||||
E: [[-3, 0], [-3, -1]],
|
scratchWallE: [
|
||||||
W: [[-4, -2], [-4, -3]],
|
[-2, -2],
|
||||||
|
[-2, -3],
|
||||||
|
],
|
||||||
|
scratchWallW: [
|
||||||
|
[-4, 0],
|
||||||
|
[-4, -1],
|
||||||
|
],
|
||||||
|
tired: [[-3, -2]],
|
||||||
|
sleeping: [
|
||||||
|
[-2, 0],
|
||||||
|
[-2, -1],
|
||||||
|
],
|
||||||
|
E: [
|
||||||
|
[-3, 0],
|
||||||
|
[-3, -1],
|
||||||
|
],
|
||||||
|
W: [
|
||||||
|
[-4, -2],
|
||||||
|
[-4, -3],
|
||||||
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
const track = document.getElementById("oneko-track");
|
const track = document.getElementById("oneko-track");
|
||||||
if (!track) return;
|
if (!track) return;
|
||||||
|
|
||||||
const el = document.createElement("div");
|
const el = document.createElement("div");
|
||||||
el.id = "oneko";
|
el.id = "oneko";
|
||||||
el.ariaHidden = "true";
|
el.ariaHidden = "true";
|
||||||
el.style.width = `${SIZE}px`;
|
el.style.width = `${SIZE}px`;
|
||||||
el.style.height = `${SIZE}px`;
|
el.style.height = `${SIZE}px`;
|
||||||
el.style.position = "absolute";
|
el.style.position = "absolute";
|
||||||
el.style.bottom = "0";
|
el.style.bottom = "0";
|
||||||
el.style.pointerEvents = "none";
|
el.style.pointerEvents = "none";
|
||||||
el.style.imageRendering = "pixelated";
|
el.style.imageRendering = "pixelated";
|
||||||
el.style.zIndex = "2147483647";
|
el.style.zIndex = "2147483647";
|
||||||
el.style.backgroundImage= "url(/oneko.gif)";
|
el.style.backgroundImage = "url(/oneko.gif)";
|
||||||
track.appendChild(el);
|
track.appendChild(el);
|
||||||
|
|
||||||
function maxX() { return track.offsetWidth - SIZE; }
|
function maxX() {
|
||||||
function clamp(v,lo,hi) { return Math.max(lo, Math.min(hi, v)); }
|
return track.offsetWidth - SIZE;
|
||||||
function randomTarget() { return Math.random() * maxX(); }
|
}
|
||||||
|
function clamp(v, lo, hi) {
|
||||||
|
return Math.max(lo, Math.min(hi, v));
|
||||||
|
}
|
||||||
|
function randomTarget() {
|
||||||
|
return Math.random() * maxX();
|
||||||
|
}
|
||||||
|
|
||||||
function setSprite(name, frame) {
|
function setSprite(name, frame) {
|
||||||
const s = spriteSets[name][frame % spriteSets[name].length];
|
const s = spriteSets[name][frame % spriteSets[name].length];
|
||||||
el.style.backgroundPosition = `${s[0] * SIZE}px ${s[1] * SIZE}px`;
|
el.style.backgroundPosition = `${s[0] * SIZE}px ${s[1] * SIZE}px`;
|
||||||
}
|
}
|
||||||
|
|
||||||
let posX = randomTarget();
|
let posX = randomTarget();
|
||||||
let targetX = posX;
|
let targetX = posX;
|
||||||
el.style.left = `${posX}px`;
|
el.style.left = `${posX}px`;
|
||||||
|
|
||||||
let frameCount = 0;
|
let frameCount = 0;
|
||||||
let idleTime = 0;
|
let idleTime = 0;
|
||||||
let idleAnim = null;
|
let idleAnim = null;
|
||||||
let idleAnimFrame = 0;
|
let idleAnimFrame = 0;
|
||||||
let lastTs = null;
|
let lastTs = null;
|
||||||
|
|
||||||
function resetIdle() { idleAnim = null; idleAnimFrame = 0; }
|
function resetIdle() {
|
||||||
|
idleAnim = null;
|
||||||
|
idleAnimFrame = 0;
|
||||||
|
}
|
||||||
|
|
||||||
function idle() {
|
function idle() {
|
||||||
idleTime++;
|
idleTime++;
|
||||||
@@ -65,16 +93,23 @@
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (idleTime > 15 && idleAnim == null && Math.floor(Math.random() * 180) === 0) {
|
if (
|
||||||
|
idleTime > 15 &&
|
||||||
|
idleAnim == null &&
|
||||||
|
Math.floor(Math.random() * 180) === 0
|
||||||
|
) {
|
||||||
const opts = ["sleeping", "scratchSelf"];
|
const opts = ["sleeping", "scratchSelf"];
|
||||||
if (posX <= SIZE) opts.push("scratchWallW");
|
if (posX <= SIZE) opts.push("scratchWallW");
|
||||||
if (posX >= maxX() - SIZE) opts.push("scratchWallE");
|
if (posX >= maxX() - SIZE) opts.push("scratchWallE");
|
||||||
idleAnim = opts[Math.floor(Math.random() * opts.length)];
|
idleAnim = opts[Math.floor(Math.random() * opts.length)];
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (idleAnim) {
|
switch (idleAnim) {
|
||||||
case "sleeping":
|
case "sleeping":
|
||||||
if (idleAnimFrame < 8) { setSprite("tired", 0); break; }
|
if (idleAnimFrame < 8) {
|
||||||
|
setSprite("tired", 0);
|
||||||
|
break;
|
||||||
|
}
|
||||||
setSprite("sleeping", Math.floor(idleAnimFrame / 4));
|
setSprite("sleeping", Math.floor(idleAnimFrame / 4));
|
||||||
if (idleAnimFrame > 192) resetIdle();
|
if (idleAnimFrame > 192) resetIdle();
|
||||||
break;
|
break;
|
||||||
@@ -119,7 +154,10 @@
|
|||||||
function loop(ts) {
|
function loop(ts) {
|
||||||
if (!el.isConnected) return;
|
if (!el.isConnected) return;
|
||||||
if (!lastTs) lastTs = ts;
|
if (!lastTs) lastTs = ts;
|
||||||
if (ts - lastTs > 100) { lastTs = ts; frame(); }
|
if (ts - lastTs > 100) {
|
||||||
|
lastTs = ts;
|
||||||
|
frame();
|
||||||
|
}
|
||||||
window.requestAnimationFrame(loop);
|
window.requestAnimationFrame(loop);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -128,14 +166,19 @@
|
|||||||
const newWidth = track.offsetWidth;
|
const newWidth = track.offsetWidth;
|
||||||
if (lastTrackWidth > 0) {
|
if (lastTrackWidth > 0) {
|
||||||
const ratio = newWidth / lastTrackWidth;
|
const ratio = newWidth / lastTrackWidth;
|
||||||
posX = clamp(posX * ratio, 0, maxX());
|
posX = clamp(posX * ratio, 0, maxX());
|
||||||
targetX = clamp(targetX * ratio, 0, maxX());
|
targetX = clamp(targetX * ratio, 0, maxX());
|
||||||
el.style.left = `${posX}px`;
|
el.style.left = `${posX}px`;
|
||||||
}
|
}
|
||||||
lastTrackWidth = newWidth;
|
lastTrackWidth = newWidth;
|
||||||
});
|
});
|
||||||
|
|
||||||
setTimeout(() => { targetX = randomTarget(); }, 800 + Math.random() * 1500);
|
setTimeout(
|
||||||
|
() => {
|
||||||
|
targetX = randomTarget();
|
||||||
|
},
|
||||||
|
800 + Math.random() * 1500,
|
||||||
|
);
|
||||||
|
|
||||||
window.requestAnimationFrame(loop);
|
window.requestAnimationFrame(loop);
|
||||||
})();
|
})();
|
||||||
|
|||||||
+7
-14
@@ -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`);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,18 +1,26 @@
|
|||||||
---
|
---
|
||||||
import { Image } from "astro:assets";
|
import { Image } from "astro:assets";
|
||||||
const avatar = "/avatar.jpg";
|
const avatar = "/avatar.jpg";
|
||||||
const username = "anotherhadi"
|
const username = "anotherhadi";
|
||||||
const bio = "Infosec engineer."
|
const bio = "Infosec engineer.";
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
<div class="flex flex-wrap gap-3 justify-start">
|
<div class="flex flex-wrap gap-3 justify-start">
|
||||||
<div
|
<div
|
||||||
class="ring-base-300 ring-offset-base-100 rounded-full ring-2 ring-offset-2 flex justify-center items-center"
|
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}/>
|
<Image
|
||||||
|
src={avatar}
|
||||||
|
alt="anotherhadi avatar"
|
||||||
|
class="rounded-full m-auto"
|
||||||
|
width={36}
|
||||||
|
height={36}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<p class="text-sm font-semibold"><a href="/"><span class="text-base-content/40">@</span>{username}</a></p>
|
<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>
|
<p class="text-xs text-base-content/60">{bio}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
---
|
---
|
||||||
import { ArrowUp } from "lucide-astro";
|
import { ArrowUp } from "@lucide/astro";
|
||||||
---
|
---
|
||||||
|
|
||||||
<button
|
<button
|
||||||
|
|||||||
@@ -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">
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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>
|
hasMultiplePlatforms ? (
|
||||||
</div>
|
<div class="dropdown dropdown-end">
|
||||||
) : (
|
<div
|
||||||
|
tabindex="0"
|
||||||
<a
|
role="button"
|
||||||
href={repo.html_url}
|
class="btn btn-soft btn-primary btn-sm gap-1"
|
||||||
target="_blank"
|
>
|
||||||
rel="noopener noreferrer"
|
<ExternalLink class="size-4" />
|
||||||
class="btn btn-primary btn-sm gap-1"
|
View Source
|
||||||
>
|
<ChevronDown class="size-3" />
|
||||||
<ExternalLink class="size-4" />
|
</div>
|
||||||
View on Gitea
|
<ul
|
||||||
</a>
|
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>
|
||||||
|
|||||||
+44
-59
@@ -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>
|
||||||
{
|
{
|
||||||
@@ -80,34 +74,40 @@ const { name, title, description, avatar, location, socialLinks, gpgKey, rssFeed
|
|||||||
class="btn btn-circle btn-ghost"
|
class="btn btn-circle btn-ghost"
|
||||||
aria-label="Gitlab"
|
aria-label="Gitlab"
|
||||||
>
|
>
|
||||||
<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="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>
|
||||||
|
|||||||
@@ -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" },
|
||||||
];
|
];
|
||||||
|
|
||||||
@@ -18,7 +18,9 @@ function isActive(href: string) {
|
|||||||
class="fixed top-0 left-0 right-0 z-[60] 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,7 +28,12 @@ function isActive(href: string) {
|
|||||||
~/hadi
|
~/hadi
|
||||||
</a>
|
</a>
|
||||||
|
|
||||||
<div id="oneko-track" class="flex-1 relative h-12 pointer-events-none"></div>
|
<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">
|
||||||
{
|
{
|
||||||
@@ -58,9 +65,15 @@ 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>
|
||||||
@@ -97,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 = "";
|
||||||
@@ -113,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 = "";
|
||||||
|
|||||||
@@ -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>
|
||||||
@@ -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>
|
||||||
@@ -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>
|
||||||
@@ -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>
|
||||||
@@ -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>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -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}
|
||||||
@@ -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>
|
||||||
@@ -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>
|
||||||
@@ -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
@@ -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",
|
||||||
|
|||||||
@@ -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`.
|
||||||
@@ -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
|
||||||
|
```
|
||||||
@@ -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
|
||||||
|
```
|
||||||
@@ -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.
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
---
|
|
||||||
title: "Notes in comming.."
|
|
||||||
description: ""
|
|
||||||
tags: []
|
|
||||||
publishDate: 2026-04-24
|
|
||||||
---
|
|
||||||
|
|
||||||
## Notes
|
|
||||||
@@ -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>
|
||||||
@@ -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>
|
||||||
@@ -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/)
|
||||||
@@ -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
|
||||||
|
```
|
||||||
+229
-117
@@ -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,
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ 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 Author from "../components/Author.astro";
|
||||||
|
import { formatDate } from "../utils/notes";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
title: string;
|
title: string;
|
||||||
@@ -19,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");
|
||||||
@@ -38,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} />
|
||||||
@@ -53,22 +45,20 @@ 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">
|
||||||
<Image
|
<Image
|
||||||
src={image}
|
src={image}
|
||||||
alt={title}
|
alt={title}
|
||||||
class="w-full aspect-video object-cover"
|
class="w-full aspect-video object-cover"
|
||||||
width={1200}
|
width={1200}
|
||||||
height={630}
|
height={630}
|
||||||
/>
|
/>
|
||||||
</figure>
|
</figure>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
<!-- 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>
|
||||||
@@ -82,64 +72,58 @@ const toc = headers.map((header) => ({
|
|||||||
<span>•</span>
|
<span>•</span>
|
||||||
<span>{readingTime} min read</span>
|
<span>{readingTime} min read</span>
|
||||||
{
|
{
|
||||||
updatedDate && (
|
updatedDate && (
|
||||||
<>
|
<>
|
||||||
<span>•</span>
|
<span>•</span>
|
||||||
<span>Updated: {formatDate(updatedDate)}</span>
|
<span>Updated: {formatDate(updatedDate)}</span>
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{
|
{
|
||||||
tags && tags.length > 0 && (
|
tags && tags.length > 0 && (
|
||||||
<div class="flex flex-wrap gap-2 mb-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 />
|
<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
|
||||||
@@ -163,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>
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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
@@ -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
@@ -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>
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
+223
-535
@@ -1,9 +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 Author from "../../components/Author.astro";
|
import NoteTOC from "../../components/NoteTOC.astro";
|
||||||
import { getCategory, extractInlineHashtags } from "../../utils/notes";
|
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");
|
||||||
@@ -14,36 +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) =>
|
||||||
|
a.data.title.localeCompare(b.data.title),
|
||||||
|
);
|
||||||
const categories = [...new Set(allNotes.map(getCategory))].sort();
|
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[] {
|
|
||||||
// Capture slug before optional #fragment: (/notes/slug) or (/notes/slug#section)
|
|
||||||
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)];
|
|
||||||
}
|
|
||||||
|
|
||||||
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 })),
|
||||||
@@ -53,516 +51,231 @@ const graphEdges = [
|
|||||||
...backlinks.map((n) => ({ from: n.id, to: entry.id })),
|
...backlinks.map((n) => ({ from: n.id, to: entry.id })),
|
||||||
];
|
];
|
||||||
|
|
||||||
// Mirrors github-slugger exactly: keeps _, keeps unicode letters/numbers, spaces → hyphens
|
const noteVars = [
|
||||||
function slugify(text: string) {
|
...new Set(
|
||||||
return text
|
Array.from(
|
||||||
.toLowerCase()
|
(entry.body ?? "").matchAll(/\$([a-zA-Z_][a-zA-Z0-9_]*)/g),
|
||||||
.replace(/[^\p{L}\p{N}\s_-]/gu, "") // keep letters (unicode), numbers, spaces, _, -
|
(m) => m[1],
|
||||||
.trim()
|
),
|
||||||
.replace(/ +/g, "-"); // spaces → hyphens (github-slugger does exactly this)
|
),
|
||||||
}
|
];
|
||||||
|
|
||||||
const headings: { depth: number; text: string; id: string }[] = [];
|
const headings = astroHeadings.map((h) => ({ depth: h.depth, text: h.text, id: h.slug }));
|
||||||
const headingRe = /^(#{2,4}) (.+)$/gm;
|
const externalLinks = extractExternalLinks(entry.body ?? "");
|
||||||
let hm;
|
|
||||||
while ((hm = headingRe.exec(entry.body ?? "")) !== null) {
|
|
||||||
// Strip markdown formatting while preserving literal _ (word-internal underscores like my_var)
|
|
||||||
// Paired markers are stripped to their content; lone * are removed; _ only stripped at word boundaries
|
|
||||||
const raw = hm[2].trim()
|
|
||||||
.replace(/`[^`]*`/g, "") // `code` → remove
|
|
||||||
.replace(/\*\*(.*?)\*\*/g, "$1") // **bold** → text
|
|
||||||
.replace(/(?<!\p{L}\p{N})__(.*?)__(?!\p{L}\p{N})/gu, "$1") // __bold__ → text
|
|
||||||
.replace(/\*(.*?)\*/g, "$1") // *italic* → text
|
|
||||||
.replace(/(?<!\p{L}\p{N})_(.*?)_(?!\p{L}\p{N})/gu, "$1") // _italic_ → text
|
|
||||||
.replace(/[*]/g, ""); // orphan * markers
|
|
||||||
headings.push({ depth: hm[1].length, text: raw, id: slugify(raw) });
|
|
||||||
}
|
|
||||||
---
|
---
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
/* Both sidebars sit below the navbar when in drawer-open mode */
|
@media (min-width: 768px) {
|
||||||
.drawer.lg\:drawer-open > .drawer-side,
|
.drawer.md\:drawer-open > .drawer-side {
|
||||||
.drawer.xl\:drawer-open > .drawer-side {
|
top: 3rem;
|
||||||
top: 3rem;
|
height: calc(100vh - 3rem);
|
||||||
height: calc(100vh - 3rem);
|
}
|
||||||
|
}
|
||||||
|
@media (min-width: 1280px) {
|
||||||
|
.drawer.xl\:drawer-open > .drawer-side {
|
||||||
|
top: 3rem;
|
||||||
|
height: calc(100vh - 3rem);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</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 xl:drawer-open 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-8">
|
|
||||||
<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>{getCategory(entry)}
|
|
||||||
</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 mb-4">
|
|
||||||
{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 flex flex-col border-r border-base-300/60 h-full"
|
|
||||||
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) => getCategory(n) === 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, ...extractInlineHashtags(n.body ?? "")].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 flex flex-col border-l border-base-300/60 h-full overflow-y-auto"
|
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;
|
||||||
role="img"
|
gap: 0;
|
||||||
aria-label="Graph of linked notes"
|
|
||||||
style="width:100%; display:block; background: oklch(2% 0 0); cursor:default;"
|
|
||||||
></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>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<div class="px-4 py-6">
|
|
||||||
<Author />
|
|
||||||
</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;
|
|
||||||
|
|
||||||
// On non-xl: let DaisyUI overlay work via checkbox
|
|
||||||
function onGraphDrawerChange() {
|
|
||||||
if (graphDrawer!.checked) {
|
|
||||||
requestAnimationFrame(() => { stopGraph = startGraph() ?? null; });
|
|
||||||
} else {
|
|
||||||
if (stopGraph) { stopGraph(); stopGraph = null; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
graphDrawer.addEventListener("change", onGraphDrawerChange);
|
|
||||||
|
|
||||||
const outerDrawer = graphDrawer.closest<HTMLElement>(".drawer.drawer-end");
|
|
||||||
const xlQuery = window.matchMedia("(min-width: 1280px)");
|
|
||||||
|
|
||||||
function setXlSidebar(open: boolean) {
|
|
||||||
if (!outerDrawer) return;
|
|
||||||
if (open) {
|
|
||||||
outerDrawer.classList.add("xl:drawer-open");
|
|
||||||
requestAnimationFrame(() => { stopGraph = startGraph() ?? null; });
|
|
||||||
} else {
|
|
||||||
outerDrawer.classList.remove("xl:drawer-open");
|
|
||||||
if (stopGraph) { stopGraph(); stopGraph = null; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// On xl: toggle the class instead of the checkbox (avoids DaisyUI overlay + scroll lock)
|
|
||||||
const graphToggle = document.getElementById("graph-toggle");
|
|
||||||
graphToggle?.addEventListener("click", (e) => {
|
|
||||||
if (!xlQuery.matches) return;
|
|
||||||
e.preventDefault();
|
|
||||||
setXlSidebar(!outerDrawer?.classList.contains("xl:drawer-open"));
|
|
||||||
});
|
|
||||||
|
|
||||||
// Auto-open on xl, close when leaving xl
|
|
||||||
if (xlQuery.matches) {
|
|
||||||
outerDrawer?.classList.add("xl:drawer-open");
|
|
||||||
requestAnimationFrame(() => { stopGraph = startGraph() ?? null; });
|
|
||||||
}
|
|
||||||
xlQuery.addEventListener("change", (e) => {
|
|
||||||
if (!e.matches) setXlSidebar(false);
|
|
||||||
});
|
|
||||||
|
|
||||||
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}`;
|
||||||
@@ -580,34 +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 raw = target.value.toLowerCase().trim();
|
|
||||||
document.querySelectorAll<HTMLInputElement>("[data-search]").forEach((o) => {
|
|
||||||
if (o !== target) o.value = target.value;
|
|
||||||
});
|
|
||||||
const isTag = raw.startsWith("#");
|
|
||||||
const search = isTag ? raw.slice(1) : raw;
|
|
||||||
navItems.forEach((item) => {
|
|
||||||
const title = item.dataset.title ?? "";
|
|
||||||
const tags = item.dataset.tags ? item.dataset.tags.split(",") : [];
|
|
||||||
const match = !search || (
|
|
||||||
isTag
|
|
||||||
? tags.some((t) => t.includes(search))
|
|
||||||
: title.includes(search) || tags.join(",").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>
|
|
||||||
|
|||||||
@@ -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>
|
||||||
+25
-155
@@ -1,174 +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 { 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(getCategory))].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) => getCategory(n) === 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}`)
|
|
||||||
: (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>
|
||||||
|
|||||||
@@ -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
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
+74
-7
@@ -1,13 +1,80 @@
|
|||||||
export function getCategory(n: { id: string; data: { category?: string } }): string {
|
export function getCategory(n: {
|
||||||
|
id: string;
|
||||||
|
data: { category?: string };
|
||||||
|
}): string {
|
||||||
if (n.data.category) return n.data.category;
|
if (n.data.category) return n.data.category;
|
||||||
const parts = n.id.split("/");
|
const parts = n.id.split("/");
|
||||||
return parts.length > 1 ? parts[0] : "General";
|
return parts.length > 1 ? parts[0] : "General";
|
||||||
}
|
}
|
||||||
|
|
||||||
export function extractInlineHashtags(body: string): string[] {
|
function slugify(text: string): string {
|
||||||
const re = /#(\w+)/g;
|
return text
|
||||||
const tags: string[] = [];
|
.toLowerCase()
|
||||||
let m;
|
.replace(/[^\p{L}\p{N}\s_-]/gu, "")
|
||||||
while ((m = re.exec(body)) !== null) tags.push(m[1].toLowerCase());
|
.trim()
|
||||||
return [...new Set(tags)];
|
.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;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user