This commit is contained in:
Hadi
2026-04-06 15:12:34 +02:00
commit 4989225671
117 changed files with 11454 additions and 0 deletions

27
front/.gitignore vendored Normal file
View File

@@ -0,0 +1,27 @@
# build output
dist/
# generated types
.astro/
# dependencies
node_modules/
# logs
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
# environment variables
.env
.env.production
# macOS-specific files
.DS_Store
result/
# code editors
.idea/
.vscode/

52
front/astro.config.mjs Normal file
View File

@@ -0,0 +1,52 @@
// @ts-check
import { defineConfig } from "astro/config";
import tailwindcss from "@tailwindcss/vite";
import svelte from "@astrojs/svelte";
import remarkGithubBlockquoteAlert from "remark-github-blockquote-alert";
import { EventEmitter } from "events";
EventEmitter.defaultMaxListeners = 25;
// https://astro.build/config
export default defineConfig({
site: "https://iky.hadi.icu",
output: "static",
vite: {
resolve: {
noExternal: ["@lucide/svelte"],
},
plugins: [
tailwindcss(),
{
name: "shell-rewrite",
configureServer(server) {
server.middlewares.use((req, _res, next) => {
if (/^\/tools\/[^/]+\/?(\?.*)?$/.test(req.url)) req.url = "/tools/_";
if (/^\/search\/[^/]+\/?(\?.*)?$/.test(req.url)) req.url = "/search/_";
next();
});
},
},
],
server: {
proxy: {
"/api": "http://localhost:8080",
},
},
},
integrations: [svelte()],
markdown: {
remarkPlugins: [remarkGithubBlockquoteAlert],
shikiConfig: {
theme: "github-dark",
transformers: [
{
name: "code-block-meta",
pre(node) {
node.properties["data-lang"] = this.options.lang || "text";
},
},
],
},
},
});

877
front/bun.lock Normal file
View File

@@ -0,0 +1,877 @@
{
"lockfileVersion": 1,
"configVersion": 1,
"workspaces": {
"": {
"name": "iknowyou",
"dependencies": {
"@astrojs/svelte": "8.0.4",
"@lucide/svelte": "^1.7.0",
"@tailwindcss/vite": "^4.2.1",
"ansi_up": "^6.0.6",
"astro": "6.1.2",
"dompurify": "^3.3.3",
"js-yaml": "^4.1.1",
"remark-github-blockquote-alert": "^2.1.0",
"svelte": "^5.53.12",
"tailwindcss": "^4.2.1",
"typescript": "^5.9.3",
},
"devDependencies": {
"@tailwindcss/typography": "^0.5.19",
"@types/dompurify": "^3.2.0",
"@types/js-yaml": "^4.0.9",
"@types/node": "^25.5.0",
"concurrently": "^9.2.1",
"daisyui": "^5.5.19",
},
},
},
"packages": {
"@astrojs/compiler": ["@astrojs/compiler@3.0.1", "", {}, "sha512-z97oYbdebO5aoWzuJ/8q5hLK232+17KcLZ7cJ8BCWk6+qNzVxn/gftC0KzMBUTD8WAaBkPpNSQK6PXLnNrZ0CA=="],
"@astrojs/internal-helpers": ["@astrojs/internal-helpers@0.8.0", "", { "dependencies": { "picomatch": "^4.0.3" } }, "sha512-J56GrhEiV+4dmrGLPNOl2pZjpHXAndWVyiVDYGDuw6MWKpBSEMLdFxHzeM/6sqaknw9M+HFfHZAcvi3OfT3D/w=="],
"@astrojs/markdown-remark": ["@astrojs/markdown-remark@7.1.0", "", { "dependencies": { "@astrojs/internal-helpers": "0.8.0", "@astrojs/prism": "4.0.1", "github-slugger": "^2.0.0", "hast-util-from-html": "^2.0.3", "hast-util-to-text": "^4.0.2", "js-yaml": "^4.1.1", "mdast-util-definitions": "^6.0.0", "rehype-raw": "^7.0.0", "rehype-stringify": "^10.0.1", "remark-gfm": "^4.0.1", "remark-parse": "^11.0.0", "remark-rehype": "^11.1.2", "remark-smartypants": "^3.0.2", "retext-smartypants": "^6.2.0", "shiki": "^4.0.0", "smol-toml": "^1.6.0", "unified": "^11.0.5", "unist-util-remove-position": "^5.0.0", "unist-util-visit": "^5.1.0", "unist-util-visit-parents": "^6.0.2", "vfile": "^6.0.3" } }, "sha512-P+HnCsu2js3BoTc8kFmu+E9gOcFeMdPris75g+Zl4sY8+bBRbSQV6xzcBDbZ27eE7yBGEGQoqjpChx+KJYIPYQ=="],
"@astrojs/prism": ["@astrojs/prism@4.0.1", "", { "dependencies": { "prismjs": "^1.30.0" } }, "sha512-nksZQVjlferuWzhPsBpQ1JE5XuKAf1id1/9Hj4a9KG4+ofrlzxUUwX4YGQF/SuDiuiGKEnzopGOt38F3AnVWsQ=="],
"@astrojs/svelte": ["@astrojs/svelte@8.0.4", "", { "dependencies": { "@sveltejs/vite-plugin-svelte": "^6.2.4", "svelte2tsx": "^0.7.52", "vite": "^7.3.1" }, "peerDependencies": { "astro": "^6.0.0", "svelte": "^5.43.6", "typescript": "^5.3.3" } }, "sha512-c5m3chjtgxBE3BzsE/bZbCFBkLPhq041rm2WJFaTIKGwt/3xNm/5efYCj23reuAcBsl4iYS8n2UwkAHQJzhkZA=="],
"@astrojs/telemetry": ["@astrojs/telemetry@3.3.0", "", { "dependencies": { "ci-info": "^4.2.0", "debug": "^4.4.0", "dlv": "^1.1.3", "dset": "^3.1.4", "is-docker": "^3.0.0", "is-wsl": "^3.1.0", "which-pm-runs": "^1.1.0" } }, "sha512-UFBgfeldP06qu6khs/yY+q1cDAaArM2/7AEIqQ9Cuvf7B1hNLq0xDrZkct+QoIGyjq56y8IaE2I3CTvG99mlhQ=="],
"@babel/helper-string-parser": ["@babel/helper-string-parser@7.27.1", "", {}, "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA=="],
"@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.28.5", "", {}, "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q=="],
"@babel/parser": ["@babel/parser@7.29.2", "", { "dependencies": { "@babel/types": "^7.29.0" }, "bin": "./bin/babel-parser.js" }, "sha512-4GgRzy/+fsBa72/RZVJmGKPmZu9Byn8o4MoLpmNe1m8ZfYnz5emHLQz3U4gLud6Zwl0RZIcgiLD7Uq7ySFuDLA=="],
"@babel/types": ["@babel/types@7.29.0", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.28.5" } }, "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A=="],
"@capsizecss/unpack": ["@capsizecss/unpack@4.0.0", "", { "dependencies": { "fontkitten": "^1.0.0" } }, "sha512-VERIM64vtTP1C4mxQ5thVT9fK0apjPFobqybMtA1UdUujWka24ERHbRHFGmpbbhp73MhV+KSsHQH9C6uOTdEQA=="],
"@clack/core": ["@clack/core@1.2.0", "", { "dependencies": { "fast-wrap-ansi": "^0.1.3", "sisteransi": "^1.0.5" } }, "sha512-qfxof/3T3t9DPU/Rj3OmcFyZInceqj/NVtO9rwIuJqCUgh32gwPjpFQQp/ben07qKlhpwq7GzfWpST4qdJ5Drg=="],
"@clack/prompts": ["@clack/prompts@1.2.0", "", { "dependencies": { "@clack/core": "1.2.0", "fast-string-width": "^1.1.0", "fast-wrap-ansi": "^0.1.3", "sisteransi": "^1.0.5" } }, "sha512-4jmztR9fMqPMjz6H/UZXj0zEmE43ha1euENwkckKKel4XpSfokExPo5AiVStdHSAlHekz4d0CA/r45Ok1E4D3w=="],
"@emnapi/runtime": ["@emnapi/runtime@1.9.1", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-VYi5+ZVLhpgK4hQ0TAjiQiZ6ol0oe4mBx7mVv7IflsiEp0OWoVsp/+f9Vc1hOhE0TtkORVrI1GvzyreqpgWtkA=="],
"@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.27.4", "", { "os": "aix", "cpu": "ppc64" }, "sha512-cQPwL2mp2nSmHHJlCyoXgHGhbEPMrEEU5xhkcy3Hs/O7nGZqEpZ2sUtLaL9MORLtDfRvVl2/3PAuEkYZH0Ty8Q=="],
"@esbuild/android-arm": ["@esbuild/android-arm@0.27.4", "", { "os": "android", "cpu": "arm" }, "sha512-X9bUgvxiC8CHAGKYufLIHGXPJWnr0OCdR0anD2e21vdvgCI8lIfqFbnoeOz7lBjdrAGUhqLZLcQo6MLhTO2DKQ=="],
"@esbuild/android-arm64": ["@esbuild/android-arm64@0.27.4", "", { "os": "android", "cpu": "arm64" }, "sha512-gdLscB7v75wRfu7QSm/zg6Rx29VLdy9eTr2t44sfTW7CxwAtQghZ4ZnqHk3/ogz7xao0QAgrkradbBzcqFPasw=="],
"@esbuild/android-x64": ["@esbuild/android-x64@0.27.4", "", { "os": "android", "cpu": "x64" }, "sha512-PzPFnBNVF292sfpfhiyiXCGSn9HZg5BcAz+ivBuSsl6Rk4ga1oEXAamhOXRFyMcjwr2DVtm40G65N3GLeH1Lvw=="],
"@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.27.4", "", { "os": "darwin", "cpu": "arm64" }, "sha512-b7xaGIwdJlht8ZFCvMkpDN6uiSmnxxK56N2GDTMYPr2/gzvfdQN8rTfBsvVKmIVY/X7EM+/hJKEIbbHs9oA4tQ=="],
"@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.27.4", "", { "os": "darwin", "cpu": "x64" }, "sha512-sR+OiKLwd15nmCdqpXMnuJ9W2kpy0KigzqScqHI3Hqwr7IXxBp3Yva+yJwoqh7rE8V77tdoheRYataNKL4QrPw=="],
"@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.27.4", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-jnfpKe+p79tCnm4GVav68A7tUFeKQwQyLgESwEAUzyxk/TJr4QdGog9sqWNcUbr/bZt/O/HXouspuQDd9JxFSw=="],
"@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.27.4", "", { "os": "freebsd", "cpu": "x64" }, "sha512-2kb4ceA/CpfUrIcTUl1wrP/9ad9Atrp5J94Lq69w7UwOMolPIGrfLSvAKJp0RTvkPPyn6CIWrNy13kyLikZRZQ=="],
"@esbuild/linux-arm": ["@esbuild/linux-arm@0.27.4", "", { "os": "linux", "cpu": "arm" }, "sha512-aBYgcIxX/wd5n2ys0yESGeYMGF+pv6g0DhZr3G1ZG4jMfruU9Tl1i2Z+Wnj9/KjGz1lTLCcorqE2viePZqj4Eg=="],
"@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.27.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-7nQOttdzVGth1iz57kxg9uCz57dxQLHWxopL6mYuYthohPKEK0vU0C3O21CcBK6KDlkYVcnDXY099HcCDXd9dA=="],
"@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.27.4", "", { "os": "linux", "cpu": "ia32" }, "sha512-oPtixtAIzgvzYcKBQM/qZ3R+9TEUd1aNJQu0HhGyqtx6oS7qTpvjheIWBbes4+qu1bNlo2V4cbkISr8q6gRBFA=="],
"@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.27.4", "", { "os": "linux", "cpu": "none" }, "sha512-8mL/vh8qeCoRcFH2nM8wm5uJP+ZcVYGGayMavi8GmRJjuI3g1v6Z7Ni0JJKAJW+m0EtUuARb6Lmp4hMjzCBWzA=="],
"@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.27.4", "", { "os": "linux", "cpu": "none" }, "sha512-1RdrWFFiiLIW7LQq9Q2NES+HiD4NyT8Itj9AUeCl0IVCA459WnPhREKgwrpaIfTOe+/2rdntisegiPWn/r/aAw=="],
"@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.27.4", "", { "os": "linux", "cpu": "ppc64" }, "sha512-tLCwNG47l3sd9lpfyx9LAGEGItCUeRCWeAx6x2Jmbav65nAwoPXfewtAdtbtit/pJFLUWOhpv0FpS6GQAmPrHA=="],
"@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.27.4", "", { "os": "linux", "cpu": "none" }, "sha512-BnASypppbUWyqjd1KIpU4AUBiIhVr6YlHx/cnPgqEkNoVOhHg+YiSVxM1RLfiy4t9cAulbRGTNCKOcqHrEQLIw=="],
"@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.27.4", "", { "os": "linux", "cpu": "s390x" }, "sha512-+eUqgb/Z7vxVLezG8bVB9SfBie89gMueS+I0xYh2tJdw3vqA/0ImZJ2ROeWwVJN59ihBeZ7Tu92dF/5dy5FttA=="],
"@esbuild/linux-x64": ["@esbuild/linux-x64@0.27.4", "", { "os": "linux", "cpu": "x64" }, "sha512-S5qOXrKV8BQEzJPVxAwnryi2+Iq5pB40gTEIT69BQONqR7JH1EPIcQ/Uiv9mCnn05jff9umq/5nqzxlqTOg9NA=="],
"@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.27.4", "", { "os": "none", "cpu": "arm64" }, "sha512-xHT8X4sb0GS8qTqiwzHqpY00C95DPAq7nAwX35Ie/s+LO9830hrMd3oX0ZMKLvy7vsonee73x0lmcdOVXFzd6Q=="],
"@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.27.4", "", { "os": "none", "cpu": "x64" }, "sha512-RugOvOdXfdyi5Tyv40kgQnI0byv66BFgAqjdgtAKqHoZTbTF2QqfQrFwa7cHEORJf6X2ht+l9ABLMP0dnKYsgg=="],
"@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.27.4", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-2MyL3IAaTX+1/qP0O1SwskwcwCoOI4kV2IBX1xYnDDqthmq5ArrW94qSIKCAuRraMgPOmG0RDTA74mzYNQA9ow=="],
"@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.27.4", "", { "os": "openbsd", "cpu": "x64" }, "sha512-u8fg/jQ5aQDfsnIV6+KwLOf1CmJnfu1ShpwqdwC0uA7ZPwFws55Ngc12vBdeUdnuWoQYx/SOQLGDcdlfXhYmXQ=="],
"@esbuild/openharmony-arm64": ["@esbuild/openharmony-arm64@0.27.4", "", { "os": "none", "cpu": "arm64" }, "sha512-JkTZrl6VbyO8lDQO3yv26nNr2RM2yZzNrNHEsj9bm6dOwwu9OYN28CjzZkH57bh4w0I2F7IodpQvUAEd1mbWXg=="],
"@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.27.4", "", { "os": "sunos", "cpu": "x64" }, "sha512-/gOzgaewZJfeJTlsWhvUEmUG4tWEY2Spp5M20INYRg2ZKl9QPO3QEEgPeRtLjEWSW8FilRNacPOg8R1uaYkA6g=="],
"@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.27.4", "", { "os": "win32", "cpu": "arm64" }, "sha512-Z9SExBg2y32smoDQdf1HRwHRt6vAHLXcxD2uGgO/v2jK7Y718Ix4ndsbNMU/+1Qiem9OiOdaqitioZwxivhXYg=="],
"@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.27.4", "", { "os": "win32", "cpu": "ia32" }, "sha512-DAyGLS0Jz5G5iixEbMHi5KdiApqHBWMGzTtMiJ72ZOLhbu/bzxgAe8Ue8CTS3n3HbIUHQz/L51yMdGMeoxXNJw=="],
"@esbuild/win32-x64": ["@esbuild/win32-x64@0.27.4", "", { "os": "win32", "cpu": "x64" }, "sha512-+knoa0BDoeXgkNvvV1vvbZX4+hizelrkwmGJBdT17t8FNPwG2lKemmuMZlmaNQ3ws3DKKCxpb4zRZEIp3UxFCg=="],
"@img/colour": ["@img/colour@1.1.0", "", {}, "sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ=="],
"@img/sharp-darwin-arm64": ["@img/sharp-darwin-arm64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-darwin-arm64": "1.2.4" }, "os": "darwin", "cpu": "arm64" }, "sha512-imtQ3WMJXbMY4fxb/Ndp6HBTNVtWCUI0WdobyheGf5+ad6xX8VIDO8u2xE4qc/fr08CKG/7dDseFtn6M6g/r3w=="],
"@img/sharp-darwin-x64": ["@img/sharp-darwin-x64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-darwin-x64": "1.2.4" }, "os": "darwin", "cpu": "x64" }, "sha512-YNEFAF/4KQ/PeW0N+r+aVVsoIY0/qxxikF2SWdp+NRkmMB7y9LBZAVqQ4yhGCm/H3H270OSykqmQMKLBhBJDEw=="],
"@img/sharp-libvips-darwin-arm64": ["@img/sharp-libvips-darwin-arm64@1.2.4", "", { "os": "darwin", "cpu": "arm64" }, "sha512-zqjjo7RatFfFoP0MkQ51jfuFZBnVE2pRiaydKJ1G/rHZvnsrHAOcQALIi9sA5co5xenQdTugCvtb1cuf78Vf4g=="],
"@img/sharp-libvips-darwin-x64": ["@img/sharp-libvips-darwin-x64@1.2.4", "", { "os": "darwin", "cpu": "x64" }, "sha512-1IOd5xfVhlGwX+zXv2N93k0yMONvUlANylbJw1eTah8K/Jtpi15KC+WSiaX/nBmbm2HxRM1gZ0nSdjSsrZbGKg=="],
"@img/sharp-libvips-linux-arm": ["@img/sharp-libvips-linux-arm@1.2.4", "", { "os": "linux", "cpu": "arm" }, "sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A=="],
"@img/sharp-libvips-linux-arm64": ["@img/sharp-libvips-linux-arm64@1.2.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw=="],
"@img/sharp-libvips-linux-ppc64": ["@img/sharp-libvips-linux-ppc64@1.2.4", "", { "os": "linux", "cpu": "ppc64" }, "sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA=="],
"@img/sharp-libvips-linux-riscv64": ["@img/sharp-libvips-linux-riscv64@1.2.4", "", { "os": "linux", "cpu": "none" }, "sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA=="],
"@img/sharp-libvips-linux-s390x": ["@img/sharp-libvips-linux-s390x@1.2.4", "", { "os": "linux", "cpu": "s390x" }, "sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ=="],
"@img/sharp-libvips-linux-x64": ["@img/sharp-libvips-linux-x64@1.2.4", "", { "os": "linux", "cpu": "x64" }, "sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw=="],
"@img/sharp-libvips-linuxmusl-arm64": ["@img/sharp-libvips-linuxmusl-arm64@1.2.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw=="],
"@img/sharp-libvips-linuxmusl-x64": ["@img/sharp-libvips-linuxmusl-x64@1.2.4", "", { "os": "linux", "cpu": "x64" }, "sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg=="],
"@img/sharp-linux-arm": ["@img/sharp-linux-arm@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-arm": "1.2.4" }, "os": "linux", "cpu": "arm" }, "sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw=="],
"@img/sharp-linux-arm64": ["@img/sharp-linux-arm64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-arm64": "1.2.4" }, "os": "linux", "cpu": "arm64" }, "sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg=="],
"@img/sharp-linux-ppc64": ["@img/sharp-linux-ppc64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-ppc64": "1.2.4" }, "os": "linux", "cpu": "ppc64" }, "sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA=="],
"@img/sharp-linux-riscv64": ["@img/sharp-linux-riscv64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-riscv64": "1.2.4" }, "os": "linux", "cpu": "none" }, "sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw=="],
"@img/sharp-linux-s390x": ["@img/sharp-linux-s390x@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-s390x": "1.2.4" }, "os": "linux", "cpu": "s390x" }, "sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg=="],
"@img/sharp-linux-x64": ["@img/sharp-linux-x64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-linux-x64": "1.2.4" }, "os": "linux", "cpu": "x64" }, "sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ=="],
"@img/sharp-linuxmusl-arm64": ["@img/sharp-linuxmusl-arm64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-linuxmusl-arm64": "1.2.4" }, "os": "linux", "cpu": "arm64" }, "sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg=="],
"@img/sharp-linuxmusl-x64": ["@img/sharp-linuxmusl-x64@0.34.5", "", { "optionalDependencies": { "@img/sharp-libvips-linuxmusl-x64": "1.2.4" }, "os": "linux", "cpu": "x64" }, "sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q=="],
"@img/sharp-wasm32": ["@img/sharp-wasm32@0.34.5", "", { "dependencies": { "@emnapi/runtime": "^1.7.0" }, "cpu": "none" }, "sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw=="],
"@img/sharp-win32-arm64": ["@img/sharp-win32-arm64@0.34.5", "", { "os": "win32", "cpu": "arm64" }, "sha512-WQ3AgWCWYSb2yt+IG8mnC6Jdk9Whs7O0gxphblsLvdhSpSTtmu69ZG1Gkb6NuvxsNACwiPV6cNSZNzt0KPsw7g=="],
"@img/sharp-win32-ia32": ["@img/sharp-win32-ia32@0.34.5", "", { "os": "win32", "cpu": "ia32" }, "sha512-FV9m/7NmeCmSHDD5j4+4pNI8Cp3aW+JvLoXcTUo0IqyjSfAZJ8dIUmijx1qaJsIiU+Hosw6xM5KijAWRJCSgNg=="],
"@img/sharp-win32-x64": ["@img/sharp-win32-x64@0.34.5", "", { "os": "win32", "cpu": "x64" }, "sha512-+29YMsqY2/9eFEiW93eqWnuLcWcufowXewwSNIT6UwZdUUCrM3oFjMWH/Z6/TMmb4hlFenmfAVbpWeup2jryCw=="],
"@jridgewell/gen-mapping": ["@jridgewell/gen-mapping@0.3.13", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA=="],
"@jridgewell/remapping": ["@jridgewell/remapping@2.3.5", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ=="],
"@jridgewell/resolve-uri": ["@jridgewell/resolve-uri@3.1.2", "", {}, "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw=="],
"@jridgewell/sourcemap-codec": ["@jridgewell/sourcemap-codec@1.5.5", "", {}, "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og=="],
"@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.31", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw=="],
"@lucide/svelte": ["@lucide/svelte@1.7.0", "", { "peerDependencies": { "svelte": "^5" } }, "sha512-YytBKOUBGox7yWcykZnYxOkn5WpR5G1qYXLYXV/j1B79SOTTEKzB+s5yF5Rq9l9OkweDStNH2b4yTqfvhEhV8g=="],
"@oslojs/encoding": ["@oslojs/encoding@1.1.0", "", {}, "sha512-70wQhgYmndg4GCPxPPxPGevRKqTIJ2Nh4OkiMWmDAVYsTQ+Ta7Sq+rPevXyXGdzr30/qZBnyOalCszoMxlyldQ=="],
"@rollup/pluginutils": ["@rollup/pluginutils@5.3.0", "", { "dependencies": { "@types/estree": "^1.0.0", "estree-walker": "^2.0.2", "picomatch": "^4.0.2" }, "peerDependencies": { "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" }, "optionalPeers": ["rollup"] }, "sha512-5EdhGZtnu3V88ces7s53hhfK5KSASnJZv8Lulpc04cWO3REESroJXg73DFsOmgbU2BhwV0E20bu2IDZb3VKW4Q=="],
"@rollup/rollup-android-arm-eabi": ["@rollup/rollup-android-arm-eabi@4.60.1", "", { "os": "android", "cpu": "arm" }, "sha512-d6FinEBLdIiK+1uACUttJKfgZREXrF0Qc2SmLII7W2AD8FfiZ9Wjd+rD/iRuf5s5dWrr1GgwXCvPqOuDquOowA=="],
"@rollup/rollup-android-arm64": ["@rollup/rollup-android-arm64@4.60.1", "", { "os": "android", "cpu": "arm64" }, "sha512-YjG/EwIDvvYI1YvYbHvDz/BYHtkY4ygUIXHnTdLhG+hKIQFBiosfWiACWortsKPKU/+dUwQQCKQM3qrDe8c9BA=="],
"@rollup/rollup-darwin-arm64": ["@rollup/rollup-darwin-arm64@4.60.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-mjCpF7GmkRtSJwon+Rq1N8+pI+8l7w5g9Z3vWj4T7abguC4Czwi3Yu/pFaLvA3TTeMVjnu3ctigusqWUfjZzvw=="],
"@rollup/rollup-darwin-x64": ["@rollup/rollup-darwin-x64@4.60.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-haZ7hJ1JT4e9hqkoT9R/19XW2QKqjfJVv+i5AGg57S+nLk9lQnJ1F/eZloRO3o9Scy9CM3wQ9l+dkXtcBgN5Ew=="],
"@rollup/rollup-freebsd-arm64": ["@rollup/rollup-freebsd-arm64@4.60.1", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-czw90wpQq3ZsAVBlinZjAYTKduOjTywlG7fEeWKUA7oCmpA8xdTkxZZlwNJKWqILlq0wehoZcJYfBvOyhPTQ6w=="],
"@rollup/rollup-freebsd-x64": ["@rollup/rollup-freebsd-x64@4.60.1", "", { "os": "freebsd", "cpu": "x64" }, "sha512-KVB2rqsxTHuBtfOeySEyzEOB7ltlB/ux38iu2rBQzkjbwRVlkhAGIEDiiYnO2kFOkJp+Z7pUXKyrRRFuFUKt+g=="],
"@rollup/rollup-linux-arm-gnueabihf": ["@rollup/rollup-linux-arm-gnueabihf@4.60.1", "", { "os": "linux", "cpu": "arm" }, "sha512-L+34Qqil+v5uC0zEubW7uByo78WOCIrBvci69E7sFASRl0X7b/MB6Cqd1lky/CtcSVTydWa2WZwFuWexjS5o6g=="],
"@rollup/rollup-linux-arm-musleabihf": ["@rollup/rollup-linux-arm-musleabihf@4.60.1", "", { "os": "linux", "cpu": "arm" }, "sha512-n83O8rt4v34hgFzlkb1ycniJh7IR5RCIqt6mz1VRJD6pmhRi0CXdmfnLu9dIUS6buzh60IvACM842Ffb3xd6Gg=="],
"@rollup/rollup-linux-arm64-gnu": ["@rollup/rollup-linux-arm64-gnu@4.60.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-Nql7sTeAzhTAja3QXeAI48+/+GjBJ+QmAH13snn0AJSNL50JsDqotyudHyMbO2RbJkskbMbFJfIJKWA6R1LCJQ=="],
"@rollup/rollup-linux-arm64-musl": ["@rollup/rollup-linux-arm64-musl@4.60.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-+pUymDhd0ys9GcKZPPWlFiZ67sTWV5UU6zOJat02M1+PiuSGDziyRuI/pPue3hoUwm2uGfxdL+trT6Z9rxnlMA=="],
"@rollup/rollup-linux-loong64-gnu": ["@rollup/rollup-linux-loong64-gnu@4.60.1", "", { "os": "linux", "cpu": "none" }, "sha512-VSvgvQeIcsEvY4bKDHEDWcpW4Yw7BtlKG1GUT4FzBUlEKQK0rWHYBqQt6Fm2taXS+1bXvJT6kICu5ZwqKCnvlQ=="],
"@rollup/rollup-linux-loong64-musl": ["@rollup/rollup-linux-loong64-musl@4.60.1", "", { "os": "linux", "cpu": "none" }, "sha512-4LqhUomJqwe641gsPp6xLfhqWMbQV04KtPp7/dIp0nzPxAkNY1AbwL5W0MQpcalLYk07vaW9Kp1PBhdpZYYcEw=="],
"@rollup/rollup-linux-ppc64-gnu": ["@rollup/rollup-linux-ppc64-gnu@4.60.1", "", { "os": "linux", "cpu": "ppc64" }, "sha512-tLQQ9aPvkBxOc/EUT6j3pyeMD6Hb8QF2BTBnCQWP/uu1lhc9AIrIjKnLYMEroIz/JvtGYgI9dF3AxHZNaEH0rw=="],
"@rollup/rollup-linux-ppc64-musl": ["@rollup/rollup-linux-ppc64-musl@4.60.1", "", { "os": "linux", "cpu": "ppc64" }, "sha512-RMxFhJwc9fSXP6PqmAz4cbv3kAyvD1etJFjTx4ONqFP9DkTkXsAMU4v3Vyc5BgzC+anz7nS/9tp4obsKfqkDHg=="],
"@rollup/rollup-linux-riscv64-gnu": ["@rollup/rollup-linux-riscv64-gnu@4.60.1", "", { "os": "linux", "cpu": "none" }, "sha512-QKgFl+Yc1eEk6MmOBfRHYF6lTxiiiV3/z/BRrbSiW2I7AFTXoBFvdMEyglohPj//2mZS4hDOqeB0H1ACh3sBbg=="],
"@rollup/rollup-linux-riscv64-musl": ["@rollup/rollup-linux-riscv64-musl@4.60.1", "", { "os": "linux", "cpu": "none" }, "sha512-RAjXjP/8c6ZtzatZcA1RaQr6O1TRhzC+adn8YZDnChliZHviqIjmvFwHcxi4JKPSDAt6Uhf/7vqcBzQJy0PDJg=="],
"@rollup/rollup-linux-s390x-gnu": ["@rollup/rollup-linux-s390x-gnu@4.60.1", "", { "os": "linux", "cpu": "s390x" }, "sha512-wcuocpaOlaL1COBYiA89O6yfjlp3RwKDeTIA0hM7OpmhR1Bjo9j31G1uQVpDlTvwxGn2nQs65fBFL5UFd76FcQ=="],
"@rollup/rollup-linux-x64-gnu": ["@rollup/rollup-linux-x64-gnu@4.60.1", "", { "os": "linux", "cpu": "x64" }, "sha512-77PpsFQUCOiZR9+LQEFg9GClyfkNXj1MP6wRnzYs0EeWbPcHs02AXu4xuUbM1zhwn3wqaizle3AEYg5aeoohhg=="],
"@rollup/rollup-linux-x64-musl": ["@rollup/rollup-linux-x64-musl@4.60.1", "", { "os": "linux", "cpu": "x64" }, "sha512-5cIATbk5vynAjqqmyBjlciMJl1+R/CwX9oLk/EyiFXDWd95KpHdrOJT//rnUl4cUcskrd0jCCw3wpZnhIHdD9w=="],
"@rollup/rollup-openbsd-x64": ["@rollup/rollup-openbsd-x64@4.60.1", "", { "os": "openbsd", "cpu": "x64" }, "sha512-cl0w09WsCi17mcmWqqglez9Gk8isgeWvoUZ3WiJFYSR3zjBQc2J5/ihSjpl+VLjPqjQ/1hJRcqBfLjssREQILw=="],
"@rollup/rollup-openharmony-arm64": ["@rollup/rollup-openharmony-arm64@4.60.1", "", { "os": "none", "cpu": "arm64" }, "sha512-4Cv23ZrONRbNtbZa37mLSueXUCtN7MXccChtKpUnQNgF010rjrjfHx3QxkS2PI7LqGT5xXyYs1a7LbzAwT0iCA=="],
"@rollup/rollup-win32-arm64-msvc": ["@rollup/rollup-win32-arm64-msvc@4.60.1", "", { "os": "win32", "cpu": "arm64" }, "sha512-i1okWYkA4FJICtr7KpYzFpRTHgy5jdDbZiWfvny21iIKky5YExiDXP+zbXzm3dUcFpkEeYNHgQ5fuG236JPq0g=="],
"@rollup/rollup-win32-ia32-msvc": ["@rollup/rollup-win32-ia32-msvc@4.60.1", "", { "os": "win32", "cpu": "ia32" }, "sha512-u09m3CuwLzShA0EYKMNiFgcjjzwqtUMLmuCJLeZWjjOYA3IT2Di09KaxGBTP9xVztWyIWjVdsB2E9goMjZvTQg=="],
"@rollup/rollup-win32-x64-gnu": ["@rollup/rollup-win32-x64-gnu@4.60.1", "", { "os": "win32", "cpu": "x64" }, "sha512-k+600V9Zl1CM7eZxJgMyTUzmrmhB/0XZnF4pRypKAlAgxmedUA+1v9R+XOFv56W4SlHEzfeMtzujLJD22Uz5zg=="],
"@rollup/rollup-win32-x64-msvc": ["@rollup/rollup-win32-x64-msvc@4.60.1", "", { "os": "win32", "cpu": "x64" }, "sha512-lWMnixq/QzxyhTV6NjQJ4SFo1J6PvOX8vUx5Wb4bBPsEb+8xZ89Bz6kOXpfXj9ak9AHTQVQzlgzBEc1SyM27xQ=="],
"@shikijs/core": ["@shikijs/core@4.0.2", "", { "dependencies": { "@shikijs/primitive": "4.0.2", "@shikijs/types": "4.0.2", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4", "hast-util-to-html": "^9.0.5" } }, "sha512-hxT0YF4ExEqB8G/qFdtJvpmHXBYJ2lWW7qTHDarVkIudPFE6iCIrqdgWxGn5s+ppkGXI0aEGlibI0PAyzP3zlw=="],
"@shikijs/engine-javascript": ["@shikijs/engine-javascript@4.0.2", "", { "dependencies": { "@shikijs/types": "4.0.2", "@shikijs/vscode-textmate": "^10.0.2", "oniguruma-to-es": "^4.3.4" } }, "sha512-7PW0Nm49DcoUIQEXlJhNNBHyoGMjalRETTCcjMqEaMoJRLljy1Bi/EGV3/qLBgLKQejdspiiYuHGQW6dX94Nag=="],
"@shikijs/engine-oniguruma": ["@shikijs/engine-oniguruma@4.0.2", "", { "dependencies": { "@shikijs/types": "4.0.2", "@shikijs/vscode-textmate": "^10.0.2" } }, "sha512-UpCB9Y2sUKlS9z8juFSKz7ZtysmeXCgnRF0dlhXBkmQnek7lAToPte8DkxmEYGNTMii72zU/lyXiCB6StuZeJg=="],
"@shikijs/langs": ["@shikijs/langs@4.0.2", "", { "dependencies": { "@shikijs/types": "4.0.2" } }, "sha512-KaXby5dvoeuZzN0rYQiPMjFoUrz4hgwIE+D6Du9owcHcl6/g16/yT5BQxSW5cGt2MZBz6Hl0YuRqf12omRfUUg=="],
"@shikijs/primitive": ["@shikijs/primitive@4.0.2", "", { "dependencies": { "@shikijs/types": "4.0.2", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-M6UMPrSa3fN5ayeJwFVl9qWofl273wtK1VG8ySDZ1mQBfhCpdd8nEx7nPZ/tk7k+TYcpqBZzj/AnwxT9lO+HJw=="],
"@shikijs/themes": ["@shikijs/themes@4.0.2", "", { "dependencies": { "@shikijs/types": "4.0.2" } }, "sha512-mjCafwt8lJJaVSsQvNVrJumbnnj1RI8jbUKrPKgE6E3OvQKxnuRoBaYC51H4IGHePsGN/QtALglWBU7DoKDFnA=="],
"@shikijs/types": ["@shikijs/types@4.0.2", "", { "dependencies": { "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-qzbeRooUTPnLE+sHD/Z8DStmaDgnbbc/pMrU203950aRqjX/6AFHeDYT+j00y2lPdz0ywJKx7o/7qnqTivtlXg=="],
"@shikijs/vscode-textmate": ["@shikijs/vscode-textmate@10.0.2", "", {}, "sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg=="],
"@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.2", "", { "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.2" } }, "sha512-pXS+wJ2gZpVXqFaUEjojq7jzMpTGf8rU6ipJz5ovJV6PUGmlJ+jvIwGrzdHdQ80Sg+wmQxUFuoW1UAAwHNEdFA=="],
"@tailwindcss/oxide": ["@tailwindcss/oxide@4.2.2", "", { "optionalDependencies": { "@tailwindcss/oxide-android-arm64": "4.2.2", "@tailwindcss/oxide-darwin-arm64": "4.2.2", "@tailwindcss/oxide-darwin-x64": "4.2.2", "@tailwindcss/oxide-freebsd-x64": "4.2.2", "@tailwindcss/oxide-linux-arm-gnueabihf": "4.2.2", "@tailwindcss/oxide-linux-arm64-gnu": "4.2.2", "@tailwindcss/oxide-linux-arm64-musl": "4.2.2", "@tailwindcss/oxide-linux-x64-gnu": "4.2.2", "@tailwindcss/oxide-linux-x64-musl": "4.2.2", "@tailwindcss/oxide-wasm32-wasi": "4.2.2", "@tailwindcss/oxide-win32-arm64-msvc": "4.2.2", "@tailwindcss/oxide-win32-x64-msvc": "4.2.2" } }, "sha512-qEUA07+E5kehxYp9BVMpq9E8vnJuBHfJEC0vPC5e7iL/hw7HR61aDKoVoKzrG+QKp56vhNZe4qwkRmMC0zDLvg=="],
"@tailwindcss/oxide-android-arm64": ["@tailwindcss/oxide-android-arm64@4.2.2", "", { "os": "android", "cpu": "arm64" }, "sha512-dXGR1n+P3B6748jZO/SvHZq7qBOqqzQ+yFrXpoOWWALWndF9MoSKAT3Q0fYgAzYzGhxNYOoysRvYlpixRBBoDg=="],
"@tailwindcss/oxide-darwin-arm64": ["@tailwindcss/oxide-darwin-arm64@4.2.2", "", { "os": "darwin", "cpu": "arm64" }, "sha512-iq9Qjr6knfMpZHj55/37ouZeykwbDqF21gPFtfnhCCKGDcPI/21FKC9XdMO/XyBM7qKORx6UIhGgg6jLl7BZlg=="],
"@tailwindcss/oxide-darwin-x64": ["@tailwindcss/oxide-darwin-x64@4.2.2", "", { "os": "darwin", "cpu": "x64" }, "sha512-BlR+2c3nzc8f2G639LpL89YY4bdcIdUmiOOkv2GQv4/4M0vJlpXEa0JXNHhCHU7VWOKWT/CjqHdTP8aUuDJkuw=="],
"@tailwindcss/oxide-freebsd-x64": ["@tailwindcss/oxide-freebsd-x64@4.2.2", "", { "os": "freebsd", "cpu": "x64" }, "sha512-YUqUgrGMSu2CDO82hzlQ5qSb5xmx3RUrke/QgnoEx7KvmRJHQuZHZmZTLSuuHwFf0DJPybFMXMYf+WJdxHy/nQ=="],
"@tailwindcss/oxide-linux-arm-gnueabihf": ["@tailwindcss/oxide-linux-arm-gnueabihf@4.2.2", "", { "os": "linux", "cpu": "arm" }, "sha512-FPdhvsW6g06T9BWT0qTwiVZYE2WIFo2dY5aCSpjG/S/u1tby+wXoslXS0kl3/KXnULlLr1E3NPRRw0g7t2kgaQ=="],
"@tailwindcss/oxide-linux-arm64-gnu": ["@tailwindcss/oxide-linux-arm64-gnu@4.2.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-4og1V+ftEPXGttOO7eCmW7VICmzzJWgMx+QXAJRAhjrSjumCwWqMfkDrNu1LXEQzNAwz28NCUpucgQPrR4S2yw=="],
"@tailwindcss/oxide-linux-arm64-musl": ["@tailwindcss/oxide-linux-arm64-musl@4.2.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-oCfG/mS+/+XRlwNjnsNLVwnMWYH7tn/kYPsNPh+JSOMlnt93mYNCKHYzylRhI51X+TbR+ufNhhKKzm6QkqX8ag=="],
"@tailwindcss/oxide-linux-x64-gnu": ["@tailwindcss/oxide-linux-x64-gnu@4.2.2", "", { "os": "linux", "cpu": "x64" }, "sha512-rTAGAkDgqbXHNp/xW0iugLVmX62wOp2PoE39BTCGKjv3Iocf6AFbRP/wZT/kuCxC9QBh9Pu8XPkv/zCZB2mcMg=="],
"@tailwindcss/oxide-linux-x64-musl": ["@tailwindcss/oxide-linux-x64-musl@4.2.2", "", { "os": "linux", "cpu": "x64" }, "sha512-XW3t3qwbIwiSyRCggeO2zxe3KWaEbM0/kW9e8+0XpBgyKU4ATYzcVSMKteZJ1iukJ3HgHBjbg9P5YPRCVUxlnQ=="],
"@tailwindcss/oxide-wasm32-wasi": ["@tailwindcss/oxide-wasm32-wasi@4.2.2", "", { "dependencies": { "@emnapi/core": "^1.8.1", "@emnapi/runtime": "^1.8.1", "@emnapi/wasi-threads": "^1.1.0", "@napi-rs/wasm-runtime": "^1.1.1", "@tybys/wasm-util": "^0.10.1", "tslib": "^2.8.1" }, "cpu": "none" }, "sha512-eKSztKsmEsn1O5lJ4ZAfyn41NfG7vzCg496YiGtMDV86jz1q/irhms5O0VrY6ZwTUkFy/EKG3RfWgxSI3VbZ8Q=="],
"@tailwindcss/oxide-win32-arm64-msvc": ["@tailwindcss/oxide-win32-arm64-msvc@4.2.2", "", { "os": "win32", "cpu": "arm64" }, "sha512-qPmaQM4iKu5mxpsrWZMOZRgZv1tOZpUm+zdhhQP0VhJfyGGO3aUKdbh3gDZc/dPLQwW4eSqWGrrcWNBZWUWaXQ=="],
"@tailwindcss/oxide-win32-x64-msvc": ["@tailwindcss/oxide-win32-x64-msvc@4.2.2", "", { "os": "win32", "cpu": "x64" }, "sha512-1T/37VvI7WyH66b+vqHj/cLwnCxt7Qt3WFu5Q8hk65aOvlwAhs7rAp1VkulBJw/N4tMirXjVnylTR72uI0HGcA=="],
"@tailwindcss/typography": ["@tailwindcss/typography@0.5.19", "", { "dependencies": { "postcss-selector-parser": "6.0.10" }, "peerDependencies": { "tailwindcss": ">=3.0.0 || insiders || >=4.0.0-alpha.20 || >=4.0.0-beta.1" } }, "sha512-w31dd8HOx3k9vPtcQh5QHP9GwKcgbMp87j58qi6xgiBnFFtKEAgCWnDw4qUT8aHwkCp8bKvb/KGKWWHedP0AAg=="],
"@tailwindcss/vite": ["@tailwindcss/vite@4.2.2", "", { "dependencies": { "@tailwindcss/node": "4.2.2", "@tailwindcss/oxide": "4.2.2", "tailwindcss": "4.2.2" }, "peerDependencies": { "vite": "^5.2.0 || ^6 || ^7 || ^8" } }, "sha512-mEiF5HO1QqCLXoNEfXVA1Tzo+cYsrqV7w9Juj2wdUFyW07JRenqMG225MvPwr3ZD9N1bFQj46X7r33iHxLUW0w=="],
"@types/debug": ["@types/debug@4.1.13", "", { "dependencies": { "@types/ms": "*" } }, "sha512-KSVgmQmzMwPlmtljOomayoR89W4FynCAi3E8PPs7vmDVPe84hT+vGPKkJfThkmXs0x0jAaa9U8uW8bbfyS2fWw=="],
"@types/dompurify": ["@types/dompurify@3.2.0", "", { "dependencies": { "dompurify": "*" } }, "sha512-Fgg31wv9QbLDA0SpTOXO3MaxySc4DKGLi8sna4/Utjo4r3ZRPdCt4UQee8BWr+Q5z21yifghREPJGYaEOEIACg=="],
"@types/estree": ["@types/estree@1.0.8", "", {}, "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w=="],
"@types/hast": ["@types/hast@3.0.4", "", { "dependencies": { "@types/unist": "*" } }, "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ=="],
"@types/js-yaml": ["@types/js-yaml@4.0.9", "", {}, "sha512-k4MGaQl5TGo/iipqb2UDG2UwjXziSWkh0uysQelTlJpX1qGlpUZYm8PnO4DxG1qBomtJUdYJ6qR6xdIah10JLg=="],
"@types/mdast": ["@types/mdast@4.0.4", "", { "dependencies": { "@types/unist": "*" } }, "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA=="],
"@types/ms": ["@types/ms@2.1.0", "", {}, "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA=="],
"@types/nlcst": ["@types/nlcst@2.0.3", "", { "dependencies": { "@types/unist": "*" } }, "sha512-vSYNSDe6Ix3q+6Z7ri9lyWqgGhJTmzRjZRqyq15N0Z/1/UnVsno9G/N40NBijoYx2seFDIl0+B2mgAb9mezUCA=="],
"@types/node": ["@types/node@25.5.0", "", { "dependencies": { "undici-types": "~7.18.0" } }, "sha512-jp2P3tQMSxWugkCUKLRPVUpGaL5MVFwF8RDuSRztfwgN1wmqJeMSbKlnEtQqU8UrhTmzEmZdu2I6v2dpp7XIxw=="],
"@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=="],
"@typescript-eslint/types": ["@typescript-eslint/types@8.58.0", "", {}, "sha512-O9CjxypDT89fbHxRfETNoAnHj/i6IpRK0CvbVN3qibxlLdo5p5hcLmUuCCrHMpxiWSwKyI8mCP7qRNYuOJ0Uww=="],
"@ungap/structured-clone": ["@ungap/structured-clone@1.3.0", "", {}, "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g=="],
"acorn": ["acorn@8.16.0", "", { "bin": { "acorn": "bin/acorn" } }, "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw=="],
"ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
"ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="],
"ansi_up": ["ansi_up@6.0.6", "", {}, "sha512-yIa1x3Ecf8jWP4UWEunNjqNX6gzE4vg2gGz+xqRGY+TBSucnYp6RRdPV4brmtg6bQ1ljD48mZ5iGSEj7QEpRKA=="],
"anymatch": ["anymatch@3.1.3", "", { "dependencies": { "normalize-path": "^3.0.0", "picomatch": "^2.0.4" } }, "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw=="],
"argparse": ["argparse@2.0.1", "", {}, "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="],
"aria-query": ["aria-query@5.3.2", "", {}, "sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw=="],
"array-iterate": ["array-iterate@2.0.1", "", {}, "sha512-I1jXZMjAgCMmxT4qxXfPXa6SthSoE8h6gkSI9BGGNv8mP8G/v0blc+qFnZu6K42vTOiuME596QaLO0TP3Lk0xg=="],
"astro": ["astro@6.1.2", "", { "dependencies": { "@astrojs/compiler": "^3.0.1", "@astrojs/internal-helpers": "0.8.0", "@astrojs/markdown-remark": "7.1.0", "@astrojs/telemetry": "3.3.0", "@capsizecss/unpack": "^4.0.0", "@clack/prompts": "^1.1.0", "@oslojs/encoding": "^1.1.0", "@rollup/pluginutils": "^5.3.0", "aria-query": "^5.3.2", "axobject-query": "^4.1.0", "ci-info": "^4.4.0", "clsx": "^2.1.1", "common-ancestor-path": "^2.0.0", "cookie": "^1.1.1", "devalue": "^5.6.3", "diff": "^8.0.3", "dlv": "^1.1.3", "dset": "^3.1.4", "es-module-lexer": "^2.0.0", "esbuild": "^0.27.3", "flattie": "^1.1.1", "fontace": "~0.4.1", "github-slugger": "^2.0.0", "html-escaper": "3.0.3", "http-cache-semantics": "^4.2.0", "js-yaml": "^4.1.1", "magic-string": "^0.30.21", "magicast": "^0.5.2", "mrmime": "^2.0.1", "neotraverse": "^0.6.18", "obug": "^2.1.1", "p-limit": "^7.3.0", "p-queue": "^9.1.0", "package-manager-detector": "^1.6.0", "piccolore": "^0.1.3", "picomatch": "^4.0.3", "rehype": "^13.0.2", "semver": "^7.7.4", "shiki": "^4.0.2", "smol-toml": "^1.6.0", "svgo": "^4.0.1", "tinyclip": "^0.1.12", "tinyexec": "^1.0.4", "tinyglobby": "^0.2.15", "tsconfck": "^3.1.6", "ultrahtml": "^1.6.0", "unifont": "~0.7.4", "unist-util-visit": "^5.1.0", "unstorage": "^1.17.4", "vfile": "^6.0.3", "vite": "^7.3.1", "vitefu": "^1.1.2", "xxhash-wasm": "^1.1.0", "yargs-parser": "^22.0.0", "zod": "^4.3.6" }, "optionalDependencies": { "sharp": "^0.34.0" }, "bin": { "astro": "bin/astro.mjs" } }, "sha512-r3iIvmB6JvQxsdJLvapybKKq7Bojd1iQK6CCx5P55eRnXJIyUpHx/1UB/GdMm+em/lwaCUasxHCmIO0lCLV2uA=="],
"axobject-query": ["axobject-query@4.1.0", "", {}, "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ=="],
"bail": ["bail@2.0.2", "", {}, "sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw=="],
"boolbase": ["boolbase@1.0.0", "", {}, "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww=="],
"ccount": ["ccount@2.0.1", "", {}, "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg=="],
"chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="],
"character-entities": ["character-entities@2.0.2", "", {}, "sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ=="],
"character-entities-html4": ["character-entities-html4@2.1.0", "", {}, "sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA=="],
"character-entities-legacy": ["character-entities-legacy@3.0.0", "", {}, "sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ=="],
"chokidar": ["chokidar@5.0.0", "", { "dependencies": { "readdirp": "^5.0.0" } }, "sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw=="],
"ci-info": ["ci-info@4.4.0", "", {}, "sha512-77PSwercCZU2Fc4sX94eF8k8Pxte6JAwL4/ICZLFjJLqegs7kCuAsqqj/70NQF6TvDpgFjkubQB2FW2ZZddvQg=="],
"cliui": ["cliui@8.0.1", "", { "dependencies": { "string-width": "^4.2.0", "strip-ansi": "^6.0.1", "wrap-ansi": "^7.0.0" } }, "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ=="],
"clsx": ["clsx@2.1.1", "", {}, "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA=="],
"color-convert": ["color-convert@2.0.1", "", { "dependencies": { "color-name": "~1.1.4" } }, "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ=="],
"color-name": ["color-name@1.1.4", "", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="],
"comma-separated-tokens": ["comma-separated-tokens@2.0.3", "", {}, "sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg=="],
"commander": ["commander@11.1.0", "", {}, "sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ=="],
"common-ancestor-path": ["common-ancestor-path@2.0.0", "", {}, "sha512-dnN3ibLeoRf2HNC+OlCiNc5d2zxbLJXOtiZUudNFSXZrNSydxcCsSpRzXwfu7BBWCIfHPw+xTayeBvJCP/D8Ng=="],
"concurrently": ["concurrently@9.2.1", "", { "dependencies": { "chalk": "4.1.2", "rxjs": "7.8.2", "shell-quote": "1.8.3", "supports-color": "8.1.1", "tree-kill": "1.2.2", "yargs": "17.7.2" }, "bin": { "conc": "dist/bin/concurrently.js", "concurrently": "dist/bin/concurrently.js" } }, "sha512-fsfrO0MxV64Znoy8/l1vVIjjHa29SZyyqPgQBwhiDcaW8wJc2W3XWVOGx4M3oJBnv/zdUZIIp1gDeS98GzP8Ng=="],
"cookie": ["cookie@1.1.1", "", {}, "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ=="],
"cookie-es": ["cookie-es@1.2.3", "", {}, "sha512-lXVyvUvrNXblMqzIRrxHb57UUVmqsSWlxqt3XIjCkUP0wDAf6uicO6KMbEgYrMNtEvWgWHwe42CKxPu9MYAnWw=="],
"crossws": ["crossws@0.3.5", "", { "dependencies": { "uncrypto": "^0.1.3" } }, "sha512-ojKiDvcmByhwa8YYqbQI/hg7MEU0NC03+pSdEq4ZUnZR9xXpwk7E43SMNGkn+JxJGPFtNvQ48+vV2p+P1ml5PA=="],
"css-select": ["css-select@5.2.2", "", { "dependencies": { "boolbase": "^1.0.0", "css-what": "^6.1.0", "domhandler": "^5.0.2", "domutils": "^3.0.1", "nth-check": "^2.0.1" } }, "sha512-TizTzUddG/xYLA3NXodFM0fSbNizXjOKhqiQQwvhlspadZokn1KDy0NZFS0wuEubIYAV5/c1/lAr0TaaFXEXzw=="],
"css-tree": ["css-tree@3.2.1", "", { "dependencies": { "mdn-data": "2.27.1", "source-map-js": "^1.2.1" } }, "sha512-X7sjQzceUhu1u7Y/ylrRZFU2FS6LRiFVp6rKLPg23y3x3c3DOKAwuXGDp+PAGjh6CSnCjYeAul8pcT8bAl+lSA=="],
"css-what": ["css-what@6.2.2", "", {}, "sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA=="],
"cssesc": ["cssesc@3.0.0", "", { "bin": { "cssesc": "bin/cssesc" } }, "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg=="],
"csso": ["csso@5.0.5", "", { "dependencies": { "css-tree": "~2.2.0" } }, "sha512-0LrrStPOdJj+SPCCrGhzryycLjwcgUSHBtxNA8aIDxf0GLsRh1cKYhB00Gd1lDOS4yGH69+SNn13+TWbVHETFQ=="],
"daisyui": ["daisyui@5.5.19", "", {}, "sha512-pbFAkl1VCEh/MPCeclKL61I/MqRIFFhNU7yiXoDDRapXN4/qNCoMxeCCswyxEEhqL5eiTTfwHvucFtOE71C9sA=="],
"debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="],
"decode-named-character-reference": ["decode-named-character-reference@1.3.0", "", { "dependencies": { "character-entities": "^2.0.0" } }, "sha512-GtpQYB283KrPp6nRw50q3U9/VfOutZOe103qlN7BPP6Ad27xYnOIWv4lPzo8HCAL+mMZofJ9KEy30fq6MfaK6Q=="],
"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.6", "", {}, "sha512-f8mefEW4WIVg4LckePx3mALjQSPQgFlg9U8yaPdlsbdYcHQyj9n2zL2LJEA52smeYxOvmd/nB7TpMtHGMTHcug=="],
"dequal": ["dequal@2.0.3", "", {}, "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA=="],
"destr": ["destr@2.0.5", "", {}, "sha512-ugFTXCtDZunbzasqBxrK93Ik/DRYsO6S/fedkWEMKqt04xZ4csmnmwGDBAb07QWNaGMAmnTIemsYZCksjATwsA=="],
"detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="],
"devalue": ["devalue@5.6.4", "", {}, "sha512-Gp6rDldRsFh/7XuouDbxMH3Mx8GMCcgzIb1pDTvNyn8pZGQ22u+Wa+lGV9dQCltFQ7uVw0MhRyb8XDskNFOReA=="],
"devlop": ["devlop@1.1.0", "", { "dependencies": { "dequal": "^2.0.0" } }, "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA=="],
"diff": ["diff@8.0.4", "", {}, "sha512-DPi0FmjiSU5EvQV0++GFDOJ9ASQUVFh5kD+OzOnYdi7n3Wpm9hWWGfB/O2blfHcMVTL5WkQXSnRiK9makhrcnw=="],
"dlv": ["dlv@1.1.3", "", {}, "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA=="],
"dom-serializer": ["dom-serializer@2.0.0", "", { "dependencies": { "domelementtype": "^2.3.0", "domhandler": "^5.0.2", "entities": "^4.2.0" } }, "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg=="],
"domelementtype": ["domelementtype@2.3.0", "", {}, "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw=="],
"domhandler": ["domhandler@5.0.3", "", { "dependencies": { "domelementtype": "^2.3.0" } }, "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w=="],
"dompurify": ["dompurify@3.3.3", "", { "optionalDependencies": { "@types/trusted-types": "^2.0.7" } }, "sha512-Oj6pzI2+RqBfFG+qOaOLbFXLQ90ARpcGG6UePL82bJLtdsa6CYJD7nmiU8MW9nQNOtCHV3lZ/Bzq1X0QYbBZCA=="],
"domutils": ["domutils@3.2.2", "", { "dependencies": { "dom-serializer": "^2.0.0", "domelementtype": "^2.3.0", "domhandler": "^5.0.3" } }, "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw=="],
"dset": ["dset@3.1.4", "", {}, "sha512-2QF/g9/zTaPDc3BjNcVTGoBbXBgYfMTTceLaYcFJ/W9kggFUkhxD/hMEeuLKbugyef9SqAx8cpgwlIP/jinUTA=="],
"emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="],
"enhanced-resolve": ["enhanced-resolve@5.20.1", "", { "dependencies": { "graceful-fs": "^4.2.4", "tapable": "^2.3.0" } }, "sha512-Qohcme7V1inbAfvjItgw0EaxVX5q2rdVEZHRBrEQdRZTssLDGsL8Lwrznl8oQ/6kuTJONLaDcGjkNP247XEhcA=="],
"entities": ["entities@6.0.1", "", {}, "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g=="],
"es-module-lexer": ["es-module-lexer@2.0.0", "", {}, "sha512-5POEcUuZybH7IdmGsD8wlf0AI55wMecM9rVBTI/qEAy2c1kTOm3DjFYjrBdI2K3BaJjJYfYFeRtM0t9ssnRuxw=="],
"esbuild": ["esbuild@0.27.4", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.27.4", "@esbuild/android-arm": "0.27.4", "@esbuild/android-arm64": "0.27.4", "@esbuild/android-x64": "0.27.4", "@esbuild/darwin-arm64": "0.27.4", "@esbuild/darwin-x64": "0.27.4", "@esbuild/freebsd-arm64": "0.27.4", "@esbuild/freebsd-x64": "0.27.4", "@esbuild/linux-arm": "0.27.4", "@esbuild/linux-arm64": "0.27.4", "@esbuild/linux-ia32": "0.27.4", "@esbuild/linux-loong64": "0.27.4", "@esbuild/linux-mips64el": "0.27.4", "@esbuild/linux-ppc64": "0.27.4", "@esbuild/linux-riscv64": "0.27.4", "@esbuild/linux-s390x": "0.27.4", "@esbuild/linux-x64": "0.27.4", "@esbuild/netbsd-arm64": "0.27.4", "@esbuild/netbsd-x64": "0.27.4", "@esbuild/openbsd-arm64": "0.27.4", "@esbuild/openbsd-x64": "0.27.4", "@esbuild/openharmony-arm64": "0.27.4", "@esbuild/sunos-x64": "0.27.4", "@esbuild/win32-arm64": "0.27.4", "@esbuild/win32-ia32": "0.27.4", "@esbuild/win32-x64": "0.27.4" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-Rq4vbHnYkK5fws5NF7MYTU68FPRE1ajX7heQ/8QXXWqNgqqJ/GkmmyxIzUnf2Sr/bakf8l54716CcMGHYhMrrQ=="],
"escalade": ["escalade@3.2.0", "", {}, "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA=="],
"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.4", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.4.15", "@typescript-eslint/types": "^8.2.0" } }, "sha512-suICpxAmZ9A8bzJjEl/+rLJiDKC0X4gYWUxT6URAWBLvlXmtbZd5ySMu/N2ZGEtMCAmflUDPSehrP9BQcsGcSg=="],
"estree-walker": ["estree-walker@2.0.2", "", {}, "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="],
"eventemitter3": ["eventemitter3@5.0.4", "", {}, "sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw=="],
"extend": ["extend@3.0.2", "", {}, "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g=="],
"fast-string-truncated-width": ["fast-string-truncated-width@1.2.1", "", {}, "sha512-Q9acT/+Uu3GwGj+5w/zsGuQjh9O1TyywhIwAxHudtWrgF09nHOPrvTLhQevPbttcxjr/SNN7mJmfOw/B1bXgow=="],
"fast-string-width": ["fast-string-width@1.1.0", "", { "dependencies": { "fast-string-truncated-width": "^1.2.0" } }, "sha512-O3fwIVIH5gKB38QNbdg+3760ZmGz0SZMgvwJbA1b2TGXceKE6A2cOlfogh1iw8lr049zPyd7YADHy+B7U4W9bQ=="],
"fast-wrap-ansi": ["fast-wrap-ansi@0.1.6", "", { "dependencies": { "fast-string-width": "^1.1.0" } }, "sha512-HlUwET7a5gqjURj70D5jl7aC3Zmy4weA1SHUfM0JFI0Ptq987NH2TwbBFLoERhfwk+E+eaq4EK3jXoT+R3yp3w=="],
"fdir": ["fdir@6.5.0", "", { "peerDependencies": { "picomatch": "^3 || ^4" }, "optionalPeers": ["picomatch"] }, "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg=="],
"flattie": ["flattie@1.1.1", "", {}, "sha512-9UbaD6XdAL97+k/n+N7JwX46K/M6Zc6KcFYskrYL8wbBV/Uyk0CTAMY0VT+qiK5PM7AIc9aTWYtq65U7T+aCNQ=="],
"fontace": ["fontace@0.4.1", "", { "dependencies": { "fontkitten": "^1.0.2" } }, "sha512-lDMvbAzSnHmbYMTEld5qdtvNH2/pWpICOqpean9IgC7vUbUJc3k+k5Dokp85CegamqQpFbXf0rAVkbzpyTA8aw=="],
"fontkitten": ["fontkitten@1.0.3", "", { "dependencies": { "tiny-inflate": "^1.0.3" } }, "sha512-Wp1zXWPVUPBmfoa3Cqc9ctaKuzKAV6uLstRqlR56kSjplf5uAce+qeyYym7F+PHbGTk+tCEdkCW6RD7DX/gBZw=="],
"fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="],
"get-caller-file": ["get-caller-file@2.0.5", "", {}, "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg=="],
"github-slugger": ["github-slugger@2.0.0", "", {}, "sha512-IaOQ9puYtjrkq7Y0Ygl9KDZnrf/aiUJYUpVf89y8kyaxbRG7Y1SrX/jaumrv81vc61+kiMempujsM3Yw7w5qcw=="],
"graceful-fs": ["graceful-fs@4.2.11", "", {}, "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="],
"h3": ["h3@1.15.11", "", { "dependencies": { "cookie-es": "^1.2.3", "crossws": "^0.3.5", "defu": "^6.1.6", "destr": "^2.0.5", "iron-webcrypto": "^1.2.1", "node-mock-http": "^1.0.4", "radix3": "^1.1.2", "ufo": "^1.6.3", "uncrypto": "^0.1.3" } }, "sha512-L3THSe2MPeBwgIZVSH5zLdBBU90TOxarvhK9d04IDY2AmVS8j2Jz2LIWtwsGOU3lu2I5jCN7FNvVfY2+XyF+mg=="],
"has-flag": ["has-flag@4.0.0", "", {}, "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="],
"hast-util-from-html": ["hast-util-from-html@2.0.3", "", { "dependencies": { "@types/hast": "^3.0.0", "devlop": "^1.1.0", "hast-util-from-parse5": "^8.0.0", "parse5": "^7.0.0", "vfile": "^6.0.0", "vfile-message": "^4.0.0" } }, "sha512-CUSRHXyKjzHov8yKsQjGOElXy/3EKpyX56ELnkHH34vDVw1N1XSQ1ZcAvTyAPtGqLTuKP/uxM+aLkSPqF/EtMw=="],
"hast-util-from-parse5": ["hast-util-from-parse5@8.0.3", "", { "dependencies": { "@types/hast": "^3.0.0", "@types/unist": "^3.0.0", "devlop": "^1.0.0", "hastscript": "^9.0.0", "property-information": "^7.0.0", "vfile": "^6.0.0", "vfile-location": "^5.0.0", "web-namespaces": "^2.0.0" } }, "sha512-3kxEVkEKt0zvcZ3hCRYI8rqrgwtlIOFMWkbclACvjlDw8Li9S2hk/d51OI0nr/gIpdMHNepwgOKqZ/sy0Clpyg=="],
"hast-util-is-element": ["hast-util-is-element@3.0.0", "", { "dependencies": { "@types/hast": "^3.0.0" } }, "sha512-Val9mnv2IWpLbNPqc/pUem+a7Ipj2aHacCwgNfTiK0vJKl0LF+4Ba4+v1oPHFpf3bLYmreq0/l3Gud9S5OH42g=="],
"hast-util-parse-selector": ["hast-util-parse-selector@4.0.0", "", { "dependencies": { "@types/hast": "^3.0.0" } }, "sha512-wkQCkSYoOGCRKERFWcxMVMOcYE2K1AaNLU8DXS9arxnLOUEWbOXKXiJUNzEpqZ3JOKpnha3jkFrumEjVliDe7A=="],
"hast-util-raw": ["hast-util-raw@9.1.0", "", { "dependencies": { "@types/hast": "^3.0.0", "@types/unist": "^3.0.0", "@ungap/structured-clone": "^1.0.0", "hast-util-from-parse5": "^8.0.0", "hast-util-to-parse5": "^8.0.0", "html-void-elements": "^3.0.0", "mdast-util-to-hast": "^13.0.0", "parse5": "^7.0.0", "unist-util-position": "^5.0.0", "unist-util-visit": "^5.0.0", "vfile": "^6.0.0", "web-namespaces": "^2.0.0", "zwitch": "^2.0.0" } }, "sha512-Y8/SBAHkZGoNkpzqqfCldijcuUKh7/su31kEBp67cFY09Wy0mTRgtsLYsiIxMJxlu0f6AA5SUTbDR8K0rxnbUw=="],
"hast-util-to-html": ["hast-util-to-html@9.0.5", "", { "dependencies": { "@types/hast": "^3.0.0", "@types/unist": "^3.0.0", "ccount": "^2.0.0", "comma-separated-tokens": "^2.0.0", "hast-util-whitespace": "^3.0.0", "html-void-elements": "^3.0.0", "mdast-util-to-hast": "^13.0.0", "property-information": "^7.0.0", "space-separated-tokens": "^2.0.0", "stringify-entities": "^4.0.0", "zwitch": "^2.0.4" } }, "sha512-OguPdidb+fbHQSU4Q4ZiLKnzWo8Wwsf5bZfbvu7//a9oTYoqD/fWpe96NuHkoS9h0ccGOTe0C4NGXdtS0iObOw=="],
"hast-util-to-parse5": ["hast-util-to-parse5@8.0.1", "", { "dependencies": { "@types/hast": "^3.0.0", "comma-separated-tokens": "^2.0.0", "devlop": "^1.0.0", "property-information": "^7.0.0", "space-separated-tokens": "^2.0.0", "web-namespaces": "^2.0.0", "zwitch": "^2.0.0" } }, "sha512-MlWT6Pjt4CG9lFCjiz4BH7l9wmrMkfkJYCxFwKQic8+RTZgWPuWxwAfjJElsXkex7DJjfSJsQIt931ilUgmwdA=="],
"hast-util-to-text": ["hast-util-to-text@4.0.2", "", { "dependencies": { "@types/hast": "^3.0.0", "@types/unist": "^3.0.0", "hast-util-is-element": "^3.0.0", "unist-util-find-after": "^5.0.0" } }, "sha512-KK6y/BN8lbaq654j7JgBydev7wuNMcID54lkRav1P0CaE1e47P72AWWPiGKXTJU271ooYzcvTAn/Zt0REnvc7A=="],
"hast-util-whitespace": ["hast-util-whitespace@3.0.0", "", { "dependencies": { "@types/hast": "^3.0.0" } }, "sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw=="],
"hastscript": ["hastscript@9.0.1", "", { "dependencies": { "@types/hast": "^3.0.0", "comma-separated-tokens": "^2.0.0", "hast-util-parse-selector": "^4.0.0", "property-information": "^7.0.0", "space-separated-tokens": "^2.0.0" } }, "sha512-g7df9rMFX/SPi34tyGCyUBREQoKkapwdY/T04Qn9TDWfHhAYt4/I0gMVirzK5wEzeUqIjEB+LXC/ypb7Aqno5w=="],
"html-escaper": ["html-escaper@3.0.3", "", {}, "sha512-RuMffC89BOWQoY0WKGpIhn5gX3iI54O6nRA0yC124NYVtzjmFWBIiFd8M0x+ZdX0P9R4lADg1mgP8C7PxGOWuQ=="],
"html-void-elements": ["html-void-elements@3.0.0", "", {}, "sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg=="],
"http-cache-semantics": ["http-cache-semantics@4.2.0", "", {}, "sha512-dTxcvPXqPvXBQpq5dUr6mEMJX4oIEFv6bwom3FDwKRDsuIjjJGANqhBuoAn9c1RQJIdAKav33ED65E2ys+87QQ=="],
"iron-webcrypto": ["iron-webcrypto@1.2.1", "", {}, "sha512-feOM6FaSr6rEABp/eDfVseKyTMDt+KGpeB35SkVn9Tyn0CqvVsY3EwI0v5i8nMHyJnzCIQf7nsy3p41TPkJZhg=="],
"is-docker": ["is-docker@3.0.0", "", { "bin": { "is-docker": "cli.js" } }, "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ=="],
"is-fullwidth-code-point": ["is-fullwidth-code-point@3.0.0", "", {}, "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="],
"is-inside-container": ["is-inside-container@1.0.0", "", { "dependencies": { "is-docker": "^3.0.0" }, "bin": { "is-inside-container": "cli.js" } }, "sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA=="],
"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=="],
"jiti": ["jiti@2.6.1", "", { "bin": { "jiti": "lib/jiti-cli.mjs" } }, "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ=="],
"js-yaml": ["js-yaml@4.1.1", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA=="],
"lightningcss": ["lightningcss@1.32.0", "", { "dependencies": { "detect-libc": "^2.0.3" }, "optionalDependencies": { "lightningcss-android-arm64": "1.32.0", "lightningcss-darwin-arm64": "1.32.0", "lightningcss-darwin-x64": "1.32.0", "lightningcss-freebsd-x64": "1.32.0", "lightningcss-linux-arm-gnueabihf": "1.32.0", "lightningcss-linux-arm64-gnu": "1.32.0", "lightningcss-linux-arm64-musl": "1.32.0", "lightningcss-linux-x64-gnu": "1.32.0", "lightningcss-linux-x64-musl": "1.32.0", "lightningcss-win32-arm64-msvc": "1.32.0", "lightningcss-win32-x64-msvc": "1.32.0" } }, "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ=="],
"lightningcss-android-arm64": ["lightningcss-android-arm64@1.32.0", "", { "os": "android", "cpu": "arm64" }, "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg=="],
"lightningcss-darwin-arm64": ["lightningcss-darwin-arm64@1.32.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ=="],
"lightningcss-darwin-x64": ["lightningcss-darwin-x64@1.32.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w=="],
"lightningcss-freebsd-x64": ["lightningcss-freebsd-x64@1.32.0", "", { "os": "freebsd", "cpu": "x64" }, "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig=="],
"lightningcss-linux-arm-gnueabihf": ["lightningcss-linux-arm-gnueabihf@1.32.0", "", { "os": "linux", "cpu": "arm" }, "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw=="],
"lightningcss-linux-arm64-gnu": ["lightningcss-linux-arm64-gnu@1.32.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ=="],
"lightningcss-linux-arm64-musl": ["lightningcss-linux-arm64-musl@1.32.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg=="],
"lightningcss-linux-x64-gnu": ["lightningcss-linux-x64-gnu@1.32.0", "", { "os": "linux", "cpu": "x64" }, "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA=="],
"lightningcss-linux-x64-musl": ["lightningcss-linux-x64-musl@1.32.0", "", { "os": "linux", "cpu": "x64" }, "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg=="],
"lightningcss-win32-arm64-msvc": ["lightningcss-win32-arm64-msvc@1.32.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw=="],
"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=="],
"lru-cache": ["lru-cache@11.2.7", "", {}, "sha512-aY/R+aEsRelme17KGQa/1ZSIpLpNYYrhcrepKTZgE+W3WM16YMCaPwOHLHsmopZHELU0Ojin1lPVxKR0MihncA=="],
"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=="],
"markdown-table": ["markdown-table@3.0.4", "", {}, "sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw=="],
"mdast-util-definitions": ["mdast-util-definitions@6.0.0", "", { "dependencies": { "@types/mdast": "^4.0.0", "@types/unist": "^3.0.0", "unist-util-visit": "^5.0.0" } }, "sha512-scTllyX6pnYNZH/AIp/0ePz6s4cZtARxImwoPJ7kS42n+MnVsI4XbnG6d4ibehRIldYMWM2LD7ImQblVhUejVQ=="],
"mdast-util-find-and-replace": ["mdast-util-find-and-replace@3.0.2", "", { "dependencies": { "@types/mdast": "^4.0.0", "escape-string-regexp": "^5.0.0", "unist-util-is": "^6.0.0", "unist-util-visit-parents": "^6.0.0" } }, "sha512-Tmd1Vg/m3Xz43afeNxDIhWRtFZgM2VLyaf4vSTYwudTyeuTneoL3qtWMA5jeLyz/O1vDJmmV4QuScFCA2tBPwg=="],
"mdast-util-from-markdown": ["mdast-util-from-markdown@2.0.3", "", { "dependencies": { "@types/mdast": "^4.0.0", "@types/unist": "^3.0.0", "decode-named-character-reference": "^1.0.0", "devlop": "^1.0.0", "mdast-util-to-string": "^4.0.0", "micromark": "^4.0.0", "micromark-util-decode-numeric-character-reference": "^2.0.0", "micromark-util-decode-string": "^2.0.0", "micromark-util-normalize-identifier": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0", "unist-util-stringify-position": "^4.0.0" } }, "sha512-W4mAWTvSlKvf8L6J+VN9yLSqQ9AOAAvHuoDAmPkz4dHf553m5gVj2ejadHJhoJmcmxEnOv6Pa8XJhpxE93kb8Q=="],
"mdast-util-gfm": ["mdast-util-gfm@3.1.0", "", { "dependencies": { "mdast-util-from-markdown": "^2.0.0", "mdast-util-gfm-autolink-literal": "^2.0.0", "mdast-util-gfm-footnote": "^2.0.0", "mdast-util-gfm-strikethrough": "^2.0.0", "mdast-util-gfm-table": "^2.0.0", "mdast-util-gfm-task-list-item": "^2.0.0", "mdast-util-to-markdown": "^2.0.0" } }, "sha512-0ulfdQOM3ysHhCJ1p06l0b0VKlhU0wuQs3thxZQagjcjPrlFRqY215uZGHHJan9GEAXd9MbfPjFJz+qMkVR6zQ=="],
"mdast-util-gfm-autolink-literal": ["mdast-util-gfm-autolink-literal@2.0.1", "", { "dependencies": { "@types/mdast": "^4.0.0", "ccount": "^2.0.0", "devlop": "^1.0.0", "mdast-util-find-and-replace": "^3.0.0", "micromark-util-character": "^2.0.0" } }, "sha512-5HVP2MKaP6L+G6YaxPNjuL0BPrq9orG3TsrZ9YXbA3vDw/ACI4MEsnoDpn6ZNm7GnZgtAcONJyPhOP8tNJQavQ=="],
"mdast-util-gfm-footnote": ["mdast-util-gfm-footnote@2.1.0", "", { "dependencies": { "@types/mdast": "^4.0.0", "devlop": "^1.1.0", "mdast-util-from-markdown": "^2.0.0", "mdast-util-to-markdown": "^2.0.0", "micromark-util-normalize-identifier": "^2.0.0" } }, "sha512-sqpDWlsHn7Ac9GNZQMeUzPQSMzR6Wv0WKRNvQRg0KqHh02fpTz69Qc1QSseNX29bhz1ROIyNyxExfawVKTm1GQ=="],
"mdast-util-gfm-strikethrough": ["mdast-util-gfm-strikethrough@2.0.0", "", { "dependencies": { "@types/mdast": "^4.0.0", "mdast-util-from-markdown": "^2.0.0", "mdast-util-to-markdown": "^2.0.0" } }, "sha512-mKKb915TF+OC5ptj5bJ7WFRPdYtuHv0yTRxK2tJvi+BDqbkiG7h7u/9SI89nRAYcmap2xHQL9D+QG/6wSrTtXg=="],
"mdast-util-gfm-table": ["mdast-util-gfm-table@2.0.0", "", { "dependencies": { "@types/mdast": "^4.0.0", "devlop": "^1.0.0", "markdown-table": "^3.0.0", "mdast-util-from-markdown": "^2.0.0", "mdast-util-to-markdown": "^2.0.0" } }, "sha512-78UEvebzz/rJIxLvE7ZtDd/vIQ0RHv+3Mh5DR96p7cS7HsBhYIICDBCu8csTNWNO6tBWfqXPWekRuj2FNOGOZg=="],
"mdast-util-gfm-task-list-item": ["mdast-util-gfm-task-list-item@2.0.0", "", { "dependencies": { "@types/mdast": "^4.0.0", "devlop": "^1.0.0", "mdast-util-from-markdown": "^2.0.0", "mdast-util-to-markdown": "^2.0.0" } }, "sha512-IrtvNvjxC1o06taBAVJznEnkiHxLFTzgonUdy8hzFVeDun0uTjxxrRGVaNFqkU1wJR3RBPEfsxmU6jDWPofrTQ=="],
"mdast-util-phrasing": ["mdast-util-phrasing@4.1.0", "", { "dependencies": { "@types/mdast": "^4.0.0", "unist-util-is": "^6.0.0" } }, "sha512-TqICwyvJJpBwvGAMZjj4J2n0X8QWp21b9l0o7eXyVJ25YNWYbJDVIyD1bZXE6WtV6RmKJVYmQAKWa0zWOABz2w=="],
"mdast-util-to-hast": ["mdast-util-to-hast@13.2.1", "", { "dependencies": { "@types/hast": "^3.0.0", "@types/mdast": "^4.0.0", "@ungap/structured-clone": "^1.0.0", "devlop": "^1.0.0", "micromark-util-sanitize-uri": "^2.0.0", "trim-lines": "^3.0.0", "unist-util-position": "^5.0.0", "unist-util-visit": "^5.0.0", "vfile": "^6.0.0" } }, "sha512-cctsq2wp5vTsLIcaymblUriiTcZd0CwWtCbLvrOzYCDZoWyMNV8sZ7krj09FSnsiJi3WVsHLM4k6Dq/yaPyCXA=="],
"mdast-util-to-markdown": ["mdast-util-to-markdown@2.1.2", "", { "dependencies": { "@types/mdast": "^4.0.0", "@types/unist": "^3.0.0", "longest-streak": "^3.0.0", "mdast-util-phrasing": "^4.0.0", "mdast-util-to-string": "^4.0.0", "micromark-util-classify-character": "^2.0.0", "micromark-util-decode-string": "^2.0.0", "unist-util-visit": "^5.0.0", "zwitch": "^2.0.0" } }, "sha512-xj68wMTvGXVOKonmog6LwyJKrYXZPvlwabaryTjLh9LuvovB/KAH+kvi8Gjj+7rJjsFi23nkUxRQv1KqSroMqA=="],
"mdast-util-to-string": ["mdast-util-to-string@4.0.0", "", { "dependencies": { "@types/mdast": "^4.0.0" } }, "sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg=="],
"mdn-data": ["mdn-data@2.27.1", "", {}, "sha512-9Yubnt3e8A0OKwxYSXyhLymGW4sCufcLG6VdiDdUGVkPhpqLxlvP5vl1983gQjJl3tqbrM731mjaZaP68AgosQ=="],
"micromark": ["micromark@4.0.2", "", { "dependencies": { "@types/debug": "^4.0.0", "debug": "^4.0.0", "decode-named-character-reference": "^1.0.0", "devlop": "^1.0.0", "micromark-core-commonmark": "^2.0.0", "micromark-factory-space": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-chunked": "^2.0.0", "micromark-util-combine-extensions": "^2.0.0", "micromark-util-decode-numeric-character-reference": "^2.0.0", "micromark-util-encode": "^2.0.0", "micromark-util-normalize-identifier": "^2.0.0", "micromark-util-resolve-all": "^2.0.0", "micromark-util-sanitize-uri": "^2.0.0", "micromark-util-subtokenize": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA=="],
"micromark-core-commonmark": ["micromark-core-commonmark@2.0.3", "", { "dependencies": { "decode-named-character-reference": "^1.0.0", "devlop": "^1.0.0", "micromark-factory-destination": "^2.0.0", "micromark-factory-label": "^2.0.0", "micromark-factory-space": "^2.0.0", "micromark-factory-title": "^2.0.0", "micromark-factory-whitespace": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-chunked": "^2.0.0", "micromark-util-classify-character": "^2.0.0", "micromark-util-html-tag-name": "^2.0.0", "micromark-util-normalize-identifier": "^2.0.0", "micromark-util-resolve-all": "^2.0.0", "micromark-util-subtokenize": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg=="],
"micromark-extension-gfm": ["micromark-extension-gfm@3.0.0", "", { "dependencies": { "micromark-extension-gfm-autolink-literal": "^2.0.0", "micromark-extension-gfm-footnote": "^2.0.0", "micromark-extension-gfm-strikethrough": "^2.0.0", "micromark-extension-gfm-table": "^2.0.0", "micromark-extension-gfm-tagfilter": "^2.0.0", "micromark-extension-gfm-task-list-item": "^2.0.0", "micromark-util-combine-extensions": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-vsKArQsicm7t0z2GugkCKtZehqUm31oeGBV/KVSorWSy8ZlNAv7ytjFhvaryUiCUJYqs+NoE6AFhpQvBTM6Q4w=="],
"micromark-extension-gfm-autolink-literal": ["micromark-extension-gfm-autolink-literal@2.1.0", "", { "dependencies": { "micromark-util-character": "^2.0.0", "micromark-util-sanitize-uri": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-oOg7knzhicgQ3t4QCjCWgTmfNhvQbDDnJeVu9v81r7NltNCVmhPy1fJRX27pISafdjL+SVc4d3l48Gb6pbRypw=="],
"micromark-extension-gfm-footnote": ["micromark-extension-gfm-footnote@2.1.0", "", { "dependencies": { "devlop": "^1.0.0", "micromark-core-commonmark": "^2.0.0", "micromark-factory-space": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-normalize-identifier": "^2.0.0", "micromark-util-sanitize-uri": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-/yPhxI1ntnDNsiHtzLKYnE3vf9JZ6cAisqVDauhp4CEHxlb4uoOTxOCJ+9s51bIB8U1N1FJ1RXOKTIlD5B/gqw=="],
"micromark-extension-gfm-strikethrough": ["micromark-extension-gfm-strikethrough@2.1.0", "", { "dependencies": { "devlop": "^1.0.0", "micromark-util-chunked": "^2.0.0", "micromark-util-classify-character": "^2.0.0", "micromark-util-resolve-all": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-ADVjpOOkjz1hhkZLlBiYA9cR2Anf8F4HqZUO6e5eDcPQd0Txw5fxLzzxnEkSkfnD0wziSGiv7sYhk/ktvbf1uw=="],
"micromark-extension-gfm-table": ["micromark-extension-gfm-table@2.1.1", "", { "dependencies": { "devlop": "^1.0.0", "micromark-factory-space": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-t2OU/dXXioARrC6yWfJ4hqB7rct14e8f7m0cbI5hUmDyyIlwv5vEtooptH8INkbLzOatzKuVbQmAYcbWoyz6Dg=="],
"micromark-extension-gfm-tagfilter": ["micromark-extension-gfm-tagfilter@2.0.0", "", { "dependencies": { "micromark-util-types": "^2.0.0" } }, "sha512-xHlTOmuCSotIA8TW1mDIM6X2O1SiX5P9IuDtqGonFhEK0qgRI4yeC6vMxEV2dgyr2TiD+2PQ10o+cOhdVAcwfg=="],
"micromark-extension-gfm-task-list-item": ["micromark-extension-gfm-task-list-item@2.1.0", "", { "dependencies": { "devlop": "^1.0.0", "micromark-factory-space": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-qIBZhqxqI6fjLDYFTBIa4eivDMnP+OZqsNwmQ3xNLE4Cxwc+zfQEfbs6tzAo2Hjq+bh6q5F+Z8/cksrLFYWQQw=="],
"micromark-factory-destination": ["micromark-factory-destination@2.0.1", "", { "dependencies": { "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA=="],
"micromark-factory-label": ["micromark-factory-label@2.0.1", "", { "dependencies": { "devlop": "^1.0.0", "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg=="],
"micromark-factory-space": ["micromark-factory-space@2.0.1", "", { "dependencies": { "micromark-util-character": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg=="],
"micromark-factory-title": ["micromark-factory-title@2.0.1", "", { "dependencies": { "micromark-factory-space": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw=="],
"micromark-factory-whitespace": ["micromark-factory-whitespace@2.0.1", "", { "dependencies": { "micromark-factory-space": "^2.0.0", "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-Ob0nuZ3PKt/n0hORHyvoD9uZhr+Za8sFoP+OnMcnWK5lngSzALgQYKMr9RJVOWLqQYuyn6ulqGWSXdwf6F80lQ=="],
"micromark-util-character": ["micromark-util-character@2.1.1", "", { "dependencies": { "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q=="],
"micromark-util-chunked": ["micromark-util-chunked@2.0.1", "", { "dependencies": { "micromark-util-symbol": "^2.0.0" } }, "sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA=="],
"micromark-util-classify-character": ["micromark-util-classify-character@2.0.1", "", { "dependencies": { "micromark-util-character": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-K0kHzM6afW/MbeWYWLjoHQv1sgg2Q9EccHEDzSkxiP/EaagNzCm7T/WMKZ3rjMbvIpvBiZgwR3dKMygtA4mG1Q=="],
"micromark-util-combine-extensions": ["micromark-util-combine-extensions@2.0.1", "", { "dependencies": { "micromark-util-chunked": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-OnAnH8Ujmy59JcyZw8JSbK9cGpdVY44NKgSM7E9Eh7DiLS2E9RNQf0dONaGDzEG9yjEl5hcqeIsj4hfRkLH/Bg=="],
"micromark-util-decode-numeric-character-reference": ["micromark-util-decode-numeric-character-reference@2.0.2", "", { "dependencies": { "micromark-util-symbol": "^2.0.0" } }, "sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw=="],
"micromark-util-decode-string": ["micromark-util-decode-string@2.0.1", "", { "dependencies": { "decode-named-character-reference": "^1.0.0", "micromark-util-character": "^2.0.0", "micromark-util-decode-numeric-character-reference": "^2.0.0", "micromark-util-symbol": "^2.0.0" } }, "sha512-nDV/77Fj6eH1ynwscYTOsbK7rR//Uj0bZXBwJZRfaLEJ1iGBR6kIfNmlNqaqJf649EP0F3NWNdeJi03elllNUQ=="],
"micromark-util-encode": ["micromark-util-encode@2.0.1", "", {}, "sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw=="],
"micromark-util-html-tag-name": ["micromark-util-html-tag-name@2.0.1", "", {}, "sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA=="],
"micromark-util-normalize-identifier": ["micromark-util-normalize-identifier@2.0.1", "", { "dependencies": { "micromark-util-symbol": "^2.0.0" } }, "sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q=="],
"micromark-util-resolve-all": ["micromark-util-resolve-all@2.0.1", "", { "dependencies": { "micromark-util-types": "^2.0.0" } }, "sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg=="],
"micromark-util-sanitize-uri": ["micromark-util-sanitize-uri@2.0.1", "", { "dependencies": { "micromark-util-character": "^2.0.0", "micromark-util-encode": "^2.0.0", "micromark-util-symbol": "^2.0.0" } }, "sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ=="],
"micromark-util-subtokenize": ["micromark-util-subtokenize@2.1.0", "", { "dependencies": { "devlop": "^1.0.0", "micromark-util-chunked": "^2.0.0", "micromark-util-symbol": "^2.0.0", "micromark-util-types": "^2.0.0" } }, "sha512-XQLu552iSctvnEcgXw6+Sx75GflAPNED1qx7eBJ+wydBb2KCbRZe+NwvIEEMM83uml1+2WSXpBAcp9IUCgCYWA=="],
"micromark-util-symbol": ["micromark-util-symbol@2.0.1", "", {}, "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q=="],
"micromark-util-types": ["micromark-util-types@2.0.2", "", {}, "sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA=="],
"mrmime": ["mrmime@2.0.1", "", {}, "sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ=="],
"ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="],
"nanoid": ["nanoid@3.3.11", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w=="],
"neotraverse": ["neotraverse@0.6.18", "", {}, "sha512-Z4SmBUweYa09+o6pG+eASabEpP6QkQ70yHj351pQoEXIs8uHbaU2DWVmzBANKgflPa47A50PtB2+NgRpQvr7vA=="],
"nlcst-to-string": ["nlcst-to-string@4.0.0", "", { "dependencies": { "@types/nlcst": "^2.0.0" } }, "sha512-YKLBCcUYKAg0FNlOBT6aI91qFmSiFKiluk655WzPF+DDMA02qIyy8uiRqI8QXtcFpEvll12LpL5MXqEmAZ+dcA=="],
"node-fetch-native": ["node-fetch-native@1.6.7", "", {}, "sha512-g9yhqoedzIUm0nTnTqAQvueMPVOuIY16bqgAJJC8XOOubYFNwz6IER9qs0Gq2Xd0+CecCKFjtdDTMA4u4xG06Q=="],
"node-mock-http": ["node-mock-http@1.0.4", "", {}, "sha512-8DY+kFsDkNXy1sJglUfuODx1/opAGJGyrTuFqEoN90oRc2Vk0ZbD4K2qmKXBBEhZQzdKHIVfEJpDU8Ak2NJEvQ=="],
"normalize-path": ["normalize-path@3.0.0", "", {}, "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA=="],
"nth-check": ["nth-check@2.1.1", "", { "dependencies": { "boolbase": "^1.0.0" } }, "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w=="],
"obug": ["obug@2.1.1", "", {}, "sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ=="],
"ofetch": ["ofetch@1.5.1", "", { "dependencies": { "destr": "^2.0.5", "node-fetch-native": "^1.6.7", "ufo": "^1.6.1" } }, "sha512-2W4oUZlVaqAPAil6FUg/difl6YhqhUR7x2eZY4bQCko22UXg3hptq9KLQdqFClV+Wu85UX7hNtdGTngi/1BxcA=="],
"ohash": ["ohash@2.0.11", "", {}, "sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ=="],
"oniguruma-parser": ["oniguruma-parser@0.12.1", "", {}, "sha512-8Unqkvk1RYc6yq2WBYRj4hdnsAxVze8i7iPfQr8e4uSP3tRv0rpZcbGUDvxfQQcdwHt/e9PrMvGCsa8OqG9X3w=="],
"oniguruma-to-es": ["oniguruma-to-es@4.3.5", "", { "dependencies": { "oniguruma-parser": "^0.12.1", "regex": "^6.1.0", "regex-recursion": "^6.0.2" } }, "sha512-Zjygswjpsewa0NLTsiizVuMQZbp0MDyM6lIt66OxsF21npUDlzpHi1Mgb/qhQdkb+dWFTzJmFbEWdvZgRho8eQ=="],
"p-limit": ["p-limit@7.3.0", "", { "dependencies": { "yocto-queue": "^1.2.1" } }, "sha512-7cIXg/Z0M5WZRblrsOla88S4wAK+zOQQWeBYfV3qJuJXMr+LnbYjaadrFaS0JILfEDPVqHyKnZ1Z/1d6J9VVUw=="],
"p-queue": ["p-queue@9.1.1", "", { "dependencies": { "eventemitter3": "^5.0.1", "p-timeout": "^7.0.0" } }, "sha512-yQS1vV2V7Q14MQrgD8jMNY5owPuGgVHVdSK8NqmKpOVajnjbaeMa6uLOzTALPtvJ7Vo4bw0BGsw7qfUT8z24Ig=="],
"p-timeout": ["p-timeout@7.0.1", "", {}, "sha512-AxTM2wDGORHGEkPCt8yqxOTMgpfbEHqF51f/5fJCmwFC3C/zNcGT63SymH2ttOAaiIws2zVg4+izQCjrakcwHg=="],
"package-manager-detector": ["package-manager-detector@1.6.0", "", {}, "sha512-61A5ThoTiDG/C8s8UMZwSorAGwMJ0ERVGj2OjoW5pAalsNOg15+iQiPzrLJ4jhZ1HJzmC2PIHT2oEiH3R5fzNA=="],
"parse-latin": ["parse-latin@7.0.0", "", { "dependencies": { "@types/nlcst": "^2.0.0", "@types/unist": "^3.0.0", "nlcst-to-string": "^4.0.0", "unist-util-modify-children": "^4.0.0", "unist-util-visit-children": "^3.0.0", "vfile": "^6.0.0" } }, "sha512-mhHgobPPua5kZ98EF4HWiH167JWBfl4pvAIXXdbaVohtK7a6YBOy56kvhCqduqyo/f3yrHFWmqmiMg/BkBkYYQ=="],
"parse5": ["parse5@7.3.0", "", { "dependencies": { "entities": "^6.0.0" } }, "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw=="],
"piccolore": ["piccolore@0.1.3", "", {}, "sha512-o8bTeDWjE086iwKrROaDf31K0qC/BENdm15/uH9usSC/uZjJOKb2YGiVHfLY4GhwsERiPI1jmwI2XrA7ACOxVw=="],
"picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="],
"picomatch": ["picomatch@4.0.4", "", {}, "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A=="],
"postcss": ["postcss@8.5.8", "", { "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg=="],
"postcss-selector-parser": ["postcss-selector-parser@6.0.10", "", { "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" } }, "sha512-IQ7TZdoaqbT+LCpShg46jnZVlhWD2w6iQYAcYXfHARZ7X1t/UGhhceQDs5X0cGqKvYlHNOuv7Oa1xmb0oQuA3w=="],
"prismjs": ["prismjs@1.30.0", "", {}, "sha512-DEvV2ZF2r2/63V+tK8hQvrR2ZGn10srHbXviTlcv7Kpzw8jWiNTqbVgjO3IY8RxrrOUF8VPMQQFysYYYv0YZxw=="],
"property-information": ["property-information@7.1.0", "", {}, "sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ=="],
"radix3": ["radix3@1.1.2", "", {}, "sha512-b484I/7b8rDEdSDKckSSBA8knMpcdsXudlE/LNL639wFoHKwLbEkQFZHWEYwDC0wa0FKUcCY+GAF73Z7wxNVFA=="],
"readdirp": ["readdirp@5.0.0", "", {}, "sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ=="],
"regex": ["regex@6.1.0", "", { "dependencies": { "regex-utilities": "^2.3.0" } }, "sha512-6VwtthbV4o/7+OaAF9I5L5V3llLEsoPyq9P1JVXkedTP33c7MfCG0/5NOPcSJn0TzXcG9YUrR0gQSWioew3LDg=="],
"regex-recursion": ["regex-recursion@6.0.2", "", { "dependencies": { "regex-utilities": "^2.3.0" } }, "sha512-0YCaSCq2VRIebiaUviZNs0cBz1kg5kVS2UKUfNIx8YVs1cN3AV7NTctO5FOKBA+UT2BPJIWZauYHPqJODG50cg=="],
"regex-utilities": ["regex-utilities@2.3.0", "", {}, "sha512-8VhliFJAWRaUiVvREIiW2NXXTmHs4vMNnSzuJVhscgmGav3g9VDxLrQndI3dZZVVdp0ZO/5v0xmX516/7M9cng=="],
"rehype": ["rehype@13.0.2", "", { "dependencies": { "@types/hast": "^3.0.0", "rehype-parse": "^9.0.0", "rehype-stringify": "^10.0.0", "unified": "^11.0.0" } }, "sha512-j31mdaRFrwFRUIlxGeuPXXKWQxet52RBQRvCmzl5eCefn/KGbomK5GMHNMsOJf55fgo3qw5tST5neDuarDYR2A=="],
"rehype-parse": ["rehype-parse@9.0.1", "", { "dependencies": { "@types/hast": "^3.0.0", "hast-util-from-html": "^2.0.0", "unified": "^11.0.0" } }, "sha512-ksCzCD0Fgfh7trPDxr2rSylbwq9iYDkSn8TCDmEJ49ljEUBxDVCzCHv7QNzZOfODanX4+bWQ4WZqLCRWYLfhag=="],
"rehype-raw": ["rehype-raw@7.0.0", "", { "dependencies": { "@types/hast": "^3.0.0", "hast-util-raw": "^9.0.0", "vfile": "^6.0.0" } }, "sha512-/aE8hCfKlQeA8LmyeyQvQF3eBiLRGNlfBJEvWH7ivp9sBqs7TNqBL5X3v157rM4IFETqDnIOO+z5M/biZbo9Ww=="],
"rehype-stringify": ["rehype-stringify@10.0.1", "", { "dependencies": { "@types/hast": "^3.0.0", "hast-util-to-html": "^9.0.0", "unified": "^11.0.0" } }, "sha512-k9ecfXHmIPuFVI61B9DeLPN0qFHfawM6RsuX48hoqlaKSF61RskNjSm1lI8PhBEM0MRdLxVVm4WmTqJQccH9mA=="],
"remark-gfm": ["remark-gfm@4.0.1", "", { "dependencies": { "@types/mdast": "^4.0.0", "mdast-util-gfm": "^3.0.0", "micromark-extension-gfm": "^3.0.0", "remark-parse": "^11.0.0", "remark-stringify": "^11.0.0", "unified": "^11.0.0" } }, "sha512-1quofZ2RQ9EWdeN34S79+KExV1764+wCUGop5CPL1WGdD0ocPpu91lzPGbwWMECpEpd42kJGQwzRfyov9j4yNg=="],
"remark-github-blockquote-alert": ["remark-github-blockquote-alert@2.1.0", "", { "dependencies": { "unist-util-visit": "^5.0.0" } }, "sha512-J392jmIP684d7iGsENN0uguL10IGbRdc8bTUSrd/jOLzdWkwg721Fj3JPQGN8tF6fTIrE5HHOIA3nBuwuaeuPQ=="],
"remark-parse": ["remark-parse@11.0.0", "", { "dependencies": { "@types/mdast": "^4.0.0", "mdast-util-from-markdown": "^2.0.0", "micromark-util-types": "^2.0.0", "unified": "^11.0.0" } }, "sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA=="],
"remark-rehype": ["remark-rehype@11.1.2", "", { "dependencies": { "@types/hast": "^3.0.0", "@types/mdast": "^4.0.0", "mdast-util-to-hast": "^13.0.0", "unified": "^11.0.0", "vfile": "^6.0.0" } }, "sha512-Dh7l57ianaEoIpzbp0PC9UKAdCSVklD8E5Rpw7ETfbTl3FqcOOgq5q2LVDhgGCkaBv7p24JXikPdvhhmHvKMsw=="],
"remark-smartypants": ["remark-smartypants@3.0.2", "", { "dependencies": { "retext": "^9.0.0", "retext-smartypants": "^6.0.0", "unified": "^11.0.4", "unist-util-visit": "^5.0.0" } }, "sha512-ILTWeOriIluwEvPjv67v7Blgrcx+LZOkAUVtKI3putuhlZm84FnqDORNXPPm+HY3NdZOMhyDwZ1E+eZB/Df5dA=="],
"remark-stringify": ["remark-stringify@11.0.0", "", { "dependencies": { "@types/mdast": "^4.0.0", "mdast-util-to-markdown": "^2.0.0", "unified": "^11.0.0" } }, "sha512-1OSmLd3awB/t8qdoEOMazZkNsfVTeY4fTsgzcQFdXNq8ToTN4ZGwrMnlda4K6smTFKD+GRV6O48i6Z4iKgPPpw=="],
"require-directory": ["require-directory@2.1.1", "", {}, "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q=="],
"retext": ["retext@9.0.0", "", { "dependencies": { "@types/nlcst": "^2.0.0", "retext-latin": "^4.0.0", "retext-stringify": "^4.0.0", "unified": "^11.0.0" } }, "sha512-sbMDcpHCNjvlheSgMfEcVrZko3cDzdbe1x/e7G66dFp0Ff7Mldvi2uv6JkJQzdRcvLYE8CA8Oe8siQx8ZOgTcA=="],
"retext-latin": ["retext-latin@4.0.0", "", { "dependencies": { "@types/nlcst": "^2.0.0", "parse-latin": "^7.0.0", "unified": "^11.0.0" } }, "sha512-hv9woG7Fy0M9IlRQloq/N6atV82NxLGveq+3H2WOi79dtIYWN8OaxogDm77f8YnVXJL2VD3bbqowu5E3EMhBYA=="],
"retext-smartypants": ["retext-smartypants@6.2.0", "", { "dependencies": { "@types/nlcst": "^2.0.0", "nlcst-to-string": "^4.0.0", "unist-util-visit": "^5.0.0" } }, "sha512-kk0jOU7+zGv//kfjXEBjdIryL1Acl4i9XNkHxtM7Tm5lFiCog576fjNC9hjoR7LTKQ0DsPWy09JummSsH1uqfQ=="],
"retext-stringify": ["retext-stringify@4.0.0", "", { "dependencies": { "@types/nlcst": "^2.0.0", "nlcst-to-string": "^4.0.0", "unified": "^11.0.0" } }, "sha512-rtfN/0o8kL1e+78+uxPTqu1Klt0yPzKuQ2BfWwwfgIUSayyzxpM1PJzkKt4V8803uB9qSy32MvI7Xep9khTpiA=="],
"rollup": ["rollup@4.60.1", "", { "dependencies": { "@types/estree": "1.0.8" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.60.1", "@rollup/rollup-android-arm64": "4.60.1", "@rollup/rollup-darwin-arm64": "4.60.1", "@rollup/rollup-darwin-x64": "4.60.1", "@rollup/rollup-freebsd-arm64": "4.60.1", "@rollup/rollup-freebsd-x64": "4.60.1", "@rollup/rollup-linux-arm-gnueabihf": "4.60.1", "@rollup/rollup-linux-arm-musleabihf": "4.60.1", "@rollup/rollup-linux-arm64-gnu": "4.60.1", "@rollup/rollup-linux-arm64-musl": "4.60.1", "@rollup/rollup-linux-loong64-gnu": "4.60.1", "@rollup/rollup-linux-loong64-musl": "4.60.1", "@rollup/rollup-linux-ppc64-gnu": "4.60.1", "@rollup/rollup-linux-ppc64-musl": "4.60.1", "@rollup/rollup-linux-riscv64-gnu": "4.60.1", "@rollup/rollup-linux-riscv64-musl": "4.60.1", "@rollup/rollup-linux-s390x-gnu": "4.60.1", "@rollup/rollup-linux-x64-gnu": "4.60.1", "@rollup/rollup-linux-x64-musl": "4.60.1", "@rollup/rollup-openbsd-x64": "4.60.1", "@rollup/rollup-openharmony-arm64": "4.60.1", "@rollup/rollup-win32-arm64-msvc": "4.60.1", "@rollup/rollup-win32-ia32-msvc": "4.60.1", "@rollup/rollup-win32-x64-gnu": "4.60.1", "@rollup/rollup-win32-x64-msvc": "4.60.1", "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-VmtB2rFU/GroZ4oL8+ZqXgSA38O6GR8KSIvWmEFv63pQ0G6KaBH9s07PO8XTXP4vI+3UJUEypOfjkGfmSBBR0w=="],
"rxjs": ["rxjs@7.8.2", "", { "dependencies": { "tslib": "^2.1.0" } }, "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA=="],
"sax": ["sax@1.6.0", "", {}, "sha512-6R3J5M4AcbtLUdZmRv2SygeVaM7IhrLXu9BmnOGmmACak8fiUtOsYNWUS4uK7upbmHIBbLBeFeI//477BKLBzA=="],
"scule": ["scule@1.3.0", "", {}, "sha512-6FtHJEvt+pVMIB9IBY+IcCJ6Z5f1iQnytgyfKMhDKgmzYG+TeH/wx1y3l27rshSbLiSanrR9ffZDrEsmjlQF2g=="],
"semver": ["semver@7.7.4", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA=="],
"sharp": ["sharp@0.34.5", "", { "dependencies": { "@img/colour": "^1.0.0", "detect-libc": "^2.1.2", "semver": "^7.7.3" }, "optionalDependencies": { "@img/sharp-darwin-arm64": "0.34.5", "@img/sharp-darwin-x64": "0.34.5", "@img/sharp-libvips-darwin-arm64": "1.2.4", "@img/sharp-libvips-darwin-x64": "1.2.4", "@img/sharp-libvips-linux-arm": "1.2.4", "@img/sharp-libvips-linux-arm64": "1.2.4", "@img/sharp-libvips-linux-ppc64": "1.2.4", "@img/sharp-libvips-linux-riscv64": "1.2.4", "@img/sharp-libvips-linux-s390x": "1.2.4", "@img/sharp-libvips-linux-x64": "1.2.4", "@img/sharp-libvips-linuxmusl-arm64": "1.2.4", "@img/sharp-libvips-linuxmusl-x64": "1.2.4", "@img/sharp-linux-arm": "0.34.5", "@img/sharp-linux-arm64": "0.34.5", "@img/sharp-linux-ppc64": "0.34.5", "@img/sharp-linux-riscv64": "0.34.5", "@img/sharp-linux-s390x": "0.34.5", "@img/sharp-linux-x64": "0.34.5", "@img/sharp-linuxmusl-arm64": "0.34.5", "@img/sharp-linuxmusl-x64": "0.34.5", "@img/sharp-wasm32": "0.34.5", "@img/sharp-win32-arm64": "0.34.5", "@img/sharp-win32-ia32": "0.34.5", "@img/sharp-win32-x64": "0.34.5" } }, "sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg=="],
"shell-quote": ["shell-quote@1.8.3", "", {}, "sha512-ObmnIF4hXNg1BqhnHmgbDETF8dLPCggZWBjkQfhZpbszZnYur5DUljTcCHii5LC3J5E0yeO/1LIMyH+UvHQgyw=="],
"shiki": ["shiki@4.0.2", "", { "dependencies": { "@shikijs/core": "4.0.2", "@shikijs/engine-javascript": "4.0.2", "@shikijs/engine-oniguruma": "4.0.2", "@shikijs/langs": "4.0.2", "@shikijs/themes": "4.0.2", "@shikijs/types": "4.0.2", "@shikijs/vscode-textmate": "^10.0.2", "@types/hast": "^3.0.4" } }, "sha512-eAVKTMedR5ckPo4xne/PjYQYrU3qx78gtJZ+sHlXEg5IHhhoQhMfZVzetTYuaJS0L2Ef3AcCRzCHV8T0WI6nIQ=="],
"sisteransi": ["sisteransi@1.0.5", "", {}, "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg=="],
"smol-toml": ["smol-toml@1.6.1", "", {}, "sha512-dWUG8F5sIIARXih1DTaQAX4SsiTXhInKf1buxdY9DIg4ZYPZK5nGM1VRIYmEbDbsHt7USo99xSLFu5Q1IqTmsg=="],
"source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="],
"space-separated-tokens": ["space-separated-tokens@2.0.2", "", {}, "sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q=="],
"string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="],
"stringify-entities": ["stringify-entities@4.0.4", "", { "dependencies": { "character-entities-html4": "^2.0.0", "character-entities-legacy": "^3.0.0" } }, "sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg=="],
"strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
"supports-color": ["supports-color@8.1.1", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q=="],
"svelte": ["svelte@5.55.1", "", { "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-QjvU7EFemf6mRzdMGlAFttMWtAAVXrax61SZYHdkD6yoVGQ89VeyKfZD4H1JrV1WLmJBxWhFch9H6ig/87VGjw=="],
"svelte2tsx": ["svelte2tsx@0.7.53", "", { "dependencies": { "dedent-js": "^1.0.1", "scule": "^1.3.0" }, "peerDependencies": { "svelte": "^3.55 || ^4.0.0-next.0 || ^4.0 || ^5.0.0-next.0", "typescript": "^4.9.4 || ^5.0.0" } }, "sha512-ljVSwmnYRDHRm8+7ICP6QoAN7U7vgOFfPBLN6T745YWNYqRRSzHxlrzUVqMjYls2Un8MzJissfziy/38e6Deeg=="],
"svgo": ["svgo@4.0.1", "", { "dependencies": { "commander": "^11.1.0", "css-select": "^5.1.0", "css-tree": "^3.0.1", "css-what": "^6.1.0", "csso": "^5.0.5", "picocolors": "^1.1.1", "sax": "^1.5.0" }, "bin": "./bin/svgo.js" }, "sha512-XDpWUOPC6FEibaLzjfe0ucaV0YrOjYotGJO1WpF0Zd+n6ZGEQUsSugaoLq9QkEZtAfQIxT42UChcssDVPP3+/w=="],
"tailwindcss": ["tailwindcss@4.2.2", "", {}, "sha512-KWBIxs1Xb6NoLdMVqhbhgwZf2PGBpPEiwOqgI4pFIYbNTfBXiKYyWoTsXgBQ9WFg/OlhnvHaY+AEpW7wSmFo2Q=="],
"tapable": ["tapable@2.3.2", "", {}, "sha512-1MOpMXuhGzGL5TTCZFItxCc0AARf1EZFQkGqMm7ERKj8+Hgr5oLvJOVFcC+lRmR8hCe2S3jC4T5D7Vg/d7/fhA=="],
"tiny-inflate": ["tiny-inflate@1.0.3", "", {}, "sha512-pkY1fj1cKHb2seWDy0B16HeWyczlJA9/WW3u3c4z/NiWDsO3DOU5D7nhTLE9CF0yXv/QZFY7sEJmj24dK+Rrqw=="],
"tinyclip": ["tinyclip@0.1.12", "", {}, "sha512-Ae3OVUqifDw0wBriIBS7yVaW44Dp6eSHQcyq4Igc7eN2TJH/2YsicswaW+J/OuMvhpDPOKEgpAZCjkb4hpoyeA=="],
"tinyexec": ["tinyexec@1.0.4", "", {}, "sha512-u9r3uZC0bdpGOXtlxUIdwf9pkmvhqJdrVCH9fapQtgy/OeTTMZ1nqH7agtvEfmGui6e1XxjcdrlxvxJvc3sMqw=="],
"tinyglobby": ["tinyglobby@0.2.15", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.3" } }, "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ=="],
"tree-kill": ["tree-kill@1.2.2", "", { "bin": { "tree-kill": "cli.js" } }, "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A=="],
"trim-lines": ["trim-lines@3.0.1", "", {}, "sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg=="],
"trough": ["trough@2.2.0", "", {}, "sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw=="],
"tsconfck": ["tsconfck@3.1.6", "", { "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"], "bin": { "tsconfck": "bin/tsconfck.js" } }, "sha512-ks6Vjr/jEw0P1gmOVwutM3B7fWxoWBL2KRDb1JfqGVawBmO5UsvmWOQFGHBPl5yxYz4eERr19E6L7NMv+Fej4w=="],
"tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
"typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="],
"ufo": ["ufo@1.6.3", "", {}, "sha512-yDJTmhydvl5lJzBmy/hyOAA0d+aqCBuwl818haVdYCRrWV84o7YyeVm4QlVHStqNrrJSTb6jKuFAVqAFsr+K3Q=="],
"ultrahtml": ["ultrahtml@1.6.0", "", {}, "sha512-R9fBn90VTJrqqLDwyMph+HGne8eqY1iPfYhPzZrvKpIfwkWZbcYlfpsb8B9dTvBfpy1/hqAD7Wi8EKfP9e8zdw=="],
"uncrypto": ["uncrypto@0.1.3", "", {}, "sha512-Ql87qFHB3s/De2ClA9e0gsnS6zXG27SkTiSJwjCc9MebbfapQfuPzumMIUMi38ezPZVNFcHI9sUIepeQfw8J8Q=="],
"undici-types": ["undici-types@7.18.2", "", {}, "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w=="],
"unified": ["unified@11.0.5", "", { "dependencies": { "@types/unist": "^3.0.0", "bail": "^2.0.0", "devlop": "^1.0.0", "extend": "^3.0.0", "is-plain-obj": "^4.0.0", "trough": "^2.0.0", "vfile": "^6.0.0" } }, "sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA=="],
"unifont": ["unifont@0.7.4", "", { "dependencies": { "css-tree": "^3.1.0", "ofetch": "^1.5.1", "ohash": "^2.0.11" } }, "sha512-oHeis4/xl42HUIeHuNZRGEvxj5AaIKR+bHPNegRq5LV1gdc3jundpONbjglKpihmJf+dswygdMJn3eftGIMemg=="],
"unist-util-find-after": ["unist-util-find-after@5.0.0", "", { "dependencies": { "@types/unist": "^3.0.0", "unist-util-is": "^6.0.0" } }, "sha512-amQa0Ep2m6hE2g72AugUItjbuM8X8cGQnFoHk0pGfrFeT9GZhzN5SW8nRsiGKK7Aif4CrACPENkA6P/Lw6fHGQ=="],
"unist-util-is": ["unist-util-is@6.0.1", "", { "dependencies": { "@types/unist": "^3.0.0" } }, "sha512-LsiILbtBETkDz8I9p1dQ0uyRUWuaQzd/cuEeS1hoRSyW5E5XGmTzlwY1OrNzzakGowI9Dr/I8HVaw4hTtnxy8g=="],
"unist-util-modify-children": ["unist-util-modify-children@4.0.0", "", { "dependencies": { "@types/unist": "^3.0.0", "array-iterate": "^2.0.0" } }, "sha512-+tdN5fGNddvsQdIzUF3Xx82CU9sMM+fA0dLgR9vOmT0oPT2jH+P1nd5lSqfCfXAw+93NhcXNY2qqvTUtE4cQkw=="],
"unist-util-position": ["unist-util-position@5.0.0", "", { "dependencies": { "@types/unist": "^3.0.0" } }, "sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA=="],
"unist-util-remove-position": ["unist-util-remove-position@5.0.0", "", { "dependencies": { "@types/unist": "^3.0.0", "unist-util-visit": "^5.0.0" } }, "sha512-Hp5Kh3wLxv0PHj9m2yZhhLt58KzPtEYKQQ4yxfYFEO7EvHwzyDYnduhHnY1mDxoqr7VUwVuHXk9RXKIiYS1N8Q=="],
"unist-util-stringify-position": ["unist-util-stringify-position@4.0.0", "", { "dependencies": { "@types/unist": "^3.0.0" } }, "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ=="],
"unist-util-visit": ["unist-util-visit@5.1.0", "", { "dependencies": { "@types/unist": "^3.0.0", "unist-util-is": "^6.0.0", "unist-util-visit-parents": "^6.0.0" } }, "sha512-m+vIdyeCOpdr/QeQCu2EzxX/ohgS8KbnPDgFni4dQsfSCtpz8UqDyY5GjRru8PDKuYn7Fq19j1CQ+nJSsGKOzg=="],
"unist-util-visit-children": ["unist-util-visit-children@3.0.0", "", { "dependencies": { "@types/unist": "^3.0.0" } }, "sha512-RgmdTfSBOg04sdPcpTSD1jzoNBjt9a80/ZCzp5cI9n1qPzLZWF9YdvWGN2zmTumP1HWhXKdUWexjy/Wy/lJ7tA=="],
"unist-util-visit-parents": ["unist-util-visit-parents@6.0.2", "", { "dependencies": { "@types/unist": "^3.0.0", "unist-util-is": "^6.0.0" } }, "sha512-goh1s1TBrqSqukSc8wrjwWhL0hiJxgA8m4kFxGlQ+8FYQ3C/m11FcTs4YYem7V664AhHVvgoQLk890Ssdsr2IQ=="],
"unstorage": ["unstorage@1.17.5", "", { "dependencies": { "anymatch": "^3.1.3", "chokidar": "^5.0.0", "destr": "^2.0.5", "h3": "^1.15.10", "lru-cache": "^11.2.7", "node-fetch-native": "^1.6.7", "ofetch": "^1.5.1", "ufo": "^1.6.3" }, "peerDependencies": { "@azure/app-configuration": "^1.8.0", "@azure/cosmos": "^4.2.0", "@azure/data-tables": "^13.3.0", "@azure/identity": "^4.6.0", "@azure/keyvault-secrets": "^4.9.0", "@azure/storage-blob": "^12.26.0", "@capacitor/preferences": "^6 || ^7 || ^8", "@deno/kv": ">=0.9.0", "@netlify/blobs": "^6.5.0 || ^7.0.0 || ^8.1.0 || ^9.0.0 || ^10.0.0", "@planetscale/database": "^1.19.0", "@upstash/redis": "^1.34.3", "@vercel/blob": ">=0.27.1", "@vercel/functions": "^2.2.12 || ^3.0.0", "@vercel/kv": "^1 || ^2 || ^3", "aws4fetch": "^1.0.20", "db0": ">=0.2.1", "idb-keyval": "^6.2.1", "ioredis": "^5.4.2", "uploadthing": "^7.4.4" }, "optionalPeers": ["@azure/app-configuration", "@azure/cosmos", "@azure/data-tables", "@azure/identity", "@azure/keyvault-secrets", "@azure/storage-blob", "@capacitor/preferences", "@deno/kv", "@netlify/blobs", "@planetscale/database", "@upstash/redis", "@vercel/blob", "@vercel/functions", "@vercel/kv", "aws4fetch", "db0", "idb-keyval", "ioredis", "uploadthing"] }, "sha512-0i3iqvRfx29hkNntHyQvJTpf5W9dQ9ZadSoRU8+xVlhVtT7jAX57fazYO9EHvcRCfBCyi5YRya7XCDOsbTgkPg=="],
"util-deprecate": ["util-deprecate@1.0.2", "", {}, "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="],
"vfile": ["vfile@6.0.3", "", { "dependencies": { "@types/unist": "^3.0.0", "vfile-message": "^4.0.0" } }, "sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q=="],
"vfile-location": ["vfile-location@5.0.3", "", { "dependencies": { "@types/unist": "^3.0.0", "vfile": "^6.0.0" } }, "sha512-5yXvWDEgqeiYiBe1lbxYF7UMAIm/IcopxMHrMQDq3nvKcjPKIhZklUKL+AE7J7uApI4kwe2snsK+eI6UTj9EHg=="],
"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@7.3.1", "", { "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-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA=="],
"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=="],
"web-namespaces": ["web-namespaces@2.0.1", "", {}, "sha512-bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ13TsorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPG2be8uPrqsQ=="],
"which-pm-runs": ["which-pm-runs@1.1.0", "", {}, "sha512-n1brCuqClxfFfq/Rb0ICg9giSZqCS+pLtccdag6C2HyufBrh3fBOiy9nb6ggRMvWOVH5GrdJskj5iGTZNxd7SA=="],
"wrap-ansi": ["wrap-ansi@7.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q=="],
"xxhash-wasm": ["xxhash-wasm@1.1.0", "", {}, "sha512-147y/6YNh+tlp6nd/2pWq38i9h6mz/EuQ6njIrmW8D1BS5nCqs0P6DG+m6zTGnNz5I+uhZ0SHxBs9BsPrwcKDA=="],
"y18n": ["y18n@5.0.8", "", {}, "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA=="],
"yargs": ["yargs@17.7.2", "", { "dependencies": { "cliui": "^8.0.1", "escalade": "^3.1.1", "get-caller-file": "^2.0.5", "require-directory": "^2.1.1", "string-width": "^4.2.3", "y18n": "^5.0.5", "yargs-parser": "^21.1.1" } }, "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w=="],
"yargs-parser": ["yargs-parser@22.0.0", "", {}, "sha512-rwu/ClNdSMpkSrUb+d6BRsSkLUq1fmfsY6TOpYzTwvwkg1/NRG85KBy3kq++A8LKQwX6lsu+aWad+2khvuXrqw=="],
"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=="],
"zwitch": ["zwitch@2.0.4", "", {}, "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A=="],
"@tailwindcss/oxide-wasm32-wasi/@emnapi/core": ["@emnapi/core@1.9.1", "", { "dependencies": { "@emnapi/wasi-threads": "1.2.0", "tslib": "^2.4.0" }, "bundled": true }, "sha512-mukuNALVsoix/w1BJwFzwXBN/dHeejQtuVzcDsfOEsdpCumXb/E9j8w11h5S54tT1xhifGfbbSm/ICrObRb3KA=="],
"@tailwindcss/oxide-wasm32-wasi/@emnapi/runtime": ["@emnapi/runtime@1.9.1", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-VYi5+ZVLhpgK4hQ0TAjiQiZ6ol0oe4mBx7mVv7IflsiEp0OWoVsp/+f9Vc1hOhE0TtkORVrI1GvzyreqpgWtkA=="],
"@tailwindcss/oxide-wasm32-wasi/@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.2.0", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-N10dEJNSsUx41Z6pZsXU8FjPjpBEplgH24sfkmITrBED1/U2Esum9F3lfLrMjKHHjmi557zQn7kR9R+XWXu5Rg=="],
"@tailwindcss/oxide-wasm32-wasi/@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@1.1.2", "", { "dependencies": { "@tybys/wasm-util": "^0.10.1" }, "peerDependencies": { "@emnapi/core": "^1.7.1", "@emnapi/runtime": "^1.7.1" }, "bundled": true }, "sha512-sNXv5oLJ7ob93xkZ1XnxisYhGYXfaG9f65/ZgYuAu3qt7b3NadcOEhLvx28hv31PgX8SZJRYrAIPQilQmFpLVw=="],
"@tailwindcss/oxide-wasm32-wasi/@tybys/wasm-util": ["@tybys/wasm-util@0.10.1", "", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg=="],
"@tailwindcss/oxide-wasm32-wasi/tslib": ["tslib@2.8.1", "", { "bundled": true }, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
"anymatch/picomatch": ["picomatch@2.3.2", "", {}, "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA=="],
"chalk/supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="],
"csso/css-tree": ["css-tree@2.2.1", "", { "dependencies": { "mdn-data": "2.0.28", "source-map-js": "^1.0.1" } }, "sha512-OA0mILzGc1kCOCSJerOeqDxDQ4HOh+G8NbOJFOTgOCzpw7fCBubk0fEyxp8AgOL/jvLgYA/uV0cMbe43ElF1JA=="],
"dom-serializer/entities": ["entities@4.5.0", "", {}, "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw=="],
"svelte/aria-query": ["aria-query@5.3.1", "", {}, "sha512-Z/ZeOgVl7bcSYZ/u/rh0fOpvEpq//LZmdbkXyc7syVzjPAhfOa9ebsdTSjEBDU4vs5nC98Kfduj1uFo0qyET3g=="],
"yargs/yargs-parser": ["yargs-parser@21.1.1", "", {}, "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw=="],
"csso/css-tree/mdn-data": ["mdn-data@2.0.28", "", {}, "sha512-aylIc7Z9y4yzHYAJNuESG3hfhC+0Ibp/MAMiaOZgNv4pmEdFyfZhhhny4MNiAfWdBQ1RQ2mfDWmM1x8SvGyp8g=="],
}
}

1700
front/bun.nix Normal file

File diff suppressed because it is too large Load Diff

34
front/package.json Normal file
View File

@@ -0,0 +1,34 @@
{
"name": "iknowyou",
"type": "module",
"version": "1.0.0",
"scripts": {
"dev:frontend": "astro dev",
"dev:backend": "cd ../back/ && air",
"dev": "concurrently \"bun dev:frontend\" \"bun dev:backend\"",
"build": "astro build",
"preview": "astro preview",
"astro": "astro"
},
"dependencies": {
"@astrojs/svelte": "8.0.4",
"@lucide/svelte": "^1.7.0",
"@tailwindcss/vite": "^4.2.1",
"ansi_up": "^6.0.6",
"astro": "6.1.2",
"dompurify": "^3.3.3",
"js-yaml": "^4.1.1",
"remark-github-blockquote-alert": "^2.1.0",
"svelte": "^5.53.12",
"tailwindcss": "^4.2.1",
"typescript": "^5.9.3"
},
"devDependencies": {
"@tailwindcss/typography": "^0.5.19",
"@types/dompurify": "^3.2.0",
"@types/js-yaml": "^4.0.9",
"@types/node": "^25.5.0",
"concurrently": "^9.2.1",
"daisyui": "^5.5.19"
}
}

View File

@@ -0,0 +1,5 @@
Contact: mailto:anotherhadi.clapped234[at]passmail.net
Expires: 2028-12-31T23:00:00.000Z
Encryption: /anotherhadi.asc
Preferred-Languages: en, fr
Canonical: /.well-known/security.txt

1
front/public/Wrench.svg Normal file
View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-wrench-icon lucide-wrench"><path d="M14.7 6.3a1 1 0 0 0 0 1.4l1.6 1.6a1 1 0 0 0 1.4 0l3.106-3.105c.32-.322.863-.22.983.218a6 6 0 0 1-8.259 7.057l-7.91 7.91a1 1 0 0 1-2.999-3l7.91-7.91a6 6 0 0 1 7.057-8.259c.438.12.54.662.219.984z"/></svg>

After

Width:  |  Height:  |  Size: 441 B

View File

@@ -0,0 +1,52 @@
-----BEGIN PGP PUBLIC KEY BLOCK-----
mQINBGlUVOcBEADC8BaIMD71bTsFTOEI5pSJTiKrMQdgYkkDiK8yBdstSLInBeTV
Xsxlgd9s9Nw9sNkbIytUB3rSbVwlYbH+o6A5qaQQkOBq/3/RR+zdPB5lonpvBPYs
agcjRLc2Z0W/83ERMuiOfrJHsOjwopL72PaG1KzuXEDI2o5vUFIvt4iER+ZGXAPU
GKh7YvTWy1qPkHYeHWN3khE4hKffx+ozxQWFQEr90DrJUwDPSMwkCxkzk3R68qSG
x6dEp21XChSJmvN+SQjhMRRCcE6PIQgWBzitEHZhWdpBfbfYlqP2Fc1d56kqSYXP
5bwUXjbJ/1RgmULWjgyAlbkYkfkw2fqvgpwz4GPFZV16Qa2yh6iuswoSp4uHP1Pp
9v8TTI2xXX8FuhhdVbSu1xAbgcD7GAaTZJz8qCqU7oZieHMLQVAdEio0dDq7pO9s
bUND1Syd4tOxpiKedwOs4YFOU5tc59Ik7amb7PUZr/OR6JmFWBHuhnw763zl046G
3NoLCb3lsKq6wxBNGPoEhlDGSe7ayIY2KdawzH62ymK53FHI2d+kpuNIGLas6+JL
RqzpG5DVK4JaozoYTnckS0dK/y28gANUaZ8dFB/gcEnHHP8rHq/I4tAcPiGc+I87
o25cBwJctOA4ucD9/G8rBjlt7wVkZjFZNKDmXhHIe9Tw5+Jv5HPA7OpUYwARAQAB
tCpIYWRpIDxhbm90aGVyaGFkaS5jbGFwcGVkMjM0QHBhc3NtYWlsLm5ldD6JAk4E
EwEKADgWIQScdA7pGcNUcalhd2qk3jJylKG9VQUCaVRU5wIbAwULCQgHAgYVCgkI
CwIEFgIDAQIeAQIXgAAKCRCk3jJylKG9VTYSD/9jZxiU9SBIpxNbKtEQo2M8/QBk
4xE5m/6gYE8fx1DndEsRK3eONIXNiRAq5fSadfF0EG8tL4RoIZksSx2usu84VH3y
y3Tn/mJnK25v6DNQWZmREhvgOw0gP4py7of0fBi+T0FYFGqxdPEYwTkqZ4Hb/phz
QQHdwa8nYd0+KHMujQwwMC9m+v5qCRv4F4sMAPfs9jZpxMD/gpsV0Cnrg1Vq+JDm
l2upEioKLbq8cPPCJyT85wACu6zb/OiObu2Fs4IKvC97kcaKUyfjGHGN15384bgM
HOihrKVbNc6SK44kOp1fdUz4zz3gXT7Qq2EJHrt33VxLT/QnhihD7MZaDqlpdrjh
w0y3tMc4dhqmDSSoDx3GANn23F5NmFtXsx1Ndhqhv5pPDLbYVAKfKCzJHrBWPUkP
n8ZS4bs9XN+Nv7lfXKxSkPg9CQARYFmrHQyL2IHTeBIOUcznji04p7AG8YtGOJ7G
ZZS3KZ4kxSKTi/QleTa9ZldRecvT4cYk1juwmoIO5pIwPOv+KsUPSn3oeLLYup/A
y8lg3zxc9jFk3tyrHgIVM7sM9VqGxOctCORNzRma8O7TbbkiPGXLZc8KaYLVo0QT
9gmwuaDpJ6hT26EVvVCj1dlOJBXqsrsaTe4ev/jllJjOMOwtc3EcCMhWMD5fTEkE
oZFDZOY3YBnbHEJaXLkCDQRpVFTnARAAn9YLGQkFiVeovLCbyHt62aVOFOp7j2AS
Jj/vg+P96D71Wn8NomNLjKokEkYnMsgKb9D+jSWRFiUmgHdQYDQuouz4w8bH6Bwz
owJL489QyQLml9uFL4I7ZwLzQSnxxVMGYe+1XKJNXPTlhwroKNCMe5wfuzMRZvGc
CzfLmkvNDXY36GVfHIRkh31R/6ILIa+s5HAmjz1560RcaJ9qxQDFdJGUdbbaP2Ag
lCYZXHTFwK8X8cFi+qpW31vY3nzSzcNc+DZdPa7Mkhsa3dHt7gLu1GLjeoyUThmu
Q/+orpqagj04i8T1pjucAmhbnw8N9UwpLAUwUhhMTsPik1EvQiKJq2CgHddtuPDx
ekJzBU6/fZQu8czaIezjqJ7yD0PYqSnOqtZ+MNoWE8HVUyXZPdVC5Fpnm5Lix/Tr
ie9ylu3V5sSVQ5FIs+cZ1vZnOTShjw26rE2FfatVJeHpC5ioV3z6lzaCi4/zkSWi
fCKhi9cOROeV+oslV7esnFGXDWLtd22H54NW8Kc140TpPq8457wbvS5r1BMMHite
OGXcMSsKiivB+nO4acAPHpl093/qYcFMpP+2GP24ogJuL58UkfiboZz7b3gLPGZJ
rgMmdlG5p0EMKOki1pruopVWW6fca8eLx5FuX5AzHZg7lsTXIi7iTfvGAo99Udbj
RNQGHofu1qkAEQEAAYkCNgQYAQoAIBYhBJx0DukZw1RxqWF3aqTeMnKUob1VBQJp
VFTnAhsMAAoJEKTeMnKUob1V8IQP/iQZAB1dtlsTkMZyUUP1ZqY0RwEkDyYgRMMV
Xg845CoNnHss+ioHf6ObneKNwogow/r9OTXSDA4gBWvk1WsJ8j6tGwMSKY+mH/rh
lER/lBeMCuvMc2/KE8VoOJkpXBktSdwEzLAxToTkyHuevxY43/g59xbBCImrIcVa
kPJBtboxLkm0BE3nwhD6m/Uo1oECq8F2cDI5luYzOsIMjKyvavTUDsNX1RE7Ula8
m8ra3QkgM8f4f1cmJP6y927RLEgXLxsbFlUjvAXRPe2sMIGV+32RTcXbR8zizuZl
eyORq45t6EO0a8x1Bh0Jkimk0wBQKA00iiHb3vJYySJltfaWmJMPVXs1zZpK3n1l
WRX4aO8MSBvqE+ZLrWJW3M8Yb6CeFh5yQv6B+1Nqyfk4pO5Q95/aRtG6qijQsxfb
c+RqhL4yT+2KLBUjj0gg98MEM9+MoFxbWpJtnyA7LmgfatG8gHj1HxLBmEhkc5d5
moM81nG/pWfUonOaVqcJji4UyDPNTOyAqHtp7iZUB80zu0IoyFRorsE9BkN4q/oY
6lyyKcOLalvL2sJ3GtzBd1nmCjE2BMLD3jn8l7ig/FNVF2AZPWYmmCBiMuAMBRcA
NXzOwAMmrgN41olFG9nAsoLLlxH69mUJTtw0sqAqtVzEnTwynbSm6H6dIL4yW7dA
/jkWmwL9
=4VM7
-----END PGP PUBLIC KEY BLOCK-----

6
front/public/favicon.svg Normal file
View File

@@ -0,0 +1,6 @@
<svg width="200" height="200" viewBox="0 0 200 200" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M140.689 59.2354C111.761 47.8765 66.799 59.5653 55.2538 91.8918C55.2538 91.8918 107.039 91.5467 111.331 93.8711C111.331 93.8711 116.552 81.3665 122.216 74.4091C127.853 67.4848 140.689 59.2354 140.689 59.2354Z" fill="#FCD770"/>
<path d="M137.06 112.673L181.592 145C187.529 104.427 176.555 73.3189 140.689 59.2354C140.689 59.2354 127.853 67.4848 122.216 74.4091C116.552 81.3665 111.331 93.8711 111.331 93.8711C119.247 98.1593 137.06 112.673 137.06 112.673Z" fill="#FCD770"/>
<path d="M16 112.673H137.06C137.06 112.673 119.247 98.1593 111.331 93.8711C107.039 91.5467 55.2538 91.8918 55.2538 91.8918C55.2538 91.8918 37.2519 91.2968 28.5348 98.1593C23.028 102.495 16 112.673 16 112.673Z" fill="#FFEAA7"/>
<path d="M137.06 112.673H16C16 112.673 23.028 102.495 28.5348 98.1593C37.2519 91.2968 55.2538 91.8918 55.2538 91.8918M137.06 112.673L181.592 145C187.529 104.427 176.555 73.3189 140.689 59.2354M137.06 112.673C137.06 112.673 119.247 98.1593 111.331 93.8711M140.689 59.2354C111.761 47.8765 66.799 59.5653 55.2538 91.8918M140.689 59.2354C140.689 59.2354 127.853 67.4848 122.216 74.4091C116.552 81.3665 111.331 93.8711 111.331 93.8711M55.2538 91.8918C55.2538 91.8918 107.039 91.5467 111.331 93.8711" stroke="#030303" stroke-width="10.5556"/>
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

View File

@@ -0,0 +1,71 @@
<svg width="470" height="86" viewBox="0 0 470 86" fill="none" xmlns="http://www.w3.org/2000/svg">
<mask id="path-1-outside-1_439_173" maskUnits="userSpaceOnUse" x="0" y="0" width="470" height="86" fill="black">
<rect fill="white" width="470" height="86"/>
<path d="M2.215 22.222L12.502 24.328L22.789 22.222V69.769H2.215V22.222ZM12.502 19.792C9.046 19.792 6.265 18.955 4.159 17.281C2.053 15.607 1 13.312 1 10.396C1 7.48 2.053 5.185 4.159 3.511C6.265 1.837 9.046 1 12.502 1C16.012 1 18.793 1.837 20.845 3.511C22.951 5.185 24.004 7.48 24.004 10.396C24.004 13.312 22.951 15.607 20.845 17.281C18.793 18.955 16.012 19.792 12.502 19.792Z"/>
<path d="M54.0499 53.812L48.5419 52.03L71.7889 23.194H92.6869L51.6199 69.769H33.4759V7.399H54.0499V53.812ZM58.5049 47.251L74.3809 37.126L93.8209 69.769H70.9789L58.5049 47.251Z"/>
<path d="M86.5493 23.194H107.123L110.363 43.039V69.769H89.7893V39.88L86.5493 23.194ZM126.482 21.736C131.018 21.736 134.879 22.654 138.065 24.49C141.251 26.272 143.681 28.81 145.355 32.104C147.083 35.344 147.947 39.151 147.947 43.525V69.769H127.373V46.441C127.373 43.849 126.644 41.851 125.186 40.447C123.782 39.043 121.784 38.341 119.192 38.341C117.356 38.341 115.763 38.719 114.413 39.475C113.117 40.177 112.118 41.23 111.416 42.634C110.714 43.984 110.363 45.631 110.363 47.575L104.288 44.497C105.044 39.421 106.502 35.209 108.662 31.861C110.822 28.459 113.441 25.921 116.519 24.247C119.597 22.573 122.918 21.736 126.482 21.736Z"/>
<path d="M175.999 71.227C169.843 71.227 164.443 70.201 159.799 68.149C155.209 66.097 151.618 63.208 149.026 59.482C146.488 55.756 145.219 51.436 145.219 46.522C145.219 41.554 146.488 37.207 149.026 33.481C151.618 29.755 155.209 26.866 159.799 24.814C164.443 22.762 169.843 21.736 175.999 21.736C182.155 21.736 187.528 22.762 192.118 24.814C196.762 26.866 200.353 29.755 202.891 33.481C205.483 37.207 206.779 41.554 206.779 46.522C206.779 51.436 205.483 55.756 202.891 59.482C200.353 63.208 196.762 66.097 192.118 68.149C187.528 70.201 182.155 71.227 175.999 71.227ZM175.999 56.242C178.159 56.242 179.968 55.864 181.426 55.108C182.938 54.352 184.072 53.245 184.828 51.787C185.638 50.329 186.043 48.574 186.043 46.522C186.043 44.416 185.638 42.634 184.828 41.176C184.072 39.718 182.938 38.611 181.426 37.855C179.968 37.099 178.159 36.721 175.999 36.721C173.893 36.721 172.084 37.099 170.572 37.855C169.06 38.611 167.899 39.718 167.089 41.176C166.333 42.634 165.955 44.416 165.955 46.522C165.955 48.574 166.333 50.329 167.089 51.787C167.899 53.245 169.06 54.352 170.572 55.108C172.084 55.864 173.893 56.242 175.999 56.242Z"/>
<path d="M267.944 57.862H261.869L273.128 23.194H293.702L277.178 69.769H256.199L242.834 33.157H249.314L235.949 69.769H214.97L198.446 23.194H219.02L230.279 57.862H224.204L236.435 23.194H255.713L267.944 57.862Z"/>
<path d="M313.838 84.592C309.95 84.592 306.521 84.106 303.551 83.134C300.635 82.216 297.719 80.677 294.803 78.517V64.747C297.665 66.583 300.311 67.906 302.741 68.716C305.225 69.472 307.898 69.85 310.76 69.85C313.028 69.85 314.999 69.391 316.673 68.473C318.401 67.501 319.805 65.665 320.885 62.965L336.68 23.194H358.55L337.328 69.121C335.546 73.009 333.359 76.087 330.767 78.355C328.229 80.623 325.502 82.216 322.586 83.134C319.67 84.106 316.754 84.592 313.838 84.592ZM311.246 63.532L292.535 23.194H315.215L330.848 63.532H311.246Z"/>
<path d="M379.954 71.227C373.798 71.227 368.398 70.201 363.754 68.149C359.164 66.097 355.573 63.208 352.981 59.482C350.443 55.756 349.174 51.436 349.174 46.522C349.174 41.554 350.443 37.207 352.981 33.481C355.573 29.755 359.164 26.866 363.754 24.814C368.398 22.762 373.798 21.736 379.954 21.736C386.11 21.736 391.483 22.762 396.073 24.814C400.717 26.866 404.308 29.755 406.846 33.481C409.438 37.207 410.734 41.554 410.734 46.522C410.734 51.436 409.438 55.756 406.846 59.482C404.308 63.208 400.717 66.097 396.073 68.149C391.483 70.201 386.11 71.227 379.954 71.227ZM379.954 56.242C382.114 56.242 383.923 55.864 385.381 55.108C386.893 54.352 388.027 53.245 388.783 51.787C389.593 50.329 389.998 48.574 389.998 46.522C389.998 44.416 389.593 42.634 388.783 41.176C388.027 39.718 386.893 38.611 385.381 37.855C383.923 37.099 382.114 36.721 379.954 36.721C377.848 36.721 376.039 37.099 374.527 37.855C373.015 38.611 371.854 39.718 371.044 41.176C370.288 42.634 369.91 44.416 369.91 46.522C369.91 48.574 370.288 50.329 371.044 51.787C371.854 53.245 373.015 54.352 374.527 55.108C376.039 55.864 377.848 56.242 379.954 56.242Z"/>
<path d="M428.843 71.146C424.469 71.146 420.716 70.255 417.584 68.473C414.452 66.637 412.049 64.099 410.375 60.859C408.755 57.565 407.945 53.758 407.945 49.438V23.194H428.519V46.522C428.519 49.06 429.167 51.031 430.463 52.435C431.813 53.839 433.703 54.541 436.133 54.541C438.023 54.541 439.562 54.217 440.75 53.569C441.992 52.867 442.91 51.841 443.504 50.491C444.152 49.087 444.476 47.386 444.476 45.388L450.551 48.466C449.849 53.488 448.418 57.7 446.258 61.102C444.152 64.45 441.587 66.961 438.563 68.635C435.593 70.309 432.353 71.146 428.843 71.146ZM447.716 69.769L444.476 49.924V23.194H465.05V53.083L468.29 69.769H447.716Z"/>
</mask>
<path d="M2.215 22.222L12.502 24.328L22.789 22.222V69.769H2.215V22.222ZM12.502 19.792C9.046 19.792 6.265 18.955 4.159 17.281C2.053 15.607 1 13.312 1 10.396C1 7.48 2.053 5.185 4.159 3.511C6.265 1.837 9.046 1 12.502 1C16.012 1 18.793 1.837 20.845 3.511C22.951 5.185 24.004 7.48 24.004 10.396C24.004 13.312 22.951 15.607 20.845 17.281C18.793 18.955 16.012 19.792 12.502 19.792Z" fill="url(#paint0_linear_439_173)"/>
<path d="M54.0499 53.812L48.5419 52.03L71.7889 23.194H92.6869L51.6199 69.769H33.4759V7.399H54.0499V53.812ZM58.5049 47.251L74.3809 37.126L93.8209 69.769H70.9789L58.5049 47.251Z" fill="url(#paint1_linear_439_173)"/>
<path d="M86.5493 23.194H107.123L110.363 43.039V69.769H89.7893V39.88L86.5493 23.194ZM126.482 21.736C131.018 21.736 134.879 22.654 138.065 24.49C141.251 26.272 143.681 28.81 145.355 32.104C147.083 35.344 147.947 39.151 147.947 43.525V69.769H127.373V46.441C127.373 43.849 126.644 41.851 125.186 40.447C123.782 39.043 121.784 38.341 119.192 38.341C117.356 38.341 115.763 38.719 114.413 39.475C113.117 40.177 112.118 41.23 111.416 42.634C110.714 43.984 110.363 45.631 110.363 47.575L104.288 44.497C105.044 39.421 106.502 35.209 108.662 31.861C110.822 28.459 113.441 25.921 116.519 24.247C119.597 22.573 122.918 21.736 126.482 21.736Z" fill="url(#paint2_linear_439_173)"/>
<path d="M175.999 71.227C169.843 71.227 164.443 70.201 159.799 68.149C155.209 66.097 151.618 63.208 149.026 59.482C146.488 55.756 145.219 51.436 145.219 46.522C145.219 41.554 146.488 37.207 149.026 33.481C151.618 29.755 155.209 26.866 159.799 24.814C164.443 22.762 169.843 21.736 175.999 21.736C182.155 21.736 187.528 22.762 192.118 24.814C196.762 26.866 200.353 29.755 202.891 33.481C205.483 37.207 206.779 41.554 206.779 46.522C206.779 51.436 205.483 55.756 202.891 59.482C200.353 63.208 196.762 66.097 192.118 68.149C187.528 70.201 182.155 71.227 175.999 71.227ZM175.999 56.242C178.159 56.242 179.968 55.864 181.426 55.108C182.938 54.352 184.072 53.245 184.828 51.787C185.638 50.329 186.043 48.574 186.043 46.522C186.043 44.416 185.638 42.634 184.828 41.176C184.072 39.718 182.938 38.611 181.426 37.855C179.968 37.099 178.159 36.721 175.999 36.721C173.893 36.721 172.084 37.099 170.572 37.855C169.06 38.611 167.899 39.718 167.089 41.176C166.333 42.634 165.955 44.416 165.955 46.522C165.955 48.574 166.333 50.329 167.089 51.787C167.899 53.245 169.06 54.352 170.572 55.108C172.084 55.864 173.893 56.242 175.999 56.242Z" fill="url(#paint3_linear_439_173)"/>
<path d="M267.944 57.862H261.869L273.128 23.194H293.702L277.178 69.769H256.199L242.834 33.157H249.314L235.949 69.769H214.97L198.446 23.194H219.02L230.279 57.862H224.204L236.435 23.194H255.713L267.944 57.862Z" fill="url(#paint4_linear_439_173)"/>
<path d="M313.838 84.592C309.95 84.592 306.521 84.106 303.551 83.134C300.635 82.216 297.719 80.677 294.803 78.517V64.747C297.665 66.583 300.311 67.906 302.741 68.716C305.225 69.472 307.898 69.85 310.76 69.85C313.028 69.85 314.999 69.391 316.673 68.473C318.401 67.501 319.805 65.665 320.885 62.965L336.68 23.194H358.55L337.328 69.121C335.546 73.009 333.359 76.087 330.767 78.355C328.229 80.623 325.502 82.216 322.586 83.134C319.67 84.106 316.754 84.592 313.838 84.592ZM311.246 63.532L292.535 23.194H315.215L330.848 63.532H311.246Z" fill="url(#paint5_linear_439_173)"/>
<path d="M379.954 71.227C373.798 71.227 368.398 70.201 363.754 68.149C359.164 66.097 355.573 63.208 352.981 59.482C350.443 55.756 349.174 51.436 349.174 46.522C349.174 41.554 350.443 37.207 352.981 33.481C355.573 29.755 359.164 26.866 363.754 24.814C368.398 22.762 373.798 21.736 379.954 21.736C386.11 21.736 391.483 22.762 396.073 24.814C400.717 26.866 404.308 29.755 406.846 33.481C409.438 37.207 410.734 41.554 410.734 46.522C410.734 51.436 409.438 55.756 406.846 59.482C404.308 63.208 400.717 66.097 396.073 68.149C391.483 70.201 386.11 71.227 379.954 71.227ZM379.954 56.242C382.114 56.242 383.923 55.864 385.381 55.108C386.893 54.352 388.027 53.245 388.783 51.787C389.593 50.329 389.998 48.574 389.998 46.522C389.998 44.416 389.593 42.634 388.783 41.176C388.027 39.718 386.893 38.611 385.381 37.855C383.923 37.099 382.114 36.721 379.954 36.721C377.848 36.721 376.039 37.099 374.527 37.855C373.015 38.611 371.854 39.718 371.044 41.176C370.288 42.634 369.91 44.416 369.91 46.522C369.91 48.574 370.288 50.329 371.044 51.787C371.854 53.245 373.015 54.352 374.527 55.108C376.039 55.864 377.848 56.242 379.954 56.242Z" fill="url(#paint6_linear_439_173)"/>
<path d="M428.843 71.146C424.469 71.146 420.716 70.255 417.584 68.473C414.452 66.637 412.049 64.099 410.375 60.859C408.755 57.565 407.945 53.758 407.945 49.438V23.194H428.519V46.522C428.519 49.06 429.167 51.031 430.463 52.435C431.813 53.839 433.703 54.541 436.133 54.541C438.023 54.541 439.562 54.217 440.75 53.569C441.992 52.867 442.91 51.841 443.504 50.491C444.152 49.087 444.476 47.386 444.476 45.388L450.551 48.466C449.849 53.488 448.418 57.7 446.258 61.102C444.152 64.45 441.587 66.961 438.563 68.635C435.593 70.309 432.353 71.146 428.843 71.146ZM447.716 69.769L444.476 49.924V23.194H465.05V53.083L468.29 69.769H447.716Z" fill="url(#paint7_linear_439_173)"/>
<path d="M2.215 22.222L12.502 24.328L22.789 22.222V69.769H2.215V22.222ZM12.502 19.792C9.046 19.792 6.265 18.955 4.159 17.281C2.053 15.607 1 13.312 1 10.396C1 7.48 2.053 5.185 4.159 3.511C6.265 1.837 9.046 1 12.502 1C16.012 1 18.793 1.837 20.845 3.511C22.951 5.185 24.004 7.48 24.004 10.396C24.004 13.312 22.951 15.607 20.845 17.281C18.793 18.955 16.012 19.792 12.502 19.792Z" stroke="black" stroke-width="2" mask="url(#path-1-outside-1_439_173)"/>
<path d="M54.0499 53.812L48.5419 52.03L71.7889 23.194H92.6869L51.6199 69.769H33.4759V7.399H54.0499V53.812ZM58.5049 47.251L74.3809 37.126L93.8209 69.769H70.9789L58.5049 47.251Z" stroke="black" stroke-width="2" mask="url(#path-1-outside-1_439_173)"/>
<path d="M86.5493 23.194H107.123L110.363 43.039V69.769H89.7893V39.88L86.5493 23.194ZM126.482 21.736C131.018 21.736 134.879 22.654 138.065 24.49C141.251 26.272 143.681 28.81 145.355 32.104C147.083 35.344 147.947 39.151 147.947 43.525V69.769H127.373V46.441C127.373 43.849 126.644 41.851 125.186 40.447C123.782 39.043 121.784 38.341 119.192 38.341C117.356 38.341 115.763 38.719 114.413 39.475C113.117 40.177 112.118 41.23 111.416 42.634C110.714 43.984 110.363 45.631 110.363 47.575L104.288 44.497C105.044 39.421 106.502 35.209 108.662 31.861C110.822 28.459 113.441 25.921 116.519 24.247C119.597 22.573 122.918 21.736 126.482 21.736Z" stroke="black" stroke-width="2" mask="url(#path-1-outside-1_439_173)"/>
<path d="M175.999 71.227C169.843 71.227 164.443 70.201 159.799 68.149C155.209 66.097 151.618 63.208 149.026 59.482C146.488 55.756 145.219 51.436 145.219 46.522C145.219 41.554 146.488 37.207 149.026 33.481C151.618 29.755 155.209 26.866 159.799 24.814C164.443 22.762 169.843 21.736 175.999 21.736C182.155 21.736 187.528 22.762 192.118 24.814C196.762 26.866 200.353 29.755 202.891 33.481C205.483 37.207 206.779 41.554 206.779 46.522C206.779 51.436 205.483 55.756 202.891 59.482C200.353 63.208 196.762 66.097 192.118 68.149C187.528 70.201 182.155 71.227 175.999 71.227ZM175.999 56.242C178.159 56.242 179.968 55.864 181.426 55.108C182.938 54.352 184.072 53.245 184.828 51.787C185.638 50.329 186.043 48.574 186.043 46.522C186.043 44.416 185.638 42.634 184.828 41.176C184.072 39.718 182.938 38.611 181.426 37.855C179.968 37.099 178.159 36.721 175.999 36.721C173.893 36.721 172.084 37.099 170.572 37.855C169.06 38.611 167.899 39.718 167.089 41.176C166.333 42.634 165.955 44.416 165.955 46.522C165.955 48.574 166.333 50.329 167.089 51.787C167.899 53.245 169.06 54.352 170.572 55.108C172.084 55.864 173.893 56.242 175.999 56.242Z" stroke="black" stroke-width="2" mask="url(#path-1-outside-1_439_173)"/>
<path d="M267.944 57.862H261.869L273.128 23.194H293.702L277.178 69.769H256.199L242.834 33.157H249.314L235.949 69.769H214.97L198.446 23.194H219.02L230.279 57.862H224.204L236.435 23.194H255.713L267.944 57.862Z" stroke="black" stroke-width="2" mask="url(#path-1-outside-1_439_173)"/>
<path d="M313.838 84.592C309.95 84.592 306.521 84.106 303.551 83.134C300.635 82.216 297.719 80.677 294.803 78.517V64.747C297.665 66.583 300.311 67.906 302.741 68.716C305.225 69.472 307.898 69.85 310.76 69.85C313.028 69.85 314.999 69.391 316.673 68.473C318.401 67.501 319.805 65.665 320.885 62.965L336.68 23.194H358.55L337.328 69.121C335.546 73.009 333.359 76.087 330.767 78.355C328.229 80.623 325.502 82.216 322.586 83.134C319.67 84.106 316.754 84.592 313.838 84.592ZM311.246 63.532L292.535 23.194H315.215L330.848 63.532H311.246Z" stroke="black" stroke-width="2" mask="url(#path-1-outside-1_439_173)"/>
<path d="M379.954 71.227C373.798 71.227 368.398 70.201 363.754 68.149C359.164 66.097 355.573 63.208 352.981 59.482C350.443 55.756 349.174 51.436 349.174 46.522C349.174 41.554 350.443 37.207 352.981 33.481C355.573 29.755 359.164 26.866 363.754 24.814C368.398 22.762 373.798 21.736 379.954 21.736C386.11 21.736 391.483 22.762 396.073 24.814C400.717 26.866 404.308 29.755 406.846 33.481C409.438 37.207 410.734 41.554 410.734 46.522C410.734 51.436 409.438 55.756 406.846 59.482C404.308 63.208 400.717 66.097 396.073 68.149C391.483 70.201 386.11 71.227 379.954 71.227ZM379.954 56.242C382.114 56.242 383.923 55.864 385.381 55.108C386.893 54.352 388.027 53.245 388.783 51.787C389.593 50.329 389.998 48.574 389.998 46.522C389.998 44.416 389.593 42.634 388.783 41.176C388.027 39.718 386.893 38.611 385.381 37.855C383.923 37.099 382.114 36.721 379.954 36.721C377.848 36.721 376.039 37.099 374.527 37.855C373.015 38.611 371.854 39.718 371.044 41.176C370.288 42.634 369.91 44.416 369.91 46.522C369.91 48.574 370.288 50.329 371.044 51.787C371.854 53.245 373.015 54.352 374.527 55.108C376.039 55.864 377.848 56.242 379.954 56.242Z" stroke="black" stroke-width="2" mask="url(#path-1-outside-1_439_173)"/>
<path d="M428.843 71.146C424.469 71.146 420.716 70.255 417.584 68.473C414.452 66.637 412.049 64.099 410.375 60.859C408.755 57.565 407.945 53.758 407.945 49.438V23.194H428.519V46.522C428.519 49.06 429.167 51.031 430.463 52.435C431.813 53.839 433.703 54.541 436.133 54.541C438.023 54.541 439.562 54.217 440.75 53.569C441.992 52.867 442.91 51.841 443.504 50.491C444.152 49.087 444.476 47.386 444.476 45.388L450.551 48.466C449.849 53.488 448.418 57.7 446.258 61.102C444.152 64.45 441.587 66.961 438.563 68.635C435.593 70.309 432.353 71.146 428.843 71.146ZM447.716 69.769L444.476 49.924V23.194H465.05V53.083L468.29 69.769H447.716Z" stroke="black" stroke-width="2" mask="url(#path-1-outside-1_439_173)"/>
<defs>
<linearGradient id="paint0_linear_439_173" x1="234.645" y1="1" x2="234.645" y2="84.592" gradientUnits="userSpaceOnUse">
<stop stop-color="#E9F7A7"/>
<stop offset="0.485632" stop-color="#E9BED9"/>
<stop offset="1" stop-color="#BBA6EB"/>
</linearGradient>
<linearGradient id="paint1_linear_439_173" x1="234.645" y1="1" x2="234.645" y2="84.592" gradientUnits="userSpaceOnUse">
<stop stop-color="#E9F7A7"/>
<stop offset="0.485632" stop-color="#E9BED9"/>
<stop offset="1" stop-color="#BBA6EB"/>
</linearGradient>
<linearGradient id="paint2_linear_439_173" x1="234.645" y1="1" x2="234.645" y2="84.592" gradientUnits="userSpaceOnUse">
<stop stop-color="#E9F7A7"/>
<stop offset="0.485632" stop-color="#E9BED9"/>
<stop offset="1" stop-color="#BBA6EB"/>
</linearGradient>
<linearGradient id="paint3_linear_439_173" x1="234.645" y1="1" x2="234.645" y2="84.592" gradientUnits="userSpaceOnUse">
<stop stop-color="#E9F7A7"/>
<stop offset="0.485632" stop-color="#E9BED9"/>
<stop offset="1" stop-color="#BBA6EB"/>
</linearGradient>
<linearGradient id="paint4_linear_439_173" x1="234.645" y1="1" x2="234.645" y2="84.592" gradientUnits="userSpaceOnUse">
<stop stop-color="#E9F7A7"/>
<stop offset="0.485632" stop-color="#E9BED9"/>
<stop offset="1" stop-color="#BBA6EB"/>
</linearGradient>
<linearGradient id="paint5_linear_439_173" x1="234.645" y1="1" x2="234.645" y2="84.592" gradientUnits="userSpaceOnUse">
<stop stop-color="#E9F7A7"/>
<stop offset="0.485632" stop-color="#E9BED9"/>
<stop offset="1" stop-color="#BBA6EB"/>
</linearGradient>
<linearGradient id="paint6_linear_439_173" x1="234.645" y1="1" x2="234.645" y2="84.592" gradientUnits="userSpaceOnUse">
<stop stop-color="#E9F7A7"/>
<stop offset="0.485632" stop-color="#E9BED9"/>
<stop offset="1" stop-color="#BBA6EB"/>
</linearGradient>
<linearGradient id="paint7_linear_439_173" x1="234.645" y1="1" x2="234.645" y2="84.592" gradientUnits="userSpaceOnUse">
<stop stop-color="#E9F7A7"/>
<stop offset="0.485632" stop-color="#E9BED9"/>
<stop offset="1" stop-color="#BBA6EB"/>
</linearGradient>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 17 KiB

6
front/public/logo.svg Normal file
View File

@@ -0,0 +1,6 @@
<svg width="125" height="72" viewBox="0 0 125 72" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M91.9198 6.48956C72.1867 -1.25905 41.5148 6.71462 33.639 28.7668C33.639 28.7668 68.9655 28.5313 71.8928 30.1169C71.8928 30.1169 75.4549 21.5867 79.3185 16.8406C83.1638 12.1171 91.9198 6.48956 91.9198 6.48956Z" fill="#FCD770"/>
<path d="M89.4445 42.9432L119.823 64.9954C123.873 37.3176 116.386 16.0969 91.9198 6.48956C91.9198 6.48956 83.1638 12.1171 79.3185 16.8406C75.4549 21.5867 71.8928 30.1169 71.8928 30.1169C77.2933 33.0422 89.4445 42.9432 89.4445 42.9432Z" fill="#FCD770"/>
<path d="M6.86133 42.9432H89.4445C89.4445 42.9432 77.2933 33.0422 71.8928 30.1169C68.9655 28.5313 33.639 28.7668 33.639 28.7668C33.639 28.7668 21.3587 28.3608 15.4122 33.0422C11.6556 35.9996 6.86133 42.9432 6.86133 42.9432Z" fill="#FFEAA7"/>
<path d="M89.4445 42.9432H6.86133C6.86133 42.9432 11.6556 35.9996 15.4122 33.0422C21.3587 28.3608 33.639 28.7668 33.639 28.7668M89.4445 42.9432L119.823 64.9954C123.873 37.3176 116.386 16.0969 91.9198 6.48956M89.4445 42.9432C89.4445 42.9432 77.2933 33.0422 71.8928 30.1169M91.9198 6.48956C72.1867 -1.25905 41.5148 6.71462 33.639 28.7668M91.9198 6.48956C91.9198 6.48956 83.1638 12.1171 79.3185 16.8406C75.4549 21.5867 71.8928 30.1169 71.8928 30.1169M33.639 28.7668C33.639 28.7668 68.9655 28.5313 71.8928 30.1169" stroke="#030303" stroke-width="7.20072"/>
</svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

BIN
front/public/op.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 60 KiB

View File

@@ -0,0 +1,5 @@
Contact: mailto:anotherhadi.clapped234[at]passmail.net
Expires: 2028-12-31T23:00:00.000Z
Encryption: /anotherhadi.asc
Preferred-Languages: en, fr
Canonical: /.well-known/security.txt

View File

@@ -0,0 +1,83 @@
<script lang="ts">
interface Sheet {
id: string;
title: string;
description?: string;
tags?: string[];
}
let { sheets }: { sheets: Sheet[] } = $props();
let search = $state("");
let activeTag: string | null = $state(
typeof window !== "undefined"
? new URLSearchParams(window.location.search).get("tag")
: null
);
const allTags = [...new Set(sheets.flatMap((s) => s.tags ?? []))].sort();
const filtered = $derived(
sheets.filter((s) => {
const q = search.toLowerCase();
const matchSearch =
!q ||
s.title.toLowerCase().includes(q) ||
(s.description?.toLowerCase().includes(q) ?? false);
const matchTag = !activeTag || (s.tags?.includes(activeTag) ?? false);
return matchSearch && matchTag;
})
);
</script>
<div class="flex flex-col gap-4">
<input
type="search"
placeholder="Search cheatsheets..."
bind:value={search}
class="input input-bordered w-full"
/>
{#if allTags.length > 0}
<div class="flex flex-wrap gap-2">
{#each allTags as tag}
<button
class="badge badge-md cursor-pointer transition-colors {activeTag === tag
? 'badge-primary'
: 'badge-ghost hover:badge-outline'}"
onclick={() => (activeTag = activeTag === tag ? null : tag)}
>
{tag}
</button>
{/each}
</div>
{/if}
<div class="flex flex-col gap-3">
{#if filtered.length === 0}
<p class="text-base-content/40 text-sm py-6 text-center">No results.</p>
{:else}
{#each filtered as sheet}
<a
href={`/cheatsheets/${sheet.id}`}
class="card bg-base-200 hover:bg-base-300 transition-colors p-4 flex flex-row items-center gap-4"
>
<div class="size-2 rounded-full bg-primary shrink-0"></div>
<div class="flex-1 min-w-0">
<div class="font-semibold text-sm">{sheet.title}</div>
{#if sheet.description}
<div class="text-base-content/50 text-xs mt-0.5">{sheet.description}</div>
{/if}
</div>
{#if sheet.tags && sheet.tags.length > 0}
<div class="flex gap-1 shrink-0">
{#each sheet.tags as tag}
<span class="badge badge-xs badge-ghost">{tag}</span>
{/each}
</div>
{/if}
</a>
{/each}
{/if}
</div>
</div>

View File

@@ -0,0 +1,24 @@
<script>
import { FlaskConical } from "@lucide/svelte";
let demo = $state(false);
async function checkDemo() {
try {
const res = await fetch("/api/config");
if (res.ok) {
const data = await res.json();
demo = data.demo === true;
}
} catch (_) {}
}
checkDemo();
</script>
{#if demo}
<div class="w-full bg-warning/15 border-b border-warning/30 py-1.5 px-4 flex items-center justify-center gap-2 text-xs text-warning">
<FlaskConical size={13} class="shrink-0" />
<span>Demo mode — searches and configuration changes are disabled</span>
</div>
{/if}

View File

@@ -0,0 +1,98 @@
<script>
import { RotateCw, AlertTriangle } from "@lucide/svelte";
import SearchBar from "./SearchBar.svelte";
import SearchList from "./SearchList.svelte";
import { onMount } from "svelte";
let searches = $state([]);
let loadError = $state("");
let redirecting = $state(false);
let redirectTarget = $state("");
let demo = $state(false);
onMount(async () => {
loadSearches();
fetch("/api/config")
.then((r) => r.ok ? r.json() : null)
.then((d) => { if (d) demo = d.demo === true; })
.catch(() => {});
const params = new URLSearchParams(window.location.search);
const target = params.get("target");
const type = params.get("type");
if (target && type) {
// Clean URL before launching so a refresh doesn't re-trigger
window.history.replaceState({}, "", window.location.pathname);
await handleSearch(target, type, params.get("profile") || "default");
}
});
async function loadSearches() {
try {
const res = await fetch("/api/searches");
if (!res.ok) throw new Error(`HTTP ${res.status}`);
const data = await res.json();
searches = (data ?? []).sort(
(a, b) => new Date(b.started_at) - new Date(a.started_at)
);
} catch (e) {
loadError = e.message;
}
}
async function handleSearch(target, inputType, profile) {
redirectTarget = target;
redirecting = true;
try {
const res = await fetch("/api/searches", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ target, input_type: inputType, profile: profile || undefined }),
});
if (!res.ok) {
const body = await res.json().catch(() => ({}));
throw new Error(body.error || `HTTP ${res.status}`);
}
const s = await res.json();
window.location.href = `/search/${s.id}`;
} catch (e) {
redirecting = false;
throw e;
}
}
async function handleDelete(id) {
await fetch(`/api/searches/${id}`, { method: "DELETE" });
searches = searches.filter((s) => s.id !== id);
}
</script>
<div class="flex flex-col gap-8">
<div class="card bg-base-200 shadow p-6">
{#if redirecting}
<div class="flex flex-col items-center justify-center gap-3 py-4">
<span class="loading loading-dots loading-md text-primary"></span>
<p class="text-sm text-base-content/60">
Searching <span class="font-mono text-base-content/90">{redirectTarget}</span>...
</p>
</div>
{:else}
<SearchBar onSearch={handleSearch} {demo} />
{/if}
</div>
<div class="flex flex-col gap-3">
<div class="flex items-center justify-between">
<h2 class="text-xs uppercase tracking-widest text-base-content/50">Recent searches</h2>
<button class="btn btn-ghost btn-xs" onclick={loadSearches}><RotateCw class="size-3" /> refresh</button>
</div>
{#if loadError}
<div class="alert alert-error text-sm gap-2"><AlertTriangle size={15} class="shrink-0" />{loadError}</div>
{:else}
<SearchList {searches} onDelete={handleDelete} />
{/if}
</div>
</div>

View File

@@ -0,0 +1,136 @@
<script lang="ts">
import {
Menu,
Search,
Hammer,
SlidersHorizontal,
GitBranch,
User,
BookOpen,
Bug,
ClipboardList,
} from "@lucide/svelte";
import type { Snippet } from "svelte";
let {
action,
}: {
title?: string;
action?: Snippet;
} = $props();
const navLinks = [
{ label: "Search", href: "/", icon: Search },
{ label: "Tools", href: "/tools", icon: Hammer },
{ label: "Profiles", href: "/profiles", icon: SlidersHorizontal },
{ label: "Cheatsheets", href: "/cheatsheets", icon: ClipboardList },
{
label: "More",
children: [
{ label: "How it works", href: "/help", icon: BookOpen },
{
label: "Source code",
href: "https://github.com/anotherhadi/iknowyou",
icon: GitBranch,
},
{
label: "Report a Bug",
href: "https://github.com/anotherhadi/iknowyou/issues",
icon: Bug,
},
{ label: "About me", href: "https://hadi.icu", icon: User },
],
},
];
</script>
<div class="bg-base-200">
<div class="navbar max-w-5xl m-auto">
<div class="navbar-start">
<div class="dropdown">
<div tabindex="0" role="button" class="btn btn-ghost lg:hidden">
<Menu size={20} />
</div>
<ul
tabindex="-1"
class="menu menu-sm dropdown-content bg-base-300 rounded-box z-50 mt-3 w-52 p-2"
>
{#each navLinks as link}
<li>
{#if link.children}
<span>{link.label}</span>
<ul class="p-2">
{#each link.children as sublink}
<li>
<a href={sublink.href} class="flex items-center gap-2">
{#if sublink.icon}
{@const Icon = sublink.icon}
<Icon size={12} />
{/if}
{sublink.label}
</a>
</li>
{/each}
</ul>
{:else}
<a href={link.href} class="flex items-center gap-2">
{#if link.icon}
{@const Icon = link.icon}
<Icon size={12} />
{/if}
{link.label}
</a>
{/if}
</li>
{/each}
</ul>
</div>
<a
href="/"
class="btn btn-ghost text-xl flex justify-center gap-2 items-center"
>
<img src="/logo.svg" class="m-auto h-6" alt="iky logo" />
<img src="/logo-large.svg" class="m-auto h-6" alt="iky logo large" />
</a>
</div>
<div class="navbar-center hidden lg:flex">
<ul class="menu menu-horizontal px-1">
{#each navLinks as link}
<li>
{#if link.children}
<details>
<summary>{link.label}</summary>
<ul class="p-2 bg-base-300 w-52 z-50">
{#each link.children as sublink}
<li>
<a href={sublink.href} class="flex items-center gap-2">
{#if sublink.icon}
{@const Icon = sublink.icon}
<Icon size={12} />
{/if}
{sublink.label}
</a>
</li>
{/each}
</ul>
</details>
{:else}
<a href={link.href} class="flex items-center gap-2">
{#if link.icon}
{@const Icon = link.icon}
<Icon size={14} />
{/if}
{link.label}
</a>
{/if}
</li>
{/each}
</ul>
</div>
<div class="navbar-end">
{@render action?.()}
</div>
</div>
</div>

View File

@@ -0,0 +1,571 @@
<script>
import { onMount } from "svelte";
import { Plus, Trash2, Save, ChevronRight, X, Lock, AlertTriangle } from "@lucide/svelte";
import Select from "./comps/Select.svelte";
import Badge from "./comps/Badge.svelte";
import InfoTip from "./comps/InfoTip.svelte";
let tools = $state([]);
let profiles = $state([]);
let loading = $state(true);
let error = $state("");
let configReadonly = $state(false);
let selectedProfile = $state(null);
let profileDetail = $state(null);
let profileLoading = $state(false);
let notesEdit = $state("");
let enabledEdit = $state([]);
let disabledEdit = $state([]);
let rulesSaving = $state(false);
let rulesMsg = $state(null);
let overrideEdits = $state({});
let overrideSaving = $state({});
let overrideMsg = $state({});
let showNewProfile = $state(false);
let newName = $state("");
let newProfileSaving = $state(false);
let newProfileError = $state("");
let overrideToolNames = $derived(Object.keys(profileDetail?.tools ?? {}));
let configurableTools = $derived(tools.filter((t) => t.config_fields?.length > 0));
let availableForOverride = $derived(configurableTools.filter((t) => !overrideToolNames.includes(t.name)));
let allToolNames = $derived(tools.map((t) => t.name));
let isReadonly = $derived((profileDetail?.readonly ?? false) || configReadonly);
onMount(loadAll);
async function loadAll() {
loading = true;
error = "";
try {
const [tr, pr, cr] = await Promise.all([
fetch("/api/tools"),
fetch("/api/config/profiles"),
fetch("/api/config"),
]);
if (!tr.ok) throw new Error(`HTTP ${tr.status}`);
if (!pr.ok) throw new Error(`HTTP ${pr.status}`);
tools = await tr.json();
profiles = await pr.json();
if (cr.ok) {
const cfg = await cr.json();
configReadonly = cfg.readonly ?? false;
}
if (!selectedProfile && profiles.length > 0) {
const def = profiles.find((p) => p.name === "default");
await selectProfile(def ? "default" : profiles[0].name);
}
} catch (e) {
error = e.message;
} finally {
loading = false;
}
}
async function selectProfile(name) {
selectedProfile = name;
profileLoading = true;
profileDetail = null;
overrideEdits = {};
overrideSaving = {};
overrideMsg = {};
rulesMsg = null;
try {
const res = await fetch(`/api/config/profiles/${encodeURIComponent(name)}`);
if (!res.ok) throw new Error(`HTTP ${res.status}`);
profileDetail = await res.json();
notesEdit = profileDetail.notes ?? "";
enabledEdit = [...(profileDetail.enabled ?? [])];
disabledEdit = [...(profileDetail.disabled ?? [])];
const nextEdits = {};
for (const [toolName, toolConf] of Object.entries(profileDetail.tools ?? {})) {
const tool = tools.find((t) => t.name === toolName);
if (!tool?.config_fields?.length) continue;
nextEdits[toolName] = {};
for (const f of tool.config_fields) {
nextEdits[toolName][f.name] =
toolConf?.[f.name] !== undefined ? toolConf[f.name] : (f.default ?? "");
}
}
overrideEdits = nextEdits;
} catch (e) {
error = e.message;
} finally {
profileLoading = false;
}
}
function validateNewName(name) {
if (!name) return "Name is required";
if (!/^[a-z0-9-]+$/.test(name)) return "Only lowercase letters (a-z), digits (0-9), and hyphens (-) are allowed";
return "";
}
async function createProfile() {
const name = newName.trim();
const nameError = validateNewName(name);
if (nameError) { newProfileError = nameError; return; }
newProfileSaving = true;
newProfileError = "";
try {
const res = await fetch("/api/config/profiles", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ name }),
});
if (!res.ok)
throw new Error((await res.json().catch(() => ({}))).error || `HTTP ${res.status}`);
showNewProfile = false;
newName = "";
await loadAll();
await selectProfile(name);
} catch (e) {
newProfileError = e.message;
} finally {
newProfileSaving = false;
}
}
async function deleteProfile(name) {
if (!confirm(`Delete profile "${name}"?`)) return;
try {
await fetch(`/api/config/profiles/${encodeURIComponent(name)}`, { method: "DELETE" });
if (selectedProfile === name) {
selectedProfile = null;
profileDetail = null;
}
await loadAll();
} catch (e) {
error = e.message;
}
}
async function saveRules() {
rulesSaving = true;
rulesMsg = null;
try {
const res = await fetch(`/api/config/profiles/${encodeURIComponent(selectedProfile)}`, {
method: "PATCH",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ enabled: enabledEdit, disabled: disabledEdit, notes: notesEdit }),
});
if (!res.ok)
throw new Error((await res.json().catch(() => ({}))).error || `HTTP ${res.status}`);
rulesMsg = { ok: true, text: "Saved" };
setTimeout(() => (rulesMsg = null), 3000);
await selectProfile(selectedProfile);
} catch (e) {
rulesMsg = { ok: false, text: e.message };
} finally {
rulesSaving = false;
}
}
async function saveOverride(toolName) {
const tool = tools.find((t) => t.name === toolName);
for (const f of tool?.config_fields ?? []) {
if (f.required) {
const v = overrideEdits[toolName]?.[f.name];
if (v === undefined || v === null || v === "") {
flashOverride(toolName, { ok: false, text: `"${f.name}" is required` });
return;
}
}
}
overrideSaving = { ...overrideSaving, [toolName]: true };
overrideMsg = { ...overrideMsg, [toolName]: null };
try {
const res = await fetch(
`/api/config/profiles/${encodeURIComponent(selectedProfile)}/tools/${encodeURIComponent(toolName)}`,
{
method: "PATCH",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(overrideEdits[toolName]),
}
);
if (!res.ok)
throw new Error((await res.json().catch(() => ({}))).error || `HTTP ${res.status}`);
flashOverride(toolName, { ok: true, text: "Saved" });
} catch (e) {
flashOverride(toolName, { ok: false, text: e.message });
} finally {
overrideSaving = { ...overrideSaving, [toolName]: false };
}
}
async function deleteOverride(toolName) {
if (!confirm(`Remove "${toolName}" override from "${selectedProfile}"?`)) return;
try {
await fetch(
`/api/config/profiles/${encodeURIComponent(selectedProfile)}/tools/${encodeURIComponent(toolName)}`,
{ method: "DELETE" }
);
await selectProfile(selectedProfile);
} catch (e) {
error = e.message;
}
}
function addOverrideFor(toolName) {
if (!toolName) return;
const tool = tools.find((t) => t.name === toolName);
if (!tool) return;
const toolEdits = {};
for (const f of tool.config_fields ?? []) toolEdits[f.name] = f.default ?? "";
overrideEdits = { ...overrideEdits, [toolName]: toolEdits };
profileDetail = {
...profileDetail,
tools: { ...(profileDetail.tools ?? {}), [toolName]: {} },
};
}
function flashOverride(toolName, val) {
overrideMsg = { ...overrideMsg, [toolName]: val };
setTimeout(() => {
overrideMsg = { ...overrideMsg, [toolName]: null };
}, 3000);
}
</script>
{#if loading}
<div class="flex justify-center py-16">
<span class="loading loading-spinner loading-lg"></span>
</div>
{:else if error}
<div class="alert alert-error gap-3"><AlertTriangle size={18} class="shrink-0" />{error}</div>
{:else}
<div class="flex flex-col md:flex-row gap-0 items-start">
<div class="w-full md:w-52 shrink-0 flex flex-col gap-1 border-b border-base-300 pb-4 mb-4 md:border-b-0 md:border-r md:pb-0 md:mb-0 md:pr-4 md:mr-4">
<div class="flex items-center justify-between mb-2">
<span class="text-xs uppercase tracking-widest text-base-content/50">Profiles</span>
{#if !configReadonly}
<button
class="btn btn-ghost btn-xs"
onclick={() => { showNewProfile = !showNewProfile; newName = ""; newProfileError = ""; }}
>
<Plus size={14} />
</button>
{/if}
</div>
{#if showNewProfile && !configReadonly}
<div class="flex flex-col gap-2 p-3 bg-base-300 rounded-box mb-1">
<input
type="text"
class="input input-bordered input-sm w-full {newProfileError && !/^[a-z0-9-]*$/.test(newName) ? 'input-error' : ''}"
placeholder="profile-name"
bind:value={newName}
onkeydown={(e) => e.key === "Enter" && createProfile()}
/>
{#if newProfileError}
<p class="text-xs text-error">{newProfileError}</p>
{/if}
<button
class="btn btn-primary btn-xs w-full"
onclick={createProfile}
disabled={newProfileSaving || !newName.trim()}
>
{#if newProfileSaving}
<span class="loading loading-spinner loading-xs"></span>
{:else}
Create
{/if}
</button>
</div>
{/if}
{#each profiles as p}
<div class="flex items-center gap-1 group">
<button
class="flex-1 btn btn-sm {selectedProfile === p.name ? 'btn-primary' : 'btn-ghost'} justify-start gap-1 truncate"
onclick={() => selectProfile(p.name)}
>
{#if selectedProfile === p.name}
<ChevronRight size={14} class="shrink-0" />
{/if}
{#if p.readonly}
<Lock size={10} class="shrink-0 opacity-50" />
{/if}
{p.name}
</button>
{#if !p.readonly}
<button
class="btn btn-ghost btn-xs text-error opacity-0 group-hover:opacity-100 transition-opacity shrink-0"
onclick={() => deleteProfile(p.name)}
>
<Trash2 size={12} />
</button>
{/if}
</div>
{/each}
{#if profiles.length === 0}
<p class="text-base-content/40 text-xs text-center py-4">No profiles yet.</p>
{/if}
</div>
<div class="flex-1 min-w-0 pt-4 md:pt-0">
{#if configReadonly}
<div class="alert alert-warning mb-4 py-2 px-3 text-sm gap-2">
<Lock size={14} class="shrink-0" />
Config is managed externally and is read-only.
</div>
{/if}
{#if !selectedProfile}
<p class="text-base-content/40 text-sm text-center py-8">Select a profile to view it.</p>
{:else if profileLoading}
<div class="flex justify-center py-12">
<span class="loading loading-spinner loading-md"></span>
</div>
{:else if profileDetail}
<div class="flex flex-col gap-4">
<div class="flex items-center gap-3 flex-wrap">
<h2 class="font-bold text-lg flex items-center gap-2">
{#if isReadonly}<Lock size={14} class="text-base-content/40" />{/if}
{selectedProfile}
</h2>
{#if isReadonly}
<Badge text="read-only" size="sm" />
{/if}
{#if profileDetail.active_tools?.length > 0}
<span class="text-xs text-base-content/50">
{profileDetail.active_tools.length} active tool{profileDetail.active_tools.length !== 1 ? "s" : ""}
</span>
{/if}
</div>
{#if isReadonly}
{#if profileDetail.notes}
<p class="text-sm text-base-content/60 italic">{profileDetail.notes}</p>
{/if}
{:else}
<div class="flex flex-col gap-1">
<span class="text-xs uppercase tracking-widest text-base-content/50">Notes</span>
<textarea
class="textarea textarea-bordered text-sm resize-none"
placeholder="Add a description for this profile..."
rows="2"
bind:value={notesEdit}
></textarea>
</div>
{/if}
<div class="card bg-base-200 shadow">
<div class="card-body gap-4 p-4">
<div class="flex items-center justify-between">
<h3 class="text-xs uppercase tracking-widest text-base-content/50">Rules</h3>
{#if !isReadonly && rulesMsg}
<span class="text-xs {rulesMsg.ok ? 'text-success' : 'text-error'}">{rulesMsg.text}</span>
{/if}
</div>
<div class="flex flex-col gap-2">
<span class="text-sm font-semibold">
Enabled
<InfoTip tooltip="Tools in the enabled list will be allowed for this profile. If the enabled list is empty, all tools will be enabled." />
</span>
<div class="flex flex-wrap gap-1 items-center min-h-8">
{#if isReadonly}
{#each (profileDetail.enabled ?? []) as toolName}
<span class="badge badge-outline gap-1">{toolName}</span>
{/each}
{#if (profileDetail.enabled ?? []).length === 0}
<span class="text-xs text-base-content/40">All tools</span>
{/if}
{:else}
{#each enabledEdit as toolName}
<span class="badge badge-outline gap-1">
{toolName}
<button onclick={() => (enabledEdit = enabledEdit.filter((x) => x !== toolName))}>
<X size={10} />
</button>
</span>
{/each}
<Select
options={allToolNames.filter((n) => !enabledEdit.includes(n))}
placeholder="add tool"
size="xs"
onselect={(val) => (enabledEdit = [...enabledEdit, val])}
/>
{/if}
</div>
</div>
<div class="flex flex-col gap-2">
<span class="text-sm font-semibold">
Disabled
<InfoTip tooltip="Tools in the disabled list will be blocked for this profile. Applied after enabled rules, so if a tool is in both lists, it will be disabled." />
</span>
<div class="flex flex-wrap gap-1 items-center min-h-8">
{#if isReadonly}
{#each (profileDetail.disabled ?? []) as toolName}
<span class="badge badge-error gap-1">{toolName}</span>
{/each}
{#if (profileDetail.disabled ?? []).length === 0}
<span class="text-xs text-base-content/40">None</span>
{/if}
{:else}
{#each disabledEdit as toolName}
<span class="badge badge-error gap-1">
{toolName}
<button onclick={() => (disabledEdit = disabledEdit.filter((x) => x !== toolName))}>
<X size={10} />
</button>
</span>
{/each}
<Select
options={allToolNames.filter((n) => !disabledEdit.includes(n))}
placeholder="add tool"
size="xs"
onselect={(val) => (disabledEdit = [...disabledEdit, val])}
/>
{/if}
</div>
</div>
{#if !isReadonly}
<button
class="btn btn-primary btn-sm gap-1 self-start"
onclick={saveRules}
disabled={rulesSaving}
>
{#if rulesSaving}
<span class="loading loading-spinner loading-xs"></span>
{:else}
<Save size={14} />
{/if}
Save
</button>
{/if}
</div>
</div>
{#if !isReadonly}
<div class="card bg-base-200 shadow">
<div class="card-body gap-4 p-4">
<div class="flex items-center justify-between gap-2">
<h3 class="text-xs uppercase tracking-widest text-base-content/50">Tool overrides</h3>
{#if availableForOverride.length > 0}
<Select
options={availableForOverride.map((t) => t.name)}
placeholder="add override"
size="xs"
onselect={(val) => addOverrideFor(val)}
/>
{/if}
</div>
{#if overrideToolNames.length === 0}
<p class="text-sm text-base-content/40">No overrides configured.</p>
{:else}
<div class="flex flex-col gap-3">
{#each overrideToolNames as toolName}
{@const tool = tools.find((t) => t.name === toolName)}
<div class="border border-base-300 rounded-box p-3 flex flex-col gap-3">
<div class="flex items-center justify-between gap-2">
<span class="font-semibold text-sm">{toolName}</span>
<div class="flex items-center gap-2">
{#if overrideMsg[toolName]}
<span class="text-xs {overrideMsg[toolName].ok ? 'text-success' : 'text-error'}">
{overrideMsg[toolName].text}
</span>
{/if}
<button
class="btn btn-ghost btn-xs text-error"
onclick={() => deleteOverride(toolName)}
>
<Trash2 size={12} />
</button>
</div>
</div>
{#if tool?.config_fields?.length && overrideEdits[toolName]}
<div class="grid grid-cols-1 sm:grid-cols-2 gap-x-6 gap-y-3">
{#each tool.config_fields as field}
<div class="flex flex-col gap-1">
<div class="flex items-center gap-2">
<span class="font-mono text-xs font-semibold">{field.name}</span>
<span class="badge badge-ghost badge-xs">{field.type}</span>
{#if field.required}
<span class="badge badge-error badge-xs">required</span>
{/if}
</div>
{#if field.type === "bool"}
<label class="flex items-center gap-2 cursor-pointer mt-1">
<input
type="checkbox"
class="toggle toggle-sm toggle-primary"
bind:checked={overrideEdits[toolName][field.name]}
/>
<span class="text-xs text-base-content/50">
{overrideEdits[toolName][field.name] ? "enabled" : "disabled"}
</span>
</label>
{:else if field.type === "int"}
<input
type="number"
step="1"
class="input input-bordered input-sm font-mono"
bind:value={overrideEdits[toolName][field.name]}
/>
{:else if field.type === "float"}
<input
type="number"
step="any"
class="input input-bordered input-sm font-mono"
bind:value={overrideEdits[toolName][field.name]}
/>
{:else if field.type === "enum"}
<select
class="select select-bordered select-sm font-mono"
bind:value={overrideEdits[toolName][field.name]}
>
{#each field.options as opt}
<option value={opt}>{opt}</option>
{/each}
</select>
{:else}
<input
type="text"
class="input input-bordered input-sm font-mono"
bind:value={overrideEdits[toolName][field.name]}
/>
{/if}
</div>
{/each}
</div>
<button
class="btn btn-primary btn-sm gap-1 self-start"
onclick={() => saveOverride(toolName)}
disabled={overrideSaving[toolName]}
>
{#if overrideSaving[toolName]}
<span class="loading loading-spinner loading-xs"></span>
{:else}
<Save size={14} />
{/if}
Save
</button>
{:else}
<p class="text-xs text-base-content/40">This tool has no configurable fields.</p>
{/if}
</div>
{/each}
</div>
{/if}
</div>
</div>
{/if}
</div>
{/if}
</div>
</div>
{/if}

View File

@@ -0,0 +1,232 @@
<script>
import { Search, AlertTriangle } from "@lucide/svelte";
import Select from "./comps/Select.svelte";
import { INPUT_TYPES } from "@src/lib/vars";
let { onSearch = async () => {}, demo = false } = $props();
const DETECTORS = {
email: (_raw, v) => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(v),
phone: (_raw, v) => /^\+\d{1,4} \d{4,}$/.test(v),
ip: (_raw, v) => /^(\d{1,3}\.){3}\d{1,3}$/.test(v) || /^[0-9a-fA-F:]{3,39}$/.test(v),
domain: (raw, v) => /^https?:\/\//.test(raw) || /^(?:[a-zA-Z0-9](?:[a-zA-Z0-9\-]{0,61}[a-zA-Z0-9])?\.)+[a-zA-Z]{2,}$/.test(v),
name: (_raw, v) => /^[a-zA--ÿ'-]+(?: [a-zA-ZÀ-ÿ'-]+){1,2}$/.test(v),
};
const VALIDATORS = {
email: { test: (v) => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(v), msg: "Invalid email address" },
username: { test: (v) => /^[a-zA-Z0-9._-]+$/.test(v), msg: "Username may only contain a-z, 0-9, . - _" },
phone: { test: (v) => /^\+\d{1,4} \d{4,}$/.test(v), msg: "Format: +INDICATIF NUMERO (ex: +33 0612345678)" },
ip: { test: (v) => /^(\d{1,3}\.){3}\d{1,3}$/.test(v) || /^[0-9a-fA-F:]{3,39}$/.test(v), msg: "Invalid IP address" },
domain: { test: (v) => /^(?:[a-zA-Z0-9](?:[a-zA-Z0-9\-]{0,61}[a-zA-Z0-9])?\.)+[a-zA-Z]{2,}$/.test(v), msg: "Invalid domain name" },
};
let target = $state("");
let inputType = $state("email");
let profile = $state("default");
let profiles = $state([]);
let loading = $state(false);
let error = $state("");
let validationError = $state("");
// null = auto-switch free; "TYPE" = auto-switched from TYPE (show revert); "__locked__" = user overrode, no auto-switch
let prevType = $state(null);
let showRevert = $derived(prevType !== null && prevType !== "__locked__");
let profileOptions = $derived(profiles.map((p) => p.name));
let strippedTarget = $derived.by(() => {
let v = target.trim();
v = v.replace(/^https?:\/\//, "");
if (v.startsWith("@")) v = v.slice(1);
return v;
});
let detectedType = $derived.by(() => {
const raw = target.trim();
const v = strippedTarget;
if (!v && !raw) return null;
if (raw.startsWith("@")) return "username";
for (const [type, fn] of Object.entries(DETECTORS)) {
if (fn(raw, v)) return type;
}
return null;
});
async function loadProfiles() {
try {
const res = await fetch("/api/config/profiles");
if (res.ok) profiles = await res.json();
} catch (_) {}
}
loadProfiles();
function sanitize(s) {
return s.replace(/[<>"'`&]/g, "").trim();
}
function validate(val, type) {
const v = VALIDATORS[type];
if (!v) return "";
return v.test(val) ? "" : v.msg;
}
function onTargetInput() {
if (!strippedTarget) prevType = null; // reset when field is cleared
if (validationError) validationError = validate(strippedTarget, inputType);
if (prevType === null && detectedType && detectedType !== inputType) {
prevType = inputType;
inputType = detectedType;
}
}
function revert() {
inputType = prevType;
prevType = "__locked__";
validationError = "";
}
function onSelectType(v) {
inputType = v;
prevType = "__locked__";
validationError = "";
}
async function submit() {
if (demo) return;
const clean = sanitize(strippedTarget);
if (!clean) return;
validationError = validate(clean, inputType);
if (validationError) return;
error = "";
loading = true;
try {
await onSearch(clean, inputType, profile);
target = "";
prevType = null;
} catch (e) {
error = e.message;
} finally {
loading = false;
}
}
</script>
<div class="flex flex-col gap-2">
{#if error}
<div class="alert alert-error text-sm py-2 gap-2"><AlertTriangle size={15} class="shrink-0" />{error}</div>
{/if}
{#if showRevert}
<div class="flex items-center gap-2 px-1 text-xs text-base-content/50">
<span>Switched to <span class="text-base-content/70 font-medium">{inputType}</span></span>
<button
class="badge badge-ghost badge-sm hover:badge-primary transition-colors cursor-pointer"
onclick={revert}
>
{prevType}
</button>
</div>
{/if}
<!-- Mobile layout -->
<div class="flex flex-col gap-2 sm:hidden">
<div class="flex items-center rounded-xl border border-base-content/15 bg-base-300
focus-within:border-primary/40 transition-colors">
<input
class="flex-1 bg-transparent px-4 py-3 outline-none text-sm
placeholder:text-base-content/30 min-w-0"
placeholder={demo ? "Search disabled in demo mode" : "Enter target..."}
bind:value={target}
oninput={onTargetInput}
onkeydown={(e) => e.key === "Enter" && submit()}
disabled={demo}
/>
</div>
<div class="flex items-center gap-2">
<div class="flex items-center gap-1">
<span class="text-xs text-base-content/25 pl-1">type</span>
<Select
options={INPUT_TYPES}
selected={inputType}
size="xs"
onselect={onSelectType}
/>
</div>
<div class="flex items-center gap-1">
<span class="text-xs text-base-content/25">profile</span>
<Select
options={profileOptions}
selected={profile}
size="xs"
onselect={(v) => { profile = v; }}
/>
</div>
<button
class="btn btn-primary btn-sm flex-1 gap-1"
onclick={submit}
disabled={demo || loading || !target.trim()}
>
{#if loading}
<span class="loading loading-spinner loading-xs"></span>
{:else}
<Search size={14} />
{/if}
Search
</button>
</div>
</div>
<!-- Desktop layout -->
<div
class="hidden sm:flex items-center rounded-xl border border-base-content/15 bg-base-300
focus-within:border-primary/40 transition-colors"
>
<div class="border-r border-base-content/10 flex items-center gap-1 pl-3">
<span class="text-xs text-base-content/25 shrink-0">type</span>
<Select
options={INPUT_TYPES}
selected={inputType}
onselect={onSelectType}
/>
</div>
<input
class="flex-1 bg-transparent px-4 py-2.5 outline-none text-sm
placeholder:text-base-content/30 min-w-0"
placeholder={demo ? "Search disabled in demo mode" : "Enter target..."}
bind:value={target}
oninput={onTargetInput}
onkeydown={(e) => e.key === "Enter" && submit()}
disabled={demo}
/>
<div class="border-l border-base-content/10 flex items-center gap-1 pr-1 pl-3">
<span class="text-xs text-base-content/25 shrink-0">profile</span>
<Select
options={profileOptions}
selected={profile}
onselect={(v) => { profile = v; }}
/>
</div>
<button
class="btn btn-primary btn-sm m-1 rounded-lg gap-1 shrink-0"
onclick={submit}
disabled={demo || loading || !target.trim()}
>
{#if loading}
<span class="loading loading-spinner loading-xs"></span>
{:else}
<Search size={14} />
{/if}
Search
</button>
</div>
{#if validationError}
<p class="text-xs text-error pl-1">{validationError}</p>
{/if}
</div>

View File

@@ -0,0 +1,349 @@
<script>
import { onDestroy, onMount } from "svelte";
import { RefreshCw, ChevronRight, Check, X, AlertTriangle } from "@lucide/svelte";
import TtyOutput from "@src/components/comps/TtyOutput.svelte";
let { id = null } = $props();
let demo = $state(false);
async function checkDemo() {
try {
const res = await fetch("/api/config");
if (res.ok) {
const data = await res.json();
demo = data.demo === true;
}
} catch (_) {}
}
checkDemo();
let resolvedId = $state("");
let search = $state(null);
let error = $state("");
let pollTimeout = null;
let pollDelay = $state(800);
const POLL_MIN = 800;
const POLL_MAX = 5000;
let grouped = $derived(groupByTool(search?.events ?? []));
let toolProgress = $derived(computeProgress(search?.planned_tools ?? [], grouped));
let totalResults = $derived(
(search?.planned_tools ?? []).reduce((sum, t) => sum + (t.result_count ?? 0), 0)
);
let sortedEntries = $derived((() => {
const entries = Object.entries(grouped);
const hasResults = ([toolName, d]) => {
const count = search?.planned_tools?.find(t => t.name === toolName)?.result_count ?? null;
return d.done && (count !== null ? count > 0 : d.output.length > 0);
};
const withResults = entries.filter(([n, d]) => hasResults([n, d]));
const running = entries.filter(([_, d]) => !d.done);
const noResults = entries.filter(([n, d]) => d.done && !hasResults([n, d]));
return { withResults, running, noResults };
})());
function groupByTool(events) {
const map = {};
for (const e of events) {
if (!map[e.tool]) map[e.tool] = { errors: [], output: "", done: false };
if (e.type === "output") map[e.tool].output += e.payload;
else if (e.type === "error") map[e.tool].errors.push(e.payload);
else if (e.type === "done") map[e.tool].done = true;
}
return map;
}
function computeProgress(planned, grouped) {
const skipped = planned.filter((t) => t.skipped);
const active = planned.filter((t) => !t.skipped);
const errored = active.filter((t) => {
const d = grouped[t.name];
return d?.done && d.errors.length > 0 && d.output.length === 0;
});
const done = active.filter((t) => grouped[t.name]?.done);
const running = active.filter((t) => !grouped[t.name]?.done);
const skippedTotal = skipped.length + errored.length;
return { skipped, active, done, running, errored, skippedTotal };
}
async function refresh() {
try {
const res = await fetch(`/api/searches/${resolvedId}`);
if (!res.ok) throw new Error(`HTTP ${res.status}`);
search = await res.json();
if (search.status !== "running") stopPolling();
} catch (e) {
error = e.message;
stopPolling();
}
}
function scheduleNext() {
pollTimeout = setTimeout(async () => {
await refresh();
if (search?.status === "running") {
pollDelay = Math.min(pollDelay * 2, POLL_MAX);
scheduleNext();
}
}, pollDelay);
}
function startPolling() { stopPolling(); pollDelay = POLL_MIN; scheduleNext(); }
function stopPolling() { if (pollTimeout) { clearTimeout(pollTimeout); pollTimeout = null; } }
onMount(async () => {
resolvedId = id ?? window.location.pathname.replace(/^\/search\//, "").replace(/\/$/, "");
await refresh();
if (search?.status === "running") startPolling();
});
onDestroy(stopPolling);
let toast = $state(null); // { msg, type }
async function cancel() {
stopPolling();
await fetch(`/api/searches/${resolvedId}`, { method: "DELETE" });
toast = { msg: "Search cancelled.", type: "alert-warning" };
setTimeout(() => { window.location.href = "/"; }, 2000);
}
function fmtDate(iso) {
return new Date(iso).toLocaleString(undefined, {
month: "short", day: "numeric",
hour: "2-digit", minute: "2-digit", second: "2-digit",
});
}
const STATUS_BADGE = {
running: "badge-warning",
done: "badge-success",
cancelled: "badge-error",
};
</script>
{#if error}
<div class="alert alert-error gap-3"><AlertTriangle size={18} class="shrink-0" />{error}</div>
{:else if !search}
<div class="flex justify-center py-16">
<span class="loading loading-spinner loading-lg"></span>
</div>
{:else}
<div class="flex flex-wrap items-start justify-between gap-4 mb-6">
<div class="min-w-0">
<div class="flex items-center gap-3 mb-1 flex-wrap">
<h1 class="font-mono text-xl sm:text-2xl font-bold truncate">{search.target}</h1>
<span class="badge {STATUS_BADGE[search.status] ?? 'badge-ghost'} shrink-0">
{#if search.status === "running"}
<span class="loading loading-ring loading-xs mr-1"></span>
{/if}
{search.status}
</span>
</div>
<div class="flex items-center gap-2 flex-wrap text-sm text-base-content/50">
<span>{search.input_type}</span>
{#if search.profile}
<span class="badge badge-outline badge-sm font-semibold">{search.profile}</span>
{/if}
<span>· started {fmtDate(search.started_at)}</span>
{#if search.status !== "running" && totalResults > 0}
<span>· <span class="text-base-content/70 font-medium">{totalResults} result{totalResults !== 1 ? "s" : ""}</span></span>
{/if}
</div>
</div>
<div class="flex gap-2 shrink-0">
<button class="btn btn-sm btn-ghost gap-1" onclick={refresh}>
<RefreshCw size={14} /> Refresh
</button>
{#if search.status === "running"}
<button class="btn btn-sm btn-error btn-outline gap-1" onclick={cancel}>
<X size={14} /> Cancel
</button>
{/if}
</div>
</div>
{#if search.planned_tools?.length > 0}
<div class="card bg-base-200 shadow mb-6">
<div class="card-body p-4 gap-3">
<div class="flex items-center justify-between flex-wrap gap-2">
<h2 class="text-xs uppercase tracking-widest text-base-content/50">Tools</h2>
<div class="flex gap-3 text-sm">
<span class="text-success font-mono">
{toolProgress.done.length}/{toolProgress.active.length} done
</span>
{#if toolProgress.skippedTotal > 0}
<span class="text-warning font-mono">
{toolProgress.skippedTotal} skipped
</span>
{/if}
</div>
</div>
<div class="w-full h-2 bg-base-300 rounded-full overflow-hidden flex relative">
<div class="h-full bg-success transition-all duration-500" style="width:{toolProgress.active.length > 0 ? (toolProgress.done.length - toolProgress.errored.length) / toolProgress.active.length * 100 : 0}%"></div>
<div class="h-full bg-warning/70 transition-all duration-500" style="width:{toolProgress.active.length > 0 ? toolProgress.errored.length / toolProgress.active.length * 100 : 0}%"></div>
{#if search.status === "running"}
<div class="shimmer absolute inset-0 pointer-events-none"></div>
{/if}
</div>
<details class="group">
<summary
class="flex items-center gap-1 cursor-pointer text-xs text-base-content/40
hover:text-base-content/70 transition-colors list-none select-none w-fit"
>
<ChevronRight size={12} class="transition-transform duration-200 group-open:rotate-90" />
Show tools
</summary>
<div class="flex flex-wrap gap-2 pt-3">
{#each search.planned_tools as t}
{@const d = grouped[t.name]}
{@const isErrored = d?.done && d.errors.length > 0 && d.output.length === 0}
{#if t.skipped}
<div class="tooltip" data-tip={t.reason}>
<span class="badge badge-warning badge-sm font">{t.name}</span>
</div>
{:else if isErrored}
<div class="tooltip" data-tip={d.errors[0] ?? "error"}>
<span class="badge badge-warning badge-sm">{t.name}</span>
</div>
{:else if d?.done}
<a href="/tools/{t.name}" class="badge badge-success badge-sm hover:badge-outline transition-all gap-1">
<Check size={10} />{t.name}
</a>
{:else if search.status === "running"}
<a href="/tools/{t.name}" class="badge badge-ghost badge-sm hover:badge-outline transition-all">
<span class="loading loading-ring loading-xs mr-1"></span>{t.name}
</a>
{:else}
<a href="/tools/{t.name}" class="badge badge-ghost badge-sm hover:badge-outline transition-all">
{t.name}
</a>
{/if}
{/each}
</div>
</details>
{#if demo}
<p class="text-xs text-base-content/40 italic">Results shown are not exhaustive — demo mode only displays a subset of what the tools can find.</p>
{/if}
</div>
</div>
{/if}
{#if Object.keys(grouped).length === 0 && search.status === "running"}
<p class="text-base-content/40 text-sm text-center py-8">Waiting for results...</p>
{:else if Object.keys(grouped).length === 0}
<p class="text-base-content/40 text-sm text-center py-8">No results.</p>
{:else}
<div class="flex flex-col gap-4">
{#each sortedEntries.withResults as [toolName, data]}
{@render toolCard(toolName, data)}
{/each}
{#each sortedEntries.running as [toolName, data]}
{@render toolCard(toolName, data)}
{/each}
{#if sortedEntries.noResults.length > 0}
<details class="group">
<summary
class="flex items-center gap-2 cursor-pointer select-none list-none
text-sm text-base-content/40 hover:text-base-content/60 transition-colors"
>
<ChevronRight size={14} class="transition-transform duration-200 group-open:rotate-90" />
No results
<span class="font-mono text-xs">({sortedEntries.noResults.length})</span>
</summary>
<div class="flex flex-col gap-3 pt-3">
{#each sortedEntries.noResults as [toolName, data]}
{@render toolCard(toolName, data)}
{/each}
</div>
</details>
{/if}
</div>
{/if}
{/if}
{#snippet toolCard(toolName, data)}
{@const toolStatus = search.planned_tools?.find((t) => t.name === toolName)}
{@const resultCount = data.output.length > 0 ? (toolStatus?.result_count ?? null) : null}
<div class="card bg-base-200 shadow">
<details class="group" open>
<summary class="card-body gap-3 p-4 cursor-pointer list-none">
<div class="flex items-center gap-3 flex-wrap">
<ChevronRight size={14} class="shrink-0 opacity-40 transition-transform duration-200 group-open:rotate-90" />
<a href="/tools/{toolName}" class="font-bold hover:underline underline-offset-2" onclick={(e) => e.stopPropagation()}>
{toolName}
</a>
{#if !data.done}
<span class="badge badge-warning badge-sm">
<span class="loading loading-ring loading-xs mr-1"></span>running
</span>
{:else if data.errors.length > 0 && data.output.length === 0}
<span class="badge badge-error badge-sm">error</span>
{:else}
<span class="badge badge-success badge-sm">done</span>
{/if}
<span class="text-xs text-base-content/40 ml-auto">
{#if resultCount === null}
output
{:else}
{resultCount} result{resultCount !== 1 ? "s" : ""}
{/if}
{#if data.errors.length > 0}
· <span class="text-error">{data.errors.length} error{data.errors.length !== 1 ? "s" : ""}</span>
{/if}
</span>
</div>
</summary>
<div class="px-4 pb-4 flex flex-col gap-3">
{#each data.errors as err}
<div class="alert alert-error py-2 text-sm gap-2"><AlertTriangle size={14} class="shrink-0" />{err}</div>
{/each}
{#if data.output.length > 0}
<TtyOutput output={data.output} />
{:else if data.done && data.errors.length === 0}
<p class="text-sm text-base-content/40">No results.</p>
{/if}
</div>
</details>
</div>
{/snippet}
{#if toast}
<div class="toast toast-end toast-bottom z-50">
<div class="alert {toast.type} shadow-lg">
<span>{toast.msg}</span>
</div>
</div>
{/if}
<style>
.shimmer {
background: linear-gradient(
90deg,
transparent 0%,
rgba(255, 255, 255, 0.18) 50%,
transparent 100%
);
animation: shimmer 1.4s ease-in-out infinite;
}
@keyframes shimmer {
from { transform: translateX(-100%); }
to { transform: translateX(100%); }
}
</style>

View File

@@ -0,0 +1,71 @@
<script>
import { INPUT_TYPE_ICON } from "@src/lib/vars";
import { FileText, X } from "@lucide/svelte";
let { searches = [], onDelete = async () => {} } = $props();
const STATUS_BADGE = {
running: "badge-warning",
done: "badge-success",
cancelled: "badge-error",
};
function fmtDate(iso) {
return new Date(iso).toLocaleString(undefined, {
month: "short", day: "numeric",
hour: "2-digit", minute: "2-digit",
});
}
</script>
{#if searches.length === 0}
<p class="text-base-content/40 text-sm text-center py-8">No searches yet. Run one above.</p>
{:else}
<div class="flex flex-col gap-2">
{#each searches as s (s.id)}
<a
href={`/search/${s.id}`}
class="card bg-base-200 hover:bg-base-300 transition-colors shadow-sm cursor-pointer"
>
<div class="card-body flex-row items-center gap-4 py-3 px-4">
<div class="text-base-content/40 w-6 flex items-center justify-center shrink-0">
{#each [INPUT_TYPE_ICON[s.input_type] ?? FileText] as Icon}
<Icon size={16} />
{/each}
</div>
<div class="flex flex-col min-w-0 flex-1">
<span class="font-mono font-semibold truncate">{s.target}</span>
<div class="flex items-center gap-1.5 flex-wrap text-xs text-base-content/50">
<span>{s.input_type}</span>
{#if s.profile}
<span class="badge badge-outline badge-xs font-semibold">{s.profile}</span>
{/if}
<span>· {fmtDate(s.started_at)}</span>
</div>
</div>
{#if s.status !== "running"}
{@const total = (s.planned_tools ?? []).reduce((sum, t) => sum + (t.result_count ?? 0), 0)}
{#if total > 0}
<span class="text-xs font-mono text-base-content/50 shrink-0">{total} result{total !== 1 ? "s" : ""}</span>
{/if}
{/if}
<span class="badge {STATUS_BADGE[s.status] ?? 'badge-ghost'} badge-sm shrink-0">
{#if s.status === "running"}
<span class="loading loading-ring loading-xs mr-1"></span>
{/if}
{s.status}
</span>
<button
class="btn btn-ghost btn-xs text-base-content/30 hover:text-error shrink-0"
onclick={(e) => { e.preventDefault(); onDelete(s.id); }}
title="Delete"
><X size={14} /></button>
</div>
</a>
{/each}
</div>
{/if}

View File

@@ -0,0 +1,265 @@
<script>
import { onMount } from "svelte";
import { Save, Trash2, AlertTriangle, Package } from "@lucide/svelte";
import ToolIcon from "./comps/ToolIcon.svelte";
let { name = null } = $props();
let resolvedName = $state("");
let tool = $state(null);
let error = $state("");
let edits = $state({});
let saving = $state(false);
let msg = $state(null);
let hasGlobalConfig = $state(false);
let configReadonly = $state(false);
onMount(async () => {
resolvedName = name ?? window.location.pathname.replace(/^\/tools\//, "").replace(/\/$/, "");
try {
const [toolRes, cfgRes] = await Promise.all([
fetch(`/api/tools/${encodeURIComponent(resolvedName)}`),
fetch("/api/config"),
]);
if (!toolRes.ok) {
const body = await toolRes.json().catch(() => ({}));
throw new Error(body.error || `HTTP ${toolRes.status}`);
}
tool = await toolRes.json();
const cfg = cfgRes.ok ? await cfgRes.json() : { tools: {} };
configReadonly = cfg.readonly ?? false;
const curMap = cfg.tools?.[resolvedName] ?? {};
hasGlobalConfig = !!cfg.tools?.[resolvedName];
const next = {};
for (const f of tool.config_fields ?? []) {
const saved = curMap[f.name];
next[f.name] = saved !== undefined && saved !== null
? saved
: (f.value !== undefined && f.value !== null ? f.value : (f.default ?? defaultForType(f.type)));
}
edits = next;
} catch (e) {
error = e.message;
}
});
function defaultForType(type) {
if (type === "bool") return false;
if (type === "int" || type === "float") return 0;
return "";
}
async function save() {
saving = true;
msg = null;
try {
const res = await fetch(`/api/config/tools/${encodeURIComponent(resolvedName)}`, {
method: "PATCH",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(edits),
});
if (!res.ok)
throw new Error((await res.json().catch(() => ({}))).error || `HTTP ${res.status}`);
hasGlobalConfig = true;
msg = { ok: true, text: "Saved" };
} catch (e) {
msg = { ok: false, text: e.message };
} finally {
saving = false;
setTimeout(() => (msg = null), 3000);
}
}
async function clearConfig() {
if (!confirm(`Clear global config for "${resolvedName}"?`)) return;
try {
await fetch(`/api/config/tools/${encodeURIComponent(resolvedName)}`, { method: "DELETE" });
hasGlobalConfig = false;
const next = {};
for (const f of tool.config_fields ?? []) next[f.name] = f.default ?? defaultForType(f.type);
edits = next;
msg = { ok: true, text: "Cleared" };
} catch (e) {
msg = { ok: false, text: e.message };
} finally {
setTimeout(() => (msg = null), 3000);
}
}
</script>
{#if error}
<div class="alert alert-error gap-3"><AlertTriangle size={18} class="shrink-0" />{error}</div>
{:else if !tool}
<div class="flex justify-center py-16">
<span class="loading loading-spinner loading-lg"></span>
</div>
{:else}
<div class="flex flex-col gap-6 max-w-2xl">
{#if tool.available === false}
<div class="alert alert-error gap-3">
<AlertTriangle size={18} class="shrink-0" />
<div>
<p class="font-semibold text-sm">Tool unavailable</p>
{#if tool.unavailable_reason}
<p class="text-sm opacity-80">{tool.unavailable_reason}</p>
{/if}
</div>
</div>
{/if}
<div class="flex items-start justify-between gap-4 flex-wrap">
<div class="flex items-center gap-3">
<ToolIcon iconName={tool.icon} size={32} />
<div class="pl-4">
<h1 class="text-2xl sm:text-3xl font-bold mb-1">{tool.name}</h1>
{#if tool.description}
<p class="text-base-content/60">{tool.description}</p>
{/if}
</div>
</div>
{#if tool.link}
<a href={tool.link} target="_blank" rel="noopener noreferrer" class="btn btn-ghost btn-sm gap-1">
↗ source
</a>
{/if}
</div>
<div class="card bg-base-200">
<div class="card-body gap-3 p-4">
<h2 class="text-xs uppercase tracking-widest text-base-content/50">Accepted input types</h2>
<div class="flex flex-wrap gap-2">
{#each tool.input_types as t}
<span class="badge badge-outline border-base-content/20">{t}</span>
{/each}
</div>
</div>
</div>
{#if tool.dependencies?.length > 0}
<div class="card bg-base-200">
<div class="card-body gap-3 p-4">
<h2 class="text-xs uppercase tracking-widest text-base-content/50 flex items-center gap-2">
<Package size={13} /> External dependencies
</h2>
<ul class="flex flex-col gap-1">
{#each tool.dependencies as dep}
<li class="flex items-center gap-2">
<span class="font-mono text-sm bg-base-300 px-2 py-0.5 rounded">{dep}</span>
<span class="text-xs text-base-content/40">must be in <code>$PATH</code></span>
</li>
{/each}
</ul>
</div>
</div>
{/if}
{#if tool.config_fields?.length > 0}
<div class="card bg-base-200">
<div class="card-body gap-4 p-4">
<div class="flex items-center justify-between flex-wrap gap-2">
<div class="flex items-center gap-2">
<h2 class="text-xs uppercase tracking-widest text-base-content/50">Global config</h2>
{#if hasGlobalConfig}
<span class="badge badge-outline badge-xs">configured</span>
{/if}
{#if configReadonly}
<span class="badge badge-ghost badge-xs">read-only</span>
{/if}
</div>
{#if msg}
<span class="text-xs {msg.ok ? 'text-success' : 'text-error'}">{msg.text}</span>
{/if}
</div>
{#each tool.config_fields as field}
<div class="flex flex-col gap-1">
<div class="flex items-center gap-2 flex-wrap">
<span class="font-mono font-semibold text-sm">{field.name}</span>
<span class="badge badge-ghost badge-xs">{field.type}</span>
{#if field.required}
<span class="badge badge-error badge-xs">required</span>
{/if}
</div>
{#if field.description}
<p class="text-sm text-base-content/60">{field.description}</p>
{/if}
<div class="text-xs text-base-content/40 font-mono mb-1">
default: {field.default ?? "-"}
</div>
{#if field.type === "bool"}
<label class="flex items-center gap-2 cursor-pointer">
<input
type="checkbox"
class="toggle toggle-sm toggle-primary"
bind:checked={edits[field.name]}
disabled={configReadonly}
/>
<span class="text-sm text-base-content/50">
{edits[field.name] ? "enabled" : "disabled"}
</span>
</label>
{:else if field.type === "int"}
<input
type="number"
step="1"
class="input input-bordered input-sm font-mono w-full max-w-48"
bind:value={edits[field.name]}
/>
{:else if field.type === "float"}
<input
type="number"
step="any"
class="input input-bordered input-sm font-mono w-full max-w-48"
bind:value={edits[field.name]}
/>
{:else if field.type === "enum"}
<select
class="select select-bordered select-sm font-mono w-full max-w-xs"
bind:value={edits[field.name]}
>
{#each field.options as opt}
<option value={opt}>{opt}</option>
{/each}
</select>
{:else}
<input
type="text"
class="input input-bordered input-sm font-mono w-full max-w-xs"
bind:value={edits[field.name]}
/>
{/if}
</div>
{/each}
{#if !configReadonly}
<div class="flex gap-2 pt-1 flex-wrap">
<button
class="btn btn-primary btn-sm gap-1"
onclick={save}
disabled={saving}
>
{#if saving}
<span class="loading loading-spinner loading-xs"></span>
{:else}
<Save size={14} />
{/if}
Save
</button>
{#if hasGlobalConfig}
<button class="btn btn-ghost btn-sm gap-1 text-error" onclick={clearConfig}>
<Trash2 size={14} /> Reset to defaults
</button>
{/if}
</div>
{/if}
</div>
</div>
{/if}
</div>
{/if}

View File

@@ -0,0 +1,346 @@
<script>
import { onMount } from "svelte";
import { AlertTriangle } from "@lucide/svelte";
import Select from "./comps/Select.svelte";
import { INPUT_TYPES } from "@src/lib/vars";
import ToolIcon from "./comps/ToolIcon.svelte";
let tools = $state([]);
let config = $state({ tools: {}, profiles: {} });
let profileSummaries = $state([]);
let selectedProfile = $state("default");
let profileDetail = $state(null);
let loading = $state(true);
let profileLoading = $state(false);
let error = $state("");
let selectedInputType = $state("all");
const inputTypeOptions = ["all", ...INPUT_TYPES];
let profileOptions = $derived(profileSummaries.map((p) => p.name));
let activeSet = $derived(
profileDetail
? new Set(profileDetail.active_tools ?? [])
: new Set(tools.map((t) => t.name)),
);
let globalToolConf = $derived(config.tools ?? {});
let profileOverrides = $derived(profileDetail?.tools ?? {});
let toolsWithStatus = $derived(
tools.map((tool) => {
const isActive = activeSet.has(tool.name);
const effective = {
...(globalToolConf[tool.name] ?? {}),
...(profileOverrides[tool.name] ?? {}),
};
const missingConfig = (tool.config_fields ?? []).some((f) => {
if (!f.required) return false;
const v = effective[f.name];
return v === undefined || v === null || v === "";
});
const unavailable = tool.available === false;
return { ...tool, isActive, missingConfig, unavailable };
}),
);
let visibleTools = $derived(
selectedInputType === "all"
? toolsWithStatus
: toolsWithStatus.filter((t) =>
t.input_types.includes(selectedInputType),
),
);
let active = $derived(
visibleTools.filter(
(t) => t.isActive && !t.missingConfig && !t.unavailable,
),
);
let activeMissing = $derived(
visibleTools.filter((t) => t.isActive && t.missingConfig && !t.unavailable),
);
let activeUnavail = $derived(
visibleTools.filter((t) => t.isActive && t.unavailable),
);
let inactive = $derived(
visibleTools.filter(
(t) => !t.isActive && !t.missingConfig && !t.unavailable,
),
);
let inactiveMissing = $derived(
visibleTools.filter(
(t) => !t.isActive && t.missingConfig && !t.unavailable,
),
);
let inactiveUnavail = $derived(
visibleTools.filter((t) => !t.isActive && t.unavailable),
);
onMount(async () => {
try {
const [tr, cr, pr] = await Promise.all([
fetch("/api/tools"),
fetch("/api/config"),
fetch("/api/config/profiles"),
]);
if (!tr.ok) throw new Error(`HTTP ${tr.status}`);
if (!cr.ok) throw new Error(`HTTP ${cr.status}`);
if (!pr.ok) throw new Error(`HTTP ${pr.status}`);
tools = await tr.json();
config = await cr.json();
profileSummaries = await pr.json();
} catch (e) {
error = e.message;
} finally {
loading = false;
}
});
async function selectProfile(name) {
selectedProfile = name;
profileLoading = true;
try {
const res = await fetch(
`/api/config/profiles/${encodeURIComponent(name)}`,
);
if (!res.ok) throw new Error(`HTTP ${res.status}`);
profileDetail = await res.json();
} catch (e) {
error = e.message;
} finally {
profileLoading = false;
}
}
</script>
{#snippet toolCard(tool, missing)}
<div
class="card bg-base-200 group-hover:bg-base-300 transition-colors shadow-sm h-full
{missing ? 'border border-warning/40' : ''}
{tool.unavailable ? 'border border-error/40' : ''}"
>
<div class="card-body p-4 flex-row items-start gap-0">
<div
class="size-10 rounded-lg bg-base-300 group-hover:bg-base-200 transition-colors
flex items-center justify-center shrink-0 mr-3 mt-0.5"
>
<ToolIcon iconName={tool.icon} size={20} />
</div>
<div class="flex flex-col min-w-0 flex-1 gap-1.5">
<div class="flex items-center gap-2 flex-wrap">
<span class="font-bold text-sm leading-tight">{tool.name}</span>
{#if tool.unavailable}
<span class="badge badge-error badge-xs gap-1">
<AlertTriangle size={9} /> unavailable
</span>
{:else if missing}
<span class="badge badge-warning badge-xs gap-1">
<AlertTriangle size={9} /> config required
</span>
{/if}
</div>
{#if tool.unavailable && tool.unavailable_reason}
<p class="text-xs text-error/70 leading-relaxed">
{tool.unavailable_reason}
</p>
{:else if tool.description}
<p class="text-xs text-base-content/50 line-clamp-2 leading-relaxed">
{tool.description}
</p>
{/if}
<div class="flex flex-wrap gap-1">
{#each tool.input_types as t}
<span class="badge badge-xs badge-outline border-base-content/20">{t}</span>
{/each}
</div>
</div>
</div>
</div>
{/snippet}
{#if loading}
<div class="flex justify-center py-12">
<span class="loading loading-spinner loading-md"></span>
</div>
{:else if error}
<div class="alert alert-error gap-3"><AlertTriangle size={18} class="shrink-0" />{error}</div>
{:else}
<div class="flex flex-wrap items-center gap-x-6 gap-y-3 mb-6">
<div class="flex items-center gap-3">
<span
class="text-xs uppercase tracking-widest text-base-content/50 shrink-0"
>Profile</span
>
<Select
options={profileOptions}
selected={selectedProfile}
onselect={selectProfile}
/>
{#if profileLoading}
<span class="loading loading-spinner loading-xs opacity-40"></span>
{/if}
</div>
<div class="flex items-center gap-3">
<span
class="text-xs uppercase tracking-widest text-base-content/50 shrink-0"
>Input</span
>
<Select
options={inputTypeOptions}
selected={selectedInputType}
onselect={(val) => (selectedInputType = val)}
/>
</div>
</div>
{#if tools.length === 0}
<p class="text-base-content/40 text-sm text-center py-8">
No tools registered.
</p>
{:else}
<div class="flex flex-col gap-6">
{#if active.length > 0}
<section>
<div class="flex items-center gap-2 mb-3">
<span class="size-1.5 rounded-full bg-success shrink-0"></span>
<span class="text-xs uppercase tracking-widest text-base-content/50"
>Active</span
>
<span class="text-xs text-base-content/30">{active.length}</span>
</div>
<div class="grid grid-cols-1 sm:grid-cols-2 gap-3">
{#each active as tool}
<a href="/tools/{tool.name}" class="group">
{@render toolCard(tool, false)}
</a>
{/each}
</div>
</section>
{/if}
{#if activeMissing.length > 0}
<section>
<div class="flex items-center gap-2 mb-3">
<span class="size-1.5 rounded-full bg-warning shrink-0"></span>
<span class="text-xs uppercase tracking-widest text-base-content/50"
>Active - required config missing</span
>
<span class="text-xs text-base-content/30"
>{activeMissing.length}</span
>
</div>
<div class="grid grid-cols-1 sm:grid-cols-2 gap-3">
{#each activeMissing as tool}
<a href="/tools/{tool.name}" class="group">
{@render toolCard(tool, true)}
</a>
{/each}
</div>
</section>
{/if}
{#if activeUnavail.length > 0}
<section>
<div class="flex items-center gap-2 mb-3">
<span class="size-1.5 rounded-full bg-error shrink-0"></span>
<span class="text-xs uppercase tracking-widest text-error/70"
>Active - unavailable</span
>
<span class="text-xs text-base-content/30"
>{activeUnavail.length}</span
>
</div>
<div class="grid grid-cols-1 sm:grid-cols-2 gap-3">
{#each activeUnavail as tool}
<a href="/tools/{tool.name}" class="group">
{@render toolCard(tool, false)}
</a>
{/each}
</div>
</section>
{/if}
{#if active.length + activeMissing.length + activeUnavail.length > 0 && inactive.length + inactiveMissing.length + inactiveUnavail.length > 0}
<div class="divider opacity-20 my-0"></div>
{/if}
{#if inactive.length > 0}
<section>
<div class="flex items-center gap-2 mb-3">
<span class="size-1.5 rounded-full bg-base-content/20 shrink-0"
></span>
<span class="text-xs uppercase tracking-widest text-base-content/30"
>Disabled</span
>
<span class="text-xs text-base-content/20">{inactive.length}</span>
</div>
<div class="grid grid-cols-1 sm:grid-cols-2 gap-3 opacity-40">
{#each inactive as tool}
<a
href="/tools/{tool.name}"
class="group hover:opacity-100 transition-opacity"
>
{@render toolCard(tool, false)}
</a>
{/each}
</div>
</section>
{/if}
{#if inactiveMissing.length > 0}
<section>
<div class="flex items-center gap-2 mb-3">
<span class="size-1.5 rounded-full bg-base-content/20 shrink-0"
></span>
<span class="text-xs uppercase tracking-widest text-base-content/30"
>Disabled - required config missing</span
>
<span class="text-xs text-base-content/20"
>{inactiveMissing.length}</span
>
</div>
<div class="grid grid-cols-1 sm:grid-cols-2 gap-3 opacity-40">
{#each inactiveMissing as tool}
<a
href="/tools/{tool.name}"
class="group hover:opacity-100 transition-opacity"
>
{@render toolCard(tool, true)}
</a>
{/each}
</div>
</section>
{/if}
{#if inactiveUnavail.length > 0}
<section>
<div class="flex items-center gap-2 mb-3">
<span class="size-1.5 rounded-full bg-base-content/20 shrink-0"
></span>
<span class="text-xs uppercase tracking-widest text-base-content/30"
>Disabled - unavailable</span
>
<span class="text-xs text-base-content/20"
>{inactiveUnavail.length}</span
>
</div>
<div class="grid grid-cols-1 sm:grid-cols-2 gap-3 opacity-40">
{#each inactiveUnavail as tool}
<a
href="/tools/{tool.name}"
class="group hover:opacity-100 transition-opacity"
>
{@render toolCard(tool, false)}
</a>
{/each}
</div>
</section>
{/if}
</div>
{/if}
{/if}

View File

@@ -0,0 +1,29 @@
<script>
let { text, color = null, icon: IconComponent = null, size = "sm", loading = false } = $props();
const colorDefaults = {
"done": "badge-success",
"error": "badge-error",
"unavailable": "badge-error",
"running": "badge-warning",
"cancelled": "badge-error",
"required": "badge-error",
"read-only": "badge-ghost",
"active": "badge-success",
"disabled": "badge-ghost",
"configured": "badge-outline",
"configurable": "badge-outline",
"config required":"badge-warning",
};
let cls = $derived(color ?? colorDefaults[text?.toLowerCase()] ?? "badge-ghost");
</script>
<span class="badge badge-{size} {cls} gap-1">
{#if loading}
<span class="loading loading-ring loading-xs"></span>
{:else if IconComponent}
<IconComponent size={9} />
{/if}
{text}
</span>

View File

@@ -0,0 +1,13 @@
<script lang="ts">
import { CircleQuestionMark } from "@lucide/svelte";
let {
tooltip,
}: {
tooltip: string;
} = $props();
</script>
<div class="tooltip" data-tip={tooltip}>
<CircleQuestionMark class="size-3 text-base-content/40 align-middle"/>
</div>

View File

@@ -0,0 +1,84 @@
<script>
import { ChevronDown } from "@lucide/svelte";
let {
options = [],
placeholder = "",
selected = null,
onselect,
size = "sm", // "xs" | "sm" | ""
} = $props();
let open = $state(false);
let query = $state("");
let container;
let input = $state();
let filtered = $derived(options.filter((o) =>
o.toLowerCase().includes(query.toLowerCase())
));
function select(value) {
onselect?.(value);
open = false;
query = "";
}
function toggle() {
open = !open;
if (open) setTimeout(() => input?.focus(), 0);
}
</script>
<svelte:window onmousedown={(e) => {
if (container && !container.contains(e.target)) {
open = false;
query = "";
}
}} />
<div class="relative" bind:this={container}>
<button
type="button"
class="btn btn-ghost btn-{size} gap-1 font-normal"
onclick={toggle}
>
<ChevronDown size={11} />
{selected ?? placeholder}
</button>
{#if open}
<div
class="absolute z-50 top-full left-0 mt-1 w-52 bg-base-300 rounded-box shadow-xl border border-base-content/10 flex flex-col"
>
<div class="p-2 border-b border-base-content/10">
<input
bind:this={input}
type="text"
class="input input-bordered input-xs w-full"
placeholder="Search..."
bind:value={query}
onkeydown={(e) => { if (e.key === "Escape") { open = false; query = ""; } }}
/>
</div>
<ul class="max-h-48 overflow-y-auto p-1">
{#if filtered.length === 0}
<li class="px-3 py-2 text-xs text-base-content/40 text-center">No results</li>
{:else}
{#each filtered as option}
<li>
<button
type="button"
class="w-full text-left px-3 py-1.5 text-sm font-mono rounded-btn transition-colors
{option === selected ? 'bg-primary/15 text-primary font-semibold' : 'hover:bg-base-content/10'}"
onclick={() => select(option)}
>
{option}
</button>
</li>
{/each}
{/if}
</ul>
</div>
{/if}
</div>

View File

@@ -0,0 +1,29 @@
<script lang="ts">
const { iconName = "", size=16 }: { iconName: string , size: number} = $props();
const genericFallbackUrl = "/Wrench.svg";
</script>
{#if iconName}
<img
src="https://cdn.simpleicons.org/{iconName}"
alt={iconName + " icon"}
class="opacity-50"
width={size}
height={size}
style="filter: brightness(0) invert(1);"
onerror={(e) => {
const target = e.currentTarget as HTMLImageElement;
target.src = genericFallbackUrl;
}}
/>
{:else}
<img
src={genericFallbackUrl}
alt={"Tool icon"}
class="opacity-50"
width={size}
height={size}
style="filter: brightness(0) invert(1);"
/>
{/if}

View File

@@ -0,0 +1,63 @@
<script>
import { AnsiUp } from "ansi_up";
import DOMPurify from "dompurify";
let { output } = $props();
const au = new AnsiUp();
au.use_classes = false;
const ansiRe = /\x1b\[[0-9;]*m/g;
const urlRe = /https?:\/\/[^\s<>"']+/g;
const tlds = "com|org|net|io|fr|de|uk|co|info|biz|eu|us|ca|au|ru|cn|jp|br|in|es|dev|app|me|tv|cc|ch|be|nl|se|no|dk|fi|pl|cz|at|hu|ro";
const bareDomainRe = new RegExp(`(?<![a-zA-Z0-9@])[a-zA-Z0-9][a-zA-Z0-9\\-]*(?:\\.[a-zA-Z0-9][a-zA-Z0-9\\-]*)*\\.(?:${tlds})(?:/[^\\s<>"']*)?`, "g");
const makeLink = (href, text) =>
`<a href="${href}" target="_blank" rel="noopener noreferrer" class="ansi-link">${text}</a>`;
function linkifyText(text) {
// First pass: full URLs
const afterUrls = text.replace(urlRe, (url) => makeLink(url, url));
// Re-split to protect the newly created <a> tags, then linkify bare domains in remaining text
return afterUrls.split(/(<[^>]+>)/).map((part) => {
if (part.startsWith("<")) return part;
return part.replace(bareDomainRe, (domain) => makeLink(`https://${domain}`, domain));
}).join("");
}
function linkify(html) {
return html.split(/(<[^>]+>)/).map((part) =>
part.startsWith("<") ? part : linkifyText(part)
).join("");
}
let html = $derived((() => {
const lines = output.split("\n");
let start = 0;
let end = lines.length - 1;
while (start <= end && lines[start].replace(ansiRe, "").trim() === "") start++;
while (end >= start && lines[end].replace(ansiRe, "").trim() === "") end--;
return DOMPurify.sanitize(linkify(au.ansi_to_html(lines.slice(start, end + 1).join("\n"))), {
ALLOWED_TAGS: ["span", "a", "b"],
ALLOWED_ATTR: ["style", "href", "target", "rel", "class"],
});
})());
</script>
{#if html}
<div class="ansi-output">{@html html}</div>
{/if}
<style>
.ansi-output {
line-height: 1.35;
}
.ansi-output :global(.ansi-link) {
text-decoration: underline;
text-underline-offset: 2px;
opacity: 0.8;
}
.ansi-output :global(.ansi-link:hover) {
opacity: 1;
}
</style>

View File

@@ -0,0 +1,14 @@
import { defineCollection, z } from "astro:content";
import { glob } from "astro/loaders";
const cheatsheets = defineCollection({
loader: glob({ pattern: "**/*.md", base: "./src/content/cheatsheets" }),
schema: z.object({
title: z.string(),
description: z.string().optional(),
order: z.number().optional(),
tags: z.array(z.string()).optional(),
}),
});
export const collections = { cheatsheets };

View File

@@ -0,0 +1,134 @@
---
title: "Unmasking Github Users: How to Identify the Person Behind Any Github Profile"
description: "Ever wondered who is behind a specific Github username? This guide covers advanced OSINT techniques to deanonymize users, find hidden email addresses, and link Github accounts to real-world identities."
tags: [github, social]
---
In the world of Open-Source Intelligence (OSINT), we often focus on social media platforms like Twitter or LinkedIn. However, developers frequently leave behind much more detailed personal information on **Github**.
Whether you are a recruiter, a security researcher, or a digital investigator, Github is a goldmine. Why? Because while a user might choose a cryptic handle like `anotherhadi`, their Git configuration often reveals their real name and email address.
## Level 1: The Low-Hanging Fruit
Before diving into technical exploits, start with the obvious. Many users forget how much they have shared in their profile settings.
- **The Bio & Location**: Even a vague location like "Montpellier, France," combined with a niche tech stack (e.g., "COBOL expert"), significantly narrows down the search.
- **External Links**: Check the personal website or blog link. Run a WHOIS lookup on that domain to find registration details. Use other OSINT tools and techniques on those websites to pivot further.
- **The Profile Picture**: Right-click the avatar and use Google Reverse Image Search, Yandex, or other reverse image engines. Developers often use the same professional headshot on Github as they do on LinkedIn.
## Level 2: Digging into Commits
This is the **most effective OSINT** method. While Github masks author names and emails in the web view, this information is permanently embedded in the commit metadata.
### The `.patch` Method
Find a repository where the target has contributed. Open any commit they made, and simply add `.patch` to the end of the URL.
- **URL**: `https://github.com/{username}/{repo}/commit/{commit_hash}.patch`
- Look at the `From:` line. It should look like this: `From: John Doe <j.doe@company.com>`
For example, check: [github.com/anotherhadi/nixy/commit/e6873e8caae491073d8ab7daad9d2e50a04490ce.patch](https://github.com/anotherhadi/nixy/commit/e6873e8caae491073d8ab7daad9d2e50a04490ce.patch)
### The API Events Method
If you cannot find a recent commit, check their **public activity** stream via the Github API.
- **Go to**: `https://api.github.com/users/{target_username}/events/public`
- Search (Ctrl+F) for the word `email`. You will often find the **email address** associated with their `PushEvent` headers, even if they have "Keep my email addresses private" enabled in their current settings.
## The Verification Loop: Linking Email to Account
If you have found an email address and want to be 100% sure it belongs to a specific Github profile, you can use Githubs own attribution engine against itself.
### The Email Spoofing Method
While the previous methods help you find an email _from_ a profile, this technique does the opposite: it identifies which Github account is linked to a specific email address.
**How it works:**
Github attributes commits based on the email address found in the Git metadata. If you push a commit using a specific email, Github will automatically link that commit to the account associated with that address as its **primary email**.
**The Process:**
1. **Initialize a local repo:** `git init investigation`
2. **Configure the target email:** `git config user.email "target@example.com"` and `git config user.name "A Username"`
3. **Create a dummy commit:** `echo "test" > probe.txt && git add . && git commit -m "Probe"`
4. **Push to a repo you own:** Create a new empty repository on your Github account and push the code there.
5. **Observe the result:** Go to the commit history on the Github web interface. The avatar and username of the account linked to that email will appear as the author of the commit.
> **Note:** This method only works if the target email is set as the **Primary Email** on the user's account. It is a foolproof way to confirm if an email address you found elsewhere belongs to a specific Github user.
### The Search Index: Finding Hidden Contributions
Even if an email address is not listed on a user's profile, it may still be indexed within Github's global search.
Github allows you to filter search results by the metadata fields of a commit.
This is particularly useful if the target has **contributed to public repositories** using their real email.
You can use these specific qualifiers in the **Github search bar** (select the "Commits" tab):
- `author-email:target@example.com`: Finds commits where the target is the original author.
- `committer-email:target@example.com`: Finds commits where the target was the one who committed the code (sometimes different from the author).
## Level 3: Technical Metadata
If the email is masked or missing, we can look at the **cryptographic keys** the user uses to communicate with Github.
### SSH Keys
Every users public **SSH keys are public**.
- **URL**: `https://github.com/{username}.keys`
- **The Pivot**: You can take the key string and search for it on platforms like **Censys** or **Shodan**. If that same key is authorized on a specific server IP, you have successfully located the users infrastructure.
### GPG Keys
If a user signs their commits, their **GPG key** is available at:
- **URL**: `https://github.com/{username}.gpg`
- **The Reveal**: Import this key into your local GPG tool (`gpg --import`). It will often reveal the **Verified Identity** and the primary email address linked to the encryption key.
## Level 4: Connecting the Dots
Once you have a **name**, an **email**, or a **unique username**, its time to _pivot_.
- **Username Pivoting**: Use tools like [Sherlock](https://github.com/sherlock-project/sherlock) or [Maigret](https://github.com/soxoj/maigret/) to search for the same username across hundreds of other platforms. Developers are creatures of habit; they likely use the same handle on Stack Overflow, Reddit, or even old gaming forums.
- **Email Pivoting**: Use tools like [holehe](https://github.com/megadose/holehe) to find other accounts registered with the email addresses you just uncovered.
## Automating the Hunt: Github-Recon
If you want to move from manual investigation to automated intelligence, check out [Github-Recon](https://github.com/anotherhadi/github-recon).
Written in Go, this powerful CLI tool aggregates public OSINT data by automating the techniques mentioned above and more. Whether you start with a username or a single email address, it can retrieve SSH/GPG keys, enumerate social accounts, and find "close friends" based on interactions.
Its standout features include a **Deep Scan** mode-which clones repositories to perform regex searches and TruffleHog secret detection—and an automated **Email Spoofing** engine that instantly identifies the account linked to any primary email address.
<a href="https://github.com/anotherhadi/github-recon" class="link-card" target="_blank">
<span>
<h4>anotherhadi/github-recon</h4>
<p>GitHub OSINT reconnaissance tool. Gathers profile info, social links, organisations, SSH/GPG keys, commits, and more from a GitHub username or email.</p>
</span>
</a>
## Conclusion and Protection: How to Stay Anonymous
If you are a developer reading this, you might be feeling exposed.
Understanding what information about you is publicly visible is the **first step to managing your online presence**. This guide and tools like [github-recon](https://github.com/anotherhadi/github-recon) can help you identify your own publicly available data on Github. Heres how you can take steps to protect your privacy and security:
- **Review your public profile**: Regularly check your Github profile and
repositories to ensure that you are not unintentionally exposing sensitive
information.
- **Manage email exposure**: Use Github's settings to control which email
addresses are visible on your profile and in commit history. You can also **use
a no-reply email** address for commits, and an
[alias email](https://proton.me/support/addresses-and-aliases) for your
account. Delete/modify any sensitive information in your commit history.
- **Be Mindful of Repository Content**: **Avoid including sensitive information** in
your repositories, such as API keys, passwords, emails or personal data. Use
`.gitignore` to exclude files that contain sensitive information.
You can also use a tool like [TruffleHog](github.com/trufflesecurity/trufflehog)
to scan your repositories specifically for exposed secrets and tokens.
**Useful links:**
- [Blocking command line pushes that expose your personal email address](https://docs.github.com/en/account-and-profile/setting-up-and-managing-your-personal-account-on-github/managing-email-preferences/blocking-command-line-pushes-that-expose-your-personal-email-address)
- [No-reply email address](https://docs.github.com/en/account-and-profile/setting-up-and-managing-your-personal-account-on-github/managing-email-preferences/setting-your-commit-email-address)
In OSINT, the best hidden secrets are the ones we forget we ever shared. Happy hunting!

View File

@@ -0,0 +1,100 @@
---
title: "Google Dorks"
description: "Essential cheatsheet for Google Dorking, using advanced search operators to perform Open Source Intelligence (OSINT) and identify publicly exposed information or misconfigurations on target websites."
tags: [google, dorks]
---
[Google](https://google.com) hacking, also named Google dorking, is a hacker technique that uses Google Search and other Google applications to find security holes in the configuration and computer code that websites are using.
Dorks also works on [Startpage](https://startpage.com) or [Duckduckgo](https://duckduckgo.com).
## Basics
- `-` excludes a term
- `OR` searches for either term
- `""` searches for an exact phrase
- `*` acts as a wildcard
- `site:` restricts the search to a specific domain
- `inurl:` restricts the search to a specific URL
- `intitle:` restricts the search to a specific title
- `intext:` restricts the search to a specific text
- `allintext:` restricts the search to all text
- `filetype:` restricts the search to a specific file type
## Information gathering
Replace "{target}" with a name or other identifiers used online. Always remember
to use these queries solely for legal and ethical purposes on information you
own or have permission to check.
- **File Types:**
- `"{target}" filetype:pdf`
- `"{target}" filetype:doc OR filetype:docx OR filetype:xls OR filetype:ppt`
- Config files:
`site:{target}+filetype:xml+|+filetype:conf+|+filetype:cnf+|+filetype:reg+|+filetype:inf+|+filetype:rdp+|+filetype:cfg+|+filetype:txt+|+filetype:ora+|+filetype:ini`
- Database files: `site:{target}+filetype:sql+|+filetype:dbf+|+filetype:mdb`
- Data files: `site:{target} ext:csv OR ext:xls OR ext:log` or `site:{target} "@gmail.com" ext:csv`
- Log files: `site:{target}+filetype:log+|filetype:txt` - Backup files:
`site:{target}+filetype:bkf+|+filetype:bkp+|+filetype:bak+|+filetype:old+|+filetype:backup`
- Setup files:
`site:{target}+inurl:readme+|+inurl:license+|+inurl:install+|+inurl:setup+|+inurl:config`
- Private files:
`site:{target} "internal use only" ( you can replace with "classified", "private", "unauthorised" )`
- Sensitive docs:
`ext:txt | ext:pdf | ext:xml | ext:xls | ext:xlsx | ext:ppt | ext:pptx | ext:doc | ext:docx intext:“confidential” | intext:“Not for Public Release” | intext:”internal use only” | intext:“do not distribute” site:{target}`
- Code leaks: Check for code snippets, secrets, configs
```txt
site:pastebin.com "{target}"
site:jsfiddle.net "{target}"
site:codebeautify.org "{target}"
site:codepen.io "{target}"`
```
- Cloud File Shares: Find exposed files linked to your target
```txt
site:http://drive.google.com "{target}"
site:http://docs.google.com inurl:"/d/" "{target}"
site:http://dropbox.com/s "{target}"
```
- Other: `site:{target}+filetype:pdf+|+filetype:xlsx+|+filetype:docx`
- **Social Media & Professional Networks:**
- `site:linkedin.com/in "{target}"`
- `site:facebook.com "{target}"`
- `site:twitter.com "{target}"`
- `site:instagram.com "{target}"`
- **Profile & Resume Searches:**
- `inurl:"profile" "{target}"`
- `intitle:"{target}" "profile"`
- `"{target}" intext:"resume"`
- `intitle:"Curriculum Vitae" OR intitle:"CV" "{target}"`
- **Email and Contact Information:**
- `"{target}" intext:"@gmail.com"`
- `"{target}" intext:"email"`
- `"{target}" AND "contact"`
- **Forums and Public Repositories:**
- `site:pastebin.com "{target}"`
- `site:github.com "{target}"`
- `site:forums "{target}"`
- **Directory Listings and Miscellaneous:**
- `site:{target}+intitle:index.of`,
- **Exclusion Searches:**
- `"{target}" -site:facebook.com`
- `"{target}" -site:twitter.com`
## Advanced Google Operators
- `related:site` finds websites similar to the specified URL
- `define:term` shows a word or phrase definition directly in the results
- `inanchor:word` filters pages where the anchor text includes the specified
word
- `around(n)` restricts results to pages where two words appear within _n_ words
of each other
## Ressources
- [TakSec's google dorks](https://github.com/TakSec/google-dorks-bug-bounty/)
- [Exploit-db Google hacking database](https://www.exploit-db.com/google-hacking-database)

View File

@@ -0,0 +1,61 @@
---
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: [sock-puppets]
---
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://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>

View File

@@ -0,0 +1,23 @@
---
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."
---
## 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.today](https://archive.ph) creates on-demand snapshots, including for JS-heavy sites, with both a functional page and screenshot version
## Bookmarklets
- [K2SOsint/Bookmarklets](https://github.com/K2SOsint/Bookmarklets)
- [MyOsint.training](https://tools.myosint.training/)

View File

@@ -0,0 +1,88 @@
---
title: "Twitter/X OSINT"
description: "Essential cheatsheet for Open Source Intelligence (OSINT) on Twitter/X, detailing advanced search operators, engagement filters, and temporal/geographic capabilities for effective data collection."
tags: [social]
---
## Banner last update time
The banner URL includes a Unix timestamp indicating when the banner was last
updated.
For example:
`https://pbs.twimg.com/profile_banners/1564326938851921921/1750897704/600x200`
In this case, `1750897704` is the timestamp. You can convert it using
[unixtimestamp.com](https://www.unixtimestamp.com/) or any other Unix time converter.
## Basic Search Operators
Twitter's advanced search functionality provides powerful filtering capabilities
for OSINT investigations:
- **Keywords**: `word1 word2` (tweets containing both words)
- **Exact phrases**: `"exact phrase"` (tweets with this exact sequence)
- **Exclusion**: `-word` (excludes tweets containing this word)
- **Either/or**: `word1 OR word2` (tweets containing either term)
- **Hashtags**: `#hashtag` (tweets with specific hashtag)
- **Accounts**: `from:username` (tweets sent by specific account)
- **Mentions**: `to:username` (tweets in reply to an account)
- **Mentions in any context**: `@username` (tweets mentioning an account)
## Advanced Filters
<a href="https://x.com/search-advanced" class="link-card" target="_blank">
<span>
<h4>Twitter/X Search advanced GUI</h4>
<p>Graphical User Interface (GUI) for the twitter search advanced functionality</p>
</span>
</a>
### Engagement Filters
- **Minimum retweets**: `min_retweets:number`
- **Minimum likes**: `min_faves:number`
- **Minimum replies**: `min_replies:number`
- **Filter for links**: `filter:links`
- **Filter for media**: `filter:media`
- **Filter for images**: `filter:images`
- **Filter for videos**: `filter:videos`
### Temporal and Geographic Filters
- **Date range**: `since:YYYY-MM-DD until:YYYY-MM-DD`
- **Geolocation**: `geocode:latitude,longitude,radius` (e.g.,
`geocode:40.7128,-74.0060,5km`)
- **Language**: `lang:code` (e.g., `lang:en` for English)
### Tweet Characteristics
- **Positive attitude**: `🙂 OR :) OR filter:positive`
- **Negative attitude**: `🙁 OR :( OR filter:negative`
- **Questions**: `?` or `filter:questions`
- **Retweets only**: `filter:retweets`
- **Native retweets only**: `filter:nativeretweets`
- **Twitter Blue subscribers**: `filter:verified` (note: since 2023, "verified" means Twitter Blue subscriber, not a traditionally verified account)
- **Safe content**: `filter:safe`
## Practical Search Combinations
- **Content from a user within a date range**:
`from:username since:2023-01-01 until:2023-12-31`
- **High-engagement tweets about a topic**:
`"artificial intelligence" min_retweets:100 lang:en -filter:retweets`
- **Media shared by a specific user**:
`from:username filter:media -filter:retweets`
- **Conversations between specific users**:
`from:username1 to:username2 OR from:username2 to:username1`
- **Link sharing on a topic by verified users**:
`"climate change" filter:links filter:verified since:2023-01-01`
## Disclaimer
Remember that all Twitter searches should comply with Twitter's Terms of Service
and appropriate legal frameworks for your jurisdiction.

View File

@@ -0,0 +1,92 @@
---
import "@src/styles/global.css";
import "@src/styles/gfm.css";
import "@src/styles/markdown.css";
import Navbar from "@src/components/Nav.svelte";
import DemoBanner from "@src/components/DemoBanner.svelte";
import { Coffee } from "@lucide/svelte";
interface Props {
title?: string;
description?: string;
}
const {
title = "iknowyou",
description = "Self-hosted OSINT aggregation. Run multiple recon tools against a target in parallel and get results in one place.",
} = Astro.props;
const pageTitle = title === "iknowyou" ? title : `${title} — iky`;
const canonicalURL = new URL(Astro.url.pathname, Astro.site ?? Astro.url.origin);
---
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="generator" content={Astro.generator} />
<title>{pageTitle}</title>
<meta name="description" content={description} />
<link rel="canonical" href={canonicalURL} />
<meta property="og:type" content="website" />
<meta property="og:title" content={pageTitle} />
<meta property="og:description" content={description} />
<meta property="og:site_name" content="iknowyou" />
</head>
<body class="bg-base-100 min-h-screen">
<DemoBanner client:only="svelte" />
<Navbar client:load>
<a
href="https://ko-fi.com/anotherhadi"
slot="action"
target="_blank"
class="btn btn-primary btn-sm"
><Coffee class="size-3" /> Support me</a
>
</Navbar>
<div class="m-auto max-w-5xl md:py-10 md:px-10 py-5 px-5 animate-fade-in">
<slot />
</div>
</body>
<script>
const COPY_ICON = `<svg xmlns="http://www.w3.org/2000/svg" width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect width="14" height="14" x="8" y="8" rx="2" ry="2"/><path d="M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2"/></svg>`;
const CHECK_ICON = `<svg xmlns="http://www.w3.org/2000/svg" width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="20 6 9 17 4 12"/></svg>`;
document.querySelectorAll<HTMLElement>("pre[data-lang]").forEach((pre) => {
const lang = pre.dataset.lang ?? "text";
const wrapper = document.createElement("div");
wrapper.className = "code-block";
const header = document.createElement("div");
header.className = "code-header";
const langSpan = document.createElement("span");
langSpan.className = "code-lang";
langSpan.textContent = lang;
const copyBtn = document.createElement("button");
copyBtn.className = "copy-btn";
copyBtn.innerHTML = `${COPY_ICON} copy`;
copyBtn.addEventListener("click", () => {
const code = pre.querySelector("code");
if (!code) return;
navigator.clipboard.writeText(code.innerText).then(() => {
copyBtn.classList.add("copied");
copyBtn.innerHTML = `${CHECK_ICON} copied!`;
setTimeout(() => {
copyBtn.classList.remove("copied");
copyBtn.innerHTML = `${COPY_ICON} copy`;
}, 2000);
});
});
header.appendChild(langSpan);
header.appendChild(copyBtn);
wrapper.appendChild(header);
pre.parentNode!.insertBefore(wrapper, pre);
wrapper.appendChild(pre);
});
</script>
</html>

17
front/src/lib/utils.ts Normal file
View File

@@ -0,0 +1,17 @@
export function cleanUserInput(query: string | undefined | null): string {
if (!query) return "";
return query.replace(/[^a-zA-Z0-9\s.\-_/]/g, "").trim();
}
export function getRandomEmoji(): string {
const emojis = [
"(·.·)",
"(>_<)",
"¯\\_(ツ)_/¯",
"(╯_╰)",
"(-_-)",
"┐(`;)┌",
"(X_X)",
];
return emojis[Math.floor(Math.random() * emojis.length)];
}

16
front/src/lib/vars.ts Normal file
View File

@@ -0,0 +1,16 @@
import { Mail, User, Phone, Globe, Server, KeyRound, Contact } from "@lucide/svelte";
export const INPUT_TYPES = [
"email", "username", "name", "phone", "ip",
"domain", "password",
];
export const INPUT_TYPE_ICON = {
email: Mail,
username: User,
name: Contact,
phone: Phone,
domain: Globe,
ip: Server,
password: KeyRound,
};

30
front/src/pages/403.astro Normal file
View File

@@ -0,0 +1,30 @@
---
import Layout from "@src/layouts/Layout.astro";
import { ShieldAlert, Home } from "@lucide/svelte";
---
<Layout title="403 - Access Denied">
<main
class="flex flex-col items-center justify-center gap-6 px-4 text-center"
>
<div class="flex flex-col items-center mt-20">
<ShieldAlert size={80} class="text-warning" />
<h1 class="text-4xl font-black text-warning opacity-50 mb-10">403</h1>
</div>
<div>
<h2 class="text-3xl font-bold logo-gradient italic">Access Denied</h2>
<p class="opacity-60 max-w-xs mx-auto mt-2">
You don't have the necessary clearance to access this sector of the app.
</p>
</div>
<div class="badge badge-outline badge-warning font-mono text-xs p-3">
ERROR_CODE: INSUFFICIENT_PERMISSIONS
</div>
<a href="/" class="btn btn-soft btn-warning gap-2">
<Home size={16} /> Return to Surface
</a>
</main>
</Layout>

26
front/src/pages/404.astro Normal file
View File

@@ -0,0 +1,26 @@
---
import Layout from "@src/layouts/Layout.astro";
import { Ghost, Home } from "@lucide/svelte";
---
<Layout title="404 - Page Not Found">
<main
class="flex flex-col items-center justify-center gap-6 px-4 text-center"
>
<div class="flex flex-col items-center mt-20">
<Ghost size={80} class="text-primary" />
<h1 class="text-4xl font-black text-primary opacity-50 mb-10">404</h1>
</div>
<div>
<h2 class="text-3xl font-bold logo-gradient italic">Lost?</h2>
<p class="opacity-60 max-w-xs mx-auto mt-2">
The page you are looking for doesn't exist or has been moved.
</p>
</div>
<a href="/" class="btn btn-soft btn-primary gap-2">
<Home size={16} /> Back to Home
</a>
</main>
</Layout>

32
front/src/pages/500.astro Normal file
View File

@@ -0,0 +1,32 @@
---
import Layout from "@src/layouts/Layout.astro";
import { AlertTriangle, RefreshCw } from "@lucide/svelte";
---
<Layout title="500 - Server Error">
<main
class="flex flex-col items-center justify-center gap-6 px-4 text-center"
>
<div class="flex flex-col items-center mt-20">
<AlertTriangle size={80} class="text-error" />
<h1 class="text-4xl font-black text-error opacity-50 mb-10">500</h1>
</div>
<div>
<h2 class="text-3xl font-bold logo-gradient italic">System Failure</h2>
<p class="opacity-60 max-w-xs mx-auto mt-2">
The server encountered an unexpected error.
</p>
</div>
<div class="flex gap-4">
<button
onclick="window.location.reload()"
class="btn btn-soft btn-error gap-2"
>
<RefreshCw size={16} /> Retry
</button>
<a href="/" class="btn btn-soft btn-primary">Go Home</a>
</div>
</main>
</Layout>

View File

@@ -0,0 +1,126 @@
---
import Layout from "@src/layouts/Layout.astro";
import { getCollection, render } from "astro:content";
export async function getStaticPaths() {
const sheets = await getCollection("cheatsheets");
return sheets.map((sheet) => ({
params: { slug: sheet.id },
props: { sheet },
}));
}
const { sheet } = Astro.props;
const { Content, headings } = await render(sheet);
const toc = headings.filter((h) => h.depth === 2 || h.depth === 3);
---
<Layout title={`${sheet.data.title} - Cheatsheets`} description={sheet.data.description}>
<div class="pb-4">
<div class="mb-6">
<a href="/cheatsheets" class="btn btn-ghost btn-sm gap-1">← Cheatsheets</a>
</div>
<div class="mb-8">
<h1 class="text-2xl font-bold tracking-tight">{sheet.data.title}</h1>
{sheet.data.description && (
<p class="text-base-content/50 text-sm mt-1">{sheet.data.description}</p>
)}
{sheet.data.tags && sheet.data.tags.length > 0 && (
<div class="flex gap-2 mt-3">
{sheet.data.tags.map((tag) => (
<a href={`/cheatsheets?tag=${tag}`} class="badge badge-ghost badge-sm">{tag}</a>
))}
</div>
)}
</div>
{toc.length > 0 && (
<details class="lg:hidden mb-6 border border-base-300 rounded-lg">
<summary class="cursor-pointer px-4 py-3 text-sm font-semibold select-none list-none flex items-center justify-between">
<span>On this page</span>
<svg class="size-4 opacity-50 details-chevron" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="6 9 12 15 18 9"/></svg>
</summary>
<nav class="px-4 pb-4 pt-1">
<ul class="flex flex-col gap-1">
{toc.map((h) => (
<li style={h.depth === 3 ? "padding-left: 0.875rem" : ""}>
<a href={`#${h.slug}`} class="toc-link text-sm text-base-content/60 hover:text-base-content transition-colors">
{h.text}
</a>
</li>
))}
</ul>
</nav>
</details>
)}
<div class="flex gap-10 items-start">
<div class="prose prose-sm max-w-none flex-1 min-w-0 break-words">
<Content />
</div>
{toc.length > 0 && (
<aside class="hidden lg:block w-48 shrink-0">
<nav class="sticky top-8">
<p class="text-xs font-semibold uppercase tracking-wider text-base-content/40 mb-3">
On this page
</p>
<ul class="flex flex-col gap-1.5">
{toc.map((h) => (
<li style={h.depth === 3 ? "padding-left: 0.75rem" : ""}>
<a
href={`#${h.slug}`}
data-toc-link
class="toc-link text-xs text-base-content/50 hover:text-base-content transition-colors leading-snug block"
>
{h.text}
</a>
</li>
))}
</ul>
</nav>
</aside>
)}
</div>
</div>
</Layout>
<style>
details[open] .details-chevron {
transform: rotate(180deg);
}
.details-chevron {
transition: transform 0.2s;
}
.toc-link.active {
color: var(--color-primary);
}
</style>
<script>
const links = document.querySelectorAll<HTMLAnchorElement>("[data-toc-link]");
if (links.length > 0) {
const headingEls = [...links].map((l) =>
document.querySelector(decodeURIComponent(new URL(l.href).hash))
);
const observer = new IntersectionObserver(
(entries) => {
for (const entry of entries) {
if (entry.isIntersecting) {
const idx = headingEls.indexOf(entry.target);
links.forEach((l) => l.classList.remove("active"));
if (idx !== -1) links[idx].classList.add("active");
}
}
},
{ rootMargin: "-20% 0px -70% 0px" }
);
headingEls.forEach((el) => el && observer.observe(el));
}
</script>

View File

@@ -0,0 +1,33 @@
---
import Layout from "@src/layouts/Layout.astro";
import { getCollection } from "astro:content";
import CheatsheetList from "@src/components/CheatsheetList.svelte";
const sheets = (await getCollection("cheatsheets"))
.sort((a, b) => (a.data.order ?? 99) - (b.data.order ?? 99))
.map((s) => ({
id: s.id,
title: s.data.title,
description: s.data.description,
tags: s.data.tags,
}));
---
<Layout title="Cheatsheets">
<div class="max-w-3xl mx-auto px-4 pb-4">
<div class="mb-6">
<a href="/" class="btn btn-ghost btn-sm gap-1">← Back</a>
</div>
<div class="mb-8">
<h1 class="text-2xl font-bold tracking-tight">OSINT Cheatsheets</h1>
<p class="text-base-content/50 text-sm mt-1">
Quick reference cards for common OSINT techniques.
</p>
</div>
<CheatsheetList {sheets} client:only="svelte" />
</div>
</Layout>

172
front/src/pages/help.astro Normal file
View File

@@ -0,0 +1,172 @@
---
import Layout from "@src/layouts/Layout.astro";
---
<Layout title="How it works">
<div class="max-w-3xl mx-auto px-4 pb-4">
<div class="mb-6">
<a href="/" class="btn btn-ghost btn-sm gap-1">← Back</a>
</div>
<div class="mb-8">
<h1 class="text-2xl font-bold tracking-tight">How it works</h1>
<p class="text-base-content/50 text-sm mt-1">
A guide to iknowyou: concepts, tools, profiles, and configuration.
</p>
</div>
<div class="flex flex-col gap-8">
<section class="flex flex-col gap-3">
<h2 class="text-lg font-bold flex items-center gap-2">
<span class="size-2 rounded-full bg-primary inline-block"></span>
What is it?
</h2>
<p class="text-base-content/70 text-sm leading-relaxed">
<strong>Iknowyou</strong> (IKY) is an OSINT aggregation platform. It runs multiple
open-source intelligence tools against a target in parallel and presents the results
in a unified interface. Targets can be email addresses, usernames, phone numbers, IP
addresses, domains, and more.
</p>
<p class="text-base-content/70 text-sm leading-relaxed">
Instead of running each tool manually, IKY handles orchestration, config management,
and result rendering so you can focus on analysis.
</p>
</section>
<div class="divider my-0"></div>
<section class="flex flex-col gap-3">
<h2 class="text-lg font-bold flex items-center gap-2">
<span class="size-2 rounded-full bg-primary inline-block"></span>
Tools
</h2>
<p class="text-base-content/70 text-sm leading-relaxed">
Each <strong>tool</strong> is a Go module that knows how to query one data source
(a website, an API, a local binary...). Tools declare:
</p>
<ul class="list-disc list-inside text-base-content/70 text-sm leading-relaxed space-y-1 ml-2">
<li>Which <strong>input types</strong> they accept (email, username, IP...)</li>
<li>Optional <strong>configuration fields</strong> (API keys, options)</li>
<li>Whether they require an <strong>external binary</strong> to be installed</li>
</ul>
<p class="text-base-content/70 text-sm leading-relaxed">
The <a href="/tools" class="link link-primary">Tools page</a> shows all registered tools
grouped by status:
</p>
<div class="flex flex-col gap-2 ml-2">
<div class="flex items-center gap-2 text-sm">
<span class="size-2 rounded-full bg-success shrink-0"></span>
<span><strong>Active</strong> - ready to run</span>
</div>
<div class="flex items-center gap-2 text-sm">
<span class="size-2 rounded-full bg-warning shrink-0"></span>
<span><strong>Active: config missing</strong> - needs an API key or required field</span>
</div>
<div class="flex items-center gap-2 text-sm">
<span class="size-2 rounded-full bg-error shrink-0"></span>
<span><strong>Active: unavailable</strong> - required binary not found on the system</span>
</div>
<div class="flex items-center gap-2 text-sm">
<span class="size-2 rounded-full bg-base-content/20 shrink-0"></span>
<span><strong>Disabled</strong> - excluded by the selected profile</span>
</div>
</div>
</section>
<div class="divider my-0"></div>
<section class="flex flex-col gap-3">
<h2 class="text-lg font-bold flex items-center gap-2">
<span class="size-2 rounded-full bg-primary inline-block"></span>
Profiles
</h2>
<p class="text-base-content/70 text-sm leading-relaxed">
A <strong>profile</strong> is a named search configuration. When you start a search,
you pick which profile to use. Profiles control:
</p>
<ul class="list-disc list-inside text-base-content/70 text-sm leading-relaxed space-y-1 ml-2">
<li><strong>Enabled list</strong> (whitelist): if set, only these tools run</li>
<li><strong>Disabled list</strong> (blacklist): these tools are always skipped</li>
<li><strong>Tool overrides</strong>: per-profile config that overrides global settings for specific tools</li>
<li><strong>Notes</strong>: a description of what the profile is for</li>
</ul>
<p class="text-base-content/70 text-sm leading-relaxed">
Two profiles are built-in and cannot be modified:
</p>
<div class="flex flex-col gap-2 ml-2">
<div class="card bg-base-200 p-3 text-sm">
<span class="font-mono font-bold">default</span>
<p class="text-base-content/60 text-xs mt-1">
All tools active with default settings. No restrictions.
</p>
</div>
<div class="card bg-base-200 p-3 text-sm">
<span class="font-mono font-bold">hard</span>
<p class="text-base-content/60 text-xs mt-1">
Aggressive mode. All tools active, including those that may send
notifications or leave traces at the target.
</p>
</div>
</div>
<p class="text-base-content/70 text-sm leading-relaxed">
You can create custom profiles on the <a href="/profiles" class="link link-primary">Profiles page</a>.
</p>
</section>
<div class="divider my-0"></div>
<section class="flex flex-col gap-3">
<h2 class="text-lg font-bold flex items-center gap-2">
<span class="size-2 rounded-full bg-primary inline-block"></span>
Configuration &amp; Overrides
</h2>
<p class="text-base-content/70 text-sm leading-relaxed">
Tool configuration works in two layers:
</p>
<ol class="list-decimal list-inside text-base-content/70 text-sm leading-relaxed space-y-2 ml-2">
<li>
<strong>Global config</strong>: set on the
<a href="/tools" class="link link-primary">Tools page</a>. Applied to every
search regardless of profile.
</li>
<li>
<strong>Profile override</strong>: set inside a profile. Takes precedence over
global config when that profile is used. Useful when you want a tool to behave
differently in specific contexts (e.g. slower rate-limiting in a "quiet" profile).
</li>
</ol>
<p class="text-base-content/70 text-sm leading-relaxed">
Config is stored in <code class="font-mono bg-base-300 px-1 rounded text-xs">config.yaml</code>.
Built-in profiles are hardcoded in Go and are never written to disk.
</p>
</section>
<div class="divider my-0"></div>
<section class="flex flex-col gap-3">
<h2 class="text-lg font-bold flex items-center gap-2">
<span class="size-2 rounded-full bg-primary inline-block"></span>
How a search runs
</h2>
<ol class="list-decimal list-inside text-base-content/70 text-sm leading-relaxed space-y-2 ml-2">
<li>You enter a target, select its type (email, username...) and pick a profile.</li>
<li>
The backend filters tools by input type and the profile's enabled/disabled rules,
then skips any tool with a missing required config field.
</li>
<li>All eligible tools run in parallel against the target.</li>
<li>
The frontend polls for results and renders them progressively as each tool finishes.
</li>
</ol>
<p class="text-base-content/70 text-sm leading-relaxed">
A search can be cancelled at any time from the results page.
Completed searches are kept in memory.
</p>
</section>
</div>
</div>
</Layout>

View File

@@ -0,0 +1,19 @@
---
import Layout from "@src/layouts/Layout.astro";
import HomePage from "@src/components/HomePage.svelte";
---
<Layout title="iknowyou">
<div class="max-w-3xl mx-auto px-4 py-6 flex flex-col gap-10">
<header class="text-center">
<img src="/logo.svg" class="m-auto w-36" alt="iknowyou" />
<h1 class="font-unbounded logo-gradient text-6xl tracking-[-0.11em] leading-normal mb-2 mt-0">i know you</h1>
<p class="text-base-content/50 text-sm max-w-xl mx-auto">
Centralizing your OSINT tools in one place.<br/>
<strong>Iknowyou</strong> is a self-hosted OSINT (Open-Source Intelligence) platform that centralises reconnaissance tools
into a single reactive web interface. Instead of juggling terminals, browser tabs, and disconnected CLI tools, you type a target once and get results in real time.
</p>
</header>
<HomePage client:only="svelte" />
</div>
</Layout>

View File

@@ -0,0 +1,21 @@
---
import Layout from "@src/layouts/Layout.astro";
import ProfileSettings from "@src/components/ProfileSettings.svelte";
---
<Layout title="Profiles">
<div class="max-w-4xl mx-auto px-4 pb-4">
<div class="mb-6">
<a href="/" class="btn btn-ghost btn-sm gap-1">← Back</a>
</div>
<div class="mb-6">
<h1 class="text-xl font-bold tracking-tight">Profiles</h1>
<p class="text-base-content/50 text-sm mt-1">
Manage search profiles: allowed/blocked tools and per-tool config overrides.
</p>
</div>
<ProfileSettings client:only="svelte" />
</div>
</Layout>

View File

@@ -0,0 +1,22 @@
---
import Layout from "@src/layouts/Layout.astro";
import SearchDetail from "@src/components/SearchDetail.svelte";
export async function getStaticPaths() {
return [{ params: { id: "_" } }];
}
// Shell page — SearchDetail reads the real ID from window.location.
const id = null;
---
<Layout title="Search">
<div class="max-w-4xl mx-auto px-4 pb-4">
<div class="mb-6">
<a href="/" class="btn btn-ghost btn-sm gap-1">← Back</a>
</div>
<SearchDetail {id} client:only="svelte" />
</div>
</Layout>

View File

@@ -0,0 +1,22 @@
---
import Layout from "@src/layouts/Layout.astro";
import ToolDetail from "@src/components/ToolDetail.svelte";
export async function getStaticPaths() {
return [{ params: { name: "_" } }];
}
// Shell page — ToolDetail reads the real name from window.location.
const name = null;
---
<Layout title="Tool">
<div class="max-w-3xl mx-auto px-4 pb-4">
<div class="mb-6">
<a href="/tools" class="btn btn-ghost btn-sm gap-1">← Tools</a>
</div>
<ToolDetail {name} client:only="svelte" />
</div>
</Layout>

View File

@@ -0,0 +1,20 @@
---
import Layout from "@src/layouts/Layout.astro";
import ToolList from "@src/components/ToolList.svelte";
---
<Layout title="Tools">
<div class="max-w-4xl mx-auto px-4 pb-4">
<div class="mb-6">
<a href="/" class="btn btn-ghost btn-sm gap-1">← Back</a>
</div>
<div class="mb-6">
<h1 class="text-xl font-bold tracking-tight">Tools</h1>
<p class="text-base-content/50 text-sm mt-1">All registered OSINT tools.</p>
</div>
<ToolList client:only="svelte" />
</div>
</Layout>

144
front/src/styles/gfm.css Normal file
View File

@@ -0,0 +1,144 @@
/* ANSI Terminal Output */
.ansi-output {
font-family: monospace;
font-size: 0.8rem;
line-height: 1.5;
white-space: pre-wrap;
word-break: break-all;
background-color: var(--color-base-100);
border: 1px solid color-mix(in srgb, var(--color-base-content) 10%, transparent);
border-radius: 0.5rem;
padding: 0.75rem 1rem;
overflow-x: auto;
max-height: 600px;
overflow-y: auto;
}
/* GitHub Flavored Markdown - Code Blocks */
.code-block {
border: 1px solid color-mix(in srgb, var(--color-base-content) 12%, transparent);
border-radius: 0.5rem;
overflow: hidden;
margin: 1.25rem 0;
font-size: 0.875rem;
}
.code-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 0.4rem 0.75rem;
background-color: color-mix(in srgb, var(--color-base-content) 6%, transparent);
border-bottom: 1px solid color-mix(in srgb, var(--color-base-content) 10%, transparent);
}
.code-lang {
font-family: monospace;
font-size: 0.75rem;
color: var(--color-base-content);
opacity: 0.5;
text-transform: lowercase;
}
.copy-btn {
display: flex;
align-items: center;
gap: 0.3rem;
font-size: 0.7rem;
padding: 0.2rem 0.5rem;
border-radius: 0.3rem;
border: 1px solid color-mix(in srgb, var(--color-base-content) 15%, transparent);
background: transparent;
color: var(--color-base-content);
opacity: 0.5;
cursor: pointer;
transition: opacity 0.15s;
}
.copy-btn:hover { opacity: 1; }
.copy-btn.copied { color: var(--color-success); opacity: 1; }
.code-block pre {
margin: 0 !important;
border-radius: 0 !important;
border: none !important;
padding: 1rem 0 !important;
}
.code-block pre code {
counter-reset: line;
}
.code-block pre code .line::before {
counter-increment: line;
content: counter(line);
display: inline-block;
width: 2rem;
text-align: right;
margin-right: 1.25rem;
color: var(--color-base-content);
opacity: 0.25;
user-select: none;
}
/* GitHub Flavored Markdown - Alerts */
.markdown-alert {
border-left: 4px solid;
padding: 0.75rem 1rem;
border-radius: 0 0.5rem 0.5rem 0;
margin: 1.25rem 0;
}
.markdown-alert p {
margin: 0;
}
.markdown-alert-title {
display: flex;
align-items: center;
gap: 0.4rem;
font-weight: 600;
font-size: 0.875rem;
margin-bottom: 0.4rem;
}
.markdown-alert-note {
border-color: var(--color-info);
background-color: color-mix(in srgb, var(--color-info) 10%, transparent);
}
.markdown-alert-note .markdown-alert-title {
color: var(--color-info);
}
.markdown-alert-tip {
border-color: var(--color-success);
background-color: color-mix(in srgb, var(--color-success) 10%, transparent);
}
.markdown-alert-tip .markdown-alert-title {
color: var(--color-success);
}
.markdown-alert-important {
border-color: var(--color-secondary);
background-color: color-mix(in srgb, var(--color-secondary) 10%, transparent);
}
.markdown-alert-important .markdown-alert-title {
color: var(--color-secondary);
}
.markdown-alert-warning {
border-color: var(--color-warning);
background-color: color-mix(in srgb, var(--color-warning) 10%, transparent);
}
.markdown-alert-warning .markdown-alert-title {
color: var(--color-warning);
}
.markdown-alert-caution {
border-color: var(--color-error);
background-color: color-mix(in srgb, var(--color-error) 10%, transparent);
}
.markdown-alert-caution .markdown-alert-title {
color: var(--color-error);
}

View File

@@ -0,0 +1,84 @@
@import "tailwindcss";
@plugin "@tailwindcss/typography";
@plugin "daisyui" {
themes: dark --prefersdark --default;
}
@plugin "daisyui/theme" {
name: "dark";
default: true;
prefersdark: true;
color-scheme: dark;
--color-primary: #E9F7A7;
--color-primary-content: #11111b;
--color-secondary: #f5c2e7;
--color-secondary-content: #11111b;
--color-accent: #94e2d5;
--color-accent-content: #11111b;
--color-neutral: #313244;
--color-neutral-content: #cdd6f4;
--color-base-300: #1a1a2a;
--color-base-200: #12121F;
--color-base-100: #0C0C16;
--color-base-content: #cdd6f4;
--color-info: #89b4fa;
--color-info-content: #11111b;
--color-success: #a6e3a1;
--color-success-content: #11111b;
--color-warning: #f9e2af;
--color-warning-content: #11111b;
--color-error: #f38ba8;
--color-error-content: #11111b;
--radius-selector: 1rem;
--radius-field: 0.5rem;
--radius-box: 1rem;
--size-selector: 0.25rem;
--size-field: 0.25rem;
--border: 1px;
--depth: 0;
--noise: 0;
}
@theme {
--font-unbounded: "Unbounded", sans-serif;
}
@utility logo-gradient {
@apply bg-gradient-to-b from-[#E9F7A7] via-[#E9BED9] to-[#BBA6EB] bg-clip-text text-transparent;
}
.animate-fade-in {
animation: fadeIn 0.6s ease-out;
}
@keyframes fadeIn {
from {
opacity: 0;
transform: translateY(10px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
@font-face {
font-family: 'Unbounded';
src: url('/fonts/unbounded-black.ttf') format('truetype');
font-weight: 900;
font-style: normal;
font-display: swap;
}

View File

@@ -0,0 +1,20 @@
@reference "./global.css";
.link-card {
@apply flex items-center justify-between p-4 bg-base-200 rounded-xl border border-base-300 no-underline hover:border-primary transition-all mb-4;
}
.link-card h4 {
@apply font-bold m-0 text-base;
}
.link-card p {
@apply text-sm opacity-70 m-0;
}
.link-card::after {
content: "";
@apply w-5 h-5 bg-current shrink-0 ml-4;
mask: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="black" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"/><polyline points="15 3 21 3 21 9"/><line x1="10" y1="14" x2="21" y2="3"/></svg>') no-repeat center;
mask-size: contain;
}

5
front/svelte.config.js Normal file
View File

@@ -0,0 +1,5 @@
import { vitePreprocess } from '@astrojs/svelte';
export default {
preprocess: vitePreprocess(),
}

12
front/tsconfig.json Normal file
View File

@@ -0,0 +1,12 @@
{
"extends": "astro/tsconfigs/strict",
"include": [".astro/types.d.ts", "**/*"],
"exclude": ["dist"],
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@src/*": ["src/*"],
"@lib/*": ["src/lib/*"]
}
}
}