From 979d2e2236da13d1c6e86b4855c39703745523d2 Mon Sep 17 00:00:00 2001 From: Falck Date: Sat, 25 Apr 2026 15:48:07 +0800 Subject: [PATCH] =?UTF-8?q?=E5=AE=8C=E6=88=90v1.1.0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- website/package-lock.json | 1250 ++++++++++++++++++++ website/package.json | 33 + website/public/css/animations.css | 555 +++++++++ website/public/css/components.css | 283 +++++ website/public/css/main.css | 239 ++++ website/public/css/pages/architecture.css | 0 website/public/css/pages/features.css | 0 website/public/css/pages/home.css | 0 website/public/css/pages/plugins.css | 0 website/public/images/logo.svg | 320 +++++ website/public/js/animations.js | 465 ++++++++ website/public/js/main.js | 408 +++++++ website/public/js/pages/architecture.js | 0 website/public/js/pages/features.js | 0 website/public/js/pages/home.js | 0 website/public/js/pages/plugins.js | 0 website/public/js/router.js | 264 +++++ website/src/controllers/apiController.js | 88 ++ website/src/controllers/pagesController.js | 48 + website/src/middleware/performance.js | 218 ++++ website/src/router.js | 40 + website/src/server.js | 175 +++ website/views/layouts/main.ejs | 87 ++ website/views/pages/features.ejs | 299 +++++ website/views/pages/home.ejs | 173 +++ website/views/partials/footer.ejs | 122 ++ website/views/partials/navbar.ejs | 108 ++ 27 files changed, 5175 insertions(+) create mode 100644 website/package-lock.json create mode 100644 website/package.json create mode 100644 website/public/css/animations.css create mode 100644 website/public/css/components.css create mode 100644 website/public/css/main.css create mode 100644 website/public/css/pages/architecture.css create mode 100644 website/public/css/pages/features.css create mode 100644 website/public/css/pages/home.css create mode 100644 website/public/css/pages/plugins.css create mode 100644 website/public/images/logo.svg create mode 100644 website/public/js/animations.js create mode 100644 website/public/js/main.js create mode 100644 website/public/js/pages/architecture.js create mode 100644 website/public/js/pages/features.js create mode 100644 website/public/js/pages/home.js create mode 100644 website/public/js/pages/plugins.js create mode 100644 website/public/js/router.js create mode 100644 website/src/controllers/apiController.js create mode 100644 website/src/controllers/pagesController.js create mode 100644 website/src/middleware/performance.js create mode 100644 website/src/router.js create mode 100644 website/src/server.js create mode 100644 website/views/layouts/main.ejs create mode 100644 website/views/pages/features.ejs create mode 100644 website/views/pages/home.ejs create mode 100644 website/views/partials/footer.ejs create mode 100644 website/views/partials/navbar.ejs diff --git a/website/package-lock.json b/website/package-lock.json new file mode 100644 index 0000000..9f8e13d --- /dev/null +++ b/website/package-lock.json @@ -0,0 +1,1250 @@ +{ + "name": "futureoss-website", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "futureoss-website", + "version": "1.0.0", + "license": "Apache-2.0", + "dependencies": { + "compression": "^1.7.4", + "cors": "^2.8.5", + "ejs": "^5.0.2", + "express": "^4.18.2", + "express-ejs-layouts": "^2.5.1", + "morgan": "^1.10.0" + }, + "devDependencies": { + "nodemon": "^3.0.1" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/accepts/node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==" + }, + "node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "dev": true, + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/basic-auth": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-2.0.1.tgz", + "integrity": "sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg==", + "dependencies": { + "safe-buffer": "5.1.2" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/basic-auth/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, + "node_modules/binary-extensions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/body-parser": { + "version": "1.20.4", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.4.tgz", + "integrity": "sha512-ZTgYYLMOXY9qKU/57FAo8F+HA2dGX7bqGc71txDRC1rS4frdFI5R7NhluHxH6M0YItAP0sHB4uqAOcYKxO6uGA==", + "dependencies": { + "bytes": "~3.1.2", + "content-type": "~1.0.5", + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "~1.2.0", + "http-errors": "~2.0.1", + "iconv-lite": "~0.4.24", + "on-finished": "~2.4.1", + "qs": "~6.14.0", + "raw-body": "~2.5.3", + "type-is": "~1.6.18", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/brace-expansion": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz", + "integrity": "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==", + "dev": true, + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "dev": true, + "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" + }, + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/compressible": { + "version": "2.0.18", + "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz", + "integrity": "sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==", + "dependencies": { + "mime-db": ">= 1.43.0 < 2" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/compression": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/compression/-/compression-1.8.1.tgz", + "integrity": "sha512-9mAqGPHLakhCLeNyxPkK4xVo746zQ/czLH1Ky+vkitMnWfWZps8r0qXuwhwizagCRttsL4lfG4pIOvaWLpAP0w==", + "dependencies": { + "bytes": "3.1.2", + "compressible": "~2.0.18", + "debug": "2.6.9", + "negotiator": "~0.6.4", + "on-headers": "~1.1.0", + "safe-buffer": "5.2.1", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/content-disposition": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.7.tgz", + "integrity": "sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA==" + }, + "node_modules/cors": { + "version": "2.8.6", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.6.tgz", + "integrity": "sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw==", + "dependencies": { + "object-assign": "^4", + "vary": "^1" + }, + "engines": { + "node": ">= 0.10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" + }, + "node_modules/ejs": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/ejs/-/ejs-5.0.2.tgz", + "integrity": "sha512-IpbUaI/CAW86l3f+T8zN0iggSc0LmMZLcIW5eRVStLVNCoTXkE0YlncbbH50fp8Cl6zHIky0sW2uUbhBqGw0Jw==", + "bin": { + "ejs": "bin/cli.js" + }, + "engines": { + "node": ">=0.12.18" + } + }, + "node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==" + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/express": { + "version": "4.22.1", + "resolved": "https://registry.npmjs.org/express/-/express-4.22.1.tgz", + "integrity": "sha512-F2X8g9P1X7uCPZMA3MVf9wcTqlyNp7IhH5qPCI0izhaOIYXaW9L535tGA3qmjRzpH+bZczqq7hVKxTR4NWnu+g==", + "dependencies": { + "accepts": "~1.3.8", + "array-flatten": "1.1.1", + "body-parser": "~1.20.3", + "content-disposition": "~0.5.4", + "content-type": "~1.0.4", + "cookie": "~0.7.1", + "cookie-signature": "~1.0.6", + "debug": "2.6.9", + "depd": "2.0.0", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "~1.3.1", + "fresh": "~0.5.2", + "http-errors": "~2.0.0", + "merge-descriptors": "1.0.3", + "methods": "~1.1.2", + "on-finished": "~2.4.1", + "parseurl": "~1.3.3", + "path-to-regexp": "~0.1.12", + "proxy-addr": "~2.0.7", + "qs": "~6.14.0", + "range-parser": "~1.2.1", + "safe-buffer": "5.2.1", + "send": "~0.19.0", + "serve-static": "~1.16.2", + "setprototypeof": "1.2.0", + "statuses": "~2.0.1", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/express-ejs-layouts": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/express-ejs-layouts/-/express-ejs-layouts-2.5.1.tgz", + "integrity": "sha512-IXROv9n3xKga7FowT06n1Qn927JR8ZWDn5Dc9CJQoiiaaDqbhW5PDmWShzbpAa2wjWT1vJqaIM1S6vJwwX11gA==" + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/finalhandler": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.2.tgz", + "integrity": "sha512-aA4RyPcd3badbdABGDuTXCMTtOneUCAYH/gxoYRTZlIJdF0YPWuGqiAsIrhNnnqdXGswYk6dGujem4w80UJFhg==", + "dependencies": { + "debug": "2.6.9", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "on-finished": "~2.4.1", + "parseurl": "~1.3.3", + "statuses": "~2.0.2", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.3.tgz", + "integrity": "sha512-ej4AhfhfL2Q2zpMmLo7U1Uv9+PyhIZpgQLGT1F9miIGmiCJIoCgSmczFdrc97mWT4kVY72KA+WnnhJ5pghSvSg==", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/http-errors": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", + "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", + "dependencies": { + "depd": "~2.0.0", + "inherits": "~2.0.4", + "setprototypeof": "~1.2.0", + "statuses": "~2.0.2", + "toidentifier": "~1.0.1" + }, + "engines": { + "node": ">= 0.8" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ignore-by-default": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz", + "integrity": "sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==", + "dev": true + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/merge-descriptors": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", + "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types/node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/minimatch": { + "version": "10.2.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", + "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==", + "dev": true, + "dependencies": { + "brace-expansion": "^5.0.5" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/morgan": { + "version": "1.10.1", + "resolved": "https://registry.npmjs.org/morgan/-/morgan-1.10.1.tgz", + "integrity": "sha512-223dMRJtI/l25dJKWpgij2cMtywuG/WiUKXdvwfbhGKBhy1puASqXwFzmWZ7+K73vUPoR7SS2Qz2cI/g9MKw0A==", + "dependencies": { + "basic-auth": "~2.0.1", + "debug": "2.6.9", + "depd": "~2.0.0", + "on-finished": "~2.3.0", + "on-headers": "~1.1.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/morgan/node_modules/on-finished": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", + "integrity": "sha512-ikqdkGAAyf/X/gPhXGvfgAytDZtDbr+bkNUJ0N9h5MI/dmdgCs3l6hoHrcUv41sRKew3jIwrp4qQDXiK99Utww==", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + }, + "node_modules/negotiator": { + "version": "0.6.4", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.4.tgz", + "integrity": "sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/nodemon": { + "version": "3.1.14", + "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.14.tgz", + "integrity": "sha512-jakjZi93UtB3jHMWsXL68FXSAosbLfY0In5gtKq3niLSkrWznrVBzXFNOEMJUfc9+Ke7SHWoAZsiMkNP3vq6Jw==", + "dev": true, + "dependencies": { + "chokidar": "^3.5.2", + "debug": "^4", + "ignore-by-default": "^1.0.1", + "minimatch": "^10.2.1", + "pstree.remy": "^1.1.8", + "semver": "^7.5.3", + "simple-update-notifier": "^2.0.0", + "supports-color": "^5.5.0", + "touch": "^3.1.0", + "undefsafe": "^2.0.5" + }, + "bin": { + "nodemon": "bin/nodemon.js" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/nodemon" + } + }, + "node_modules/nodemon/node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/nodemon/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/on-headers": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.1.0.tgz", + "integrity": "sha512-737ZY3yNnXy37FHkQxPzt4UZ2UWPWiCZWLvFZ4fu5cueciegX0zGPnrlY6bwRg4FdQOe9YU8MkmJwGhoMybl8A==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/path-to-regexp": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.13.tgz", + "integrity": "sha512-A/AGNMFN3c8bOlvV9RreMdrv7jsmF9XIfDeCd87+I8RNg6s78BhJxMu69NEMHBSJFxKidViTEdruRwEk/WIKqA==" + }, + "node_modules/picomatch": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", + "dev": true, + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/pstree.remy": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz", + "integrity": "sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==", + "dev": true + }, + "node_modules/qs": { + "version": "6.14.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.2.tgz", + "integrity": "sha512-V/yCWTTF7VJ9hIh18Ugr2zhJMP01MY7c5kh4J870L7imm6/DIzBsNLTXzMwUA3yZ5b/KBqLx8Kp3uRvd7xSe3Q==", + "dependencies": { + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "2.5.3", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.3.tgz", + "integrity": "sha512-s4VSOf6yN0rvbRZGxs8Om5CWj6seneMwK3oDb4lWDH0UPhWcxwOWw5+qk24bxq87szX1ydrwylIOp2uG1ojUpA==", + "dependencies": { + "bytes": "~3.1.2", + "http-errors": "~2.0.1", + "iconv-lite": "~0.4.24", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + }, + "node_modules/semver": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/send": { + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/send/-/send-0.19.2.tgz", + "integrity": "sha512-VMbMxbDeehAxpOtWJXlcUS5E8iXh6QmN+BkRX1GARS3wRaXEEgzCcB10gTQazO42tpNIya8xIyNx8fll1OFPrg==", + "dependencies": { + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "~0.5.2", + "http-errors": "~2.0.1", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "~2.4.1", + "range-parser": "~1.2.1", + "statuses": "~2.0.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/send/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + }, + "node_modules/serve-static": { + "version": "1.16.3", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.3.tgz", + "integrity": "sha512-x0RTqQel6g5SY7Lg6ZreMmsOzncHFU7nhnRWkKgWuMTu5NN0DR5oruckMqRvacAN9d5w6ARnRBXl9xhDCgfMeA==", + "dependencies": { + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "~0.19.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" + }, + "node_modules/side-channel": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.1.tgz", + "integrity": "sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/simple-update-notifier": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz", + "integrity": "sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==", + "dev": true, + "dependencies": { + "semver": "^7.5.3" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/statuses": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/touch": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/touch/-/touch-3.1.1.tgz", + "integrity": "sha512-r0eojU4bI8MnHr8c5bNo7lJDdI2qXlWWJk6a9EAFG7vbhTjElYhBVS3/miuE0uOuoLdb8Mc/rVfsmm6eo5o9GA==", + "dev": true, + "bin": { + "nodetouch": "bin/nodetouch.js" + } + }, + "node_modules/type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/undefsafe": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz", + "integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==", + "dev": true + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "engines": { + "node": ">= 0.8" + } + } + } +} diff --git a/website/package.json b/website/package.json new file mode 100644 index 0000000..dbd734a --- /dev/null +++ b/website/package.json @@ -0,0 +1,33 @@ +{ + "name": "futureoss-website", + "version": "1.0.0", + "description": "FutureOSS官方网站 - 企业化简约风格", + "main": "src/server.js", + "scripts": { + "start": "node src/server.js", + "dev": "nodemon src/server.js", + "build": "echo 'Build step not required for this project'" + }, + "keywords": [ + "futureoss", + "plugin", + "framework", + "website" + ], + "author": "FutureOSS Team", + "license": "Apache-2.0", + "dependencies": { + "compression": "^1.7.4", + "cors": "^2.8.5", + "ejs": "^5.0.2", + "express": "^4.18.2", + "express-ejs-layouts": "^2.5.1", + "morgan": "^1.10.0" + }, + "devDependencies": { + "nodemon": "^3.0.1" + }, + "engines": { + "node": ">=16.0.0" + } +} diff --git a/website/public/css/animations.css b/website/public/css/animations.css new file mode 100644 index 0000000..0898961 --- /dev/null +++ b/website/public/css/animations.css @@ -0,0 +1,555 @@ +/* 动画样式文件 */ + +/* 淡入动画 */ +@keyframes fadeIn { + from { + opacity: 0; + transform: translateY(20px); + } + to { + opacity: 1; + transform: translateY(0); + } +} + +@keyframes fadeInUp { + from { + opacity: 0; + transform: translateY(40px); + } + to { + opacity: 1; + transform: translateY(0); + } +} + +@keyframes fadeInDown { + from { + opacity: 0; + transform: translateY(-40px); + } + to { + opacity: 1; + transform: translateY(0); + } +} + +@keyframes fadeInLeft { + from { + opacity: 0; + transform: translateX(-40px); + } + to { + opacity: 1; + transform: translateX(0); + } +} + +@keyframes fadeInRight { + from { + opacity: 0; + transform: translateX(40px); + } + to { + opacity: 1; + transform: translateX(0); + } +} + +/* 缩放动画 */ +@keyframes scaleIn { + from { + opacity: 0; + transform: scale(0.9); + } + to { + opacity: 1; + transform: scale(1); + } +} + +@keyframes scaleOut { + from { + opacity: 1; + transform: scale(1); + } + to { + opacity: 0; + transform: scale(0.9); + } +} + +/* 旋转动画 */ +@keyframes spin { + from { + transform: rotate(0deg); + } + to { + transform: rotate(360deg); + } +} + +@keyframes spinReverse { + from { + transform: rotate(0deg); + } + to { + transform: rotate(-360deg); + } +} + +/* 脉动动画 */ +@keyframes pulse { + 0%, 100% { + opacity: 1; + transform: scale(1); + } + 50% { + opacity: 0.8; + transform: scale(1.05); + } +} + +@keyframes pulseSoft { + 0%, 100% { + transform: scale(1); + } + 50% { + transform: scale(1.02); + } +} + +/* 滑动动画 */ +@keyframes slideInUp { + from { + transform: translateY(100%); + opacity: 0; + } + to { + transform: translateY(0); + opacity: 1; + } +} + +@keyframes slideInDown { + from { + transform: translateY(-100%); + opacity: 0; + } + to { + transform: translateY(0); + opacity: 1; + } +} + +@keyframes slideInLeft { + from { + transform: translateX(-100%); + opacity: 0; + } + to { + transform: translateX(0); + opacity: 1; + } +} + +@keyframes slideInRight { + from { + transform: translateX(100%); + opacity: 0; + } + to { + transform: translateX(0); + opacity: 1; + } +} + +/* 弹跳动画 */ +@keyframes bounce { + 0%, 20%, 50%, 80%, 100% { + transform: translateY(0); + } + 40% { + transform: translateY(-20px); + } + 60% { + transform: translateY(-10px); + } +} + +/* 闪烁动画 */ +@keyframes blink { + 0%, 100% { + opacity: 1; + } + 50% { + opacity: 0.5; + } +} + +/* 波浪动画 */ +@keyframes wave { + 0% { + transform: translateX(0); + } + 50% { + transform: translateX(-10px); + } + 100% { + transform: translateX(0); + } +} + +/* 动画工具类 */ +.animate-fade-in { + animation: fadeIn var(--transition-normal); +} + +.animate-fade-in-up { + animation: fadeInUp var(--transition-normal); +} + +.animate-fade-in-down { + animation: fadeInDown var(--transition-normal); +} + +.animate-fade-in-left { + animation: fadeInLeft var(--transition-normal); +} + +.animate-fade-in-right { + animation: fadeInRight var(--transition-normal); +} + +.animate-scale-in { + animation: scaleIn var(--transition-normal); +} + +.animate-scale-out { + animation: scaleOut var(--transition-normal); +} + +.animate-spin { + animation: spin 1s linear infinite; +} + +.animate-spin-reverse { + animation: spinReverse 1s linear infinite; +} + +.animate-pulse { + animation: pulse 2s ease-in-out infinite; +} + +.animate-pulse-soft { + animation: pulseSoft 3s ease-in-out infinite; +} + +.animate-slide-in-up { + animation: slideInUp var(--transition-normal); +} + +.animate-slide-in-down { + animation: slideInDown var(--transition-normal); +} + +.animate-slide-in-left { + animation: slideInLeft var(--transition-normal); +} + +.animate-slide-in-right { + animation: slideInRight var(--transition-normal); +} + +.animate-bounce { + animation: bounce 2s infinite; +} + +.animate-blink { + animation: blink 1s infinite; +} + +.animate-wave { + animation: wave 2s ease-in-out infinite; +} + +/* 延迟动画 */ +.delay-100 { + animation-delay: 100ms; +} + +.delay-200 { + animation-delay: 200ms; +} + +.delay-300 { + animation-delay: 300ms; +} + +.delay-400 { + animation-delay: 400ms; +} + +.delay-500 { + animation-delay: 500ms; +} + +.delay-600 { + animation-delay: 600ms; +} + +.delay-700 { + animation-delay: 700ms; +} + +.delay-800 { + animation-delay: 800ms; +} + +.delay-900 { + animation-delay: 900ms; +} + +.delay-1000 { + animation-delay: 1000ms; +} + +/* 动画持续时间 */ +.duration-fast { + animation-duration: var(--transition-fast); +} + +.duration-normal { + animation-duration: var(--transition-normal); +} + +.duration-slow { + animation-duration: var(--transition-slow); +} + +/* 页面切换动画 */ +.page-transition { + animation: fadeIn var(--transition-normal); +} + +.page-transition.active { + animation: fadeOut var(--transition-normal); +} + +/* 悬停动画 */ +.hover-lift { + transition: transform var(--transition-normal); +} + +.hover-lift:hover { + transform: translateY(-4px); +} + +.hover-scale { + transition: transform var(--transition-normal); +} + +.hover-scale:hover { + transform: scale(1.05); +} + +.hover-rotate { + transition: transform var(--transition-normal); +} + +.hover-rotate:hover { + transform: rotate(5deg); +} + +/* 加载动画 */ +.loading-spinner { + width: 40px; + height: 40px; + border: 3px solid var(--border-color); + border-top-color: var(--primary-color); + border-radius: 50%; + animation: spin 1s linear infinite; +} + +.loading-dots { + display: flex; + gap: 4px; +} + +.loading-dots span { + width: 8px; + height: 8px; + background-color: var(--primary-color); + border-radius: 50%; + animation: pulse 1.4s ease-in-out infinite; +} + +.loading-dots span:nth-child(2) { + animation-delay: 0.2s; +} + +.loading-dots span:nth-child(3) { + animation-delay: 0.4s; +} + +/* 进度条动画 */ +@keyframes progress { + from { + width: 0%; + } + to { + width: 100%; + } +} + +.progress-bar { + animation: progress 2s ease-in-out infinite; +} + +/* 打字机效果 */ +@keyframes typing { + from { + width: 0; + } + to { + width: 100%; + } +} + +@keyframes blink-caret { + from, to { + border-color: transparent; + } + 50% { + border-color: var(--primary-color); + } +} + +.typewriter { + overflow: hidden; + border-right: 2px solid var(--primary-color); + white-space: nowrap; + animation: typing 3.5s steps(40, end), blink-caret 0.75s step-end infinite; +} + +/* 加载动画样式 */ +.loading-overlay { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + background-color: var(--bg-primary); + display: flex; + align-items: center; + justify-content: center; + z-index: 9999; + transition: opacity 0.5s ease, visibility 0.5s ease; +} + +.loading-overlay.hidden { + opacity: 0; + visibility: hidden; +} + +.loading-spinner { + text-align: center; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; +} + +.spinner { + width: 60px; + height: 60px; + border: 4px solid rgba(30, 111, 187, 0.1); + border-top-color: var(--primary-color); + border-radius: 50%; + animation: spin 1s linear infinite; + margin-bottom: 20px; +} + +.loading-text { + color: var(--text-secondary); + font-size: var(--font-size-lg); + font-weight: 500; + text-align: center; + animation: none !important; /* 确保文字不应用任何动画 */ +} + +/* 骨架屏加载动画 */ +.skeleton { + background: linear-gradient(90deg, #f0f0f0 25%, #e0e0e0 50%, #f0f0f0 75%); + background-size: 200% 100%; + animation: skeleton-loading 1.5s infinite; + border-radius: var(--radius-md); +} + +@keyframes skeleton-loading { + 0% { + background-position: 200% 0; + } + 100% { + background-position: -200% 0; + } +} + +/* 渐进式加载动画 */ +.fade-in-load { + opacity: 0; + transform: translateY(20px); + animation: fadeInUp 0.6s ease forwards; +} + +.fade-in-load.delay-1 { + animation-delay: 0.1s; +} + +.fade-in-load.delay-2 { + animation-delay: 0.2s; +} + +.fade-in-load.delay-3 { + animation-delay: 0.3s; +} + +.fade-in-load.delay-4 { + animation-delay: 0.4s; +} + +/* 性能优化:减少重绘 */ +.will-change-transform { + will-change: transform; +} + +.will-change-opacity { + will-change: opacity; +} + +/* 图片懒加载占位符 */ +.lazy-image { + background-color: var(--bg-secondary); + position: relative; + overflow: hidden; +} + +.lazy-image::after { + content: ''; + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: linear-gradient(90deg, transparent, rgba(255,255,255,0.4), transparent); + animation: shimmer 2s infinite; +} + +@keyframes shimmer { + 0% { + transform: translateX(-100%); + } + 100% { + transform: translateX(100%); + } +} \ No newline at end of file diff --git a/website/public/css/components.css b/website/public/css/components.css new file mode 100644 index 0000000..1fdc458 --- /dev/null +++ b/website/public/css/components.css @@ -0,0 +1,283 @@ +/* 组件样式文件 */ + +/* 导航栏样式 */ +.navbar { + background-color: var(--bg-primary); + border-bottom: 1px solid var(--border-color); + padding: var(--spacing-md) 0; + position: sticky; + top: 0; + z-index: 1000; + box-shadow: var(--shadow-sm); +} + +.navbar-container { + display: flex; + align-items: center; + justify-content: space-between; +} + +.navbar-brand { + display: flex; + align-items: center; + gap: var(--spacing-sm); + text-decoration: none; + color: var(--text-primary); + font-weight: 600; + font-size: var(--font-size-lg); +} + +.navbar-logo { + height: 32px; + width: auto; +} + +.navbar-menu { + display: flex; + gap: var(--spacing-lg); + list-style: none; + margin: 0; + padding: 0; +} + +.navbar-item { + position: relative; +} + +.navbar-link { + color: var(--text-secondary); + text-decoration: none; + padding: var(--spacing-sm) var(--spacing-md); + border-radius: var(--radius-sm); + transition: all var(--transition-fast); + font-weight: 500; +} + +.navbar-link:hover { + color: var(--primary-color); + background-color: var(--bg-secondary); +} + +.navbar-link.active { + color: var(--primary-color); + background-color: rgba(30, 111, 187, 0.1); +} + +/* 页脚样式 */ +.footer { + background-color: var(--bg-dark); + color: white; + padding: var(--spacing-xl) 0; + margin-top: auto; +} + +.footer-container { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); + gap: var(--spacing-xl); +} + +.footer-section h3 { + color: white; + margin-bottom: var(--spacing-lg); + font-size: var(--font-size-lg); +} + +.footer-links { + list-style: none; + padding: 0; + margin: 0; +} + +.footer-links li { + margin-bottom: var(--spacing-sm); +} + +.footer-links a { + color: rgba(255, 255, 255, 0.8); + text-decoration: none; + transition: color var(--transition-fast); +} + +.footer-links a:hover { + color: white; +} + +.footer-bottom { + text-align: center; + padding-top: var(--spacing-xl); + margin-top: var(--spacing-xl); + border-top: 1px solid rgba(255, 255, 255, 0.1); + color: rgba(255, 255, 255, 0.6); +} + +/* 卡片组件 */ +.card { + background-color: var(--bg-primary); + border: 1px solid var(--border-color); + border-radius: var(--radius-lg); + padding: var(--spacing-lg); + box-shadow: var(--shadow-sm); + transition: all var(--transition-normal); +} + +.card:hover { + box-shadow: var(--shadow-md); + transform: translateY(-4px); +} + +.card-header { + margin-bottom: var(--spacing-md); +} + +.card-title { + font-size: var(--font-size-lg); + font-weight: 600; + margin: 0; +} + +.card-content { + color: var(--text-secondary); +} + +/* 页面头部 */ +.page-header { + background: linear-gradient(135deg, var(--primary-color), var(--secondary-color)); + color: white; + padding: var(--spacing-xxl) 0; + text-align: center; +} + +.page-title { + font-size: var(--font-size-xxxl); + font-weight: 700; + margin-bottom: var(--spacing-md); +} + +.page-subtitle { + font-size: var(--font-size-lg); + opacity: 0.9; + margin-bottom: var(--spacing-xl); + max-width: 600px; + margin-left: auto; + margin-right: auto; +} + +.page-actions { + display: flex; + gap: var(--spacing-md); + justify-content: center; + flex-wrap: wrap; +} + +/* 特性卡片 */ +.feature-card { + height: 100%; + display: flex; + flex-direction: column; +} + +.feature-header { + display: flex; + align-items: center; + gap: var(--spacing-md); + margin-bottom: var(--spacing-md); +} + +.feature-icon { + width: 48px; + height: 48px; + background-color: rgba(30, 111, 187, 0.1); + border-radius: var(--radius-round); + display: flex; + align-items: center; + justify-content: center; + color: var(--primary-color); + font-size: var(--font-size-xl); +} + +.feature-title { + font-size: var(--font-size-lg); + font-weight: 600; + margin: 0; + flex: 1; +} + +.feature-badge { + background-color: var(--primary-color); + color: white; + padding: var(--spacing-xs) var(--spacing-sm); + border-radius: var(--radius-sm); + font-size: var(--font-size-xs); + font-weight: 600; +} + +.feature-content { + flex: 1; +} + +.feature-desc { + color: var(--text-secondary); + margin-bottom: var(--spacing-md); +} + +.feature-list { + list-style: none; + padding: 0; + margin: 0; +} + +.feature-item { + display: flex; + align-items: center; + gap: var(--spacing-sm); + margin-bottom: var(--spacing-sm); + color: var(--text-secondary); +} + +.feature-item i { + color: var(--success-color); +} + +.feature-footer { + display: flex; + gap: var(--spacing-sm); + flex-wrap: wrap; + margin-top: var(--spacing-md); + padding-top: var(--spacing-md); + border-top: 1px solid var(--border-color); +} + +.feature-tag { + background-color: var(--bg-secondary); + color: var(--text-secondary); + padding: var(--spacing-xs) var(--spacing-sm); + border-radius: var(--radius-sm); + font-size: var(--font-size-xs); +} + +/* 响应式设计 */ +@media (max-width: 768px) { + .navbar-menu { + display: none; + } + + .navbar-container { + flex-direction: column; + gap: var(--spacing-md); + } + + .footer-container { + grid-template-columns: 1fr; + } + + .page-actions { + flex-direction: column; + align-items: center; + } + + .page-actions .btn { + width: 100%; + max-width: 300px; + } +} \ No newline at end of file diff --git a/website/public/css/main.css b/website/public/css/main.css new file mode 100644 index 0000000..54c6e02 --- /dev/null +++ b/website/public/css/main.css @@ -0,0 +1,239 @@ +/* 主CSS文件 - 基础样式和重置 */ +* { + margin: 0; + padding: 0; + box-sizing: border-box; +} + +:root { + /* 颜色变量 - 企业化简约风格 */ + --primary-color: #1e6fbb; /* Gitee蓝 */ + --primary-dark: #155a9e; + --primary-light: #2c8ae6; + --secondary-color: #667eea; + --secondary-dark: #764ba2; + --text-primary: #333333; + --text-secondary: #666666; + --text-light: #999999; + --bg-primary: #ffffff; + --bg-secondary: #f8f9fa; + --bg-dark: #1a1a1a; + --border-color: #e0e0e0; + --success-color: #28a745; + --warning-color: #ffc107; + --danger-color: #dc3545; + --info-color: #17a2b8; + + /* 间距变量 */ + --spacing-xs: 0.25rem; + --spacing-sm: 0.5rem; + --spacing-md: 1rem; + --spacing-lg: 1.5rem; + --spacing-xl: 2rem; + --spacing-xxl: 3rem; + + /* 字体变量 */ + --font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; + --font-size-xs: 0.75rem; + --font-size-sm: 0.875rem; + --font-size-md: 1rem; + --font-size-lg: 1.125rem; + --font-size-xl: 1.25rem; + --font-size-xxl: 1.5rem; + --font-size-xxxl: 2rem; + + /* 阴影变量 */ + --shadow-sm: 0 1px 3px rgba(0, 0, 0, 0.1); + --shadow-md: 0 4px 6px rgba(0, 0, 0, 0.1); + --shadow-lg: 0 10px 25px rgba(0, 0, 0, 0.1); + --shadow-xl: 0 20px 40px rgba(0, 0, 0, 0.15); + + /* 边框半径 */ + --radius-sm: 4px; + --radius-md: 8px; + --radius-lg: 12px; + --radius-xl: 16px; + --radius-round: 50%; + + /* 过渡动画 */ + --transition-fast: 150ms ease; + --transition-normal: 300ms ease; + --transition-slow: 500ms ease; +} + +html { + font-size: 16px; + scroll-behavior: smooth; +} + +body { + font-family: var(--font-family); + font-size: var(--font-size-md); + line-height: 1.6; + color: var(--text-primary); + background-color: var(--bg-primary); + overflow-x: hidden; +} + +/* 容器 */ +.container { + width: 100%; + max-width: 1200px; + margin: 0 auto; + padding: 0 var(--spacing-md); +} + +/* 排版 */ +h1, h2, h3, h4, h5, h6 { + font-weight: 600; + line-height: 1.3; + margin-bottom: var(--spacing-md); +} + +h1 { font-size: var(--font-size-xxxl); } +h2 { font-size: var(--font-size-xxl); } +h3 { font-size: var(--font-size-xl); } +h4 { font-size: var(--font-size-lg); } +h5 { font-size: var(--font-size-md); } +h6 { font-size: var(--font-size-sm); } + +p { + margin-bottom: var(--spacing-md); +} + +a { + color: var(--primary-color); + text-decoration: none; + transition: color var(--transition-fast); +} + +a:hover { + color: var(--primary-dark); +} + +/* 按钮 */ +.btn { + display: inline-flex; + align-items: center; + justify-content: center; + gap: var(--spacing-sm); + padding: var(--spacing-sm) var(--spacing-lg); + font-size: var(--font-size-md); + font-weight: 500; + line-height: 1.5; + border: 2px solid transparent; + border-radius: var(--radius-md); + cursor: pointer; + transition: all var(--transition-normal); + text-align: center; + white-space: nowrap; + user-select: none; +} + +.btn-primary { + background-color: var(--primary-color); + color: white; + border-color: var(--primary-color); +} + +.btn-primary:hover { + background-color: var(--primary-dark); + border-color: var(--primary-dark); + color: white; + transform: translateY(-2px); + box-shadow: var(--shadow-md); +} + +.btn-secondary { + background-color: var(--secondary-color); + color: white; + border-color: var(--secondary-color); +} + +.btn-secondary:hover { + background-color: var(--secondary-dark); + border-color: var(--secondary-dark); + color: white; + transform: translateY(-2px); + box-shadow: var(--shadow-md); +} + +.btn-outline { + background-color: transparent; + color: var(--primary-color); + border-color: var(--primary-color); +} + +.btn-outline:hover { + background-color: var(--primary-color); + color: white; + transform: translateY(-2px); + box-shadow: var(--shadow-md); +} + +.btn-large { + padding: var(--spacing-md) var(--spacing-xl); + font-size: var(--font-size-lg); +} + +/* 工具类 */ +.text-center { text-align: center; } +.text-left { text-align: left; } +.text-right { text-align: right; } + +.mt-1 { margin-top: var(--spacing-xs); } +.mt-2 { margin-top: var(--spacing-sm); } +.mt-3 { margin-top: var(--spacing-md); } +.mt-4 { margin-top: var(--spacing-lg); } +.mt-5 { margin-top: var(--spacing-xl); } + +.mb-1 { margin-bottom: var(--spacing-xs); } +.mb-2 { margin-bottom: var(--spacing-sm); } +.mb-3 { margin-bottom: var(--spacing-md); } +.mb-4 { margin-bottom: var(--spacing-lg); } +.mb-5 { margin-bottom: var(--spacing-xl); } + +/* 页面过渡动画 */ +.page-transition { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + background-color: var(--primary-color); + z-index: 9999; + opacity: 0; + pointer-events: none; + transition: opacity var(--transition-normal); +} + +.page-transition.active { + opacity: 1; + pointer-events: all; +} + +/* 响应式设计 */ +@media (max-width: 768px) { + :root { + --font-size-xxxl: 1.75rem; + --font-size-xxl: 1.25rem; + --font-size-xl: 1.125rem; + --font-size-lg: 1rem; + } + + .container { + padding: 0 var(--spacing-sm); + } + + .btn-large { + padding: var(--spacing-sm) var(--spacing-lg); + font-size: var(--font-size-md); + } +} + +@media (max-width: 480px) { + :root { + --font-size-xxxl: 1.5rem; + --font-size-xxl: 1.125rem; + } +} \ No newline at end of file diff --git a/website/public/css/pages/architecture.css b/website/public/css/pages/architecture.css new file mode 100644 index 0000000..e69de29 diff --git a/website/public/css/pages/features.css b/website/public/css/pages/features.css new file mode 100644 index 0000000..e69de29 diff --git a/website/public/css/pages/home.css b/website/public/css/pages/home.css new file mode 100644 index 0000000..e69de29 diff --git a/website/public/css/pages/plugins.css b/website/public/css/pages/plugins.css new file mode 100644 index 0000000..e69de29 diff --git a/website/public/images/logo.svg b/website/public/images/logo.svg new file mode 100644 index 0000000..1400964 --- /dev/null +++ b/website/public/images/logo.svg @@ -0,0 +1,320 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Python 3.10+ Apache 2.0 Plugin Driven + + + + FutureOSS + 一切皆为插件的开发者工具运行时框架 + + + + 一个空壳框架,通过插件获得无限能力 + + + + + + + + 一切皆插件 + 协议、中间件、工具 + 所有功能以插件加载 + + + + + + 依赖自动解析 + 拓扑排序 + 循环检测 + 自动识别多级依赖 + + + + + + 企业级稳定 + 熔断、降级、重试 + 隔离 + 资源限制 + + + + + + 事件驱动 + 发布/订阅事件总线 + 40+ 种系统事件 + + + + + 快速开始 + + + + + + + + + $ git clone https://gitee.com/starlight-apk/feature-oss.git + $ cd feature-oss && bash start.sh + ✓ 服务已启动 → http://localhost:8080/ + + + + 核心特性 + + + + + + + 插件热插拔 + + + + + + 依赖自动解析 + + + + + + 事件总线 + + + + + + 完整配置 + + + + + + 协议适配 + + + + + + 熔断降级 + + + + + + 🏗️ 架构设计 + + + + + + 用户层 + + CLI │ HTTP API │ WebSocket │ 社区论坛 + + 插件层 + + http-api + + ws-api + + plugin-storage + + 核心层 + + PluginManager | EventBus | Config | Logger | Loader + + + + 文档 + + + + + + + + + + + + + + + + + + + + + + 完整文档请访问项目 Wiki + + + + Wiki/项目介绍 + + + Wiki/快速开始 + + + Wiki/插件开发 + + + Wiki/插件文档 + + + Wiki/包管理 + + + Wiki/配置参考 + + + Wiki/部署运维 + + + Wiki/社区与贡献 + + + + + 许可证 + Apache License 2.0 + Copyright 2026 Falck + + + + + Made with love by Falck & yongwanxing + https://gitee.com/starlight-apk · https://gitcode.com/yongwanxing + + + FUTURE OSS + EVERYTHING IS A PLUGIN + + + diff --git a/website/public/js/animations.js b/website/public/js/animations.js new file mode 100644 index 0000000..d0c091c --- /dev/null +++ b/website/public/js/animations.js @@ -0,0 +1,465 @@ +/** + * 动画和过渡效果管理 + */ + +/** + * 初始化所有动画 + */ +function initAnimations() { + console.log('初始化动画系统...'); + + // 初始化滚动动画 + initScrollAnimations(); + + // 初始化视差效果 + initParallaxEffects(); + + // 初始化计数器动画 + initCounterAnimations(); + + // 初始化进度条动画 + initProgressBars(); + + // 初始化打字机效果 + initTypewriterEffects(); + + // 初始化骨架屏 + initSkeletonScreens(); + + console.log('动画系统初始化完成'); +} + +/** + * 初始化滚动动画 + */ +function initScrollAnimations() { + const animatedElements = document.querySelectorAll('.animate-on-scroll'); + + if ('IntersectionObserver' in window) { + const observer = new IntersectionObserver((entries) => { + entries.forEach(entry => { + if (entry.isIntersecting) { + entry.target.classList.add('animated'); + observer.unobserve(entry.target); + } + }); + }, { + threshold: 0.1, + rootMargin: '0px 0px -50px 0px' + }); + + animatedElements.forEach(element => observer.observe(element)); + } else { + // 回退方案:直接添加动画类 + animatedElements.forEach(element => { + element.classList.add('animated'); + }); + } +} + +/** + * 初始化视差效果 + */ +function initParallaxEffects() { + const parallaxElements = document.querySelectorAll('.parallax'); + + if (parallaxElements.length === 0) return; + + window.addEventListener('scroll', () => { + const scrolled = window.pageYOffset; + + parallaxElements.forEach(element => { + const speed = parseFloat(element.dataset.speed) || 0.5; + const yPos = -(scrolled * speed); + element.style.transform = `translateY(${yPos}px)`; + }); + }); +} + +/** + * 初始化计数器动画 + */ +function initCounterAnimations() { + const counters = document.querySelectorAll('.counter'); + + if (counters.length === 0) return; + + const observer = new IntersectionObserver((entries) => { + entries.forEach(entry => { + if (entry.isIntersecting) { + const counter = entry.target; + const target = parseInt(counter.dataset.target) || 0; + const duration = parseInt(counter.dataset.duration) || 2000; + const increment = target / (duration / 16); // 60fps + let current = 0; + + const updateCounter = () => { + current += increment; + if (current < target) { + counter.textContent = Math.floor(current).toLocaleString(); + requestAnimationFrame(updateCounter); + } else { + counter.textContent = target.toLocaleString(); + } + }; + + updateCounter(); + observer.unobserve(counter); + } + }); + }); + + counters.forEach(counter => observer.observe(counter)); +} + +/** + * 初始化进度条动画 + */ +function initProgressBars() { + const progressBars = document.querySelectorAll('.progress-bar'); + + if (progressBars.length === 0) return; + + const observer = new IntersectionObserver((entries) => { + entries.forEach(entry => { + if (entry.isIntersecting) { + const progressBar = entry.target; + const width = progressBar.dataset.width || '100%'; + const duration = parseInt(progressBar.dataset.duration) || 1000; + + progressBar.style.transition = `width ${duration}ms ease`; + progressBar.style.width = width; + + observer.unobserve(progressBar); + } + }); + }); + + progressBars.forEach(bar => observer.observe(bar)); +} + +/** + * 初始化打字机效果 + */ +function initTypewriterEffects() { + const typewriters = document.querySelectorAll('.typewriter'); + + typewriters.forEach(element => { + const text = element.textContent; + element.textContent = ''; + element.style.width = '0'; + + let i = 0; + const type = () => { + if (i < text.length) { + element.textContent += text.charAt(i); + element.style.width = `${(i + 1) / text.length * 100}%`; + i++; + setTimeout(type, 100); + } + }; + + // 延迟开始打字效果 + setTimeout(type, 500); + }); +} + +/** + * 初始化骨架屏 + */ +function initSkeletonScreens() { + const skeletonElements = document.querySelectorAll('.skeleton'); + + if (skeletonElements.length === 0) return; + + // 模拟内容加载 + setTimeout(() => { + skeletonElements.forEach(element => { + element.classList.remove('skeleton'); + element.classList.add('fade-in-load'); + }); + }, 1000); +} + +/** + * 创建骨架屏占位符 + * @param {HTMLElement} container - 容器元素 + * @param {number} count - 骨架屏数量 + * @param {string} type - 骨架屏类型: 'text', 'card', 'image' + */ +function createSkeletonPlaceholders(container, count = 3, type = 'card') { + const skeletonClasses = { + 'text': 'skeleton skeleton-text', + 'card': 'skeleton skeleton-card', + 'image': 'skeleton skeleton-image' + }; + + const skeletonClass = skeletonClasses[type] || skeletonClasses.card; + + for (let i = 0; i < count; i++) { + const skeleton = document.createElement('div'); + skeleton.className = skeletonClass; + container.appendChild(skeleton); + } +} + +/** + * 显示页面切换动画 + * @param {string} direction - 切换方向: 'left', 'right', 'up', 'down' + */ +function showPageTransition(direction = 'right') { + const transition = document.getElementById('page-transition'); + if (!transition) return; + + const directions = { + 'left': 'slideInLeft', + 'right': 'slideInRight', + 'up': 'slideInUp', + 'down': 'slideInDown' + }; + + const animation = directions[direction] || 'slideInRight'; + transition.className = `page-transition animate-${animation}`; + transition.classList.add('active'); + + return new Promise(resolve => { + setTimeout(() => { + transition.classList.remove('active'); + setTimeout(resolve, 300); + }, 300); + }); +} + +/** + * 显示元素动画 + * @param {HTMLElement} element - 要动画的元素 + * @param {string} animation - 动画名称 + * @param {number} duration - 动画持续时间(毫秒) + */ +function animateElement(element, animation = 'fadeIn', duration = 300) { + if (!element) return; + + element.style.animation = `${animation} ${duration}ms ease`; + element.classList.add('animated'); + + // 动画完成后移除动画类 + setTimeout(() => { + element.style.animation = ''; + }, duration); +} + +/** + * 创建波纹效果 + * @param {Event} event - 点击事件 + * @param {string} color - 波纹颜色 + */ +function createRippleEffect(event, color = 'rgba(255, 255, 255, 0.7)') { + const button = event.currentTarget; + const circle = document.createElement('span'); + const diameter = Math.max(button.clientWidth, button.clientHeight); + const radius = diameter / 2; + + circle.style.width = circle.style.height = `${diameter}px`; + circle.style.left = `${event.clientX - button.offsetLeft - radius}px`; + circle.style.top = `${event.clientY - button.offsetTop - radius}px`; + circle.style.backgroundColor = color; + circle.style.position = 'absolute'; + circle.style.borderRadius = '50%'; + circle.style.transform = 'scale(0)'; + circle.style.animation = 'ripple 600ms linear'; + + const ripple = button.querySelector('.ripple'); + if (ripple) { + ripple.remove(); + } + + button.appendChild(circle); + + // 动画完成后移除波纹元素 + setTimeout(() => { + if (circle.parentNode === button) { + button.removeChild(circle); + } + }, 600); +} + +/** + * 添加波纹效果到按钮 + * @param {string} selector - 按钮选择器 + */ +function addRippleEffectToButtons(selector = '.btn') { + const buttons = document.querySelectorAll(selector); + + buttons.forEach(button => { + button.style.position = 'relative'; + button.style.overflow = 'hidden'; + + button.addEventListener('click', function(event) { + createRippleEffect(event); + }); + }); + + // 添加波纹动画CSS + if (!document.querySelector('#ripple-styles')) { + const style = document.createElement('style'); + style.id = 'ripple-styles'; + style.textContent = ` + @keyframes ripple { + to { + transform: scale(4); + opacity: 0; + } + } + `; + document.head.appendChild(style); + } +} + +/** + * 初始化图片加载动画 + */ +function initImageLoadingAnimations() { + const images = document.querySelectorAll('img:not([data-src])'); + + images.forEach(img => { + if (!img.complete) { + img.classList.add('loading'); + + img.addEventListener('load', function() { + this.classList.remove('loading'); + this.classList.add('loaded'); + animateElement(this, 'scaleIn', 500); + }); + + img.addEventListener('error', function() { + this.classList.remove('loading'); + this.classList.add('error'); + console.error('图片加载失败:', this.src); + }); + } else { + img.classList.add('loaded'); + } + }); +} + +/** + * 添加滚动到顶部按钮 + */ +function initScrollToTopButton() { + // 创建按钮 + const button = document.createElement('button'); + button.id = 'scroll-to-top'; + button.className = 'scroll-to-top-btn'; + button.innerHTML = ''; + button.title = '回到顶部'; + button.style.cssText = ` + position: fixed; + bottom: 30px; + right: 30px; + width: 50px; + height: 50px; + background-color: var(--primary-color); + color: white; + border: none; + border-radius: 50%; + cursor: pointer; + opacity: 0; + visibility: hidden; + transition: all 0.3s ease; + box-shadow: var(--shadow-lg); + z-index: 1000; + display: flex; + align-items: center; + justify-content: center; + font-size: 20px; + `; + + document.body.appendChild(button); + + // 滚动事件监听 + window.addEventListener('scroll', () => { + if (window.pageYOffset > 300) { + button.style.opacity = '1'; + button.style.visibility = 'visible'; + button.style.transform = 'translateY(0)'; + } else { + button.style.opacity = '0'; + button.style.visibility = 'hidden'; + button.style.transform = 'translateY(20px)'; + } + }); + + // 点击事件 + button.addEventListener('click', () => { + window.scrollTo({ + top: 0, + behavior: 'smooth' + }); + }); + + // 添加悬停效果 + button.addEventListener('mouseenter', () => { + button.style.backgroundColor = 'var(--primary-dark)'; + button.style.transform = 'scale(1.1)'; + }); + + button.addEventListener('mouseleave', () => { + button.style.backgroundColor = 'var(--primary-color)'; + button.style.transform = 'scale(1)'; + }); +} + +/** + * 性能优化:减少重绘 + */ +function optimizeForPerformance() { + // 为动画元素添加 will-change + const animatedElements = document.querySelectorAll('.animate-on-scroll, .parallax, .counter'); + animatedElements.forEach(element => { + element.style.willChange = 'transform, opacity'; + }); + + // 使用 requestAnimationFrame 优化动画 + let ticking = false; + + window.addEventListener('scroll', () => { + if (!ticking) { + window.requestAnimationFrame(() => { + // 执行滚动相关的动画 + ticking = false; + }); + ticking = true; + } + }); + + // 使用 transform 和 opacity 进行动画(GPU加速) + console.log('性能优化已应用:使用GPU加速动画'); +} + +// DOM加载完成后初始化动画 +if (document.readyState === 'loading') { + document.addEventListener('DOMContentLoaded', () => { + initAnimations(); + addRippleEffectToButtons(); + initImageLoadingAnimations(); + initScrollToTopButton(); + optimizeForPerformance(); + }); +} else { + initAnimations(); + addRippleEffectToButtons(); + initImageLoadingAnimations(); + initScrollToTopButton(); + optimizeForPerformance(); +} + +// 导出函数 +if (typeof module !== 'undefined' && module.exports) { + module.exports = { + initAnimations, + showPageTransition, + animateElement, + createSkeletonPlaceholders, + addRippleEffectToButtons + }; +} \ No newline at end of file diff --git a/website/public/js/main.js b/website/public/js/main.js new file mode 100644 index 0000000..f40d529 --- /dev/null +++ b/website/public/js/main.js @@ -0,0 +1,408 @@ +// 主JavaScript文件 + +/** + * 初始化导航栏 + */ +function initNavigation() { + const navbarLinks = document.querySelectorAll('.navbar-link'); + const currentPage = document.body.classList.contains('page-home') ? 'home' : + document.body.classList.contains('page-features') ? 'features' : + document.body.classList.contains('page-architecture') ? 'architecture' : + document.body.classList.contains('page-plugins') ? 'plugins' : 'home'; + + navbarLinks.forEach(link => { + if (link.getAttribute('data-page') === currentPage) { + link.classList.add('active'); + } + + // 添加点击事件 + link.addEventListener('click', function(e) { + if (this.getAttribute('href') === '#') { + e.preventDefault(); + const page = this.getAttribute('data-page'); + if (page && page !== currentPage) { + navigateToPage(page); + } + } + }); + }); +} + +/** + * 初始化页面切换 + */ +function initPageTransitions() { + const pageTransition = document.getElementById('page-transition'); + if (!pageTransition) return; + + // 监听页面链接点击 + document.addEventListener('click', function(e) { + const link = e.target.closest('a[data-page]'); + if (link && link.getAttribute('href') === '#') { + e.preventDefault(); + const page = link.getAttribute('data-page'); + const currentPage = document.body.className.match(/page-(\w+)/)?.[1]; + + if (page && page !== currentPage) { + // 显示页面切换动画 + pageTransition.classList.add('active'); + + // 延迟导航 + setTimeout(() => { + navigateToPage(page); + }, 300); + } + } + }); +} + +/** + * 导航到指定页面 + * @param {string} page - 页面名称 + */ +function navigateToPage(page) { + console.log(`导航到页面: ${page}`); + + // 这里应该使用前端路由或实际页面跳转 + // 目前先更新URL和页面状态 + const url = `/${page === 'home' ? '' : page}`; + window.history.pushState({ page }, '', url); + + // 更新页面内容 + updatePageContent(page); + + // 更新导航栏激活状态 + updateNavigation(page); + + // 隐藏页面切换动画 + const pageTransition = document.getElementById('page-transition'); + if (pageTransition) { + setTimeout(() => { + pageTransition.classList.remove('active'); + }, 300); + } +} + +/** + * 更新页面内容 + * @param {string} page - 页面名称 + */ +function updatePageContent(page) { + // 更新body类名 + document.body.className = document.body.className.replace(/page-\w+/, `page-${page}`); + + // 更新页面标题 + const pageTitles = { + 'home': 'FutureOSS - 插件化运行时框架', + 'features': '核心特性 - FutureOSS', + 'architecture': '系统架构 - FutureOSS', + 'plugins': '插件生态 - FutureOSS' + }; + + document.title = pageTitles[page] || pageTitles.home; + + // 这里应该加载新的页面内容 + // 目前先滚动到顶部 + window.scrollTo({ top: 0, behavior: 'smooth' }); +} + +/** + * 更新导航栏激活状态 + * @param {string} page - 页面名称 + */ +function updateNavigation(page) { + const navbarLinks = document.querySelectorAll('.navbar-link'); + navbarLinks.forEach(link => { + link.classList.remove('active'); + if (link.getAttribute('data-page') === page) { + link.classList.add('active'); + } + }); +} + +/** + * 初始化按钮交互 + */ +function initButtonInteractions() { + // 为所有按钮添加悬停效果 + const buttons = document.querySelectorAll('.btn'); + buttons.forEach(button => { + button.addEventListener('mouseenter', function() { + this.style.transform = 'translateY(-2px)'; + }); + + button.addEventListener('mouseleave', function() { + this.style.transform = 'translateY(0)'; + }); + }); + + // 为卡片添加悬停效果 + const cards = document.querySelectorAll('.card, .feature-card'); + cards.forEach(card => { + card.addEventListener('mouseenter', function() { + this.style.transform = 'translateY(-4px)'; + }); + + card.addEventListener('mouseleave', function() { + this.style.transform = 'translateY(0)'; + }); + }); +} + +/** + * 初始化滚动效果 + */ +function initScrollEffects() { + let lastScrollTop = 0; + const navbar = document.querySelector('.navbar'); + + if (!navbar) return; + + window.addEventListener('scroll', function() { + const scrollTop = window.pageYOffset || document.documentElement.scrollTop; + + // 隐藏/显示导航栏 + if (scrollTop > lastScrollTop && scrollTop > 100) { + // 向下滚动,隐藏导航栏 + navbar.style.transform = 'translateY(-100%)'; + } else { + // 向上滚动,显示导航栏 + navbar.style.transform = 'translateY(0)'; + } + + lastScrollTop = scrollTop; + + // 添加滚动阴影 + if (scrollTop > 10) { + navbar.style.boxShadow = 'var(--shadow-md)'; + } else { + navbar.style.boxShadow = 'var(--shadow-sm)'; + } + }); +} + +/** + * 显示消息提示 + * @param {string} message - 消息内容 + * @param {string} type - 消息类型: 'success', 'error', 'info', 'warning' + */ +function showMessage(message, type = 'info') { + const messageDiv = document.createElement('div'); + messageDiv.className = `message message-${type}`; + messageDiv.textContent = message; + messageDiv.style.cssText = ` + position: fixed; + top: 20px; + right: 20px; + padding: 12px 20px; + background-color: ${getMessageColor(type)}; + color: white; + border-radius: var(--radius-md); + box-shadow: var(--shadow-lg); + z-index: 9999; + animation: fadeIn 0.3s ease; + `; + + document.body.appendChild(messageDiv); + + // 3秒后自动消失 + setTimeout(() => { + messageDiv.style.animation = 'fadeOut 0.3s ease'; + setTimeout(() => { + if (messageDiv.parentNode) { + messageDiv.parentNode.removeChild(messageDiv); + } + }, 300); + }, 3000); +} + +/** + * 获取消息颜色 + * @param {string} type - 消息类型 + * @returns {string} - 颜色值 + */ +function getMessageColor(type) { + const colors = { + 'success': 'var(--success-color)', + 'error': 'var(--danger-color)', + 'info': 'var(--info-color)', + 'warning': 'var(--warning-color)' + }; + return colors[type] || colors.info; +} + +/** + * 防抖函数 + * @param {Function} func - 要执行的函数 + * @param {number} wait - 等待时间(毫秒) + * @returns {Function} - 防抖后的函数 + */ +function debounce(func, wait) { + let timeout; + return function executedFunction(...args) { + const later = () => { + clearTimeout(timeout); + func(...args); + }; + clearTimeout(timeout); + timeout = setTimeout(later, wait); + }; +} + +/** + * 节流函数 + * @param {Function} func - 要执行的函数 + * @param {number} limit - 限制时间(毫秒) + * @returns {Function} - 节流后的函数 + */ +function throttle(func, limit) { + let inThrottle; + return function(...args) { + if (!inThrottle) { + func.apply(this, args); + inThrottle = true; + setTimeout(() => inThrottle = false, limit); + } + }; +} + +/** + * 隐藏加载动画 + */ +function hideLoadingAnimation() { + const loadingOverlay = document.getElementById('loading-overlay'); + if (loadingOverlay) { + // 简单隐藏 + loadingOverlay.style.display = 'none'; + } +} + +/** + * 显示加载动画 + */ +function showLoadingAnimation() { + const loadingOverlay = document.getElementById('loading-overlay'); + if (loadingOverlay) { + loadingOverlay.style.display = 'flex'; + } +} + +/** + * 初始化页面 + */ +function initPage() { + console.log('FutureOSS 网站初始化...'); + + // 确保加载动画被隐藏 + setTimeout(() => { + hideLoadingAnimation(); + }, 100); + + // 初始化导航栏 + initNavigation(); + + // 初始化页面切换 + initPageTransitions(); + + // 初始化按钮交互 + initButtonInteractions(); + + // 初始化滚动效果 + initScrollEffects(); + + // 初始化图片懒加载 + initLazyLoading(); + + console.log('页面初始化完成'); +} + +/** + * 初始化图片懒加载 + */ +function initLazyLoading() { + const lazyImages = document.querySelectorAll('img[data-src]'); + + if ('IntersectionObserver' in window) { + const imageObserver = new IntersectionObserver((entries, observer) => { + entries.forEach(entry => { + if (entry.isIntersecting) { + const img = entry.target; + img.src = img.dataset.src; + img.classList.remove('lazy-image'); + observer.unobserve(img); + } + }); + }); + + lazyImages.forEach(img => imageObserver.observe(img)); + } else { + // 回退方案:直接加载所有图片 + lazyImages.forEach(img => { + img.src = img.dataset.src; + img.classList.remove('lazy-image'); + }); + } +} + +/** + * 性能监控 + */ +function initPerformanceMonitoring() { + // 监控页面加载性能 + window.addEventListener('load', () => { + if (window.performance) { + const timing = performance.timing; + const loadTime = timing.loadEventEnd - timing.navigationStart; + console.log(`页面加载时间: ${loadTime}ms`); + + if (loadTime > 3000) { + console.warn('页面加载时间过长,建议优化'); + } + } + }); + + // 监控长任务 + if ('PerformanceObserver' in window) { + const observer = new PerformanceObserver((list) => { + for (const entry of list.getEntries()) { + if (entry.duration > 50) { + console.warn('检测到长任务:', entry); + } + } + }); + + observer.observe({ entryTypes: ['longtask'] }); + } +} + +// 页面加载完成后初始化 +document.addEventListener('DOMContentLoaded', () => { + console.log('DOMContentLoaded 事件触发'); + initPerformanceMonitoring(); + initPage(); +}); + +// 窗口加载完成事件作为最后保障 +window.addEventListener('load', () => { + console.log('window.load 事件触发'); + // 确保加载动画被隐藏 + hideLoadingAnimation(); +}); + +// 立即隐藏加载动画(如果可能) +if (document.readyState !== 'loading') { + console.log('文档已加载完成,直接初始化'); + initPerformanceMonitoring(); + initPage(); +} + +// 导出函数供其他模块使用 +if (typeof module !== 'undefined' && module.exports) { + module.exports = { + initPage, + navigateToPage, + showMessage, + debounce, + throttle + }; +} \ No newline at end of file diff --git a/website/public/js/pages/architecture.js b/website/public/js/pages/architecture.js new file mode 100644 index 0000000..e69de29 diff --git a/website/public/js/pages/features.js b/website/public/js/pages/features.js new file mode 100644 index 0000000..e69de29 diff --git a/website/public/js/pages/home.js b/website/public/js/pages/home.js new file mode 100644 index 0000000..e69de29 diff --git a/website/public/js/pages/plugins.js b/website/public/js/pages/plugins.js new file mode 100644 index 0000000..e69de29 diff --git a/website/public/js/router.js b/website/public/js/router.js new file mode 100644 index 0000000..b8f4554 --- /dev/null +++ b/website/public/js/router.js @@ -0,0 +1,264 @@ +// 前端路由系统 + +/** + * 路由配置 + */ +const routes = { + '/': { + name: 'home', + title: 'FutureOSS - 插件化运行时框架', + description: 'FutureOSS 是一个高度插件化的运行时框架,专为现代应用设计。' + }, + '/features': { + name: 'features', + title: '核心特性 - FutureOSS', + description: 'FutureOSS 的六大核心特性,专为现代开发需求设计。' + }, + '/architecture': { + name: 'architecture', + title: '系统架构 - FutureOSS', + description: '深入了解 FutureOSS 的微内核架构和插件化设计。' + }, + '/plugins': { + name: 'plugins', + title: '插件生态 - FutureOSS', + description: '探索 FutureOSS 丰富的插件生态系统。' + } +}; + +/** + * 初始化路由 + */ +function initRouter() { + console.log('初始化前端路由...'); + + // 监听浏览器前进/后退 + window.addEventListener('popstate', handlePopState); + + // 拦截链接点击 + document.addEventListener('click', handleLinkClick); + + // 初始路由处理 + handleRoute(window.location.pathname); + + console.log('路由初始化完成'); +} + +/** + * 处理路由 + * @param {string} path - 路径 + */ +function handleRoute(path) { + const route = routes[path] || routes['/']; + + // 更新页面状态 + updatePageState(route.name); + + // 更新文档标题和描述 + document.title = route.title; + updateMetaDescription(route.description); + + // 触发路由变化事件 + window.dispatchEvent(new CustomEvent('routechange', { + detail: { route: route.name, path: path } + })); + + console.log(`路由切换到: ${path} (${route.name})`); +} + +/** + * 更新页面状态 + * @param {string} pageName - 页面名称 + */ +function updatePageState(pageName) { + // 更新body类名 + const bodyClass = document.body.className; + const newClass = bodyClass.replace(/page-\w+/, `page-${pageName}`); + document.body.className = newClass.includes('page-') ? newClass : `${newClass} page-${pageName}`; + + // 更新导航栏激活状态 + updateNavActiveState(pageName); + + // 滚动到顶部 + window.scrollTo({ top: 0, behavior: 'smooth' }); +} + +/** + * 更新导航栏激活状态 + * @param {string} pageName - 页面名称 + */ +function updateNavActiveState(pageName) { + const navLinks = document.querySelectorAll('.navbar-link'); + navLinks.forEach(link => { + link.classList.remove('active'); + if (link.getAttribute('data-page') === pageName) { + link.classList.add('active'); + } + }); +} + +/** + * 更新meta描述 + * @param {string} description - 描述内容 + */ +function updateMetaDescription(description) { + let metaDescription = document.querySelector('meta[name="description"]'); + if (!metaDescription) { + metaDescription = document.createElement('meta'); + metaDescription.name = 'description'; + document.head.appendChild(metaDescription); + } + metaDescription.content = description; +} + +/** + * 处理链接点击 + * @param {Event} event - 点击事件 + */ +function handleLinkClick(event) { + const link = event.target.closest('a[data-page]'); + if (!link) return; + + const href = link.getAttribute('href'); + const page = link.getAttribute('data-page'); + + // 如果是内部链接且不是当前页面 + if (href === '#' && page) { + event.preventDefault(); + + const currentPage = document.body.className.match(/page-(\w+)/)?.[1]; + if (page === currentPage) return; + + // 显示页面切换动画 + showPageTransition(); + + // 延迟导航 + setTimeout(() => { + navigateTo(page); + }, 300); + } +} + +/** + * 处理浏览器前进/后退 + * @param {PopStateEvent} event - popstate事件 + */ +function handlePopState(event) { + if (event.state && event.state.page) { + handleRoute(`/${event.state.page === 'home' ? '' : event.state.page}`); + } else { + handleRoute(window.location.pathname); + } +} + +/** + * 导航到指定页面 + * @param {string} pageName - 页面名称 + */ +function navigateTo(pageName) { + const path = pageName === 'home' ? '/' : `/${pageName}`; + const route = routes[path]; + + if (!route) { + console.error(`路由未找到: ${pageName}`); + return; + } + + // 更新浏览器历史 + window.history.pushState({ page: pageName }, route.title, path); + + // 处理路由 + handleRoute(path); + + // 隐藏页面切换动画 + hidePageTransition(); +} + +/** + * 显示页面切换动画 + */ +function showPageTransition() { + const transition = document.getElementById('page-transition'); + if (transition) { + transition.classList.add('active'); + } +} + +/** + * 隐藏页面切换动画 + */ +function hidePageTransition() { + const transition = document.getElementById('page-transition'); + if (transition) { + setTimeout(() => { + transition.classList.remove('active'); + }, 300); + } +} + +/** + * 获取当前路由信息 + * @returns {Object} 当前路由信息 + */ +function getCurrentRoute() { + const path = window.location.pathname; + return routes[path] || routes['/']; +} + +/** + * 检查路由是否存在 + * @param {string} path - 路径 + * @returns {boolean} 是否存在 + */ +function routeExists(path) { + return routes.hasOwnProperty(path); +} + +/** + * 添加路由 + * @param {string} path - 路径 + * @param {Object} config - 路由配置 + */ +function addRoute(path, config) { + if (routes[path]) { + console.warn(`路由已存在: ${path}`); + return false; + } + + routes[path] = { + name: config.name || path.slice(1), + title: config.title || 'FutureOSS', + description: config.description || '' + }; + + console.log(`路由添加成功: ${path}`); + return true; +} + +/** + * 移除路由 + * @param {string} path - 路径 + */ +function removeRoute(path) { + if (!routes[path]) { + console.warn(`路由不存在: ${path}`); + return false; + } + + delete routes[path]; + console.log(`路由移除成功: ${path}`); + return true; +} + +// 导出路由函数 +if (typeof module !== 'undefined' && module.exports) { + module.exports = { + initRouter, + navigateTo, + getCurrentRoute, + routeExists, + addRoute, + removeRoute, + routes + }; +} \ No newline at end of file diff --git a/website/src/controllers/apiController.js b/website/src/controllers/apiController.js new file mode 100644 index 0000000..9a39768 --- /dev/null +++ b/website/src/controllers/apiController.js @@ -0,0 +1,88 @@ +/** + * API控制器 + * 提供系统状态和健康检查接口 + */ + +/** + * 健康检查 + */ +exports.healthCheck = (req, res) => { + const health = { + status: 'healthy', + timestamp: new Date().toISOString(), + uptime: process.uptime(), + memory: process.memoryUsage(), + nodeVersion: process.version, + platform: process.platform, + pid: process.pid + }; + + res.json(health); +}; + +/** + * 性能指标 + */ +exports.performanceMetrics = (req, res) => { + const metrics = { + timestamp: new Date().toISOString(), + memory: { + heapUsed: process.memoryUsage().heapUsed, + heapTotal: process.memoryUsage().heapTotal, + rss: process.memoryUsage().rss + }, + cpu: process.cpuUsage(), + uptime: process.uptime(), + activeHandles: process._getActiveHandles().length, + activeRequests: process._getActiveRequests().length + }; + + res.json(metrics); +}; + +/** + * 服务器信息 + */ +exports.serverInfo = (req, res) => { + const info = { + name: 'FutureOSS 官方网站', + version: '1.0.0', + description: 'FutureOSS 插件化运行时框架官方网站', + repository: 'https://gitee.com/starlight-apk/future-oss', + endpoints: [ + { path: '/', method: 'GET', description: '首页' }, + { path: '/features', method: 'GET', description: '特性页面' }, + { path: '/architecture', method: 'GET', description: '架构页面' }, + { path: '/plugins', method: 'GET', description: '插件页面' }, + { path: '/api/health', method: 'GET', description: '健康检查' }, + { path: '/api/metrics', method: 'GET', description: '性能指标' }, + { path: '/api/info', method: 'GET', description: '服务器信息' } + ], + environment: process.env.NODE_ENV || 'development' + }; + + res.json(info); +}; + +/** + * 压力测试端点(仅开发环境) + */ +exports.stressTest = (req, res) => { + if (process.env.NODE_ENV !== 'development') { + return res.status(403).json({ error: '仅开发环境可用' }); + } + + const n = parseInt(req.query.n) || 1000000; + let result = 0; + + // 模拟CPU密集型操作 + for (let i = 0; i < n; i++) { + result += Math.sqrt(i) * Math.sin(i); + } + + res.json({ + result: result, + iterations: n, + message: '压力测试完成' + }); +}; \ No newline at end of file diff --git a/website/src/controllers/pagesController.js b/website/src/controllers/pagesController.js new file mode 100644 index 0000000..8886256 --- /dev/null +++ b/website/src/controllers/pagesController.js @@ -0,0 +1,48 @@ +/** + * 页面控制器 + * 每个方法只负责渲染一个页面,业务逻辑分离 + */ + +/** + * 渲染首页 + */ +exports.renderHome = (req, res) => { + res.render('pages/home', { + title: 'FutureOSS - 一切皆为插件的开发者运行时框架', + page: 'home', + description: 'FutureOSS 是一款面向开发者的插件化运行时框架,秉承「一切皆为插件」的设计理念,让功能扩展变得前所未有的简单。' + }); +}; + +/** + * 渲染特性页面 + */ +exports.renderFeatures = (req, res) => { + res.render('pages/features', { + title: '核心特性 - FutureOSS', + page: 'features', + description: 'FutureOSS 的核心特性:插件化架构、安全沙箱、热重载支持、可视化控制台、双协议服务、依赖自动解析。' + }); +}; + +/** + * 渲染架构页面 + */ +exports.renderArchitecture = (req, res) => { + res.render('pages/architecture', { + title: '技术架构 - FutureOSS', + page: 'architecture', + description: '深入了解 FutureOSS 的技术架构和设计思想,包括插件系统架构、安全机制、数据流等。' + }); +}; + +/** + * 渲染插件页面 + */ +exports.renderPlugins = (req, res) => { + res.render('pages/plugins', { + title: '插件生态系统 - FutureOSS', + page: 'plugins', + description: 'FutureOSS 插件生态系统:核心插件、社区插件、插件开发指南、插件市场。' + }); +}; \ No newline at end of file diff --git a/website/src/middleware/performance.js b/website/src/middleware/performance.js new file mode 100644 index 0000000..cce032b --- /dev/null +++ b/website/src/middleware/performance.js @@ -0,0 +1,218 @@ +/** + * 性能优化中间件 + */ + +/** + * 响应时间头中间件 + * 添加X-Response-Time头到响应中 + */ +function responseTime(req, res, next) { + const start = Date.now(); + + // 保存原始的 end 方法 + const originalEnd = res.end; + + // 重写 end 方法以在响应发送前设置头部 + res.end = function(...args) { + const duration = Date.now() - start; + + // 只有在头部尚未发送时才能设置 + if (!res.headersSent) { + res.setHeader('X-Response-Time', `${duration}ms`); + } + + // 记录慢响应 + if (duration > 1000) { + console.warn(`慢响应: ${req.method} ${req.url} - ${duration}ms`); + } + + // 调用原始的 end 方法 + return originalEnd.apply(this, args); + }; + + next(); +} + +/** + * 压缩中间件 + * 已经使用compression,这里添加额外的压缩头 + */ +function compressionHeaders(req, res, next) { + // 为文本资源启用压缩 + if (req.url.match(/\.(html|css|js|json|xml)$/)) { + res.setHeader('Vary', 'Accept-Encoding'); + } + + next(); +} + +/** + * 缓存控制中间件 + */ +function cacheControl(req, res, next) { + // 静态资源缓存策略 + if (req.url.match(/\.(css|js|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$/)) { + // 静态资源缓存1年 + res.setHeader('Cache-Control', 'public, max-age=31536000, immutable'); + } else if (req.url.match(/\.(html)$/)) { + // HTML文件不缓存 + res.setHeader('Cache-Control', 'no-cache, no-store, must-revalidate'); + } else { + // 默认缓存策略 + res.setHeader('Cache-Control', 'no-cache'); + } + + next(); +} + +/** + * 安全头中间件 + */ +function securityHeaders(req, res, next) { + // 防止MIME类型嗅探 + res.setHeader('X-Content-Type-Options', 'nosniff'); + + // 防止点击劫持 + res.setHeader('X-Frame-Options', 'DENY'); + + // XSS保护 + res.setHeader('X-XSS-Protection', '1; mode=block'); + + // 推荐使用HTTPS + res.setHeader('Strict-Transport-Security', 'max-age=31536000; includeSubDomains'); + + // 内容安全策略 + const csp = [ + "default-src 'self'", + "script-src 'self' 'unsafe-inline' https://cdnjs.cloudflare.com https://fonts.googleapis.com", + "style-src 'self' 'unsafe-inline' https://cdnjs.cloudflare.com https://fonts.googleapis.com", + "font-src 'self' https://cdnjs.cloudflare.com https://fonts.gstatic.com", + "img-src 'self' data: https:", + "connect-src 'self'", + "frame-ancestors 'none'", + "base-uri 'self'", + "form-action 'self'" + ].join('; '); + + res.setHeader('Content-Security-Policy', csp); + + next(); +} + +/** + * Gzip预压缩中间件 + * 检查是否存在预压缩的.gz文件 + */ +function gzipStatic(req, res, next) { + const acceptEncoding = req.headers['accept-encoding'] || ''; + + if (acceptEncoding.includes('gzip') && req.url.match(/\.(js|css|html|json)$/)) { + req.url = req.url + '.gz'; + res.setHeader('Content-Encoding', 'gzip'); + + // 设置正确的Content-Type + if (req.url.endsWith('.js.gz')) { + res.setHeader('Content-Type', 'application/javascript'); + } else if (req.url.endsWith('.css.gz')) { + res.setHeader('Content-Type', 'text/css'); + } else if (req.url.endsWith('.html.gz')) { + res.setHeader('Content-Type', 'text/html'); + } + } + + next(); +} + +/** + * 请求限流中间件(简单版本) + */ +function rateLimiter(maxRequests = 100, windowMs = 15 * 60 * 1000) { + const requests = new Map(); + + return function(req, res, next) { + const ip = req.ip || req.connection.remoteAddress; + const now = Date.now(); + + if (!requests.has(ip)) { + requests.set(ip, []); + } + + const timestamps = requests.get(ip); + + // 清理过期的请求记录 + const windowStart = now - windowMs; + while (timestamps.length && timestamps[0] < windowStart) { + timestamps.shift(); + } + + // 检查是否超过限制 + if (timestamps.length >= maxRequests) { + res.status(429).json({ + error: '请求过多', + message: '请稍后再试' + }); + return; + } + + // 记录当前请求 + timestamps.push(now); + + // 设置限流头 + res.setHeader('X-RateLimit-Limit', maxRequests); + res.setHeader('X-RateLimit-Remaining', maxRequests - timestamps.length); + res.setHeader('X-RateLimit-Reset', Math.ceil((timestamps[0] + windowMs) / 1000)); + + next(); + }; +} + +/** + * 数据库查询优化中间件(示例) + */ +function queryOptimizer(req, res, next) { + // 这里可以添加数据库查询优化逻辑 + // 例如:限制查询结果数量、添加索引提示等 + + // 示例:为API请求添加默认分页 + if (req.path.startsWith('/api/') && req.method === 'GET') { + req.query.limit = req.query.limit || '50'; + req.query.offset = req.query.offset || '0'; + } + + next(); +} + +/** + * 内存使用监控 + */ +function memoryMonitor(req, res, next) { + const memoryUsage = process.memoryUsage(); + + // 记录高内存使用 + if (memoryUsage.heapUsed > 500 * 1024 * 1024) { // 500MB + console.warn('高内存使用:', { + heapUsed: Math.round(memoryUsage.heapUsed / 1024 / 1024) + 'MB', + heapTotal: Math.round(memoryUsage.heapTotal / 1024 / 1024) + 'MB', + rss: Math.round(memoryUsage.rss / 1024 / 1024) + 'MB', + url: req.url + }); + } + + // 添加内存使用头(仅开发环境) + if (process.env.NODE_ENV === 'development') { + res.setHeader('X-Memory-Usage', Math.round(memoryUsage.heapUsed / 1024 / 1024) + 'MB'); + } + + next(); +} + +module.exports = { + responseTime, + compressionHeaders, + cacheControl, + securityHeaders, + gzipStatic, + rateLimiter, + queryOptimizer, + memoryMonitor +}; \ No newline at end of file diff --git a/website/src/router.js b/website/src/router.js new file mode 100644 index 0000000..742edfb --- /dev/null +++ b/website/src/router.js @@ -0,0 +1,40 @@ +/** + * FutureOSS 网站路由配置 + * 一个文件只管一个功能:路由只负责路由,不处理业务逻辑 + */ + +const express = require('express'); +const router = express.Router(); +const path = require('path'); + +// 导入页面控制器 +const pagesController = require('./controllers/pagesController'); +const apiController = require('./controllers/apiController'); + +// API路由 +router.get('/api/health', apiController.healthCheck); +router.get('/api/metrics', apiController.performanceMetrics); +router.get('/api/info', apiController.serverInfo); +router.get('/api/stress-test', apiController.stressTest); + +// 页面路由 - 每个路由对应一个独立的页面文件 +router.get('/', pagesController.renderHome); +router.get('/features', pagesController.renderFeatures); +router.get('/architecture', pagesController.renderArchitecture); +router.get('/plugins', pagesController.renderPlugins); + +// 静态文件路由(备用) +router.get('/static/*', (req, res) => { + const filePath = path.join(__dirname, '../public', req.params[0]); + res.sendFile(filePath, (err) => { + if (err) { + res.status(404).json({ error: '文件未找到' }); + } + }); +}); + +// 重定向旧路由(如果需要) +router.get('/home', (req, res) => res.redirect('/')); +router.get('/index', (req, res) => res.redirect('/')); + +module.exports = router; \ No newline at end of file diff --git a/website/src/server.js b/website/src/server.js new file mode 100644 index 0000000..874ca16 --- /dev/null +++ b/website/src/server.js @@ -0,0 +1,175 @@ +/** + * FutureOSS 官方网站服务器 + * 支持端口自动切换:8080被占用则使用8081 + */ + +const express = require('express'); +const morgan = require('morgan'); +const cors = require('cors'); +const compression = require('compression'); +const path = require('path'); +const fs = require('fs'); +const expressLayouts = require('express-ejs-layouts'); +const performanceMiddleware = require('./middleware/performance'); + +// 创建Express应用 +const app = express(); + +// 中间件配置 +app.use(morgan('dev')); // 日志 +app.use(cors()); // CORS支持 +app.use(compression()); // 压缩响应 +app.use(performanceMiddleware.responseTime); // 响应时间监控 +app.use(performanceMiddleware.compressionHeaders); // 压缩头 +app.use(performanceMiddleware.cacheControl); // 缓存控制 +app.use(performanceMiddleware.securityHeaders); // 安全头 +app.use(performanceMiddleware.memoryMonitor); // 内存监控 +app.use(express.json()); // JSON解析 +app.use(express.urlencoded({ extended: true })); // URL编码解析 + +// 静态文件服务 +app.use(express.static(path.join(__dirname, '../public'))); + +// 设置视图引擎和布局 +app.use(expressLayouts); +app.set('layout', 'layouts/main'); +app.set('view engine', 'ejs'); +app.set('views', path.join(__dirname, '../views')); + +// 导入路由 +const router = require('./router'); +app.use('/', router); + +// 错误处理中间件 +app.use((err, req, res, next) => { + console.error('服务器错误:', err); + res.status(500).json({ + error: '服务器内部错误', + message: process.env.NODE_ENV === 'development' ? err.message : undefined + }); +}); + +// 404处理 +app.use((req, res) => { + res.status(404).json({ error: '页面未找到' }); +}); + +/** + * 检查端口是否可用 + * @param {number} port - 要检查的端口 + * @returns {Promise} - 端口是否可用 + */ +function checkPort(port) { + return new Promise((resolve) => { + const net = require('net'); + const server = net.createServer(); + + server.once('error', (err) => { + if (err.code === 'EADDRINUSE') { + console.log(`端口 ${port} 已被占用`); + resolve(false); + } else { + resolve(false); + } + }); + + server.once('listening', () => { + server.close(); + console.log(`端口 ${port} 可用`); + resolve(true); + }); + + server.listen(port); + }); +} + +/** + * 获取可用端口 + * @param {number} startPort - 起始端口 + * @param {number} maxAttempts - 最大尝试次数 + * @returns {Promise} - 可用的端口 + */ +async function getAvailablePort(startPort = 8080, maxAttempts = 10) { + for (let i = 0; i < maxAttempts; i++) { + const port = startPort + i; + const isAvailable = await checkPort(port); + if (isAvailable) { + return port; + } + } + throw new Error(`在端口 ${startPort} 到 ${startPort + maxAttempts - 1} 范围内找不到可用端口`); +} + +/** + * 启动服务器 + */ +async function startServer() { + try { + // 获取可用端口 + const port = await getAvailablePort(8080, 5); + + // 启动服务器 + const server = app.listen(port, () => { + console.log(` +╔══════════════════════════════════════════════════════════╗ +║ FutureOSS 官方网站 ║ +╠══════════════════════════════════════════════════════════╣ +║ 服务器已启动! ║ +║ ║ +║ 本地访问: http://localhost:${port} ║ +║ 环境: ${process.env.NODE_ENV || 'development'} ║ +║ 进程ID: ${process.pid} ║ +║ ║ +║ 可用路由: ║ +║ • GET / - 首页 ║ +║ • GET /features - 特性页面 ║ +║ • GET /architecture - 架构页面 ║ +║ • GET /plugins - 插件页面 ║ +║ • GET /api/health - 健康检查 ║ +║ ║ +║ 按 Ctrl+C 停止服务器 ║ +╚══════════════════════════════════════════════════════════╝ + `); + }); + + // 优雅关闭 + const signals = ['SIGINT', 'SIGTERM', 'SIGQUIT']; + signals.forEach(signal => { + process.on(signal, () => { + console.log(`\n接收到 ${signal} 信号,正在关闭服务器...`); + server.close(() => { + console.log('服务器已关闭'); + process.exit(0); + }); + + // 5秒后强制退出 + setTimeout(() => { + console.error('强制关闭服务器'); + process.exit(1); + }, 5000); + }); + }); + + // 未捕获异常处理 + process.on('uncaughtException', (err) => { + console.error('未捕获异常:', err); + server.close(() => process.exit(1)); + }); + + process.on('unhandledRejection', (reason, promise) => { + console.error('未处理的Promise拒绝:', reason); + }); + + return server; + } catch (error) { + console.error('启动服务器失败:', error); + process.exit(1); + } +} + +// 如果是直接运行此文件,则启动服务器 +if (require.main === module) { + startServer(); +} + +module.exports = { app, startServer }; \ No newline at end of file diff --git a/website/views/layouts/main.ejs b/website/views/layouts/main.ejs new file mode 100644 index 0000000..de9e003 --- /dev/null +++ b/website/views/layouts/main.ejs @@ -0,0 +1,87 @@ + + + + + + <%= title %> + + + + + + + + + + + + + + + + + <% if (page === 'home') { %> + + <% } else if (page === 'features') { %> + + <% } else if (page === 'architecture') { %> + + <% } else if (page === 'plugins') { %> + + <% } %> + + + + + + + <%- include('../partials/navbar', { page: page }) %> + + +
+ <%- body %> +
+ + + <%- include('../partials/footer') %> + + +
+ + + + + + + + <% if (page === 'home') { %> + + <% } else if (page === 'features') { %> + + <% } else if (page === 'architecture') { %> + + <% } else if (page === 'plugins') { %> + + <% } %> + + + + + \ No newline at end of file diff --git a/website/views/pages/features.ejs b/website/views/pages/features.ejs new file mode 100644 index 0000000..a2ac183 --- /dev/null +++ b/website/views/pages/features.ejs @@ -0,0 +1,299 @@ + +
+ + + + +
+
+
+ +
+
+
+ +
+

插件化架构

+ 核心 +
+
+

+ 核心功能全部插件化,按需加载,极致轻量。从 HTTP 服务到 Web 界面,一切皆为插件。 +

+
    +
  • + + 按需加载,减少资源占用 +
  • +
  • + + 热插拔,无需重启服务 +
  • +
  • + + 模块化设计,易于维护 +
  • +
+
+ +
+ + +
+
+
+ +
+

安全沙箱

+ 安全 +
+
+

+ 数字签名验证 + 权限分级控制,确保插件来源可信,运行安全。 +

+
    +
  • + + 插件数字签名验证 +
  • +
  • + + 权限分级控制系统 +
  • +
  • + + 可选沙箱环境隔离 +
  • +
+
+ +
+ + +
+
+
+ +
+

热重载支持

+ 开发 +
+
+

+ 开发阶段插件实时更新,无需重启服务,提升开发效率。 +

+
    +
  • + + 代码变更实时生效 +
  • +
  • + + 开发模式专属功能 +
  • +
  • + + 状态保持,无需重新登录 +
  • +
+
+ +
+ + +
+
+
+ +
+

可视化控制台

+ 监控 +
+
+

+ Web 仪表盘实时监控系统状态与插件运行情况,管理更直观。 +

+
    +
  • + + 实时系统状态监控 +
  • +
  • + + 插件运行状态可视化 +
  • +
  • + + 一键启用/禁用插件 +
  • +
+
+ +
+ + +
+
+
+ +
+

双协议服务

+ 性能 +
+
+

+ 同时支持 HTTP API 和 TCP 高性能模式,满足不同场景需求。 +

+
    +
  • + + HTTP RESTful API 服务 +
  • +
  • + + TCP 高性能 HTTP 服务 +
  • +
  • + + WebSocket 实时通信 +
  • +
+
+ +
+ + +
+
+
+ +
+

依赖自动解析

+ 便捷 +
+
+

+ 插件依赖自动安装,告别手动配置烦恼,开箱即用。 +

+
    +
  • + + 依赖自动检测与安装 +
  • +
  • + + 版本冲突智能解决 +
  • +
  • + + 离线安装支持 +
  • +
+
+ +
+
+
+
+ + +
+
+
+

与传统框架对比

+

FutureOSS 带来的变革性优势

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
特性FutureOSS传统框架
架构设计插件化微内核单体或微服务
资源占用按需加载,极致轻量全量加载,资源浪费
扩展性无限扩展,热插拔有限扩展,需重启
安全性数字签名 + 沙箱依赖开发者自觉
开发体验热重载,实时更新修改后需重启
+
+
+
+ + +
+
+
+

深入了解技术实现?

+

查看 FutureOSS 的技术架构和设计思想

+ +
+
+
+
\ No newline at end of file diff --git a/website/views/pages/home.ejs b/website/views/pages/home.ejs new file mode 100644 index 0000000..48db6d7 --- /dev/null +++ b/website/views/pages/home.ejs @@ -0,0 +1,173 @@ + +
+ +
+
+
+

+ 一切皆为 + 插件 +

+

+ FutureOSS 是一款面向开发者的插件化运行时框架,让功能扩展变得前所未有的简单。 +

+ +
+
+
100%
+
插件化
+
+
+
15+
+
核心插件
+
+
+
3
+
协议支持
+
+
+
+
扩展可能
+
+
+
+
+
+
+
+ + 核心 +
+
+ + HTTP +
+
+ + WebSocket +
+
+ + Dashboard +
+
+ + Security +
+
+
+
+
+
+ + +
+
+
+

为什么选择 FutureOSS?

+

专为现代开发需求设计的强大框架

+
+
+
+
+ +
+

极致轻量

+

核心框架最小化,所有功能通过插件实现,按需加载,资源占用极低。

+
+
+
+ +
+

安全可靠

+

数字签名验证 + 权限分级控制 + 沙箱环境,确保插件来源可信。

+
+
+
+ +
+

高性能

+

支持 HTTP、TCP、WebSocket 多协议,满足不同场景的性能需求。

+
+
+
+ +
+

无限扩展

+

插件化架构让功能扩展变得简单,社区生态持续壮大。

+
+
+
+
+ + +
+
+
+

快速开始

+

几分钟内启动你的第一个 FutureOSS 项目

+
+
+
+
1
+

安装依赖

+
pip install -e .
+

安装 FutureOSS 及其依赖

+
+
+
2
+

启动服务

+
oss serve
+

启动 FutureOSS 服务器

+
+
+
3
+

访问控制台

+
http://localhost:8080
+

打开浏览器访问 Web 控制台

+
+
+
4
+

安装插件

+
# 通过控制台或API安装插件
+

按需安装功能插件

+
+
+ +
+
+ + +
+
+
+

准备好开始了吗?

+

加入 FutureOSS 社区,一起构建更好的开发者工具生态

+ +
+
+
+
\ No newline at end of file diff --git a/website/views/partials/footer.ejs b/website/views/partials/footer.ejs new file mode 100644 index 0000000..20d6669 --- /dev/null +++ b/website/views/partials/footer.ejs @@ -0,0 +1,122 @@ + + \ No newline at end of file diff --git a/website/views/partials/navbar.ejs b/website/views/partials/navbar.ejs new file mode 100644 index 0000000..20e680c --- /dev/null +++ b/website/views/partials/navbar.ejs @@ -0,0 +1,108 @@ + + \ No newline at end of file