6 Commits

18 changed files with 360 additions and 127 deletions

View File

@ -7,10 +7,10 @@
"@astrojs/react": "^4.2.0", "@astrojs/react": "^4.2.0",
"@types/react": "^19.0.8", "@types/react": "^19.0.8",
"@types/react-dom": "^19.0.3", "@types/react-dom": "^19.0.3",
"astro": "^5.2.3", "astro": "^5.2.5",
"react": "^19.0.0", "react": "^19.0.0",
"react-dom": "^19.0.0", "react-dom": "^19.0.0",
"sass": "^1.83.4", "sass": "^1.84.0",
"scss": "^0.2.4", "scss": "^0.2.4",
}, },
}, },
@ -20,7 +20,7 @@
"@astrojs/compiler": ["@astrojs/compiler@2.10.3", "", {}, "sha512-bL/O7YBxsFt55YHU021oL+xz+B/9HvGNId3F9xURN16aeqDK9juHGktdkCSXz+U4nqFACq6ZFvWomOzhV+zfPw=="], "@astrojs/compiler": ["@astrojs/compiler@2.10.3", "", {}, "sha512-bL/O7YBxsFt55YHU021oL+xz+B/9HvGNId3F9xURN16aeqDK9juHGktdkCSXz+U4nqFACq6ZFvWomOzhV+zfPw=="],
"@astrojs/internal-helpers": ["@astrojs/internal-helpers@0.5.0", "", {}, "sha512-CgB5ZaZO1PFG+rbjF3HnA7G6gIBjJ070xb7bUjeu5Gqqufma+t6fpuRWMXnK2iEO3zVyX7e/xplPlqtFKy/lvw=="], "@astrojs/internal-helpers": ["@astrojs/internal-helpers@0.5.1", "", {}, "sha512-M7rAge1n2+aOSxNvKUFa0u/KFn0W+sZy7EW91KOSERotm2Ti8qs+1K0xx3zbOxtAVrmJb5/J98eohVvvEqtNkw=="],
"@astrojs/markdown-remark": ["@astrojs/markdown-remark@6.1.0", "", { "dependencies": { "@astrojs/prism": "3.2.0", "github-slugger": "^2.0.0", "hast-util-from-html": "^2.0.3", "hast-util-to-text": "^4.0.2", "import-meta-resolve": "^4.1.0", "js-yaml": "^4.1.0", "mdast-util-definitions": "^6.0.0", "rehype-raw": "^7.0.0", "rehype-stringify": "^10.0.1", "remark-gfm": "^4.0.0", "remark-parse": "^11.0.0", "remark-rehype": "^11.1.1", "remark-smartypants": "^3.0.2", "shiki": "^1.29.1", "smol-toml": "^1.3.1", "unified": "^11.0.5", "unist-util-remove-position": "^5.0.0", "unist-util-visit": "^5.0.0", "unist-util-visit-parents": "^6.0.1", "vfile": "^6.0.3" } }, "sha512-emZNNSTPGgPc3V399Cazpp5+snogjaF04ocOSQn9vy3Kw/eIC4vTQjXOrWDEoSEy+AwPDZX9bQ4wd3bxhpmGgQ=="], "@astrojs/markdown-remark": ["@astrojs/markdown-remark@6.1.0", "", { "dependencies": { "@astrojs/prism": "3.2.0", "github-slugger": "^2.0.0", "hast-util-from-html": "^2.0.3", "hast-util-to-text": "^4.0.2", "import-meta-resolve": "^4.1.0", "js-yaml": "^4.1.0", "mdast-util-definitions": "^6.0.0", "rehype-raw": "^7.0.0", "rehype-stringify": "^10.0.1", "remark-gfm": "^4.0.0", "remark-parse": "^11.0.0", "remark-rehype": "^11.1.1", "remark-smartypants": "^3.0.2", "shiki": "^1.29.1", "smol-toml": "^1.3.1", "unified": "^11.0.5", "unist-util-remove-position": "^5.0.0", "unist-util-visit": "^5.0.0", "unist-util-visit-parents": "^6.0.1", "vfile": "^6.0.3" } }, "sha512-emZNNSTPGgPc3V399Cazpp5+snogjaF04ocOSQn9vy3Kw/eIC4vTQjXOrWDEoSEy+AwPDZX9bQ4wd3bxhpmGgQ=="],
@ -304,7 +304,7 @@
"array-iterate": ["array-iterate@2.0.1", "", {}, "sha512-I1jXZMjAgCMmxT4qxXfPXa6SthSoE8h6gkSI9BGGNv8mP8G/v0blc+qFnZu6K42vTOiuME596QaLO0TP3Lk0xg=="], "array-iterate": ["array-iterate@2.0.1", "", {}, "sha512-I1jXZMjAgCMmxT4qxXfPXa6SthSoE8h6gkSI9BGGNv8mP8G/v0blc+qFnZu6K42vTOiuME596QaLO0TP3Lk0xg=="],
"astro": ["astro@5.2.3", "", { "dependencies": { "@astrojs/compiler": "^2.10.3", "@astrojs/internal-helpers": "0.5.0", "@astrojs/markdown-remark": "6.1.0", "@astrojs/telemetry": "3.2.0", "@oslojs/encoding": "^1.1.0", "@rollup/pluginutils": "^5.1.4", "@types/cookie": "^0.6.0", "acorn": "^8.14.0", "aria-query": "^5.3.2", "axobject-query": "^4.1.0", "boxen": "8.0.1", "ci-info": "^4.1.0", "clsx": "^2.1.1", "common-ancestor-path": "^1.0.1", "cookie": "^0.7.2", "cssesc": "^3.0.0", "debug": "^4.4.0", "deterministic-object-hash": "^2.0.2", "devalue": "^5.1.1", "diff": "^5.2.0", "dlv": "^1.1.3", "dset": "^3.1.4", "es-module-lexer": "^1.6.0", "esbuild": "^0.24.2", "estree-walker": "^3.0.3", "fast-glob": "^3.3.3", "flattie": "^1.1.1", "github-slugger": "^2.0.0", "html-escaper": "3.0.3", "http-cache-semantics": "^4.1.1", "js-yaml": "^4.1.0", "kleur": "^4.1.5", "magic-string": "^0.30.17", "magicast": "^0.3.5", "micromatch": "^4.0.8", "mrmime": "^2.0.0", "neotraverse": "^0.6.18", "p-limit": "^6.2.0", "p-queue": "^8.1.0", "preferred-pm": "^4.1.0", "prompts": "^2.4.2", "rehype": "^13.0.2", "semver": "^7.7.0", "shiki": "^1.29.2", "tinyexec": "^0.3.2", "tsconfck": "^3.1.4", "ultrahtml": "^1.5.3", "unist-util-visit": "^5.0.0", "unstorage": "^1.14.4", "vfile": "^6.0.3", "vite": "^6.0.11", "vitefu": "^1.0.5", "which-pm": "^3.0.0", "xxhash-wasm": "^1.1.0", "yargs-parser": "^21.1.1", "yocto-spinner": "^0.2.0", "zod": "^3.24.1", "zod-to-json-schema": "^3.24.1", "zod-to-ts": "^1.2.0" }, "optionalDependencies": { "sharp": "^0.33.3" }, "bin": { "astro": "astro.js" } }, "sha512-04AceE/gVihtO3V28Ss0+tPPgLHGoulXrm1E7fUVPYyQu7y6w8+oXfYyzh1lpy+uEYiebjH7AFlJoz73anBmZA=="], "astro": ["astro@5.2.5", "", { "dependencies": { "@astrojs/compiler": "^2.10.3", "@astrojs/internal-helpers": "0.5.1", "@astrojs/markdown-remark": "6.1.0", "@astrojs/telemetry": "3.2.0", "@oslojs/encoding": "^1.1.0", "@rollup/pluginutils": "^5.1.4", "@types/cookie": "^0.6.0", "acorn": "^8.14.0", "aria-query": "^5.3.2", "axobject-query": "^4.1.0", "boxen": "8.0.1", "ci-info": "^4.1.0", "clsx": "^2.1.1", "common-ancestor-path": "^1.0.1", "cookie": "^0.7.2", "cssesc": "^3.0.0", "debug": "^4.4.0", "deterministic-object-hash": "^2.0.2", "devalue": "^5.1.1", "diff": "^5.2.0", "dlv": "^1.1.3", "dset": "^3.1.4", "es-module-lexer": "^1.6.0", "esbuild": "^0.24.2", "estree-walker": "^3.0.3", "fast-glob": "^3.3.3", "flattie": "^1.1.1", "github-slugger": "^2.0.0", "html-escaper": "3.0.3", "http-cache-semantics": "^4.1.1", "js-yaml": "^4.1.0", "kleur": "^4.1.5", "magic-string": "^0.30.17", "magicast": "^0.3.5", "micromatch": "^4.0.8", "mrmime": "^2.0.0", "neotraverse": "^0.6.18", "p-limit": "^6.2.0", "p-queue": "^8.1.0", "preferred-pm": "^4.1.1", "prompts": "^2.4.2", "rehype": "^13.0.2", "semver": "^7.7.1", "shiki": "^1.29.2", "tinyexec": "^0.3.2", "tsconfck": "^3.1.4", "ultrahtml": "^1.5.3", "unist-util-visit": "^5.0.0", "unstorage": "^1.14.4", "vfile": "^6.0.3", "vite": "^6.0.11", "vitefu": "^1.0.5", "which-pm": "^3.0.1", "xxhash-wasm": "^1.1.0", "yargs-parser": "^21.1.1", "yocto-spinner": "^0.2.0", "zod": "^3.24.1", "zod-to-json-schema": "^3.24.1", "zod-to-ts": "^1.2.0" }, "optionalDependencies": { "sharp": "^0.33.3" }, "bin": { "astro": "astro.js" } }, "sha512-AYXyYkc+c5xbKTm48FyQA91y81nXyNPAaoyafR0LUugE4lAwuvIUcXDBfMzmbuP1lGRvsE33G2oypv6gbGaPFg=="],
"axobject-query": ["axobject-query@4.1.0", "", {}, "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ=="], "axobject-query": ["axobject-query@4.1.0", "", {}, "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ=="],
@ -664,7 +664,7 @@
"postcss": ["postcss@8.5.1", "", { "dependencies": { "nanoid": "^3.3.8", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-6oz2beyjc5VMn/KV1pPw8fliQkhBXrVn1Z3TVyqZxU8kZpzEKhBdmCFqI6ZbmGtamQvQGuU1sgPTk8ZrXDD7jQ=="], "postcss": ["postcss@8.5.1", "", { "dependencies": { "nanoid": "^3.3.8", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-6oz2beyjc5VMn/KV1pPw8fliQkhBXrVn1Z3TVyqZxU8kZpzEKhBdmCFqI6ZbmGtamQvQGuU1sgPTk8ZrXDD7jQ=="],
"preferred-pm": ["preferred-pm@4.1.0", "", { "dependencies": { "find-up-simple": "^1.0.0", "find-yarn-workspace-root2": "1.2.16", "which-pm": "^3.0.0" } }, "sha512-cNKMVcSvE3hZBRukdRsvPtlUaM2fYLFbIvBz620XaDA5SjHsCRsAjNE2baI90Hh7QvJIjCHJYnpPtyqJ1/kOjQ=="], "preferred-pm": ["preferred-pm@4.1.1", "", { "dependencies": { "find-up-simple": "^1.0.0", "find-yarn-workspace-root2": "1.2.16", "which-pm": "^3.0.1" } }, "sha512-rU+ZAv1Ur9jAUZtGPebQVQPzdGhNzaEiQ7VL9+cjsAWPHFYOccNXPNiev1CCDSOg/2j7UujM7ojNhpkuILEVNQ=="],
"prismjs": ["prismjs@1.29.0", "", {}, "sha512-Kx/1w86q/epKcmte75LNrEoT+lX8pBpavuAbvJWRXar7Hz8jrtF+e3vY751p0R8H9HdArwaCTNDDzHg/ScJK1Q=="], "prismjs": ["prismjs@1.29.0", "", {}, "sha512-Kx/1w86q/epKcmte75LNrEoT+lX8pBpavuAbvJWRXar7Hz8jrtF+e3vY751p0R8H9HdArwaCTNDDzHg/ScJK1Q=="],
@ -722,13 +722,13 @@
"run-parallel": ["run-parallel@1.2.0", "", { "dependencies": { "queue-microtask": "^1.2.2" } }, "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA=="], "run-parallel": ["run-parallel@1.2.0", "", { "dependencies": { "queue-microtask": "^1.2.2" } }, "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA=="],
"sass": ["sass@1.83.4", "", { "dependencies": { "chokidar": "^4.0.0", "immutable": "^5.0.2", "source-map-js": ">=0.6.2 <2.0.0" }, "optionalDependencies": { "@parcel/watcher": "^2.4.1" }, "bin": { "sass": "sass.js" } }, "sha512-B1bozCeNQiOgDcLd33e2Cs2U60wZwjUUXzh900ZyQF5qUasvMdDZYbQ566LJu7cqR+sAHlAfO6RMkaID5s6qpA=="], "sass": ["sass@1.84.0", "", { "dependencies": { "chokidar": "^4.0.0", "immutable": "^5.0.2", "source-map-js": ">=0.6.2 <2.0.0" }, "optionalDependencies": { "@parcel/watcher": "^2.4.1" }, "bin": { "sass": "sass.js" } }, "sha512-XDAbhEPJRxi7H0SxrnOpiXFQoUJHwkR2u3Zc4el+fK/Tt5Hpzw5kkQ59qVDfvdaUq6gCrEZIbySFBM2T9DNKHg=="],
"scheduler": ["scheduler@0.25.0", "", {}, "sha512-xFVuu11jh+xcO7JOAGJNOXld8/TcEHK/4CituBUeUb5hqxJLj9YuemAEuvm9gQ/+pgXYfbQuqAkiYu+u7YEsNA=="], "scheduler": ["scheduler@0.25.0", "", {}, "sha512-xFVuu11jh+xcO7JOAGJNOXld8/TcEHK/4CituBUeUb5hqxJLj9YuemAEuvm9gQ/+pgXYfbQuqAkiYu+u7YEsNA=="],
"scss": ["scss@0.2.4", "", { "dependencies": { "ometa": "0.2.2" } }, "sha512-4u8V87F+Q/upVhUmhPnB4C1R11xojkRkWjExL2v0CX2EXTg18VrKd+9JWoeyCp2VEMdSpJsyAvVU+rVjogh51A=="], "scss": ["scss@0.2.4", "", { "dependencies": { "ometa": "0.2.2" } }, "sha512-4u8V87F+Q/upVhUmhPnB4C1R11xojkRkWjExL2v0CX2EXTg18VrKd+9JWoeyCp2VEMdSpJsyAvVU+rVjogh51A=="],
"semver": ["semver@7.7.0", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-DrfFnPzblFmNrIZzg5RzHegbiRWg7KMR7btwi2yjHwx06zsUbO5g613sVwEV7FTwmzJu+Io0lJe2GJ3LxqpvBQ=="], "semver": ["semver@7.7.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA=="],
"sharp": ["sharp@0.33.5", "", { "dependencies": { "color": "^4.2.3", "detect-libc": "^2.0.3", "semver": "^7.6.3" }, "optionalDependencies": { "@img/sharp-darwin-arm64": "0.33.5", "@img/sharp-darwin-x64": "0.33.5", "@img/sharp-libvips-darwin-arm64": "1.0.4", "@img/sharp-libvips-darwin-x64": "1.0.4", "@img/sharp-libvips-linux-arm": "1.0.5", "@img/sharp-libvips-linux-arm64": "1.0.4", "@img/sharp-libvips-linux-s390x": "1.0.4", "@img/sharp-libvips-linux-x64": "1.0.4", "@img/sharp-libvips-linuxmusl-arm64": "1.0.4", "@img/sharp-libvips-linuxmusl-x64": "1.0.4", "@img/sharp-linux-arm": "0.33.5", "@img/sharp-linux-arm64": "0.33.5", "@img/sharp-linux-s390x": "0.33.5", "@img/sharp-linux-x64": "0.33.5", "@img/sharp-linuxmusl-arm64": "0.33.5", "@img/sharp-linuxmusl-x64": "0.33.5", "@img/sharp-wasm32": "0.33.5", "@img/sharp-win32-ia32": "0.33.5", "@img/sharp-win32-x64": "0.33.5" } }, "sha512-haPVm1EkS9pgvHrQ/F3Xy+hgcuMV0Wm9vfIBSiwZ05k+xgb0PkBQpGsAA/oWdDobNaZTH5ppvHtzCFbnSEwHVw=="], "sharp": ["sharp@0.33.5", "", { "dependencies": { "color": "^4.2.3", "detect-libc": "^2.0.3", "semver": "^7.6.3" }, "optionalDependencies": { "@img/sharp-darwin-arm64": "0.33.5", "@img/sharp-darwin-x64": "0.33.5", "@img/sharp-libvips-darwin-arm64": "1.0.4", "@img/sharp-libvips-darwin-x64": "1.0.4", "@img/sharp-libvips-linux-arm": "1.0.5", "@img/sharp-libvips-linux-arm64": "1.0.4", "@img/sharp-libvips-linux-s390x": "1.0.4", "@img/sharp-libvips-linux-x64": "1.0.4", "@img/sharp-libvips-linuxmusl-arm64": "1.0.4", "@img/sharp-libvips-linuxmusl-x64": "1.0.4", "@img/sharp-linux-arm": "0.33.5", "@img/sharp-linux-arm64": "0.33.5", "@img/sharp-linux-s390x": "0.33.5", "@img/sharp-linux-x64": "0.33.5", "@img/sharp-linuxmusl-arm64": "0.33.5", "@img/sharp-linuxmusl-x64": "0.33.5", "@img/sharp-wasm32": "0.33.5", "@img/sharp-win32-ia32": "0.33.5", "@img/sharp-win32-x64": "0.33.5" } }, "sha512-haPVm1EkS9pgvHrQ/F3Xy+hgcuMV0Wm9vfIBSiwZ05k+xgb0PkBQpGsAA/oWdDobNaZTH5ppvHtzCFbnSEwHVw=="],
@ -814,7 +814,7 @@
"web-namespaces": ["web-namespaces@2.0.1", "", {}, "sha512-bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ13TsorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPG2be8uPrqsQ=="], "web-namespaces": ["web-namespaces@2.0.1", "", {}, "sha512-bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ13TsorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPG2be8uPrqsQ=="],
"which-pm": ["which-pm@3.0.0", "", { "dependencies": { "load-yaml-file": "^0.2.0" } }, "sha512-ysVYmw6+ZBhx3+ZkcPwRuJi38ZOTLJJ33PSHaitLxSKUMsh0LkKd0nC69zZCwt5D+AYUcMK2hhw4yWny20vSGg=="], "which-pm": ["which-pm@3.0.1", "", { "dependencies": { "load-yaml-file": "^0.2.0" } }, "sha512-v2JrMq0waAI4ju1xU5x3blsxBBMgdgZve580iYMN5frDaLGjbA24fok7wKCsya8KLVO19Ju4XDc5+zTZCJkQfg=="],
"which-pm-runs": ["which-pm-runs@1.1.0", "", {}, "sha512-n1brCuqClxfFfq/Rb0ICg9giSZqCS+pLtccdag6C2HyufBrh3fBOiy9nb6ggRMvWOVH5GrdJskj5iGTZNxd7SA=="], "which-pm-runs": ["which-pm-runs@1.1.0", "", {}, "sha512-n1brCuqClxfFfq/Rb0ICg9giSZqCS+pLtccdag6C2HyufBrh3fBOiy9nb6ggRMvWOVH5GrdJskj5iGTZNxd7SA=="],
@ -864,6 +864,8 @@
"prompts/kleur": ["kleur@3.0.3", "", {}, "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w=="], "prompts/kleur": ["kleur@3.0.3", "", {}, "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w=="],
"sharp/semver": ["semver@7.7.0", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-DrfFnPzblFmNrIZzg5RzHegbiRWg7KMR7btwi2yjHwx06zsUbO5g613sVwEV7FTwmzJu+Io0lJe2GJ3LxqpvBQ=="],
"unstorage/chokidar": ["chokidar@3.6.0", "", { "dependencies": { "anymatch": "~3.1.2", "braces": "~3.0.2", "glob-parent": "~5.1.2", "is-binary-path": "~2.1.0", "is-glob": "~4.0.1", "normalize-path": "~3.0.0", "readdirp": "~3.6.0" }, "optionalDependencies": { "fsevents": "~2.3.2" } }, "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw=="], "unstorage/chokidar": ["chokidar@3.6.0", "", { "dependencies": { "anymatch": "~3.1.2", "braces": "~3.0.2", "glob-parent": "~5.1.2", "is-binary-path": "~2.1.0", "is-glob": "~4.0.1", "normalize-path": "~3.0.0", "readdirp": "~3.6.0" }, "optionalDependencies": { "fsevents": "~2.3.2" } }, "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw=="],
"ansi-align/string-width/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="], "ansi-align/string-width/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="],

View File

@ -12,10 +12,10 @@
"@astrojs/react": "^4.2.0", "@astrojs/react": "^4.2.0",
"@types/react": "^19.0.8", "@types/react": "^19.0.8",
"@types/react-dom": "^19.0.3", "@types/react-dom": "^19.0.3",
"astro": "^5.2.3", "astro": "^5.2.5",
"react": "^19.0.0", "react": "^19.0.0",
"react-dom": "^19.0.0", "react-dom": "^19.0.0",
"sass": "^1.83.4", "sass": "^1.84.0",
"scss": "^0.2.4" "scss": "^0.2.4"
} }
} }

View File

@ -16,6 +16,10 @@ const {href, display, color = "transparent"} = Astro.props
height: 100%; height: 100%;
padding: 0 20px 0 20px; padding: 0 20px 0 20px;
&:hover { background-color: var(--hf-button-hover-color) } &:hover {
background-color: var(--hf-button-hover-color) !important;
& > a { color: black }
}
} }
</style> </style>

View File

@ -1,21 +0,0 @@
import { working_dir } from "./fs"
function ls() {
}
function pwd() {
}
function cat() {
}
const commands = {
["ls"]: ls,
["pwd"]: pwd,
["cat"]: cat,
}
export { commands }

View File

@ -1,32 +0,0 @@
let working_dir = "user"
const enum EntryType {
Directory,
File
}
type File = string
type Entry<T> = {
readonly inner: T,
readonly type: EntryType
}
function Entry<T = File>(inner: T): Entry<T> {
const type = typeof inner == "object" ? EntryType.Directory : EntryType.File
return { inner: inner, type: type }
}
const user = {
["about_me.txt"]: Entry(""),
["services.txt"]: Entry("")
}
const home = {
["user"]: Entry(user)
}
const root = {
["bin"]: Entry({}),
["home"]: Entry(home)
}
const fs = {
["/"]: Entry(root)
}
export { fs, working_dir }

View File

@ -2,6 +2,7 @@ const red = (s: string) => <span className="red">{s}</span>
const green = (s: string) => <span className="green">{s}</span> const green = (s: string) => <span className="green">{s}</span>
const blue = (s: string) => <span className="blue">{s}</span> const blue = (s: string) => <span className="blue">{s}</span>
const cyan = (s: string) => <span className="cyan">{s}</span> const cyan = (s: string) => <span className="cyan">{s}</span>
const bold = (s: string) => <span className="bold">{s}</span>
export default function rgb(s: string, Ru8: number, Gu8: number, Bu8: number) { export default function rgb(s: string, Ru8: number, Gu8: number, Bu8: number) {
return <span style={{color: `rgb(${Ru8},${Gu8},${Bu8})`}}>{s}</span> return <span style={{color: `rgb(${Ru8},${Gu8},${Bu8})`}}>{s}</span>
@ -11,5 +12,6 @@ export {
red, red,
green, green,
blue, blue,
cyan cyan,
bold
} }

View File

@ -0,0 +1,53 @@
import type { JSX } from "react"
import { bold } from "../color"
import { get_working_dir_name_full, set_working_dir, SetDirStatus } from "../fs/fn"
type args = string[]
type command = JSX.Element | boolean
function parse_ls(entries: JSX.Element[]) {
return <div className="horizontal-display">
</div>
}
function cd(args: args): command {
const new_dir_status = set_working_dir(args[1])
if (new_dir_status === SetDirStatus.NotADirectory) {
return <p>{"cd: \""}{bold(args[1])}{"\" is not a directory"}</p>
} else if (new_dir_status === SetDirStatus.NotFound) {
return <p>{"cd: The directory \""}{bold(args[1])}{"\" does not exist"}</p>
}
return true
}
function ls(args: args): command {
// if (args[1] === undefined) {
// for (const dir_name in working_dir) {
// }
// return <p>{`${working_dir}`}</p>
// }
return true
}
function pwd(args: args): command {
return <p>{`${get_working_dir_name_full()}`}</p>
}
function cat(args: args): command {
return true
}
interface commands_list {
[index: string]: (args: args) => command
}
const commands: commands_list = {
["cd"]: cd,
["ls"]: ls,
["pwd"]: pwd,
["cat"]: cat,
}
export default commands

View File

@ -0,0 +1,37 @@
import type { JSX } from "react";
import commands from "./list";
import { bold } from "../color";
function trim(stdin: string): string {
const trimmed_str: string[] = []
stdin.split(" ").forEach(s => { if (s !== "") { trimmed_str.push(s) } })
return trimmed_str.join(" ")
}
function to_args(trimmed_str: string): string[] {
return trimmed_str.split(" ")
}
function valid_command(args: string[]): JSX.Element | undefined {
for (const command_in_list in commands) {
const command = args[0]
if (command === command_in_list) {
return commands[command_in_list](args)
}
}
return
}
function unknown_command(cmd_name: string) {
return <p>{"shell: Unknown command: "}{bold(cmd_name)}</p>
}
export default function run(stdin: string) {
const args = to_args(trim(stdin))
const command = valid_command(args)
if (args[0] !== "" && !command) {
return unknown_command(args[0])
}
return command ? command : <></>
}

View File

@ -0,0 +1,60 @@
import Display from "./prompt"
import run from "./command/run"
import { type newElement } from "../terminal/exec";
import type { JSX } from "react";
const enum Key {
Enter = "Enter",
ArrowUp = "ArrowUp",
ArrowDown = "ArrowDown",
Tab = "Tab"
}
function display_prompt() {
return <div className="shell-prompt">
<Display/>
<input className="shell-ps1" type="text" spellCheck={false}/>
</div>
}
function get_current_prompt(): HTMLInputElement | undefined {
const shell_input = document.getElementsByClassName("shell-ps1")
return shell_input[shell_input.length-1] as HTMLInputElement
}
function new_prompt(): JSX.Element {
const shell_prompts = document.getElementsByClassName("shell-ps1")
Array.from(shell_prompts).forEach(shellps1 => {
(shellps1 as HTMLInputElement).disabled = true
})
return display_prompt()
}
function keyboard_events(terminal_window: HTMLElement, new_elements_f: newElement) {
const terminal_event = (keyboard_event: KeyboardEvent) => {
if (keyboard_event.key === Key.Enter) {
const current_prompt = get_current_prompt()
if (current_prompt) {
const prompt = new_prompt()
const output = run(current_prompt.value)
new_elements_f([output, prompt])
terminal_window.removeEventListener("keydown", terminal_event)
}
} else if (keyboard_event.key === Key.ArrowUp) {
} else if (keyboard_event.key === Key.ArrowDown) {
} else if (keyboard_event.key === Key.Tab) {
}
}
terminal_window.addEventListener("keydown", terminal_event)
}
export {
keyboard_events,
display_prompt
}

View File

@ -0,0 +1,75 @@
import { Entry, EntryType, fs, type FsEntrySignature } from "./fs"
let working_dir = ["/", "home", "user"]
function get_working_dir_name(): string {
return working_dir[working_dir.length-1]
}
function get_working_dir_name_full(): string {
const w_dir_clone = [...working_dir]
const root = w_dir_clone.shift()
if (root) {
return root+w_dir_clone.join("/")
}
return "shift-error"
}
const enum SetDirStatus {
Valid,
NotFound,
NotADirectory
}
interface FsIterEntry {
readonly entry: FsEntrySignature | null,
readonly status: SetDirStatus
}
function iter_fs_to_goal(w_dir_clone: string[]): FsIterEntry {
let next_iter = fs[0]
for (const w_dir of w_dir_clone) {
if (w_dir === "/") { continue }
if (next_iter.inner) {
const found = next_iter.inner.find(entry => entry.name === w_dir)
if (!found) {
return { entry: null, status: SetDirStatus.NotFound }
}
if (found.type !== EntryType.Directory) {
return { entry: null, status: SetDirStatus.NotADirectory }
}
if (found.name === w_dir_clone[w_dir_clone.length-1]) {
return { entry: next_iter, status: SetDirStatus.Valid }
} else {
next_iter = found.inner as FsEntrySignature
}
}
}
return { entry: null, status: SetDirStatus.NotFound }
}
function set_working_dir(name: string): SetDirStatus {
if (name === ".") { return SetDirStatus.Valid }
const w_dir_clone = [...working_dir]
if (name === "..") { w_dir_clone.pop() } else { w_dir_clone.push(name) }
const iter_status = iter_fs_to_goal(w_dir_clone)
if (iter_status.status === SetDirStatus.Valid) {
working_dir = w_dir_clone
}
return iter_status.status
}
function working_dir_entries() {
const w_dir_clone = [...working_dir]
const iter_status = iter_fs_to_goal(w_dir_clone)
}
export {
get_working_dir_name,
get_working_dir_name_full,
set_working_dir,
SetDirStatus
}

View File

@ -0,0 +1,51 @@
const enum EntryType {
Directory,
File
}
const enum Permissions {
r,
w,
rw
}
type FsEntrySignature = Entry<Entry<{}>[]> //I did this!
const user = [
Entry("about_me.txt", "about me inside", Permissions.rw),
Entry("services.txt", "services inside", Permissions.rw),
Entry("hi", [], Permissions.rw)
]
const home = [
Entry("user", user, Permissions.rw)
]
const root = [
Entry("home", home, Permissions.r),
Entry("bin", {}, Permissions.r),
]
const fs = [
Entry("/", root, Permissions.r)
]
type File = string
interface Entry<T = File> {
readonly inner?: T,
readonly name: string,
readonly type: EntryType,
readonly permissions: Permissions
}
function Entry<T = File>(name: string, inner: T, permissions: Permissions): Entry<T> {
return {
type: typeof inner == "object" ? EntryType.Directory : EntryType.File,
inner: inner,
name: name,
permissions: permissions
}
}
export {
fs,
type FsEntrySignature,
EntryType,
Permissions,
Entry
}

View File

@ -1,10 +1,23 @@
import { working_dir } from "../fs" import { get_working_dir_name } from "./fs/fn"
import rgb, { cyan, green } from "./color" import { cyan, green } from "./color"
const GetWorkingDir = () => working_dir === "user" ? "~" : working_dir const userAgent = navigator.userAgent
const browser_name_fallible = userAgent.match(/Firefox.\d+[\d.\d]+|Chrome.\d+[\d.\d]+/gm)?.map(f => f.split("/")[0])
export default function Prompt() { let browser_name = "unknown"
if (browser_name_fallible) {
browser_name = browser_name_fallible[0] === "Firefox" ? "gecko" : "chromium"
}
function working_dir() {
const name = get_working_dir_name()
return name === "user" ? "~" : name
}
export default function Display() {
const user = cyan("user") const user = cyan("user")
const dir = green(GetWorkingDir()) const dir = green(working_dir())
return <p>{user}@host {dir}{"> "}</p> return <p>{user}@{browser_name} {dir}{"> "}</p>
} }
export { userAgent }

View File

@ -1,10 +0,0 @@
const terminal_window = document.querySelector("main");
export function TermEvents() {
if (terminal_window) {
// terminal_window.addEventListener("click", (_event) => {
// const shell_input = document.getElementById("shell-input")
// if (shell_input) { shell_input.focus() }
// })
}
}

View File

@ -1,33 +0,0 @@
import type { JSX } from "react/jsx-dev-runtime"
import { TermEvents } from "./term_events"
import { createRoot } from "react-dom/client"
import { useState } from "react"
import Prompt from "./shell/prompt"
const active_shell_prompt = ShellPrompt()
function ShellEvents() {
const shell_input = document.querySelector("main")
if (shell_input) {
const [prompts, newPrompt] = useState([])
shell_input.addEventListener("keydown", (keyboard_event) => {
if (keyboard_event.key == "Enter") {
}
})
}
}
function ShellPrompt() {
return <div className="shell-prompt">
<Prompt/>
<input id="shell-input" type="text" spellCheck={false} autoFocus/>
</div>
}
export default function Shell() {
TermEvents()
ShellEvents()
return active_shell_prompt
}

View File

@ -0,0 +1,35 @@
import { useState, type JSX } from "react"
import { red } from "../shell/color"
import { display_prompt, keyboard_events } from "../shell/events"
import React from "react"
const terminal_window = document.querySelector("main")
function panic(message: string) {
return <>
<p>{red("=================================================")}</p>
<p>{red("An unexpected JavaScript error occured:")}</p>
<p>{red(message)}</p>
<p>{red("=================================================")}</p>
</>
}
type newElement = (elements: JSX.Element[]) => void
export default function Shell() {
if (terminal_window) {
const [renderedElements, renderElement] = useState([display_prompt()])
const new_elements_f = (elements: JSX.Element[]) => renderElement([...renderedElements, ...elements])
keyboard_events(terminal_window, new_elements_f)
return renderedElements.map((element, k) => <React.Fragment key={k}>{element}</React.Fragment>)
}
return panic("The <main> element is missing")
}
export {
panic,
type newElement,
}

View File

@ -2,11 +2,6 @@
import Metas from "../components/metas.astro" import Metas from "../components/metas.astro"
import Header from "../components/header.astro" import Header from "../components/header.astro"
import Footer from "../components/footer.astro" import Footer from "../components/footer.astro"
interface Props {
title: string,
}
const {title} = Astro.props
--- ---
<!doctype html> <!doctype html>
@ -14,10 +9,10 @@ const {title} = Astro.props
<head> <head>
<Metas/> <Metas/>
<link rel="icon" type="image/png" href="/logo.png"/> <link rel="icon" type="image/png" href="/logo.png"/>
<title>{`rhpidfyre.io | ${title}`}</title> <title>rhpidfyre.io</title>
</head> </head>
<body> <body>
<Header title={title}/> <Header/>
<slot/> <slot/>
<Footer/> <Footer/>
</body> </body>
@ -30,7 +25,7 @@ const {title} = Astro.props
color-scheme: dark; color-scheme: dark;
--body-background-color: rgb(0,0,0); --body-background-color: rgb(0,0,0);
--hf-button-hover-color: rgb(50,50,50); --hf-button-hover-color: rgb(255,255,255);
} }
::selection { ::selection {
background-color: rgb(255,255,255); background-color: rgb(255,255,255);

View File

@ -1,13 +1,13 @@
--- ---
import Webpage from '../layouts/Webpage.astro'; import Webpage from '../layouts/Webpage.astro';
import Motd from '../components/terminal/motd.astro'; import Motd from '../components/terminal/motd.astro';
import Terminal from '../components/react/terminal'; import Terminal from '../components/react/terminal/exec';
--- ---
<Webpage title="Home"> <Webpage>
<main> <main>
<Motd/> <Motd/>
<Terminal client:only/> <Terminal client:only="react"/>
</main> </main>
</Webpage> </Webpage>
@ -31,5 +31,7 @@ import Terminal from '../components/react/terminal';
width: 90%; width: 90%;
/* Pester me when this gets undrafted */ /* Pester me when this gets undrafted */
caret-shape: block; caret-shape: block;
&:disabled { color: white }
} }
</style> </style>

View File

@ -8,7 +8,7 @@
"dist" "dist"
], ],
"compilerOptions": { "compilerOptions": {
"noImplicitAny": true, "noImplicitAny": true,
"jsx": "react-jsx", "jsx": "react-jsx",
"jsxImportSource": "react" "jsxImportSource": "react"
} }