feat: 新增包详情页路由与 README markdown 渲染

接入 vue-router(/ 首页、/project/:name 详情页、catch-all 回首页),新增 ProjectView:包信息头、README 渲染、依赖列表、版本历史与安装命令复制。

新增 markdown.ts(markdown-it + DOMPurify,外链加 target/rel)与 api/venom.ts 数据客户端;TheHeader 品牌改为 RouterLink,i18n 扩充 project 词条。
This commit is contained in:
TermiNexus committed 2026-10-07 19:35:55 +08:00
1 parent 5021207790
commit 0fdf80778e
18 files changed
+1433 -100

No files matched your search

+593 -39
View File
@@ -8,10 +8,14 @@
"name": "venom-website",
"version": "0.1.0",
"dependencies": {
"dompurify": "^3.4.16",
"lenis": "^1.3.26",
"vue": "^3.5.40"
"markdown-it": "^15.0.2",
"vue": "^3.5.40",
"vue-router": "^5.3.1"
},
"devDependencies": {
"@types/markdown-it": "^14.2.0",
"@types/node": "^24.13.3",
"@vitejs/plugin-vue": "^6.0.8",
"@vue/tsconfig": "^0.9.1",
@@ -66,17 +70,56 @@
"node": ">=6.9.0"
}
},
"node_modules/@jridgewell/gen-mapping": {
"version": "0.3.13",
"resolved": "https://registry.npmmirror.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz",
"integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==",
"license": "MIT",
"dependencies": {
"@jridgewell/sourcemap-codec": "^1.5.0",
"@jridgewell/trace-mapping": "^0.3.24"
}
},
"node_modules/@jridgewell/remapping": {
"version": "2.3.5",
"resolved": "https://registry.npmmirror.com/@jridgewell/remapping/-/remapping-2.3.5.tgz",
"integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==",
"license": "MIT",
"dependencies": {
"@jridgewell/gen-mapping": "^0.3.5",
"@jridgewell/trace-mapping": "^0.3.24"
}
},
"node_modules/@jridgewell/resolve-uri": {
"version": "3.1.2",
"resolved": "https://registry.npmmirror.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
"integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
"license": "MIT",
"engines": {
"node": ">=6.0.0"
}
},
"node_modules/@jridgewell/sourcemap-codec": {
"version": "1.6.0",
"resolved": "https://registry.npmmirror.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.6.0.tgz",
"integrity": "sha512-T7jf+5zgsZHwNJ4lvQ7/aezbyk0nNX+zJVWpmHA7VYsEx7a7qr5Rg5IbtJFqkgze5Y2sruq1RUY8Q837Od7iFw==",
"license": "MIT"
},
"node_modules/@jridgewell/trace-mapping": {
"version": "0.3.31",
"resolved": "https://registry.npmmirror.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz",
"integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==",
"license": "MIT",
"dependencies": {
"@jridgewell/resolve-uri": "^3.1.0",
"@jridgewell/sourcemap-codec": "^1.4.14"
}
},
"node_modules/@oxc-project/types": {
"version": "0.148.0",
"resolved": "https://registry.npmmirror.com/@oxc-project/types/-/types-0.148.0.tgz",
"integrity": "sha512-Nm4s/jB+4FpFsPhWGEC4h7rzksesmtnMXomo6rCMcg/b8zLQuOziRgkCS1fxDCXOlJB/6Q8oABOZ/OP6RIPj9A==",
"dev": true,
"devOptional": true,
"license": "MIT",
"funding": {
"url": "https://github.com/sponsors/oxc-project"
@@ -89,7 +132,6 @@
"cpu": [
"arm"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -106,7 +148,6 @@
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -123,7 +164,6 @@
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -140,7 +180,6 @@
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -157,7 +196,6 @@
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -174,7 +212,6 @@
"cpu": [
"arm"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -191,7 +228,6 @@
"cpu": [
"arm64"
],
"dev": true,
"libc": [
"glibc"
],
@@ -211,7 +247,6 @@
"cpu": [
"arm64"
],
"dev": true,
"libc": [
"musl"
],
@@ -231,7 +266,6 @@
"cpu": [
"ppc64"
],
"dev": true,
"libc": [
"glibc"
],
@@ -251,7 +285,6 @@
"cpu": [
"s390x"
],
"dev": true,
"libc": [
"glibc"
],
@@ -271,7 +304,6 @@
"cpu": [
"x64"
],
"dev": true,
"libc": [
"glibc"
],
@@ -291,7 +323,6 @@
"cpu": [
"x64"
],
"dev": true,
"libc": [
"musl"
],
@@ -311,7 +342,6 @@
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -328,7 +358,6 @@
"cpu": [
"arm64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -345,7 +374,6 @@
"cpu": [
"x64"
],
"dev": true,
"license": "MIT",
"optional": true,
"os": [
@@ -359,6 +387,31 @@
"version": "1.0.1",
"resolved": "https://registry.npmmirror.com/@rolldown/pluginutils/-/pluginutils-1.0.1.tgz",
"integrity": "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==",
"devOptional": true,
"license": "MIT"
},
"node_modules/@types/linkify-it": {
"version": "5.0.0",
"resolved": "https://registry.npmmirror.com/@types/linkify-it/-/linkify-it-5.0.0.tgz",
"integrity": "sha512-sVDA58zAw4eWAffKOaQH5/5j3XeayukzDk+ewSsnv3p4yJEZHCCzMDiZM8e0OUrRvmpGZ85jf4yDHkHsgBNr9Q==",
"dev": true,
"license": "MIT"
},
"node_modules/@types/markdown-it": {
"version": "14.2.0",
"resolved": "https://registry.npmmirror.com/@types/markdown-it/-/markdown-it-14.2.0.tgz",
"integrity": "sha512-NoQ2yGlLWj4wpxMs+TYmRKk3thDrQ97agr7sFqfLsAlvoS8SNQuTrlObhFqG9iugdTtgOE9jpJ6FNM4ZGsa5xQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@types/linkify-it": "^5",
"@types/mdurl": "^2"
}
},
"node_modules/@types/mdurl": {
"version": "2.0.0",
"resolved": "https://registry.npmmirror.com/@types/mdurl/-/mdurl-2.0.0.tgz",
"integrity": "sha512-RGdgjQUZba5p6QEFAVx2OGb8rQDL/cPRG7GiedRzMcJ1tYnUANBncjbSB1NRGwbvjcPeikRABz2nshyPk1bhWg==",
"dev": true,
"license": "MIT"
},
@@ -366,12 +419,19 @@
"version": "24.13.3",
"resolved": "https://registry.npmmirror.com/@types/node/-/node-24.13.3.tgz",
"integrity": "sha512-Dh8vAsV36ig5wa9OX4pXvMc9D3Veibfw2wix0CUwYODLD8nkj9UsLjASr49nPg+2eKzxhBV+v7L8pXvT4e639Q==",
"dev": true,
"devOptional": true,
"license": "MIT",
"dependencies": {
"undici-types": "~7.18.0"
}
},
"node_modules/@types/trusted-types": {
"version": "2.0.7",
"resolved": "https://registry.npmmirror.com/@types/trusted-types/-/trusted-types-2.0.7.tgz",
"integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==",
"license": "MIT",
"optional": true
},
"node_modules/@vitejs/plugin-vue": {
"version": "6.0.8",
"resolved": "https://registry.npmmirror.com/@vitejs/plugin-vue/-/plugin-vue-6.0.8.tgz",
@@ -418,6 +478,33 @@
"vscode-uri": "^3.0.8"
}
},
"node_modules/@vue-macros/common": {
"version": "3.1.4",
"resolved": "https://registry.npmmirror.com/@vue-macros/common/-/common-3.1.4.tgz",
"integrity": "sha512-/5Fv+6DgIcM9ajY05ZmKBv+LMX1M9A0X+IUwDRVdt67ciw8OV9bvG2r34p3RiEadlsQybjhKPRKNXDC8Bp23cw==",
"license": "MIT",
"dependencies": {
"@vue/compiler-sfc": "^3.5.22",
"ast-kit": "^2.1.2",
"local-pkg": "^1.1.2",
"magic-string-ast": "^1.0.2",
"unplugin-utils": "^0.3.0"
},
"engines": {
"node": ">=20.19.0"
},
"funding": {
"url": "https://github.com/sponsors/vue-macros"
},
"peerDependencies": {
"vue": "^2.7.0 || ^3.2.25"
},
"peerDependenciesMeta": {
"vue": {
"optional": true
}
}
},
"node_modules/@vue/compiler-core": {
"version": "3.5.42",
"resolved": "https://registry.npmmirror.com/@vue/compiler-core/-/compiler-core-3.5.42.tgz",
@@ -468,6 +555,33 @@
"@vue/shared": "3.5.42"
}
},
"node_modules/@vue/devtools-api": {
"version": "8.2.1",
"resolved": "https://registry.npmmirror.com/@vue/devtools-api/-/devtools-api-8.2.1.tgz",
"integrity": "sha512-6u4vXBlIBAC1wMplIZgpyPn7uh/s4Bf6F5bMzvLv+EdJ0aHs/+4B7Ygv864EStQSjRbsRzTko/kUG1A1IejQ3A==",
"license": "MIT",
"dependencies": {
"@vue/devtools-kit": "^8.2.1"
}
},
"node_modules/@vue/devtools-kit": {
"version": "8.2.1",
"resolved": "https://registry.npmmirror.com/@vue/devtools-kit/-/devtools-kit-8.2.1.tgz",
"integrity": "sha512-FIGIuq3AWReEpbAHY/cRGeHDfI0qOb8OCQ3YjbEAX04uaxIDbGc9rhkbVcG7rnfHPXE3RsU5KrWOu9V/okd8AQ==",
"license": "MIT",
"dependencies": {
"@vue/devtools-shared": "^8.2.1",
"birpc": "^2.6.1",
"hookable": "^5.5.3",
"perfect-debounce": "^2.0.0"
}
},
"node_modules/@vue/devtools-shared": {
"version": "8.2.1",
"resolved": "https://registry.npmmirror.com/@vue/devtools-shared/-/devtools-shared-8.2.1.tgz",
"integrity": "sha512-Fkac7lUdGReh6pVOi3AYPRGe82LQqRmAfThW7RRligOAP0ZA/Z1z9XLHDM9dv34pV2HRc79DK8uKPeG2fLnA/g==",
"license": "MIT"
},
"node_modules/@vue/language-core": {
"version": "3.3.11",
"resolved": "https://registry.npmmirror.com/@vue/language-core/-/language-core-3.3.11.tgz",
@@ -551,6 +665,18 @@
}
}
},
"node_modules/acorn": {
"version": "8.19.0",
"resolved": "https://registry.npmmirror.com/acorn/-/acorn-8.19.0.tgz",
"integrity": "sha512-oJlA3XiRm7Cyk6qFD2Jn8ak9B7jSy0qy00ADO3+8dpT0LSjFihQYv4C02LFCSYJV3Q37xYwwRy5m+IpIiUzqWw==",
"license": "MIT",
"bin": {
"acorn": "bin/acorn"
},
"engines": {
"node": ">=0.4.0"
}
},
"node_modules/alien-signals": {
"version": "3.2.1",
"resolved": "https://registry.npmmirror.com/alien-signals/-/alien-signals-3.2.1.tgz",
@@ -558,6 +684,85 @@
"dev": true,
"license": "MIT"
},
"node_modules/argparse": {
"version": "3.0.2",
"resolved": "https://registry.npmmirror.com/argparse/-/argparse-3.0.2.tgz",
"integrity": "sha512-mFdDM6WqWKraGLsVb+C9CahPnzTXOefAOLq3jYcca2YZ8bEWpr++Tzj+zSaKW9+X9L5uSxcm1AZ3Y6aZJ09OhQ==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/puzrin"
},
{
"type": "github",
"url": "https://github.com/sponsors/nodeca"
}
],
"license": "PSF-2.0"
},
"node_modules/ast-kit": {
"version": "2.2.0",
"resolved": "https://registry.npmmirror.com/ast-kit/-/ast-kit-2.2.0.tgz",
"integrity": "sha512-m1Q/RaVOnTp9JxPX+F+Zn7IcLYMzM8kZofDImfsKZd8MbR+ikdOzTeztStWqfrqIxZnYWryyI9ePm3NGjnZgGw==",
"license": "MIT",
"dependencies": {
"@babel/parser": "^7.28.5",
"pathe": "^2.0.3"
},
"engines": {
"node": ">=20.19.0"
},
"funding": {
"url": "https://github.com/sponsors/sxzz"
}
},
"node_modules/ast-walker-scope": {
"version": "0.9.0",
"resolved": "https://registry.npmmirror.com/ast-walker-scope/-/ast-walker-scope-0.9.0.tgz",
"integrity": "sha512-IJdzo2vLiElBxKzwS36VsCue/62d6IdWjnPB2v3nuPKeWGynp6FF/CYoLa5i/3jXH/z97ZDdsXz6abpgM6w07A==",
"license": "MIT",
"dependencies": {
"@babel/parser": "^7.29.2",
"@babel/types": "^7.29.0",
"ast-kit": "^2.2.0"
},
"engines": {
"node": ">=20.19.0"
},
"funding": {
"url": "https://github.com/sponsors/sxzz"
}
},
"node_modules/birpc": {
"version": "2.9.0",
"resolved": "https://registry.npmmirror.com/birpc/-/birpc-2.9.0.tgz",
"integrity": "sha512-KrayHS5pBi69Xi9JmvoqrIgYGDkD6mcSe/i6YKi3w5kekCLzrX4+nawcXqrj2tIp50Kw/mT/s3p+GVK0A0sKxw==",
"license": "MIT",
"funding": {
"url": "https://github.com/sponsors/antfu"
}
},
"node_modules/chokidar": {
"version": "5.0.0",
"resolved": "https://registry.npmmirror.com/chokidar/-/chokidar-5.0.0.tgz",
"integrity": "sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw==",
"license": "MIT",
"dependencies": {
"readdirp": "^5.0.0"
},
"engines": {
"node": ">= 20.19.0"
},
"funding": {
"url": "https://paulmillr.com/funding/"
}
},
"node_modules/confbox": {
"version": "0.2.4",
"resolved": "https://registry.npmmirror.com/confbox/-/confbox-0.2.4.tgz",
"integrity": "sha512-ysOGlgTFbN2/Y6Cg3Iye8YKulHw+R2fNXHrgSmXISQdMnomY6eNDprVdW9R5xBguEqI954+S6709UyiO7B+6OQ==",
"license": "MIT"
},
"node_modules/csstype": {
"version": "3.2.3",
"resolved": "https://registry.npmmirror.com/csstype/-/csstype-3.2.3.tgz",
@@ -568,12 +773,21 @@
"version": "2.1.2",
"resolved": "https://registry.npmmirror.com/detect-libc/-/detect-libc-2.1.2.tgz",
"integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==",
"dev": true,
"devOptional": true,
"license": "Apache-2.0",
"engines": {
"node": ">=8"
}
},
"node_modules/dompurify": {
"version": "3.4.16",
"resolved": "https://registry.npmmirror.com/dompurify/-/dompurify-3.4.16.tgz",
"integrity": "sha512-sqo+pNp3qRhCIpbgRi1y8Tgk27Bo2Ry7w0dC1NBeNTdZChWjz9Xb/KOoZbRP/R6pQZ80Qw8YhXw13hWWBbMRnQ==",
"license": "(MPL-2.0 OR Apache-2.0)",
"optionalDependencies": {
"@types/trusted-types": "^2.0.7"
}
},
"node_modules/entities": {
"version": "7.0.1",
"resolved": "https://registry.npmmirror.com/entities/-/entities-7.0.1.tgz",
@@ -592,11 +806,16 @@
"integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==",
"license": "MIT"
},
"node_modules/exsolve": {
"version": "1.1.3",
"resolved": "https://registry.npmmirror.com/exsolve/-/exsolve-1.1.3.tgz",
"integrity": "sha512-kuzL9zherLQoqDeQaS55AiiIg1kb4KYXaSusTtgZTPlvKP2gGCI82LNmBNh5RjDVUXIcmgx+/Mx7vPwRzBbhJA==",
"license": "MIT"
},
"node_modules/fdir": {
"version": "6.5.0",
"resolved": "https://registry.npmmirror.com/fdir/-/fdir-6.5.0.tgz",
"integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=12.0.0"
@@ -614,7 +833,6 @@
"version": "2.3.3",
"resolved": "https://registry.npmmirror.com/fsevents/-/fsevents-2.3.3.tgz",
"integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
"dev": true,
"hasInstallScript": true,
"license": "MIT",
"optional": true,
@@ -625,6 +843,12 @@
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
}
},
"node_modules/hookable": {
"version": "5.5.3",
"resolved": "https://registry.npmmirror.com/hookable/-/hookable-5.5.3.tgz",
"integrity": "sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ==",
"license": "MIT"
},
"node_modules/lenis": {
"version": "1.3.26",
"resolved": "https://registry.npmmirror.com/lenis/-/lenis-1.3.26.tgz",
@@ -660,7 +884,7 @@
"version": "1.33.0",
"resolved": "https://registry.npmmirror.com/lightningcss/-/lightningcss-1.33.0.tgz",
"integrity": "sha512-WkUDrojuJs0xkgGf2udWxa3yGBRxPtxUkB79i6aCZLRgc7PM8fZe9TosfPDcvEpQZbuFASnHYmRLBLUbmLOIIA==",
"dev": true,
"devOptional": true,
"license": "MPL-2.0",
"dependencies": {
"detect-libc": "^2.0.3"
@@ -693,7 +917,6 @@
"cpu": [
"arm64"
],
"dev": true,
"license": "MPL-2.0",
"optional": true,
"os": [
@@ -714,7 +937,6 @@
"cpu": [
"arm64"
],
"dev": true,
"license": "MPL-2.0",
"optional": true,
"os": [
@@ -735,7 +957,6 @@
"cpu": [
"x64"
],
"dev": true,
"license": "MPL-2.0",
"optional": true,
"os": [
@@ -756,7 +977,6 @@
"cpu": [
"x64"
],
"dev": true,
"license": "MPL-2.0",
"optional": true,
"os": [
@@ -777,7 +997,6 @@
"cpu": [
"arm"
],
"dev": true,
"license": "MPL-2.0",
"optional": true,
"os": [
@@ -798,7 +1017,6 @@
"cpu": [
"arm64"
],
"dev": true,
"libc": [
"glibc"
],
@@ -822,7 +1040,6 @@
"cpu": [
"arm64"
],
"dev": true,
"libc": [
"musl"
],
@@ -846,7 +1063,6 @@
"cpu": [
"x64"
],
"dev": true,
"libc": [
"glibc"
],
@@ -870,7 +1086,6 @@
"cpu": [
"x64"
],
"dev": true,
"libc": [
"musl"
],
@@ -894,7 +1109,6 @@
"cpu": [
"arm64"
],
"dev": true,
"license": "MPL-2.0",
"optional": true,
"os": [
@@ -915,7 +1129,6 @@
"cpu": [
"x64"
],
"dev": true,
"license": "MPL-2.0",
"optional": true,
"os": [
@@ -929,6 +1142,42 @@
"url": "https://opencollective.com/parcel"
}
},
"node_modules/linkify-it": {
"version": "6.1.0",
"resolved": "https://registry.npmmirror.com/linkify-it/-/linkify-it-6.1.0.tgz",
"integrity": "sha512-wJ/TwpSDTLepCrQoYWYIExIKg5Zchex2Nn5yk2mFnB+6PtdkHtyLx742md9csRjjOnGkKIS/RrbY7l8D6gT9Vw==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/puzrin"
},
{
"type": "github",
"url": "https://github.com/sponsors/markdown-it"
}
],
"license": "MIT",
"dependencies": {
"uc.micro": "^3.0.0"
}
},
"node_modules/local-pkg": {
"version": "1.2.1",
"resolved": "https://registry.npmmirror.com/local-pkg/-/local-pkg-1.2.1.tgz",
"integrity": "sha512-++gUqRDEvcnN6Zhqrr+y/CkVEHhlrR96vZn3nZZPYzMcBUyBtTKzB9NadClFIsIVSsu+3i9tfk/erqy9kAmt7Q==",
"license": "MIT",
"dependencies": {
"mlly": "^1.7.4",
"pkg-types": "^2.3.0",
"quansync": "^0.2.11"
},
"engines": {
"node": ">=14"
},
"funding": {
"url": "https://github.com/sponsors/antfu"
}
},
"node_modules/magic-string": {
"version": "0.30.21",
"resolved": "https://registry.npmmirror.com/magic-string/-/magic-string-0.30.21.tgz",
@@ -938,11 +1187,99 @@
"@jridgewell/sourcemap-codec": "^1.5.5"
}
},
"node_modules/magic-string-ast": {
"version": "1.0.3",
"resolved": "https://registry.npmmirror.com/magic-string-ast/-/magic-string-ast-1.0.3.tgz",
"integrity": "sha512-CvkkH1i81zl7mmb94DsRiFeG9V2fR2JeuK8yDgS8oiZSFa++wWLEgZ5ufEOyLHbvSbD1gTRKv9NdX69Rnvr9JA==",
"license": "MIT",
"dependencies": {
"magic-string": "^0.30.19"
},
"engines": {
"node": ">=20.19.0"
},
"funding": {
"url": "https://github.com/sponsors/sxzz"
}
},
"node_modules/markdown-it": {
"version": "15.0.2",
"resolved": "https://registry.npmmirror.com/markdown-it/-/markdown-it-15.0.2.tgz",
"integrity": "sha512-q4IGxMv56jCqT4OCRCADBoDP3LO4MhmTXjFbphHPXs4g3j9Xg5RDnxqN8IF/3vIWEU+VCnUq+7JUg/cfy2E6Qw==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/puzrin"
},
{
"type": "github",
"url": "https://github.com/sponsors/markdown-it"
}
],
"license": "MIT",
"dependencies": {
"argparse": "^3.0.0",
"entities": "^8.0.0",
"linkify-it": "^6.0.0",
"mdurl": "^2.1.0",
"punycode.js": "^2.3.1",
"uc.micro": "^3.0.0"
},
"bin": {
"markdown-it": "bin/markdown-it.mjs"
}
},
"node_modules/markdown-it/node_modules/entities": {
"version": "8.1.0",
"resolved": "https://registry.npmmirror.com/entities/-/entities-8.1.0.tgz",
"integrity": "sha512-kxL7msIffSuh9aaFAMD7rxAIuTRMAHMeBtgHW2yUdWw732ZNh4MehkF2gdjvtdmikkaIP9bFDDJOPlsvm7avrA==",
"license": "BSD-2-Clause",
"engines": {
"node": ">=20.19.0"
},
"funding": {
"url": "https://github.com/fb55/entities?sponsor=1"
}
},
"node_modules/mdurl": {
"version": "2.1.0",
"resolved": "https://registry.npmmirror.com/mdurl/-/mdurl-2.1.0.tgz",
"integrity": "sha512-1+HBaOx0zi/dQWht8rNv9MYf9qqpqL/kxI0hXImU6Y547zM6Sni8BQibt7ifgMcYtQg41ao3Ivd6cnSM86inpg==",
"license": "MIT"
},
"node_modules/mlly": {
"version": "1.8.2",
"resolved": "https://registry.npmmirror.com/mlly/-/mlly-1.8.2.tgz",
"integrity": "sha512-d+ObxMQFmbt10sretNDytwt85VrbkhhUA/JBGm1MPaWJ65Cl4wOgLaB1NYvJSZ0Ef03MMEU/0xpPMXUIQ29UfA==",
"license": "MIT",
"dependencies": {
"acorn": "^8.16.0",
"pathe": "^2.0.3",
"pkg-types": "^1.3.1",
"ufo": "^1.6.3"
}
},
"node_modules/mlly/node_modules/confbox": {
"version": "0.1.8",
"resolved": "https://registry.npmmirror.com/confbox/-/confbox-0.1.8.tgz",
"integrity": "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==",
"license": "MIT"
},
"node_modules/mlly/node_modules/pkg-types": {
"version": "1.3.1",
"resolved": "https://registry.npmmirror.com/pkg-types/-/pkg-types-1.3.1.tgz",
"integrity": "sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==",
"license": "MIT",
"dependencies": {
"confbox": "^0.1.8",
"mlly": "^1.7.4",
"pathe": "^2.0.1"
}
},
"node_modules/muggle-string": {
"version": "0.4.1",
"resolved": "https://registry.npmmirror.com/muggle-string/-/muggle-string-0.4.1.tgz",
"integrity": "sha512-VNTrAak/KhO2i8dqqnqnAHOa3cYBwXEZe9h+D5h/1ZqFSTEFHdM65lR7RoIqq3tBBYavsOXV84NoHXZ0AkPyqQ==",
"dev": true,
"license": "MIT"
},
"node_modules/nanoid": {
@@ -963,6 +1300,12 @@
"node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
}
},
"node_modules/nostics": {
"version": "1.3.0",
"resolved": "https://registry.npmmirror.com/nostics/-/nostics-1.3.0.tgz",
"integrity": "sha512-wzRsrJwNlFbqIRQt8aCeL2WE8USip/uuvGSOZV1D02CxX8ABTMbw8IywKjH7yWHETMXbWJI4OBpihg9rmz2Vyg==",
"license": "MIT"
},
"node_modules/path-browserify": {
"version": "1.0.1",
"resolved": "https://registry.npmmirror.com/path-browserify/-/path-browserify-1.0.1.tgz",
@@ -970,6 +1313,18 @@
"dev": true,
"license": "MIT"
},
"node_modules/pathe": {
"version": "2.0.3",
"resolved": "https://registry.npmmirror.com/pathe/-/pathe-2.0.3.tgz",
"integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==",
"license": "MIT"
},
"node_modules/perfect-debounce": {
"version": "2.1.0",
"resolved": "https://registry.npmmirror.com/perfect-debounce/-/perfect-debounce-2.1.0.tgz",
"integrity": "sha512-LjgdTytVFXeUgtHZr9WYViYSM/g8MkcTPYDlPa3cDqMirHjKiSZPYd6DoL7pK8AJQr+uWkQvCjHNdiMqsrJs+g==",
"license": "MIT"
},
"node_modules/picocolors": {
"version": "1.1.1",
"resolved": "https://registry.npmmirror.com/picocolors/-/picocolors-1.1.1.tgz",
@@ -980,7 +1335,6 @@
"version": "4.0.7",
"resolved": "https://registry.npmmirror.com/picomatch/-/picomatch-4.0.7.tgz",
"integrity": "sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=12"
@@ -989,6 +1343,23 @@
"url": "https://github.com/sponsors/jonschlinkert"
}
},
"node_modules/pkg-types": {
"version": "2.3.3",
"resolved": "https://registry.npmmirror.com/pkg-types/-/pkg-types-2.3.3.tgz",
"integrity": "sha512-j/lCFdcppV0JxWpCEITdbDltBxPP6cHT+yNJ6Go2OgoSA9518X847X9z0p6LtA4Nc16+eQzCZjRrWanTGvHJ5w==",
"license": "MIT",
"dependencies": {
"confbox": "^0.3.1",
"exsolve": "^1.1.1",
"pathe": "^2.0.3"
}
},
"node_modules/pkg-types/node_modules/confbox": {
"version": "0.3.1",
"resolved": "https://registry.npmmirror.com/confbox/-/confbox-0.3.1.tgz",
"integrity": "sha512-cKUSoKa8YxFZZSmraVi7onONx3amu77ngK3kGpsYHDH7drPwCRkQE1RYMPlLRrMtnciRj274XNRxcHxnKmDSnA==",
"license": "MIT"
},
"node_modules/postcss": {
"version": "8.5.28",
"resolved": "https://registry.npmmirror.com/postcss/-/postcss-8.5.28.tgz",
@@ -1017,11 +1388,49 @@
"node": "^10 || ^12 || >=14"
}
},
"node_modules/punycode.js": {
"version": "2.3.1",
"resolved": "https://registry.npmmirror.com/punycode.js/-/punycode.js-2.3.1.tgz",
"integrity": "sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==",
"license": "MIT",
"engines": {
"node": ">=6"
}
},
"node_modules/quansync": {
"version": "0.2.11",
"resolved": "https://registry.npmmirror.com/quansync/-/quansync-0.2.11.tgz",
"integrity": "sha512-AifT7QEbW9Nri4tAwR5M/uzpBuqfZf+zwaEM/QkzEjj7NBuFD2rBuy0K3dE+8wltbezDV7JMA0WfnCPYRSYbXA==",
"funding": [
{
"type": "individual",
"url": "https://github.com/sponsors/antfu"
},
{
"type": "individual",
"url": "https://github.com/sponsors/sxzz"
}
],
"license": "MIT"
},
"node_modules/readdirp": {
"version": "5.1.1",
"resolved": "https://registry.npmmirror.com/readdirp/-/readdirp-5.1.1.tgz",
"integrity": "sha512-Kko+Y5XQ6fM+Ce3dq3m9YGxnacYZYl9cA1wZjaF3Vbry2L3i1qVg8+CAgNPsXRArPMUMCaOR7oa9Nqntc43JKA==",
"license": "MIT",
"engines": {
"node": ">= 20.19.0"
},
"funding": {
"type": "individual",
"url": "https://paulmillr.com/funding/"
}
},
"node_modules/rolldown": {
"version": "1.2.7",
"resolved": "https://registry.npmmirror.com/rolldown/-/rolldown-1.2.7.tgz",
"integrity": "sha512-g0EtLvBjTUB7jhyV0S/TCup3v/XSVl45vUIGbOGU4QPiyjTenCe4mKuFvW9fEgYmS2Fo42AUssRmNuMziXdrig==",
"dev": true,
"devOptional": true,
"license": "MIT",
"dependencies": {
"@oxc-project/types": "=0.148.0",
@@ -1051,6 +1460,12 @@
"@rolldown/binding-win32-x64-msvc": "1.2.7"
}
},
"node_modules/scule": {
"version": "1.3.0",
"resolved": "https://registry.npmmirror.com/scule/-/scule-1.3.0.tgz",
"integrity": "sha512-6FtHJEvt+pVMIB9IBY+IcCJ6Z5f1iQnytgyfKMhDKgmzYG+TeH/wx1y3l27rshSbLiSanrR9ffZDrEsmjlQF2g==",
"license": "MIT"
},
"node_modules/source-map-js": {
"version": "1.2.1",
"resolved": "https://registry.npmmirror.com/source-map-js/-/source-map-js-1.2.1.tgz",
@@ -1064,7 +1479,6 @@
"version": "0.2.17",
"resolved": "https://registry.npmmirror.com/tinyglobby/-/tinyglobby-0.2.17.tgz",
"integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==",
"dev": true,
"license": "MIT",
"dependencies": {
"fdir": "^6.5.0",
@@ -1091,18 +1505,104 @@
"node": ">=14.17"
}
},
"node_modules/uc.micro": {
"version": "3.0.0",
"resolved": "https://registry.npmmirror.com/uc.micro/-/uc.micro-3.0.0.tgz",
"integrity": "sha512-U3PppEkleoTnIfi8BozMx3yju3qc/L6SwqWo2Sw+54PX+PX0q9I+r1Um5HCmqD7n9VDX5/v3vQH/AjA6deDdtw==",
"license": "MIT"
},
"node_modules/ufo": {
"version": "1.6.4",
"resolved": "https://registry.npmmirror.com/ufo/-/ufo-1.6.4.tgz",
"integrity": "sha512-JFNbkD1Svwe0KvGi8GOeLcP4kAWQ609twvCdcHxq1oSL8svv39ZuSvajcD8B+5D0eL4+s1Is2D/O6KN3qcTeRA==",
"license": "MIT"
},
"node_modules/undici-types": {
"version": "7.18.2",
"resolved": "https://registry.npmmirror.com/undici-types/-/undici-types-7.18.2.tgz",
"integrity": "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==",
"dev": true,
"devOptional": true,
"license": "MIT"
},
"node_modules/unplugin": {
"version": "3.4.0",
"resolved": "https://registry.npmmirror.com/unplugin/-/unplugin-3.4.0.tgz",
"integrity": "sha512-9skdIFlCsPdFV7wUfZxNsFInlW+7nJmGu2gkTu0OUhF56aXGsHab9x52/QhdJ4lC7ZDPWTxbiC4ANqVlsuaW3w==",
"license": "MIT",
"dependencies": {
"@jridgewell/remapping": "^2.3.5",
"picomatch": "^4.0.7",
"webpack-virtual-modules": "^0.6.2"
},
"engines": {
"node": "^20.19.0 || >=22.12.0"
},
"peerDependencies": {
"@farmfe/core": "*",
"@rsbuild/core": "*",
"@rspack/core": "*",
"bun-types-no-globals": "*",
"esbuild": "*",
"rolldown": "*",
"rollup": "*",
"unloader": "*",
"vite": "*",
"webpack": "*"
},
"peerDependenciesMeta": {
"@farmfe/core": {
"optional": true
},
"@rsbuild/core": {
"optional": true
},
"@rspack/core": {
"optional": true
},
"bun-types-no-globals": {
"optional": true
},
"esbuild": {
"optional": true
},
"rolldown": {
"optional": true
},
"rollup": {
"optional": true
},
"unloader": {
"optional": true
},
"vite": {
"optional": true
},
"webpack": {
"optional": true
}
}
},
"node_modules/unplugin-utils": {
"version": "0.3.2",
"resolved": "https://registry.npmmirror.com/unplugin-utils/-/unplugin-utils-0.3.2.tgz",
"integrity": "sha512-xVToRh2CTmLk2HnEG7ac4rl1MJTT3RFkpS8B++/SnB0kXvuaavD+n3m/vrzyWQOdJNSZQACnbz01pnppbwV5BA==",
"license": "MIT",
"dependencies": {
"pathe": "^2.0.3",
"picomatch": "^4.0.4"
},
"engines": {
"node": ">=20.19.0"
},
"funding": {
"url": "https://github.com/sponsors/sxzz"
}
},
"node_modules/vite": {
"version": "8.2.2",
"resolved": "https://registry.npmmirror.com/vite/-/vite-8.2.2.tgz",
"integrity": "sha512-cFKLV/PRgAUlIRm5WjMjJ86jrftzpqcgH+Us+DS8mI3CDNiH30Whrz8uHL3+MOLPAgqbMBAqWdAHAphOAM+z/Q==",
"dev": true,
"devOptional": true,
"license": "MIT",
"dependencies": {
"lightningcss": "^1.33.0",
@@ -1204,6 +1704,54 @@
}
}
},
"node_modules/vue-router": {
"version": "5.3.1",
"resolved": "https://registry.npmmirror.com/vue-router/-/vue-router-5.3.1.tgz",
"integrity": "sha512-GDBZzgmILxA/kFnkFbJjQZdZ2QQbngnIMMuoUcjhZIfH1RGMaPjPwX5ASnV38qamuA9uhO0RDjSBHTDNG2uXyQ==",
"license": "MIT",
"dependencies": {
"@vue-macros/common": "^3.1.3",
"@vue/devtools-api": "^8.1.5",
"ast-walker-scope": "^0.9.0",
"chokidar": "^5.0.0",
"confbox": "^0.2.4",
"local-pkg": "^1.2.1",
"magic-string": "^0.30.21",
"mlly": "^1.8.2",
"muggle-string": "^0.4.1",
"nostics": "^1.1.4",
"pathe": "^2.0.3",
"picomatch": "^4.0.5",
"scule": "^1.3.0",
"tinyglobby": "^0.2.17",
"unplugin": "^3.3.0",
"unplugin-utils": "^0.3.2"
},
"funding": {
"url": "https://github.com/sponsors/posva"
},
"peerDependencies": {
"@pinia/colada": ">=0.21.2",
"@vue/compiler-sfc": "^3.5.34 || ^4.0.0",
"pinia": "^3.0.4 || ^4.0.2",
"vite": "^7.3.0 || ^8.0.0",
"vue": "^3.5.34 || ^4.0.0"
},
"peerDependenciesMeta": {
"@pinia/colada": {
"optional": true
},
"@vue/compiler-sfc": {
"optional": true
},
"pinia": {
"optional": true
},
"vite": {
"optional": true
}
}
},
"node_modules/vue-tsc": {
"version": "3.3.11",
"resolved": "https://registry.npmmirror.com/vue-tsc/-/vue-tsc-3.3.11.tgz",
@@ -1220,6 +1768,12 @@
"peerDependencies": {
"typescript": ">=5.0.0"
}
},
"node_modules/webpack-virtual-modules": {
"version": "0.6.2",
"resolved": "https://registry.npmmirror.com/webpack-virtual-modules/-/webpack-virtual-modules-0.6.2.tgz",
"integrity": "sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ==",
"license": "MIT"
}
}
}
+5 -1
View File
@@ -10,10 +10,14 @@
"preview": "vite preview"
},
"dependencies": {
"dompurify": "^3.4.16",
"lenis": "^1.3.26",
"vue": "^3.5.40"
"markdown-it": "^15.0.2",
"vue": "^3.5.40",
"vue-router": "^5.3.1"
},
"devDependencies": {
"@types/markdown-it": "^14.2.0",
"@types/node": "^24.13.3",
"@vitejs/plugin-vue": "^6.0.8",
"@vue/tsconfig": "^0.9.1",
+1 -1
View File
@@ -1,5 +1,5 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
<rect width="64" height="64" rx="14" fill="#0e8a5d"/>
<rect width="64" height="64" rx="14" fill="#1d6fb8"/>
<path d="M14 47 V17 h8 l10 19 10-19 h8 V47 H40 V28 L32 47 24 28 v19 Z"
fill="#ffffff" fill-rule="evenodd"/>
</svg>

Before

Width:  |  Height:  |  Size: 242 B

After

Width:  |  Height:  |  Size: 242 B

+27 -10
View File
@@ -1,19 +1,27 @@
<script setup lang="ts">
import { onMounted, onBeforeUnmount, watchEffect, nextTick } from 'vue'
/**
* 根组件:站点外壳(顶栏 + 路由出口 + 页脚)
*
* 全局职责:i18n 语言与文档标题同步、滚动揭示观察、Lenis 平滑滚动。
* 页面内容由路由决定:`/` 首页、`/project/<包名>` 包详情。
*/
import { onMounted, onBeforeUnmount, watch, watchEffect, nextTick } from 'vue'
import { RouterView, useRoute } from 'vue-router'
import Lenis from 'lenis'
import TheHeader from '@/components/TheHeader.vue'
import HeroSection from '@/components/HeroSection.vue'
import CapabilitiesSection from '@/components/CapabilitiesSection.vue'
import TerminalSection from '@/components/TerminalSection.vue'
import TheFooter from '@/components/TheFooter.vue'
import { currentLang } from '@/i18n'
import { currentLang, t } from '@/i18n'
// html lang 与页面语言同步
const route = useRoute()
// html lang 与文档标题随语言/路由同步
watchEffect(() => {
document.documentElement.lang = currentLang.value === 'zh' ? 'zh-CN' : 'en'
const name = String(route.params.name || '')
document.title = route.name === 'project' && name ? `${name} · Venom` : t.site.title
})
// 全局滚动揭示:观察 [data-reveal],进入视口即落定一次
/** 全局滚动揭示:观察 [data-reveal],进入视口即落定一次 */
function setupRevealObserver() {
const els = document.querySelectorAll('[data-reveal]')
if (!('IntersectionObserver' in window) || els.length === 0) return
@@ -34,6 +42,17 @@ function setupRevealObserver() {
// Lenis 平滑惯性滚动实例(接管滚轮,手势结束后继续滑行一段距离再停)
let lenis: Lenis | null = null
// 路由切换:回到页首 + 观察新页面的揭示元素
watch(
() => route.fullPath,
async () => {
await nextTick()
if (lenis) lenis.scrollTo(0, { immediate: true })
else window.scrollTo(0, 0)
setupRevealObserver()
},
)
onMounted(async () => {
await nextTick()
setupRevealObserver()
@@ -59,9 +78,7 @@ onBeforeUnmount(() => {
<template>
<TheHeader />
<main>
<HeroSection />
<CapabilitiesSection />
<TerminalSection />
<RouterView />
</main>
<TheFooter />
</template>
+68
View File
@@ -0,0 +1,68 @@
/** Venom 官网 API 客户端
*
* 同源调用 `/API/Venom/*`(venom.gvsds.com 的 /API/ 前缀由主服务接管):
* 包数据存在提供者子服务器,主服务按域名反查产品后回源读取。
*/
export interface VenomDependency {
Name: string
Version: string
}
export interface VenomRelease {
ReleaseUUID: string
Version: string
Notes: string
PublisherName: string
CreateTime: string
}
export interface VenomProject {
PackageUUID: string
PackageName: string
PackageDesc: string
CreatorName: string
CreateTime: string
UpdateTime: string
ReleaseCount: number
Readme: string
Dependencies: VenomDependency[]
LatestVersion: string
Releases: VenomRelease[]
}
/** API 错误(带 HTTP 状态码,供页面区分「不存在」与「服务异常」) */
export class VenomApiError extends Error {
readonly Status: number
constructor(message: string, status: number) {
super(message)
this.name = 'VenomApiError'
this.Status = status
}
}
/** 逐段编码包名(保留 `/`,兼容 @scope/name 这类作用域包名) */
function encodeName(name: string): string {
return name
.split('/')
.map((seg) => encodeURIComponent(seg))
.join('/')
}
/** 取包详情(含 README / 依赖列表 / 发布历史) */
export async function getProject(name: string): Promise<VenomProject> {
const resp = await fetch(`/API/Venom/Project/${encodeName(name)}`, {
headers: { Accept: 'application/json' },
})
let payload: { code?: number; msg?: string; data?: VenomProject } | null = null
try {
payload = await resp.json()
} catch {
throw new VenomApiError(`服务响应异常(HTTP ${resp.status})`, resp.status)
}
if (!resp.ok || payload?.code !== 0 || !payload.data) {
throw new VenomApiError(payload?.msg || `请求失败(HTTP ${resp.status})`, resp.status)
}
return payload.data
}
+27 -27
View File
@@ -1,27 +1,27 @@
/* ============================================================
Venom 官网 · 设计系统
一张暗色画布 + 单一强调色(翡翠绿),双字体族,克制运动
一张白色画布 + 单一强调色(企业蓝),双字体族,克制运动
与 Viper 官网共享同一套设计 token(同属 GVSDS 语言生态)
============================================================ */
:root {
/* 色板 —— 浅色企业风格 */
/* 色板 —— 蓝白企业风格 */
--bg: #ffffff;
--bg-soft: #f4f8f6;
--bg-deep: #0b1712; /* 深色区(代码窗 / 页脚) */
--panel: rgba(16, 32, 26, 0.03);
--panel-strong: rgba(16, 32, 26, 0.05);
--line: rgba(14, 138, 93, 0.3);
--line-soft: #dfe8e3;
--text: #10201a;
--text-dim: #5b6e64;
--text-faint: #8da097;
--accent: #0e8a5d;
--accent-rgb: 14, 138, 93; /* 主题色分量,供 rgba(var(--accent-rgb), α) 使用 */
--accent-strong: #0b7650;
--bg-soft: #f1f6fc;
--bg-deep: #0a1a2f; /* 深色区(代码窗 / 页脚),深海军蓝 */
--panel: rgba(16, 42, 74, 0.03);
--panel-strong: rgba(16, 42, 74, 0.05);
--line: rgba(29, 111, 184, 0.3);
--line-soft: #dce7f3;
--text: #0f1f33;
--text-dim: #55697f;
--text-faint: #8497ab;
--accent: #1d6fb8;
--accent-rgb: 29, 111, 184; /* 主题色分量,供 rgba(var(--accent-rgb), α) 使用 */
--accent-strong: #155a99;
--accent-ink: #ffffff;
--glow: rgba(14, 138, 93, 0.16);
--shadow-code: 0 26px 60px -30px rgba(15, 42, 32, 0.4);
--glow: rgba(29, 111, 184, 0.16);
--shadow-code: 0 26px 60px -30px rgba(10, 26, 47, 0.4);
/* 字体(本地栈,无外网依赖) */
--font-sans: 'Segoe UI', 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei',
@@ -33,10 +33,10 @@
--tok-str: #a5d6ff;
--tok-num: #79c0ff;
--tok-type: #ffa657;
--tok-fn: #a8e063;
--tok-cmt: #6e8b79;
--tok-punc: #8b9f8f;
--tok-plain: #d5e3d8;
--tok-fn: #7ab8ff;
--tok-cmt: #6b83a0;
--tok-punc: #8fa4bc;
--tok-plain: #d6e2f2;
--radius: 10px;
--radius-sm: 6px;
@@ -108,12 +108,12 @@ button {
height: 11px;
}
::-webkit-scrollbar-thumb {
background: #c3d3cb;
background: #c2d3e6;
border-radius: 6px;
border: 3px solid var(--bg);
}
::-webkit-scrollbar-thumb:hover {
background: #9db3a3;
background: #9ab4cd;
}
/* ------- 版式工具 ------- */
@@ -186,13 +186,13 @@ button {
.btn-primary {
background: var(--accent);
color: var(--accent-ink);
box-shadow: 0 0 0 1px var(--accent), 0 10px 26px -14px rgba(14, 138, 93, 0.7);
box-shadow: 0 0 0 1px var(--accent), 0 10px 26px -14px rgba(29, 111, 184, 0.7);
}
.btn-primary:hover {
background: var(--accent-strong);
transform: translateY(-2px);
box-shadow: 0 0 0 1px var(--accent-strong), 0 16px 34px -14px rgba(14, 138, 93, 0.8);
box-shadow: 0 0 0 1px var(--accent-strong), 0 16px 34px -14px rgba(29, 111, 184, 0.8);
}
.btn-ghost {
@@ -203,7 +203,7 @@ button {
.btn-ghost:hover {
border-color: var(--line);
background: rgba(14, 138, 93, 0.06);
background: rgba(29, 111, 184, 0.06);
color: var(--accent-strong);
transform: translateY(-2px);
}
@@ -255,7 +255,7 @@ button {
/* 代码块基础(页面上保留深色代码窗,形成浅色页的对比锚点) */
.code-win {
--line-soft: rgba(255, 255, 255, 0.08);
--text-faint: #6d8a7c;
--text-faint: #6f88a0;
background: var(--bg-deep);
border: 1px solid rgba(255, 255, 255, 0.08);
border-radius: var(--radius);
@@ -276,7 +276,7 @@ button {
width: 10px;
height: 10px;
border-radius: 50%;
background: #3c4a42;
background: #35485e;
}
.code-win__dot:nth-child(1) {
+1 -1
View File
@@ -102,7 +102,7 @@ function onCardPointerMove(e: PointerEvent) {
.cap-card:hover {
border-color: var(--line);
transform: translateY(-3px);
box-shadow: 0 22px 46px -30px rgba(14, 138, 93, 0.5);
box-shadow: 0 22px 46px -30px rgba(29, 111, 184, 0.5);
}
/* 卡片内容压在光晕之上 */
+5 -5
View File
@@ -64,7 +64,7 @@ import { t } from '@/i18n'
}
}
/* 背景:白底 + 极淡翡翠辉光(下缘越过 hero 底边,继续可见于下一 section) */
/* 背景:白底 + 极淡蓝色辉光(下缘越过 hero 底边,继续可见于下一 section) */
.hero-bg {
position: absolute;
inset: 0;
@@ -80,7 +80,7 @@ import { t } from '@/i18n'
width: 1100px;
height: 155%;
border-radius: 50%;
background: radial-gradient(ellipse at 50% 41%, rgba(14, 138, 93, 0.1), transparent 68%);
background: radial-gradient(ellipse at 50% 41%, rgba(29, 111, 184, 0.12), transparent 68%);
filter: blur(24px);
}
@@ -95,8 +95,8 @@ import { t } from '@/i18n'
display: inline-flex;
align-items: center;
gap: 8px;
border: 1px solid rgba(29, 111, 184, 0.35);
color: #1d6fb8;
border: 1px solid rgba(var(--accent-rgb), 0.35);
color: var(--accent);
font-size: 0.7rem;
letter-spacing: 0.12em;
text-transform: uppercase;
@@ -110,7 +110,7 @@ import { t } from '@/i18n'
width: 6px;
height: 6px;
border-radius: 50%;
background: #1d6fb8;
background: var(--accent);
animation: caretBlink 1.4s steps(1) infinite;
}
+2 -2
View File
@@ -33,12 +33,12 @@ import { t } from '@/i18n'
}
.term .out {
color: #5ad7a6;
color: #7cc4ff;
}
.term .p::before {
content: '$ ';
color: #5ad7a6;
color: #7cc4ff;
font-weight: 700;
user-select: none;
}
+9 -9
View File
@@ -31,15 +31,15 @@ const year = new Date().getFullYear()
<style scoped>
.footer {
--text: #e7f2ec;
--text-dim: #9db9ab;
--text-faint: #6f8d7e;
--text: #e8f1fa;
--text-dim: #9db8cf;
--text-faint: #6f88a0;
--line-soft: rgba(255, 255, 255, 0.09);
--accent: #5ad7a6;
--accent: #7cc4ff;
border-top: 1px solid rgba(255, 255, 255, 0.08);
background: var(--bg-deep);
padding: 34px 0 30px;
color: #6f8d7e;
color: #6f88a0;
}
.footer-inner {
@@ -64,7 +64,7 @@ const year = new Date().getFullYear()
}
.f-name {
color: #e7f2ec;
color: #e8f1fa;
font-size: 1.05rem;
font-weight: 750;
}
@@ -80,16 +80,16 @@ const year = new Date().getFullYear()
}
.foot-links a {
color: #9db9ab;
color: #9db8cf;
transition: color 0.2s;
}
.foot-links a:hover {
color: #5ad7a6;
color: #7cc4ff;
}
.f-copy {
color: #6f8d7e;
color: #6f88a0;
font-size: 0.76rem;
letter-spacing: 0.04em;
}
+5 -4
View File
@@ -1,5 +1,6 @@
<script setup lang="ts">
import { ref, onMounted, onBeforeUnmount } from 'vue'
import { RouterLink } from 'vue-router'
import { t, currentLang, setLang } from '@/i18n'
const scrolled = ref(false)
@@ -25,14 +26,14 @@ onBeforeUnmount(() => {
<template>
<header class="site-header" :class="{ scrolled }">
<div class="container header-inner">
<a class="brand" href="#top">
<RouterLink class="brand" to="/">
<svg viewBox="0 0 64 64" aria-hidden="true">
<rect width="64" height="64" rx="14" fill="none" />
<path d="M14 47 V17 h8 l10 19 10-19 h8 V47 H40 V28 L32 47 24 28 v19 Z" />
</svg>
<span class="brand-name">Venom</span>
<span class="brand-sub mono">PKG&nbsp;MANAGER</span>
</a>
</RouterLink>
<div class="header-actions">
<button class="lang-btn mono" @click="toggleLang" aria-label="Switch language">
@@ -123,7 +124,7 @@ onBeforeUnmount(() => {
.lang-btn:hover {
border-color: var(--line);
color: var(--accent-strong);
background: rgba(14, 138, 93, 0.06);
background: rgba(29, 111, 184, 0.06);
}
.nav-pill {
@@ -139,7 +140,7 @@ onBeforeUnmount(() => {
.nav-pill:hover {
color: var(--accent-strong);
border-color: var(--line);
background: rgba(14, 138, 93, 0.05);
background: rgba(29, 111, 184, 0.05);
}
@media (max-width: 640px) {
+30
View File
@@ -3,8 +3,12 @@
import type zh from './zh'
const en: typeof zh = {
site: {
title: 'Venom · the Viper package manager',
},
header: {
backToViper: '← viper.gvsds.com',
toHome: 'Home',
},
hero: {
badge: 'UNDER DEVELOPMENT',
@@ -43,6 +47,32 @@ const en: typeof zh = {
cmdComment: '# sample commands; the real CLI lands with the release',
soon: '// a brand placeholder — ecosystem & interfaces to follow',
},
project: {
back: '← Back to home',
loading: 'Loading package…',
notFound: 'Package not found',
loadFailed: 'Failed to load',
retry: 'Retry',
noDesc: 'No description provided',
install: 'Install',
installHint: 'Run inside a Viper project',
latest: 'Latest',
released: 'Releases',
dependencies: 'Dependencies',
dependency: 'Package',
constraint: 'Constraint',
noDeps: 'No external dependencies',
versions: 'Version history',
version: 'Version',
publishedBy: 'Publisher',
publishedAt: 'Published',
notes: 'Notes',
noVersions: 'No releases yet',
readme: 'README',
noReadme: 'No README provided',
author: 'Author',
updatedAt: 'Updated',
},
footer: {
tagline: 'The Viper package manager · work in progress',
copyright: '© GVSDS · Venom, the Viper package manager',
+30
View File
@@ -1,8 +1,12 @@
// Venom 官网 · 中文文案字典
// 注意:结构与 en.ts 完全一致(en.ts 以 typeof zh 校验)
export default {
site: {
title: 'Venom · Viper 官方包管理器',
},
header: {
backToViper: '← viper.gvsds.com',
toHome: '首页',
},
hero: {
badge: 'UNDER DEVELOPMENT · 开发中',
@@ -41,6 +45,32 @@ export default {
cmdComment: '# 命令示意,正式接口将于发布时确定',
soon: '// 当前为品牌占位页 · 生态与接口随后公布 —— placeholder page, details will follow',
},
project: {
back: '← 返回首页',
loading: '正在读取包信息…',
notFound: '未找到该包',
loadFailed: '加载失败',
retry: '重试',
noDesc: '该包未填写说明',
install: '安装',
installHint: '在 Viper 工程中执行',
latest: '最新版本',
released: '发布',
dependencies: '依赖',
dependency: '包名',
constraint: '版本约束',
noDeps: '无外部依赖',
versions: '版本历史',
version: '版本',
publishedBy: '发布人',
publishedAt: '发布时间',
notes: '说明',
noVersions: '暂无发布记录',
readme: 'README',
noReadme: '该包暂未提供 README',
author: '作者',
updatedAt: '最近更新',
},
footer: {
tagline: 'Viper 官方包管理器 · 开发中',
copyright: '© GVSDS · Venom, the Viper package manager',
+2 -1
View File
@@ -1,6 +1,7 @@
import { createApp } from 'vue'
import router from '@/router'
import App from './App.vue'
import 'lenis/dist/lenis.css'
import '@/assets/base.css'
createApp(App).mount('#app')
createApp(App).use(router).mount('#app')
+28
View File
@@ -0,0 +1,28 @@
/** README 渲染:Markdown → 消毒后的 HTML
*
* 关闭 markdown-it 的原始 HTML(`html: false`),再用 DOMPurify 消毒一次,
* 双重防护:README 由包作者自由填写,属于不可信内容。
*/
import MarkdownIt from 'markdown-it'
import DOMPurify from 'dompurify'
const md = new MarkdownIt({
html: false,
linkify: true,
breaks: false,
typographer: false,
})
// 外链一律新窗口打开,并阻断 referrer / window.opener
const defaultLinkOpen = md.renderer.rules.link_open
md.renderer.rules.link_open = (tokens, idx, options, env, self) => {
tokens[idx].attrSet('target', '_blank')
tokens[idx].attrSet('rel', 'noopener noreferrer')
if (defaultLinkOpen) return defaultLinkOpen(tokens, idx, options, env, self)
return self.renderToken(tokens, idx, options)
}
/** 渲染 README(Markdown 原文 → 可安全 v-html 的 HTML) */
export function renderMarkdown(source: string): string {
return DOMPurify.sanitize(md.render(source || ''))
}
+27
View File
@@ -0,0 +1,27 @@
/** Venom 官网路由
*
* 两个页面:首页(品牌落地页)与包详情页 `/project/<包名>`。
* history 模式:venom.gvsds.com 由 Main.py 静态托管并做了 SPA 兜底
* (无扩展名的路径回退 index.html),深链/刷新可直接打开详情页。
*/
import { createRouter, createWebHistory } from 'vue-router'
const router = createRouter({
history: createWebHistory(),
routes: [
{
path: '/',
name: 'home',
component: () => import('@/views/HomeView.vue'),
},
{
path: '/project/:name',
name: 'project',
component: () => import('@/views/ProjectView.vue'),
props: true,
},
{ path: '/:pathMatch(.*)*', redirect: '/' },
],
})
export default router
+12
View File
@@ -0,0 +1,12 @@
<script setup lang="ts">
/** 首页:品牌落地页(Hero → 能力 → 终端示意) */
import HeroSection from '@/components/HeroSection.vue'
import CapabilitiesSection from '@/components/CapabilitiesSection.vue'
import TerminalSection from '@/components/TerminalSection.vue'
</script>
<template>
<HeroSection />
<CapabilitiesSection />
<TerminalSection />
</template>
+561
View File
@@ -0,0 +1,561 @@
<script setup lang="ts">
/**
* 包详情页 `/project/<包名>`
*
* 数据来自 `/API/Venom/Project/<包名>`(主服务按域名反查产品并回源子服务器)。
* 左侧渲染 README(Markdown → 消毒 HTML),右侧为安装命令 / 依赖列表 / 版本历史。
*/
import { computed, ref, watch } from 'vue'
import { RouterLink, useRoute } from 'vue-router'
import { VenomApiError, getProject, type VenomProject } from '@/api/venom'
import { renderMarkdown } from '@/markdown'
import { t } from '@/i18n'
const route = useRoute()
const loading = ref(false)
const error = ref('')
const notFound = ref(false)
const project = ref<VenomProject | null>(null)
const copied = ref(false)
const readmeHtml = computed(() =>
project.value ? renderMarkdown(project.value.Readme || '') : '',
)
const installCommand = computed(() =>
project.value ? `venom install ${project.value.PackageName}` : '',
)
function encodeName(name: string): string {
return name
.split('/')
.map((seg) => encodeURIComponent(seg))
.join('/')
}
async function load(): Promise<void> {
const name = String(route.params.name || '').trim()
loading.value = true
error.value = ''
notFound.value = false
project.value = null
copied.value = false
if (!name) {
notFound.value = true
loading.value = false
return
}
try {
project.value = await getProject(name)
} catch (e) {
const err = e as VenomApiError
if (err.Status === 404) notFound.value = true
else error.value = err.message || t.project.loadFailed
} finally {
loading.value = false
}
}
async function copyInstall(): Promise<void> {
if (!installCommand.value) return
try {
await navigator.clipboard.writeText(installCommand.value)
copied.value = true
window.setTimeout(() => {
copied.value = false
}, 1600)
} catch {
/* 剪贴板不可用(非 https / 无权限)时忽略 */
}
}
watch(() => route.params.name, load, { immediate: true })
</script>
<template>
<section class="project">
<div class="container">
<RouterLink class="back mono" to="/">{{ t.project.back }}</RouterLink>
<p v-if="loading" class="state mono" data-reveal>{{ t.project.loading }}</p>
<div v-else-if="notFound" class="state" data-reveal>
<p class="state-title">{{ t.project.notFound }}</p>
<p class="state-sub mono">{{ route.params.name }}</p>
<RouterLink class="btn btn-ghost" to="/">{{ t.project.back }}</RouterLink>
</div>
<div v-else-if="error" class="state" data-reveal>
<p class="state-title">{{ t.project.loadFailed }}</p>
<p class="state-sub mono">{{ error }}</p>
<button class="btn btn-ghost" @click="load">{{ t.project.retry }}</button>
</div>
<template v-else-if="project">
<!-- 包信息头 -->
<header class="pkg-head" data-reveal>
<div class="pkg-title">
<h1 class="pkg-name mono">{{ project.PackageName }}</h1>
<span v-if="project.LatestVersion" class="ver-pill mono">
v{{ project.LatestVersion }}
</span>
</div>
<p class="pkg-desc">{{ project.PackageDesc || t.project.noDesc }}</p>
<div class="pkg-meta mono">
<span v-if="project.CreatorName">{{ t.project.author }} {{ project.CreatorName }}</span>
<span>{{ t.project.released }} {{ project.ReleaseCount }}</span>
<span v-if="project.UpdateTime">{{ t.project.updatedAt }} {{ project.UpdateTime }}</span>
<span class="pkg-uuid">{{ project.PackageUUID }}</span>
</div>
</header>
<div class="pkg-body">
<!-- README -->
<article class="readme" data-reveal>
<div class="eyebrow">{{ t.project.readme }}</div>
<div v-if="readmeHtml" class="md" v-html="readmeHtml"></div>
<p v-else class="empty">{{ t.project.noReadme }}</p>
</article>
<!-- 侧栏 -->
<aside class="side">
<div class="card" data-reveal>
<h2 class="card-title mono">{{ t.project.install }}</h2>
<div class="install">
<code class="install-cmd mono">{{ installCommand }}</code>
<button class="copy mono" @click="copyInstall">
{{ copied ? 'OK' : 'COPY' }}
</button>
</div>
<p class="hint">{{ t.project.installHint }}</p>
</div>
<div class="card" data-reveal>
<h2 class="card-title mono">{{ t.project.dependencies }}</h2>
<ul v-if="project.Dependencies.length" class="deps">
<li v-for="d in project.Dependencies" :key="d.Name">
<RouterLink class="dep-name mono" :to="`/project/${encodeName(d.Name)}`">
{{ d.Name }}
</RouterLink>
<span class="dep-ver mono">{{ d.Version || '*' }}</span>
</li>
</ul>
<p v-else class="empty">{{ t.project.noDeps }}</p>
</div>
<div class="card" data-reveal>
<h2 class="card-title mono">{{ t.project.versions }}</h2>
<ul v-if="project.Releases.length" class="rels">
<li v-for="r in project.Releases" :key="r.ReleaseUUID">
<div class="rel-line">
<span class="rel-ver mono">{{ r.Version }}</span>
<span class="rel-time mono">{{ r.CreateTime }}</span>
</div>
<p v-if="r.Notes" class="rel-notes">{{ r.Notes }}</p>
<p v-if="r.PublisherName" class="rel-by mono">
{{ t.project.publishedBy }} {{ r.PublisherName }}
</p>
</li>
</ul>
<p v-else class="empty">{{ t.project.noVersions }}</p>
</div>
</aside>
</div>
</template>
</div>
</section>
</template>
<style scoped>
/* 页面基线:让开固定顶栏 */
.project {
padding-top: calc(var(--nav-h) + 44px);
padding-bottom: clamp(4rem, 8vw, 6rem);
min-height: 60vh;
}
.back {
display: inline-flex;
font-size: 0.82rem;
letter-spacing: 0.06em;
color: var(--text-dim);
padding: 6px 12px;
border: 1px solid var(--line-soft);
border-radius: 999px;
transition: color 0.2s var(--ease-out), border-color 0.2s var(--ease-out);
}
.back:hover {
color: var(--accent-strong);
border-color: var(--line);
}
/* ------- 状态(加载 / 不存在 / 失败) ------- */
.state {
margin-top: 40px;
display: flex;
flex-direction: column;
align-items: flex-start;
gap: 12px;
color: var(--text-dim);
}
.state-title {
font-size: 1.3rem;
font-weight: 650;
color: var(--text);
}
.state-sub {
font-size: 0.86rem;
color: var(--text-faint);
word-break: break-all;
}
/* ------- 包信息头 ------- */
.pkg-head {
margin: 22px 0 34px;
padding-bottom: 22px;
border-bottom: 1px solid var(--line-soft);
}
.pkg-title {
display: flex;
align-items: center;
flex-wrap: wrap;
gap: 12px;
}
.pkg-name {
font-size: clamp(1.5rem, 3.4vw, 2.1rem);
font-weight: 700;
letter-spacing: -0.01em;
word-break: break-all;
}
.ver-pill {
font-size: 0.78rem;
color: var(--accent-strong);
background: rgba(var(--accent-rgb), 0.09);
border: 1px solid rgba(var(--accent-rgb), 0.22);
border-radius: 999px;
padding: 3px 10px;
}
.pkg-desc {
margin-top: 10px;
color: var(--text-dim);
max-width: 70ch;
}
.pkg-meta {
margin-top: 14px;
display: flex;
flex-wrap: wrap;
gap: 8px 20px;
font-size: 0.76rem;
color: var(--text-faint);
}
.pkg-uuid {
opacity: 0.6;
}
/* ------- 主体两栏 ------- */
.pkg-body {
display: grid;
grid-template-columns: minmax(0, 1fr) 320px;
gap: 40px;
align-items: start;
}
@media (max-width: 980px) {
.pkg-body {
grid-template-columns: minmax(0, 1fr);
gap: 28px;
}
}
/* ------- README ------- */
.readme {
min-width: 0;
}
.readme .eyebrow {
margin-bottom: 12px;
}
.md {
color: var(--text);
font-size: 0.97rem;
line-height: 1.78;
word-wrap: break-word;
}
.md :deep(h1),
.md :deep(h2),
.md :deep(h3),
.md :deep(h4) {
font-weight: 650;
line-height: 1.3;
letter-spacing: -0.01em;
margin: 1.9em 0 0.7em;
}
.md :deep(h1) {
font-size: 1.6rem;
padding-bottom: 0.3em;
border-bottom: 1px solid var(--line-soft);
}
.md :deep(h2) {
font-size: 1.28rem;
padding-bottom: 0.28em;
border-bottom: 1px solid var(--line-soft);
}
.md :deep(h3) {
font-size: 1.06rem;
}
.md :deep(> :first-child) {
margin-top: 0;
}
.md :deep(p),
.md :deep(ul),
.md :deep(ol),
.md :deep(table),
.md :deep(blockquote) {
margin: 0.85em 0;
}
.md :deep(ul),
.md :deep(ol) {
padding-left: 1.4em;
}
.md :deep(li) {
margin: 0.3em 0;
}
.md :deep(a) {
color: var(--accent);
border-bottom: 1px solid rgba(var(--accent-rgb), 0.3);
}
.md :deep(a:hover) {
color: var(--accent-strong);
border-bottom-color: var(--accent-strong);
}
.md :deep(code) {
font-family: var(--font-mono);
font-size: 0.86em;
background: var(--bg-soft);
border: 1px solid var(--line-soft);
border-radius: var(--radius-sm);
padding: 0.12em 0.4em;
}
.md :deep(pre) {
background: var(--bg-soft);
border: 1px solid var(--line-soft);
border-radius: var(--radius);
padding: 14px 16px;
overflow-x: auto;
}
.md :deep(pre code) {
background: none;
border: none;
padding: 0;
font-size: 0.85rem;
line-height: 1.7;
}
.md :deep(blockquote) {
border-left: 3px solid rgba(var(--accent-rgb), 0.35);
background: var(--panel);
padding: 2px 16px;
color: var(--text-dim);
}
.md :deep(table) {
width: 100%;
border-collapse: collapse;
font-size: 0.9rem;
}
.md :deep(th),
.md :deep(td) {
border: 1px solid var(--line-soft);
padding: 8px 12px;
text-align: left;
}
.md :deep(th) {
background: var(--bg-soft);
font-weight: 600;
}
.md :deep(hr) {
border: none;
border-top: 1px solid var(--line-soft);
margin: 2em 0;
}
.md :deep(img) {
border-radius: var(--radius);
}
/* ------- 侧栏卡片 ------- */
.side {
display: flex;
flex-direction: column;
gap: 18px;
position: sticky;
top: calc(var(--nav-h) + 20px);
}
.card {
border: 1px solid var(--line-soft);
border-radius: var(--radius);
padding: 16px 16px 18px;
background: #fff;
}
.card-title {
font-size: 0.74rem;
letter-spacing: 0.18em;
text-transform: uppercase;
color: var(--accent);
margin-bottom: 12px;
}
.install {
display: flex;
align-items: center;
gap: 8px;
background: var(--bg-soft);
border: 1px solid var(--line-soft);
border-radius: var(--radius-sm);
padding: 9px 10px;
}
.install-cmd {
flex: 1;
min-width: 0;
font-size: 0.8rem;
overflow-x: auto;
white-space: nowrap;
}
.copy {
flex-shrink: 0;
font-size: 0.68rem;
letter-spacing: 0.12em;
color: var(--text-dim);
border: 1px solid var(--line-soft);
border-radius: var(--radius-sm);
padding: 4px 8px;
transition: color 0.2s, border-color 0.2s;
}
.copy:hover {
color: var(--accent-strong);
border-color: var(--line);
}
.hint {
margin-top: 8px;
font-size: 0.76rem;
color: var(--text-faint);
}
.deps {
list-style: none;
display: flex;
flex-direction: column;
gap: 8px;
}
.deps li {
display: flex;
align-items: baseline;
justify-content: space-between;
gap: 10px;
font-size: 0.84rem;
}
.dep-name {
color: var(--accent);
word-break: break-all;
}
.dep-name:hover {
color: var(--accent-strong);
text-decoration: underline;
}
.dep-ver {
flex-shrink: 0;
font-size: 0.76rem;
color: var(--text-faint);
}
.rels {
list-style: none;
display: flex;
flex-direction: column;
gap: 12px;
}
.rels li {
padding-bottom: 12px;
border-bottom: 1px dashed var(--line-soft);
}
.rels li:last-child {
padding-bottom: 0;
border-bottom: none;
}
.rel-line {
display: flex;
align-items: baseline;
justify-content: space-between;
gap: 10px;
}
.rel-ver {
font-size: 0.86rem;
font-weight: 650;
color: var(--text);
}
.rel-time {
font-size: 0.72rem;
color: var(--text-faint);
}
.rel-notes {
margin-top: 5px;
font-size: 0.8rem;
color: var(--text-dim);
white-space: pre-wrap;
}
.rel-by {
margin-top: 4px;
font-size: 0.72rem;
color: var(--text-faint);
}
.empty {
font-size: 0.84rem;
color: var(--text-faint);
}
</style>