diff --git a/site/.gitignore b/site/.gitignore new file mode 100644 index 0000000..9045c73 --- /dev/null +++ b/site/.gitignore @@ -0,0 +1,6 @@ +node_modules/ +dist/ +# generated from the repo at build time (npm run data) +src/_data/apps.json +src/_data/categories.json +*.bak diff --git a/site/eleventy.config.cjs b/site/eleventy.config.cjs new file mode 100644 index 0000000..eb0ebf1 --- /dev/null +++ b/site/eleventy.config.cjs @@ -0,0 +1,12 @@ +module.exports = function (eleventyConfig) { + // copy static assets straight through to the build output + eleventyConfig.addPassthroughCopy({ "src/assets": "assets" }); + eleventyConfig.addWatchTarget("src/assets"); + + return { + dir: { input: "src", includes: "_includes", data: "_data", output: "dist" }, + htmlTemplateEngine: "njk", + markdownTemplateEngine: "njk", + templateFormats: ["njk", "html", "md"], + }; +}; diff --git a/site/package-lock.json b/site/package-lock.json new file mode 100644 index 0000000..54c11ee --- /dev/null +++ b/site/package-lock.json @@ -0,0 +1,1522 @@ +{ + "name": "libreportal-site", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "libreportal-site", + "version": "0.1.0", + "devDependencies": { + "@11ty/eleventy": "^3.1.5" + } + }, + "node_modules/@11ty/dependency-tree": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@11ty/dependency-tree/-/dependency-tree-4.0.2.tgz", + "integrity": "sha512-RTF6VTZHatYf7fSZBUN3RKwiUeJh5dhWV61gDPrHhQF2/gzruAkYz8yXuvGLx3w3ZBKreGrR+MfYpSVkdbdbLA==", + "dev": true, + "dependencies": { + "@11ty/eleventy-utils": "^2.0.1" + } + }, + "node_modules/@11ty/dependency-tree-esm": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@11ty/dependency-tree-esm/-/dependency-tree-esm-2.0.4.tgz", + "integrity": "sha512-MYKC0Ac77ILr1HnRJalzKDlb9Z8To3kXQCltx299pUXXUFtJ1RIONtULlknknqW8cLe19DLVgmxVCtjEFm7h0A==", + "dev": true, + "dependencies": { + "@11ty/eleventy-utils": "^2.0.7", + "acorn": "^8.15.0", + "dependency-graph": "^1.0.0", + "normalize-path": "^3.0.0" + } + }, + "node_modules/@11ty/eleventy": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/@11ty/eleventy/-/eleventy-3.1.5.tgz", + "integrity": "sha512-hZ0g6MwZyRxCqXsPm82gIM304LraKbUz3ZmezOSjsqxttZG6cHTib3Qq8QkESJoKwnr+yX1eyfOkPC5/mEgZnQ==", + "dev": true, + "dependencies": { + "@11ty/dependency-tree": "^4.0.2", + "@11ty/dependency-tree-esm": "^2.0.4", + "@11ty/eleventy-dev-server": "^2.0.8", + "@11ty/eleventy-plugin-bundle": "^3.0.7", + "@11ty/eleventy-utils": "^2.0.7", + "@11ty/lodash-custom": "^4.17.21", + "@11ty/posthtml-urls": "^1.0.2", + "@11ty/recursive-copy": "^4.0.4", + "@sindresorhus/slugify": "^2.2.1", + "bcp-47-normalize": "^2.3.0", + "chokidar": "^3.6.0", + "debug": "^4.4.3", + "dependency-graph": "^1.0.0", + "entities": "^6.0.1", + "filesize": "^10.1.6", + "gray-matter": "^4.0.3", + "iso-639-1": "^3.1.5", + "js-yaml": "^4.1.1", + "kleur": "^4.1.5", + "liquidjs": "^10.25.0", + "luxon": "^3.7.2", + "markdown-it": "^14.1.1", + "minimist": "^1.2.8", + "moo": "0.5.2", + "node-retrieve-globals": "^6.0.1", + "nunjucks": "^3.2.4", + "picomatch": "^4.0.3", + "please-upgrade-node": "^3.2.0", + "posthtml": "^0.16.7", + "posthtml-match-helper": "^2.0.3", + "semver": "^7.7.4", + "slugify": "^1.6.8", + "tinyglobby": "^0.2.15" + }, + "bin": { + "eleventy": "cmd.cjs" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/11ty" + } + }, + "node_modules/@11ty/eleventy-dev-server": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/@11ty/eleventy-dev-server/-/eleventy-dev-server-2.0.8.tgz", + "integrity": "sha512-15oC5M1DQlCaOMUq4limKRYmWiGecDaGwryr7fTE/oM9Ix8siqMvWi+I8VjsfrGr+iViDvWcH/TVI6D12d93mA==", + "dev": true, + "dependencies": { + "@11ty/eleventy-utils": "^2.0.1", + "chokidar": "^3.6.0", + "debug": "^4.4.0", + "finalhandler": "^1.3.1", + "mime": "^3.0.0", + "minimist": "^1.2.8", + "morphdom": "^2.7.4", + "please-upgrade-node": "^3.2.0", + "send": "^1.1.0", + "ssri": "^11.0.0", + "urlpattern-polyfill": "^10.0.0", + "ws": "^8.18.1" + }, + "bin": { + "eleventy-dev-server": "cmd.js" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/11ty" + } + }, + "node_modules/@11ty/eleventy-plugin-bundle": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/@11ty/eleventy-plugin-bundle/-/eleventy-plugin-bundle-3.0.7.tgz", + "integrity": "sha512-QK1tRFBhQdZASnYU8GMzpTdsMMFLVAkuU0gVVILqNyp09xJJZb81kAS3AFrNrwBCsgLxTdWHJ8N64+OTTsoKkA==", + "dev": true, + "dependencies": { + "@11ty/eleventy-utils": "^2.0.2", + "debug": "^4.4.0", + "posthtml-match-helper": "^2.0.3" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/11ty" + } + }, + "node_modules/@11ty/eleventy-utils": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/@11ty/eleventy-utils/-/eleventy-utils-2.0.7.tgz", + "integrity": "sha512-6QE+duqSQ0GY9rENXYb4iPR4AYGdrFpqnmi59tFp9VrleOl0QSh8VlBr2yd6dlhkdtj7904poZW5PvGr9cMiJQ==", + "dev": true, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/11ty" + } + }, + "node_modules/@11ty/lodash-custom": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/@11ty/lodash-custom/-/lodash-custom-4.17.21.tgz", + "integrity": "sha512-Mqt6im1xpb1Ykn3nbcCovWXK3ggywRJa+IXIdoz4wIIK+cvozADH63lexcuPpGS/gJ6/m2JxyyXDyupkMr5DHw==", + "dev": true, + "engines": { + "node": ">=14" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/11ty" + } + }, + "node_modules/@11ty/posthtml-urls": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@11ty/posthtml-urls/-/posthtml-urls-1.0.3.tgz", + "integrity": "sha512-1YvhnkaNlFnnJic1rBMWmTC2adbuy+JQiBfl1Hecr1Wjjik1pQZmGyk/eC9zKX/FQv52s2Nht1Gi/UwhYqrBeg==", + "dev": true, + "dependencies": { + "evaluate-value": "^2.0.0", + "http-equiv-refresh": "^2.0.1", + "list-to-array": "^1.1.0", + "parse-srcset": "^1.0.2" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/@11ty/recursive-copy": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@11ty/recursive-copy/-/recursive-copy-4.0.4.tgz", + "integrity": "sha512-oI7m8pa7/IAU/3lqRU9vjBbs20iKFo7x+1K9kT3aVira6scc1X9MjBdgLCHzLJeJ7iB6wydioA+kr9/qPnvmlQ==", + "dev": true, + "dependencies": { + "errno": "^1.0.0", + "junk": "^3.1.0", + "minimatch": "^3.1.5", + "slash": "^3.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@sindresorhus/slugify": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/@sindresorhus/slugify/-/slugify-2.2.1.tgz", + "integrity": "sha512-MkngSCRZ8JdSOCHRaYd+D01XhvU3Hjy6MGl06zhOk614hp9EOAp5gIkBeQg7wtmxpitU6eAL4kdiRMcJa2dlrw==", + "dev": true, + "dependencies": { + "@sindresorhus/transliterate": "^1.0.0", + "escape-string-regexp": "^5.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@sindresorhus/transliterate": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/transliterate/-/transliterate-1.6.0.tgz", + "integrity": "sha512-doH1gimEu3A46VX6aVxpHTeHrytJAG6HgdxntYnCFiIFHEM/ZGpG8KiZGBChchjQmG0XFIBL552kBTjVcMZXwQ==", + "dev": true, + "dependencies": { + "escape-string-regexp": "^5.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/a-sync-waterfall": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/a-sync-waterfall/-/a-sync-waterfall-1.0.1.tgz", + "integrity": "sha512-RYTOHHdWipFUliRFMCS4X2Yn2X8M87V/OpSqWzKKOGhzqyUxzyVmhHDH9sAvG+ZuQf/TAOFsLCpMw09I1ufUnA==", + "dev": true + }, + "node_modules/acorn": { + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", + "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", + "dev": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-walk": { + "version": "8.3.5", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.5.tgz", + "integrity": "sha512-HEHNfbars9v4pgpW6SO1KSPkfoS0xVOM/9UzkJltjlsHZmJasxg8aXkuZa7SMf8vKGIBhpUsPluQSqhJFCqebw==", + "dev": true, + "dependencies": { + "acorn": "^8.11.0" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "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/anymatch/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/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "node_modules/asap": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", + "integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==", + "dev": true + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true + }, + "node_modules/bcp-47": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/bcp-47/-/bcp-47-2.1.0.tgz", + "integrity": "sha512-9IIS3UPrvIa1Ej+lVDdDwO7zLehjqsaByECw0bu2RRGP73jALm6FYbzI5gWbgHLvNdkvfXB5YrSbocZdOS0c0w==", + "dev": true, + "dependencies": { + "is-alphabetical": "^2.0.0", + "is-alphanumerical": "^2.0.0", + "is-decimal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/bcp-47-match": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/bcp-47-match/-/bcp-47-match-2.0.3.tgz", + "integrity": "sha512-JtTezzbAibu8G0R9op9zb3vcWZd9JF6M0xOYGPn0fNCd7wOpRB1mU2mH9T8gaBGbAAyIIVgB2G7xG0GP98zMAQ==", + "dev": true, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/bcp-47-normalize": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/bcp-47-normalize/-/bcp-47-normalize-2.3.0.tgz", + "integrity": "sha512-8I/wfzqQvttUFz7HVJgIZ7+dj3vUaIyIxYXaTRP1YWoSDfzt6TUmxaKZeuXR62qBmYr+nvuWINFRl6pZ5DlN4Q==", + "dev": true, + "dependencies": { + "bcp-47": "^2.0.0", + "bcp-47-match": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "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/brace-expansion": { + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.14.tgz", + "integrity": "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "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/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/commander": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-10.0.1.tgz", + "integrity": "sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==", + "dev": true, + "engines": { + "node": ">=14" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true + }, + "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/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/dependency-graph": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/dependency-graph/-/dependency-graph-1.0.0.tgz", + "integrity": "sha512-cW3gggJ28HZ/LExwxP2B++aiKxhJXMSIt9K48FOXQkm+vuG5gyatXnLsONRJdzO/7VfjDIiaOOa/bs4l464Lwg==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/dom-serializer": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-1.4.1.tgz", + "integrity": "sha512-VHwB3KfrcOOkelEG2ZOfxqLZdfkil8PtJi4P8N2MMXucZq2yLp75ClViUlOVwyoHEDjYU433Aq+5zWP61+RGag==", + "dev": true, + "dependencies": { + "domelementtype": "^2.0.1", + "domhandler": "^4.2.0", + "entities": "^2.0.0" + }, + "funding": { + "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" + } + }, + "node_modules/dom-serializer/node_modules/entities": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz", + "integrity": "sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==", + "dev": true, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/domelementtype": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", + "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ] + }, + "node_modules/domhandler": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-4.3.1.tgz", + "integrity": "sha512-GrwoxYN+uWlzO8uhUXRl0P+kHE4GtVPfYzVLcUxPL7KNdHKj66vvlhiweIHqYYXWlw+T8iLMp42Lm67ghw4WMQ==", + "dev": true, + "dependencies": { + "domelementtype": "^2.2.0" + }, + "engines": { + "node": ">= 4" + }, + "funding": { + "url": "https://github.com/fb55/domhandler?sponsor=1" + } + }, + "node_modules/domutils": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-2.8.0.tgz", + "integrity": "sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A==", + "dev": true, + "dependencies": { + "dom-serializer": "^1.0.1", + "domelementtype": "^2.2.0", + "domhandler": "^4.2.0" + }, + "funding": { + "url": "https://github.com/fb55/domutils?sponsor=1" + } + }, + "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==", + "dev": true + }, + "node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/entities": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz", + "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==", + "dev": true, + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/errno": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/errno/-/errno-1.0.0.tgz", + "integrity": "sha512-3zV5mFS1E8/1bPxt/B0xxzI1snsg3uSCIh6Zo1qKg6iMw93hzPANk9oBFzSFBFrwuVoQuE3rLoouAUfwOAj1wQ==", + "dev": true, + "dependencies": { + "prr": "~1.0.1" + }, + "bin": { + "errno": "cli.js" + } + }, + "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==", + "dev": true + }, + "node_modules/escape-string-regexp": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz", + "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/esm-import-transformer": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/esm-import-transformer/-/esm-import-transformer-3.0.5.tgz", + "integrity": "sha512-1GKLvfuMnnpI75l8c6sHoz0L3Z872xL5akGuBudgqTDPv4Vy6f2Ec7jEMKTxlqWl/3kSvNbHELeimJtnqgYniw==", + "dev": true, + "dependencies": { + "acorn": "^8.15.0" + } + }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true, + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/evaluate-value": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/evaluate-value/-/evaluate-value-2.0.0.tgz", + "integrity": "sha512-VonfiuDJc0z4sOO7W0Pd130VLsXN6vmBWZlrog1mCb/o7o/Nl5Lr25+Kj/nkCCAhG+zqeeGjxhkK9oHpkgTHhQ==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", + "dev": true, + "dependencies": { + "is-extendable": "^0.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/filesize": { + "version": "10.1.6", + "resolved": "https://registry.npmjs.org/filesize/-/filesize-10.1.6.tgz", + "integrity": "sha512-sJslQKU2uM33qH5nqewAwVB2QgR6w1aMNsYUp3aN5rMRyXEwJGmZvaWzeJFNTOXWlHQyBFCWrdj3fV/fsTOX8w==", + "dev": true, + "engines": { + "node": ">= 10.4.0" + } + }, + "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==", + "dev": true, + "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/finalhandler/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/finalhandler/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true + }, + "node_modules/fresh": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz", + "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "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/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/gray-matter": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/gray-matter/-/gray-matter-4.0.3.tgz", + "integrity": "sha512-5v6yZd4JK3eMI3FqqCouswVqwugaA9r4dNZB1wwcmrD02QkV5H0y7XBQW8QwQqEaZY1pM9aqORSORhJRdNK44Q==", + "dev": true, + "dependencies": { + "js-yaml": "^3.13.1", + "kind-of": "^6.0.2", + "section-matter": "^1.0.0", + "strip-bom-string": "^1.0.0" + }, + "engines": { + "node": ">=6.0" + } + }, + "node_modules/gray-matter/node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, + "node_modules/gray-matter/node_modules/js-yaml": { + "version": "3.14.2", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.2.tgz", + "integrity": "sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==", + "dev": true, + "dependencies": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/htmlparser2": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-7.2.0.tgz", + "integrity": "sha512-H7MImA4MS6cw7nbyURtLPO1Tms7C5H602LRETv95z1MxO/7CP7rDVROehUYeYBUYEON94NXXDEPmZuq+hX4sog==", + "dev": true, + "funding": [ + "https://github.com/fb55/htmlparser2?sponsor=1", + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "dependencies": { + "domelementtype": "^2.0.1", + "domhandler": "^4.2.2", + "domutils": "^2.8.0", + "entities": "^3.0.1" + } + }, + "node_modules/htmlparser2/node_modules/entities": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-3.0.1.tgz", + "integrity": "sha512-WiyBqoomrwMdFG1e0kqvASYfnlb0lp8M5o5Fw2OFq1hNZxxcNk8Ik0Xm7LxzBhuidnZB/UtBqVCgUz3kBOP51Q==", + "dev": true, + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/http-equiv-refresh": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/http-equiv-refresh/-/http-equiv-refresh-2.0.1.tgz", + "integrity": "sha512-XJpDL/MLkV3dKwLzHwr2dY05dYNfBNlyPu4STQ8WvKCFdc6vC5tPXuq28of663+gHVg03C+16pHHs/+FmmDjcw==", + "dev": true, + "engines": { + "node": ">= 6" + } + }, + "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==", + "dev": true, + "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/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "node_modules/is-alphabetical": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-alphabetical/-/is-alphabetical-2.0.1.tgz", + "integrity": "sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ==", + "dev": true, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-alphanumerical": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-alphanumerical/-/is-alphanumerical-2.0.1.tgz", + "integrity": "sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw==", + "dev": true, + "dependencies": { + "is-alphabetical": "^2.0.0", + "is-decimal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "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-decimal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-decimal/-/is-decimal-2.0.1.tgz", + "integrity": "sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A==", + "dev": true, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/is-extendable": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", + "integrity": "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "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-json": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-json/-/is-json-2.0.1.tgz", + "integrity": "sha512-6BEnpVn1rcf3ngfmViLM6vjUjGErbdrL4rwlv+u1NO1XO8kqT4YGL8+19Q+Z/bas8tY90BTWMk2+fW1g6hQjbA==", + "dev": true + }, + "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/iso-639-1": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/iso-639-1/-/iso-639-1-3.1.5.tgz", + "integrity": "sha512-gXkz5+KN7HrG0Q5UGqSMO2qB9AsbEeyLP54kF1YrMsIxmu+g4BdB7rflReZTSTZGpfj8wywu6pfPBCylPIzGQA==", + "dev": true, + "engines": { + "node": ">=6.0" + } + }, + "node_modules/js-yaml": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", + "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", + "dev": true, + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/junk": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/junk/-/junk-3.1.0.tgz", + "integrity": "sha512-pBxcB3LFc8QVgdggvZWyeys+hnrNWg4OcZIU/1X59k5jQdLBlCsYGRQaz234SqoRLTCgMH00fY0xRJH+F9METQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/kleur": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.5.tgz", + "integrity": "sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/linkify-it": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-5.0.0.tgz", + "integrity": "sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ==", + "dev": true, + "dependencies": { + "uc.micro": "^2.0.0" + } + }, + "node_modules/liquidjs": { + "version": "10.27.0", + "resolved": "https://registry.npmjs.org/liquidjs/-/liquidjs-10.27.0.tgz", + "integrity": "sha512-tw/OA59K7aIBlMKIrKlumr37fiZUheShVHXY8cVctWisgY1p9mc5hreOvlreoS0wTiwlWk14Ya7305c2a/Cg5w==", + "dev": true, + "dependencies": { + "commander": "^10.0.0" + }, + "bin": { + "liquid": "bin/liquid.js", + "liquidjs": "bin/liquid.js" + }, + "engines": { + "node": ">=16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/liquidjs" + } + }, + "node_modules/list-to-array": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/list-to-array/-/list-to-array-1.1.0.tgz", + "integrity": "sha512-+dAZZ2mM+/m+vY9ezfoueVvrgnHIGi5FvgSymbIgJOFwiznWyA59mav95L+Mc6xPtL3s9gm5eNTlNtxJLbNM1g==", + "dev": true + }, + "node_modules/luxon": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/luxon/-/luxon-3.7.2.tgz", + "integrity": "sha512-vtEhXh/gNjI9Yg1u4jX/0YVPMvxzHuGgCm6tC5kZyb08yjGWGnqAjGJvcXbqQR2P3MyMEFnRbpcdFS6PBcLqew==", + "dev": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/markdown-it": { + "version": "14.1.1", + "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-14.1.1.tgz", + "integrity": "sha512-BuU2qnTti9YKgK5N+IeMubp14ZUKUUw7yeJbkjtosvHiP0AZ5c8IAgEMk79D0eC8F23r4Ac/q8cAIFdm2FtyoA==", + "dev": true, + "dependencies": { + "argparse": "^2.0.1", + "entities": "^4.4.0", + "linkify-it": "^5.0.0", + "mdurl": "^2.0.0", + "punycode.js": "^2.3.1", + "uc.micro": "^2.1.0" + }, + "bin": { + "markdown-it": "bin/markdown-it.mjs" + } + }, + "node_modules/markdown-it/node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "dev": true, + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/mdurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-2.0.0.tgz", + "integrity": "sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==", + "dev": true + }, + "node_modules/mime": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-3.0.0.tgz", + "integrity": "sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==", + "dev": true, + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "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==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", + "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", + "dev": true, + "dependencies": { + "mime-db": "^1.54.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/minipass": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz", + "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==", + "dev": true, + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/moo": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/moo/-/moo-0.5.2.tgz", + "integrity": "sha512-iSAJLHYKnX41mKcJKjqvnAN9sf0LMDTXDEvFv+ffuRR9a1MIuXLjMNL6EsnDHSkKLTWNqQQ5uo61P4EbU4NU+Q==", + "dev": true + }, + "node_modules/morphdom": { + "version": "2.7.8", + "resolved": "https://registry.npmjs.org/morphdom/-/morphdom-2.7.8.tgz", + "integrity": "sha512-D/fR4xgGUyVRbdMGU6Nejea1RFzYxYtyurG4Fbv2Fi/daKlWKuXGLOdXtl+3eIwL110cI2hz1ZojGICjjFLgTg==", + "dev": true + }, + "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/node-retrieve-globals": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/node-retrieve-globals/-/node-retrieve-globals-6.0.1.tgz", + "integrity": "sha512-j0DeFuZ/Wg3VlklfbxUgZF/mdHMTEiEipBb3q0SpMMbHaV3AVfoUQF8UGxh1s/yjqO0TgRZd4Pi/x2yRqoQ4Eg==", + "dev": true, + "dependencies": { + "acorn": "^8.14.1", + "acorn-walk": "^8.3.4", + "esm-import-transformer": "^3.0.3" + } + }, + "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/nunjucks": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/nunjucks/-/nunjucks-3.2.4.tgz", + "integrity": "sha512-26XRV6BhkgK0VOxfbU5cQI+ICFUtMLixv1noZn1tGU38kQH5A5nmmbk/O45xdyBhD1esk47nKrY0mvQpZIhRjQ==", + "dev": true, + "dependencies": { + "a-sync-waterfall": "^1.0.0", + "asap": "^2.0.3", + "commander": "^5.1.0" + }, + "bin": { + "nunjucks-precompile": "bin/precompile" + }, + "engines": { + "node": ">= 6.9.0" + }, + "peerDependencies": { + "chokidar": "^3.3.0" + }, + "peerDependenciesMeta": { + "chokidar": { + "optional": true + } + } + }, + "node_modules/nunjucks/node_modules/commander": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-5.1.0.tgz", + "integrity": "sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg==", + "dev": true, + "engines": { + "node": ">= 6" + } + }, + "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==", + "dev": true, + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/parse-srcset": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/parse-srcset/-/parse-srcset-1.0.2.tgz", + "integrity": "sha512-/2qh0lav6CmI15FzA3i/2Bzk2zCgQhGMkvhOhKNcBVQ1ldgpbfiNTVslmooUmWJcADi1f1kIeynbDRVzNlfR6Q==", + "dev": true + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/picomatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/please-upgrade-node": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/please-upgrade-node/-/please-upgrade-node-3.2.0.tgz", + "integrity": "sha512-gQR3WpIgNIKwBMVLkpMUeR3e1/E1y42bqDQZfql+kDeXd8COYfM8PQA4X6y7a8u9Ua9FHmsrrmirW2vHs45hWg==", + "dev": true, + "dependencies": { + "semver-compare": "^1.0.0" + } + }, + "node_modules/posthtml": { + "version": "0.16.7", + "resolved": "https://registry.npmjs.org/posthtml/-/posthtml-0.16.7.tgz", + "integrity": "sha512-7Hc+IvlQ7hlaIfQFZnxlRl0jnpWq2qwibORBhQYIb0QbNtuicc5ZxvKkVT71HJ4Py1wSZ/3VR1r8LfkCtoCzhw==", + "dev": true, + "dependencies": { + "posthtml-parser": "^0.11.0", + "posthtml-render": "^3.0.0" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/posthtml-match-helper": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/posthtml-match-helper/-/posthtml-match-helper-2.0.3.tgz", + "integrity": "sha512-p9oJgTdMF2dyd7WE54QI1LvpBIkNkbSiiECKezNnDVYhGhD1AaOnAkw0Uh0y5TW+OHO8iBdSqnd8Wkpb6iUqmw==", + "dev": true, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "posthtml": "^0.16.6" + } + }, + "node_modules/posthtml-parser": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/posthtml-parser/-/posthtml-parser-0.11.0.tgz", + "integrity": "sha512-QecJtfLekJbWVo/dMAA+OSwY79wpRmbqS5TeXvXSX+f0c6pW4/SE6inzZ2qkU7oAMCPqIDkZDvd/bQsSFUnKyw==", + "dev": true, + "dependencies": { + "htmlparser2": "^7.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/posthtml-render": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/posthtml-render/-/posthtml-render-3.0.0.tgz", + "integrity": "sha512-z+16RoxK3fUPgwaIgH9NGnK1HKY9XIDpydky5eQGgAFVXTCSezalv9U2jQuNV+Z9qV1fDWNzldcw4eK0SSbqKA==", + "dev": true, + "dependencies": { + "is-json": "^2.0.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/prr": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz", + "integrity": "sha512-yPw4Sng1gWghHQWj0B3ZggWUm4qVbPwPFcRG8KyxiU7J2OHFSoEHKS+EZ3fv5l1t9CyCiop6l/ZYeWbrgoQejw==", + "dev": true + }, + "node_modules/punycode.js": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode.js/-/punycode.js-2.3.1.tgz", + "integrity": "sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "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==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "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/readdirp/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/section-matter": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/section-matter/-/section-matter-1.0.0.tgz", + "integrity": "sha512-vfD3pmTzGpufjScBh50YHKzEu2lxBWhVEHsNGoEXmCmn2hKGfeNLYMzCJpe8cD7gqX7TJluOVpBkAequ6dgMmA==", + "dev": true, + "dependencies": { + "extend-shallow": "^2.0.1", + "kind-of": "^6.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/semver": { + "version": "7.8.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.1.tgz", + "integrity": "sha512-rkVq3IXh+4FDGch+KwzX3aV9W3kO54GyEgpvBzSyctDA6Xtd7RJQV1xmXbeQp5v7+VzLOfVqiutSE6GICgPFvg==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/semver-compare": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/semver-compare/-/semver-compare-1.0.0.tgz", + "integrity": "sha512-YM3/ITh2MJ5MtzaM429anh+x2jiLVjqILF4m4oyQB18W7Ggea7BfqdH/wGMK7dDiMghv/6WG7znWMwUDzJiXow==", + "dev": true + }, + "node_modules/send": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/send/-/send-1.2.1.tgz", + "integrity": "sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==", + "dev": true, + "dependencies": { + "debug": "^4.4.3", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "fresh": "^2.0.0", + "http-errors": "^2.0.1", + "mime-types": "^3.0.2", + "ms": "^2.1.3", + "on-finished": "^2.4.1", + "range-parser": "^1.2.1", + "statuses": "^2.0.2" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "dev": true + }, + "node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/slugify": { + "version": "1.6.9", + "resolved": "https://registry.npmjs.org/slugify/-/slugify-1.6.9.tgz", + "integrity": "sha512-vZ7rfeehZui7wQs438JXBckYLkIIdfHOXsaVEUMyS5fHo1483l1bMdo0EDSWYclY0yZKFOipDy4KHuKs6ssvdg==", + "dev": true, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", + "dev": true + }, + "node_modules/ssri": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/ssri/-/ssri-11.0.0.tgz", + "integrity": "sha512-aZpUoMN/Jj2MqA4vMCeiKGnc/8SuSyHbGSBdgFbZxP8OJGF/lFkIuElzPxsN0q8TQQ+prw3P4EDfB3TBHHgfXw==", + "dev": true, + "dependencies": { + "minipass": "^7.0.3" + }, + "engines": { + "node": "^16.14.0 || >=18.0.0" + } + }, + "node_modules/statuses": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/strip-bom-string": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/strip-bom-string/-/strip-bom-string-1.0.0.tgz", + "integrity": "sha512-uCC2VHvQRYu+lMh4My/sFNmF2klFymLX1wHJeXnbEJERpV/ZsVuonzerjfrGpIGF7LBVa1O7i9kjiWvJiFck8g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.16", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.16.tgz", + "integrity": "sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==", + "dev": true, + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "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==", + "dev": true, + "engines": { + "node": ">=0.6" + } + }, + "node_modules/uc.micro": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-2.1.0.tgz", + "integrity": "sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==", + "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==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/urlpattern-polyfill": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/urlpattern-polyfill/-/urlpattern-polyfill-10.1.0.tgz", + "integrity": "sha512-IGjKp/o0NL3Bso1PymYURCJxMPNAf/ILOpendP9f5B6e1rTJgdgiOvgfoT8VxCAdY+Wisb9uhGaJJf3yZ2V9nw==", + "dev": true + }, + "node_modules/ws": { + "version": "8.20.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.20.1.tgz", + "integrity": "sha512-It4dO0K5v//JtTXuPkfEOaI3uUN87iYPnqo/ZzqCoG3g8uhA66QUMs/SrM0YK7/NAu+r4LMh/9dq2A7k+rHs+w==", + "dev": true, + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + } + } +} diff --git a/site/package.json b/site/package.json new file mode 100644 index 0000000..803889c --- /dev/null +++ b/site/package.json @@ -0,0 +1,15 @@ +{ + "name": "libreportal-site", + "version": "0.1.0", + "private": true, + "description": "LibrePortal marketing/get site — data-driven from the repo via Eleventy.", + "scripts": { + "data": "node scripts/gen-data.mjs", + "build": "npm run data && eleventy", + "start": "npm run data && eleventy --serve --port 8777", + "clean": "rm -rf dist" + }, + "devDependencies": { + "@11ty/eleventy": "^3.1.5" + } +} diff --git a/site/scripts/gen-data.mjs b/site/scripts/gen-data.mjs new file mode 100644 index 0000000..506d84e --- /dev/null +++ b/site/scripts/gen-data.mjs @@ -0,0 +1,78 @@ +#!/usr/bin/env node +/* + * gen-data.mjs — single source of truth: the LibrePortal repo. + * + * Reads every containers//.config METADATA block and emits + * src/_data/apps.json (array of apps, used by the app grid) + * src/_data/categories.json (categories present, with friendly names + counts) + * + * Run via `npm run data` (also runs automatically before `npm run build`). + * Add an app to the repo -> rebuild -> the website updates itself. + */ +import { readdirSync, readFileSync, existsSync, writeFileSync, mkdirSync, statSync } from 'node:fs'; +import { fileURLToPath } from 'node:url'; +import { dirname, join, resolve } from 'node:path'; + +const here = dirname(fileURLToPath(import.meta.url)); +const repoRoot = resolve(here, '..', '..'); // site/scripts -> repo root +const containersDir = join(repoRoot, 'containers'); +const dataDir = join(here, '..', 'src', '_data'); +const iconsDir = join(here, '..', 'src', 'assets', 'apps'); + +// Only these metadata fields are pulled from each .config. LONG_DESCRIPTION must +// precede DESCRIPTION in the alternation so it wins the match. +const FIELD_RE = /^CFG_.+?_(LONG_DESCRIPTION|DESCRIPTION|CATEGORY|TITLE|URL)\s*=\s*"?(.*?)"?\s*$/; + +const CATEGORY_NAMES = { + recommended: 'Recommended', communication: 'Communication', development: 'Development', + knowledge: 'Knowledge', media: 'Media', monitoring: 'Monitoring', storage: 'Storage', + network: 'Network', security: 'Security', productivity: 'Productivity', finance: 'Finance', + ai: 'AI', utilities: 'Utilities', privacy: 'Privacy', social: 'Social', system: 'System', +}; +const titleCase = (s) => s.replace(/[-_]/g, ' ').replace(/\b\w/g, (m) => m.toUpperCase()); + +function parseConfig(text) { + const out = {}; + for (const raw of text.split(/\r?\n/)) { + const m = FIELD_RE.exec(raw.trim()); + if (m && out[m[1]] === undefined) out[m[1]] = m[2]; + } + return out; +} + +const apps = []; +for (const slug of readdirSync(containersDir)) { + const dir = join(containersDir, slug); + if (!statSync(dir).isDirectory()) continue; + const cfg = join(dir, `${slug}.config`); + if (!existsSync(cfg)) continue; + const meta = parseConfig(readFileSync(cfg, 'utf8')); + if (!meta.TITLE) continue; // skip anything without a display title (infra without metadata) + const cats = (meta.CATEGORY || 'utilities').split(',').map((s) => s.trim()).filter(Boolean); + apps.push({ + slug, + title: meta.TITLE, + category: cats[0], // primary category + categories: cats, // an app can belong to several + description: meta.DESCRIPTION || '', + longDescription: meta.LONG_DESCRIPTION || meta.DESCRIPTION || '', + url: meta.URL || '', + icon: existsSync(join(iconsDir, `${slug}.svg`)) ? `assets/apps/${slug}.svg` : 'assets/apps/default.svg', + }); +} +apps.sort((a, b) => a.title.localeCompare(b.title)); + +const catMap = new Map(); +for (const a of apps) { + for (const cat of a.categories) { + const c = catMap.get(cat) || { id: cat, name: CATEGORY_NAMES[cat] || titleCase(cat), count: 0 }; + c.count++; + catMap.set(cat, c); + } +} +const categories = [...catMap.values()].sort((a, b) => b.count - a.count || a.name.localeCompare(b.name)); + +mkdirSync(dataDir, { recursive: true }); +writeFileSync(join(dataDir, 'apps.json'), JSON.stringify(apps, null, 2) + '\n'); +writeFileSync(join(dataDir, 'categories.json'), JSON.stringify(categories, null, 2) + '\n'); +console.log(`✓ ${apps.length} apps across ${categories.length} categories → src/_data/{apps,categories}.json`); diff --git a/site/src/_data/site.json b/site/src/_data/site.json new file mode 100644 index 0000000..315890e --- /dev/null +++ b/site/src/_data/site.json @@ -0,0 +1,12 @@ +{ + "name": "LibrePortal", + "tagline": "your own private corner of the universe", + "version": "v0.1.0", + "installUrl": "https://get.libreportal.org", + "installCmd": "bash <(curl -fsSL https://get.libreportal.org) init", + "repo": "https://gitea.scottwebstar.co.uk/Webstar/LibrePortal", + "promiseUrl": "https://gitea.scottwebstar.co.uk/Webstar/LibrePortal/raw/branch/main/PROMISE.md", + "contributingUrl": "https://gitea.scottwebstar.co.uk/Webstar/LibrePortal/raw/branch/main/CONTRIBUTING.md", + "initUrl": "https://gitea.scottwebstar.co.uk/Webstar/LibrePortal/raw/branch/main/init.sh", + "inspiration": "https://gitlab.com/bmcgonag/docker_installs" +} diff --git a/site/src/_includes/app-card.njk b/site/src/_includes/app-card.njk new file mode 100644 index 0000000..e2b5a4e --- /dev/null +++ b/site/src/_includes/app-card.njk @@ -0,0 +1,10 @@ +
+
+ +
+

{{ app.title }}

+ {{ app.category }} +
+
+

{{ app.description }}

+
diff --git a/site/src/_includes/footer.njk b/site/src/_includes/footer.njk new file mode 100644 index 0000000..56c0765 --- /dev/null +++ b/site/src/_includes/footer.njk @@ -0,0 +1,26 @@ + diff --git a/site/src/_includes/layout.njk b/site/src/_includes/layout.njk new file mode 100644 index 0000000..2070463 --- /dev/null +++ b/site/src/_includes/layout.njk @@ -0,0 +1,24 @@ + + + + + +{{ site.name }} — {{ site.tagline }} + + + + + + +
+
+ +{% include "topbar.njk" %} + +{{ content | safe }} + +{% include "footer.njk" %} + + + + diff --git a/site/src/_includes/topbar.njk b/site/src/_includes/topbar.njk new file mode 100644 index 0000000..2927da7 --- /dev/null +++ b/site/src/_includes/topbar.njk @@ -0,0 +1,31 @@ + diff --git a/site/src/assets/apps/adguard.svg b/site/src/assets/apps/adguard.svg new file mode 100644 index 0000000..f6118fc --- /dev/null +++ b/site/src/assets/apps/adguard.svg @@ -0,0 +1 @@ + diff --git a/site/src/assets/apps/authelia.svg b/site/src/assets/apps/authelia.svg new file mode 100644 index 0000000..9880b3b --- /dev/null +++ b/site/src/assets/apps/authelia.svg @@ -0,0 +1 @@ + diff --git a/site/src/assets/apps/bookstack.svg b/site/src/assets/apps/bookstack.svg new file mode 100644 index 0000000..a6ad581 --- /dev/null +++ b/site/src/assets/apps/bookstack.svg @@ -0,0 +1 @@ + diff --git a/site/src/assets/apps/crowdsec.svg b/site/src/assets/apps/crowdsec.svg new file mode 100644 index 0000000..fd4ffac --- /dev/null +++ b/site/src/assets/apps/crowdsec.svg @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/site/src/assets/apps/dashy.svg b/site/src/assets/apps/dashy.svg new file mode 100644 index 0000000..ce68744 --- /dev/null +++ b/site/src/assets/apps/dashy.svg @@ -0,0 +1,161 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/site/src/assets/apps/default.svg b/site/src/assets/apps/default.svg new file mode 100644 index 0000000..343d2de --- /dev/null +++ b/site/src/assets/apps/default.svg @@ -0,0 +1 @@ + diff --git a/site/src/assets/apps/focalboard.svg b/site/src/assets/apps/focalboard.svg new file mode 100644 index 0000000..b78e7e3 --- /dev/null +++ b/site/src/assets/apps/focalboard.svg @@ -0,0 +1 @@ + diff --git a/site/src/assets/apps/gitea.svg b/site/src/assets/apps/gitea.svg new file mode 100644 index 0000000..11c6df8 --- /dev/null +++ b/site/src/assets/apps/gitea.svg @@ -0,0 +1,4 @@ + + + + diff --git a/site/src/assets/apps/gluetun.svg b/site/src/assets/apps/gluetun.svg new file mode 100644 index 0000000..a39521c --- /dev/null +++ b/site/src/assets/apps/gluetun.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + diff --git a/site/src/assets/apps/grafana.svg b/site/src/assets/apps/grafana.svg new file mode 100644 index 0000000..54be1e2 --- /dev/null +++ b/site/src/assets/apps/grafana.svg @@ -0,0 +1,70 @@ + + + + + + + + + + + + + + + + + + + diff --git a/site/src/assets/apps/headscale.svg b/site/src/assets/apps/headscale.svg new file mode 100644 index 0000000..06f406a --- /dev/null +++ b/site/src/assets/apps/headscale.svg @@ -0,0 +1 @@ + diff --git a/site/src/assets/apps/invidious.svg b/site/src/assets/apps/invidious.svg new file mode 100644 index 0000000..80e78a4 --- /dev/null +++ b/site/src/assets/apps/invidious.svg @@ -0,0 +1,2 @@ + + diff --git a/site/src/assets/apps/ipinfo.svg b/site/src/assets/apps/ipinfo.svg new file mode 100644 index 0000000..656169c --- /dev/null +++ b/site/src/assets/apps/ipinfo.svg @@ -0,0 +1 @@ + diff --git a/site/src/assets/apps/jellyfin.svg b/site/src/assets/apps/jellyfin.svg new file mode 100644 index 0000000..0e56a50 --- /dev/null +++ b/site/src/assets/apps/jellyfin.svg @@ -0,0 +1 @@ + diff --git a/site/src/assets/apps/jitsimeet.svg b/site/src/assets/apps/jitsimeet.svg new file mode 100644 index 0000000..5a3526a --- /dev/null +++ b/site/src/assets/apps/jitsimeet.svg @@ -0,0 +1,650 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/site/src/assets/apps/libreportal.svg b/site/src/assets/apps/libreportal.svg new file mode 100644 index 0000000..a476796 --- /dev/null +++ b/site/src/assets/apps/libreportal.svg @@ -0,0 +1,605 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/site/src/assets/apps/linkding.svg b/site/src/assets/apps/linkding.svg new file mode 100644 index 0000000..089630d --- /dev/null +++ b/site/src/assets/apps/linkding.svg @@ -0,0 +1 @@ + diff --git a/site/src/assets/apps/mastodon.svg b/site/src/assets/apps/mastodon.svg new file mode 100644 index 0000000..dd5075e --- /dev/null +++ b/site/src/assets/apps/mastodon.svg @@ -0,0 +1 @@ + diff --git a/site/src/assets/apps/nextcloud.svg b/site/src/assets/apps/nextcloud.svg new file mode 100644 index 0000000..336aff5 --- /dev/null +++ b/site/src/assets/apps/nextcloud.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/site/src/assets/apps/ollama.svg b/site/src/assets/apps/ollama.svg new file mode 100644 index 0000000..6bba73a --- /dev/null +++ b/site/src/assets/apps/ollama.svg @@ -0,0 +1 @@ + diff --git a/site/src/assets/apps/onlyoffice.svg b/site/src/assets/apps/onlyoffice.svg new file mode 100644 index 0000000..364522c --- /dev/null +++ b/site/src/assets/apps/onlyoffice.svg @@ -0,0 +1 @@ + diff --git a/site/src/assets/apps/owncloud.svg b/site/src/assets/apps/owncloud.svg new file mode 100644 index 0000000..cf650c7 --- /dev/null +++ b/site/src/assets/apps/owncloud.svg @@ -0,0 +1 @@ + diff --git a/site/src/assets/apps/pihole.svg b/site/src/assets/apps/pihole.svg new file mode 100644 index 0000000..5bda461 --- /dev/null +++ b/site/src/assets/apps/pihole.svg @@ -0,0 +1 @@ + diff --git a/site/src/assets/apps/portainer.svg b/site/src/assets/apps/portainer.svg new file mode 100644 index 0000000..45cf83a --- /dev/null +++ b/site/src/assets/apps/portainer.svg @@ -0,0 +1 @@ + diff --git a/site/src/assets/apps/prometheus.svg b/site/src/assets/apps/prometheus.svg new file mode 100644 index 0000000..309d704 --- /dev/null +++ b/site/src/assets/apps/prometheus.svg @@ -0,0 +1 @@ + diff --git a/site/src/assets/apps/searxng.svg b/site/src/assets/apps/searxng.svg new file mode 100644 index 0000000..2ddf53b --- /dev/null +++ b/site/src/assets/apps/searxng.svg @@ -0,0 +1 @@ + diff --git a/site/src/assets/apps/speedtest.svg b/site/src/assets/apps/speedtest.svg new file mode 100644 index 0000000..2fd0d2b --- /dev/null +++ b/site/src/assets/apps/speedtest.svg @@ -0,0 +1 @@ + diff --git a/site/src/assets/apps/traefik.svg b/site/src/assets/apps/traefik.svg new file mode 100644 index 0000000..a86b9b7 --- /dev/null +++ b/site/src/assets/apps/traefik.svg @@ -0,0 +1 @@ + diff --git a/site/src/assets/apps/trilium.svg b/site/src/assets/apps/trilium.svg new file mode 100644 index 0000000..2ecb6e4 --- /dev/null +++ b/site/src/assets/apps/trilium.svg @@ -0,0 +1 @@ + diff --git a/site/src/assets/apps/unbound.svg b/site/src/assets/apps/unbound.svg new file mode 100644 index 0000000..cfc5d8d --- /dev/null +++ b/site/src/assets/apps/unbound.svg @@ -0,0 +1 @@ + diff --git a/site/src/assets/apps/vaultwarden.svg b/site/src/assets/apps/vaultwarden.svg new file mode 100644 index 0000000..41ca105 --- /dev/null +++ b/site/src/assets/apps/vaultwarden.svg @@ -0,0 +1 @@ + diff --git a/site/src/assets/apps/wireguard.svg b/site/src/assets/apps/wireguard.svg new file mode 100644 index 0000000..b778001 --- /dev/null +++ b/site/src/assets/apps/wireguard.svg @@ -0,0 +1 @@ + diff --git a/site/src/assets/favicon.ico b/site/src/assets/favicon.ico new file mode 100644 index 0000000..622f2d3 Binary files /dev/null and b/site/src/assets/favicon.ico differ diff --git a/site/src/assets/libreportal.svg b/site/src/assets/libreportal.svg new file mode 100644 index 0000000..a476796 --- /dev/null +++ b/site/src/assets/libreportal.svg @@ -0,0 +1,605 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/site/src/assets/main.js b/site/src/assets/main.js new file mode 100644 index 0000000..d9dda04 --- /dev/null +++ b/site/src/assets/main.js @@ -0,0 +1,66 @@ +// LibrePortal site — interactions (mobile drawer, copy, scrollspy, reveal, app filter) +(() => { + // mobile drawer (same behaviour as the dashboard) + const menuToggle = document.getElementById('mobile-menu-toggle'); + const drawer = document.getElementById('mobile-drawer'); + if (menuToggle && drawer) { + menuToggle.addEventListener('click', () => { + const open = drawer.classList.toggle('mobile-open'); + document.body.style.overflow = open ? 'hidden' : ''; + }); + drawer.querySelectorAll('a').forEach((a) => + a.addEventListener('click', () => { drawer.classList.remove('mobile-open'); document.body.style.overflow = ''; })); + } + + // copy the install command + const cmdEl = document.querySelector('[data-install-cmd]'); + const CMD = cmdEl ? cmdEl.getAttribute('data-install-cmd') : ''; + document.querySelectorAll('.copy').forEach((btn) => { + btn.addEventListener('click', async () => { + try { await navigator.clipboard.writeText(CMD); } + catch { const t = document.createElement('textarea'); t.value = CMD; document.body.appendChild(t); t.select(); document.execCommand('copy'); t.remove(); } + btn.classList.add('done'); + const span = btn.querySelector('span'); const prev = span.textContent; span.textContent = 'Copied ✓'; + setTimeout(() => { btn.classList.remove('done'); span.textContent = prev; }, 1700); + }); + }); + + // scrollspy → light up the active topbar pill + const spies = [...document.querySelectorAll('.nav-item[data-spy]')]; + if (spies.length) { + const spyIO = new IntersectionObserver((entries) => { + entries.forEach((e) => { + if (e.isIntersecting) spies.forEach((s) => s.classList.toggle('nav-active', s.dataset.spy === e.target.id)); + }); + }, { rootMargin: '-45% 0px -50% 0px', threshold: 0 }); + spies.map((s) => document.getElementById(s.dataset.spy)).filter(Boolean).forEach((s) => spyIO.observe(s)); + } + + // reveal on scroll + const io = new IntersectionObserver((entries) => { + entries.forEach((e) => { if (e.isIntersecting) { e.target.classList.add('in'); io.unobserve(e.target); } }); + }, { threshold: 0.16, rootMargin: '0px 0px -8% 0px' }); + document.querySelectorAll('.reveal, .stagger').forEach((el) => io.observe(el)); + + // app category filter + const filters = document.querySelector('.app-filters'); + if (filters) { + const cards = [...document.querySelectorAll('.app-card')]; + const countEl = document.querySelector('.app-count'); + const update = (cat) => { + let shown = 0; + cards.forEach((c) => { + const match = cat === 'all' || (c.dataset.cats || '').split(' ').includes(cat); + c.classList.toggle('hidden', !match); + if (match) shown++; + }); + if (countEl) countEl.textContent = `${shown} app${shown === 1 ? '' : 's'}`; + }; + filters.addEventListener('click', (e) => { + const chip = e.target.closest('.chip'); + if (!chip) return; + filters.querySelectorAll('.chip').forEach((c) => c.classList.toggle('active', c === chip)); + update(chip.dataset.cat); + }); + } +})(); diff --git a/site/src/assets/style.css b/site/src/assets/style.css new file mode 100644 index 0000000..c187888 --- /dev/null +++ b/site/src/assets/style.css @@ -0,0 +1,276 @@ +/* ============ NEBULA THEME (lifted from the dashboard) ============ */ +:root{ + --gradient-from:#1a1442; /* indigo */ + --gradient-mid:#1b2a5e; /* violet-blue */ + --gradient-to:#0f3b6e; /* ocean */ + --surface-solid:#0f1729; + + --accent:#00d4ff; + --accent-hover:#0099cc; + --accent-rgb:0,212,255; + --accent-soft:rgba(0,212,255,.15); + + --text-primary:#ffffff; + --text-secondary:rgba(255,255,255,.82); + --text-muted:rgba(255,255,255,.65); + --text-faint:rgba(255,255,255,.45); + --text-on-accent:#0a1426; + --text-rgb:255,255,255; + + --border:rgba(255,255,255,.16); + --border-strong:rgba(255,255,255,.26); + --border-subtle:rgba(255,255,255,.08); + + --card-bg:linear-gradient(155deg, rgba(255,255,255,.09) 0%, rgba(0,212,255,.05) 100%); + --card-border:rgba(255,255,255,.16); + --card-shadow:0 4px 18px rgba(0,0,0,.30), inset 0 1px 0 rgba(255,255,255,.06); + --card-shadow-hover:0 10px 32px rgba(0,212,255,.18), 0 4px 18px rgba(0,0,0,.40), inset 0 1px 0 rgba(255,255,255,.10); + + --topbar-bg:rgba(15,25,50,.40); + --console-bg:rgba(0,0,0,.40); + --surface-bg-solid:#0f1729; + + --font-sans:-apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif; + --font-mono:"SF Mono", "Monaco", "Menlo", "Ubuntu Mono", "Courier New", monospace; + + --maxw:1180px; + --ease:cubic-bezier(.22,.61,.36,1); +} + +*{box-sizing:border-box;margin:0;padding:0} +html{scroll-behavior:smooth} +body{font-family:var(--font-sans);background:var(--surface-solid);color:var(--text-primary); + line-height:1.6;overflow-x:hidden;-webkit-font-smoothing:antialiased;text-rendering:optimizeLegibility} + +/* ============ AURORA BACKDROP (from aurora-background.css) ============ */ +.cosmos{position:fixed;inset:0;z-index:-2;pointer-events:none;overflow:hidden; + background: + radial-gradient(ellipse at 20% 22%, var(--gradient-mid) 0%, transparent 55%), + radial-gradient(ellipse at 82% 74%, var(--gradient-to) 0%, transparent 55%), + linear-gradient(135deg, var(--gradient-from) 0%, var(--gradient-from) 40%, var(--gradient-mid) 100%);} +.cosmos::before{content:'';position:absolute;inset:-25%; + background:conic-gradient(from 0deg at 50% 50%, + rgba(var(--accent-rgb),0) 0deg, rgba(var(--accent-rgb),.18) 60deg, + rgba(var(--accent-rgb),.22) 130deg, rgba(var(--accent-rgb),.20) 200deg, + rgba(var(--accent-rgb),.18) 280deg, rgba(var(--accent-rgb),0) 360deg); + filter:blur(70px);animation:auroraSpin 38s linear infinite;} +.cosmos::after{content:'';position:absolute;inset:-10%; + background: + radial-gradient(circle at 18% 22%, rgba(var(--accent-rgb),.38) 0%, transparent 35%), + radial-gradient(circle at 78% 18%, rgba(var(--accent-rgb),.32) 0%, transparent 32%), + radial-gradient(circle at 30% 80%, rgba(var(--accent-rgb),.30) 0%, transparent 38%), + radial-gradient(circle at 84% 82%, rgba(var(--accent-rgb),.34) 0%, transparent 36%), + radial-gradient(circle at 50% 50%, rgba(var(--accent-rgb),.14) 0%, transparent 50%); + filter:blur(50px);animation:auroraDrift 22s ease-in-out infinite alternate;} +.stars{position:fixed;inset:0;z-index:-1;pointer-events:none} +.stars::before,.stars::after{content:'';position:absolute;inset:0;background-repeat:repeat} +.stars::before{background-image: + radial-gradient(1.5px 1.5px at 12px 18px, rgba(var(--text-rgb),.9), transparent 60%), + radial-gradient(1px 1px at 47px 92px, rgba(var(--accent-rgb),.85), transparent 60%), + radial-gradient(1.2px 1.2px at 110px 40px, rgba(var(--text-rgb),.75), transparent 60%), + radial-gradient(1px 1px at 165px 130px, rgba(var(--accent-rgb),.7), transparent 60%); + background-size:200px 200px;animation:auroraTwinkleA 4.5s ease-in-out infinite;} +.stars::after{background-image: + radial-gradient(1px 1px at 30px 60px, rgba(var(--accent-rgb),.8), transparent 60%), + radial-gradient(1.4px 1.4px at 88px 22px, rgba(var(--text-rgb),.7), transparent 60%), + radial-gradient(1px 1px at 140px 100px, rgba(var(--accent-rgb),.85), transparent 60%), + radial-gradient(1.2px 1.2px at 195px 70px, rgba(var(--text-rgb),.6), transparent 60%); + background-size:240px 240px;background-position:80px 50px;animation:auroraTwinkleB 6.5s ease-in-out infinite;} +@keyframes auroraSpin{to{transform:rotate(360deg)}} +@keyframes auroraDrift{0%{transform:translate(0,0) scale(1);opacity:.85}50%{transform:translate(-3%,4%) scale(1.08);opacity:1}100%{transform:translate(2%,-3%) scale(.95);opacity:.9}} +@keyframes auroraTwinkleA{0%,100%{opacity:.55}50%{opacity:1}} +@keyframes auroraTwinkleB{0%,100%{opacity:1}50%{opacity:.45}} + +/* ============ shared ============ */ +.grad{background:linear-gradient(100deg,#7ae9ff,#00d4ff 55%,#0aa6d6); + -webkit-background-clip:text;background-clip:text;-webkit-text-fill-color:transparent;color:transparent} +.wrap{max-width:var(--maxw);margin:0 auto;padding:0 clamp(18px,5vw,48px)} +section{position:relative;z-index:1} +.eyebrow{font-family:var(--font-mono);font-size:.74rem;letter-spacing:.22em;text-transform:uppercase;color:var(--accent);margin-bottom:16px;display:block} +h2{font-size:clamp(2rem,4.6vw,3.2rem);line-height:1.08;letter-spacing:-.02em;font-weight:800} +.lead{color:var(--text-secondary);font-size:1.08rem;max-width:54ch;margin-top:18px;font-weight:400} +.pad{padding:clamp(80px,12vh,140px) 0} +.glass{background:var(--card-bg);border:1px solid var(--card-border);box-shadow:var(--card-shadow); + backdrop-filter:blur(10px);-webkit-backdrop-filter:blur(10px)} + +/* ============ TOPBAR — grafted from the dashboard (topbar.css) ============ */ +.topbar{display:flex;justify-content:space-between;align-items:center;padding:0 24px;height:60px; + position:fixed;top:0;left:0;right:0;z-index:1000;background:var(--topbar-bg); + border-bottom:1px solid var(--border);backdrop-filter:blur(12px) saturate(140%);-webkit-backdrop-filter:blur(12px) saturate(140%)} +.mobile-menu-toggle{display:none;background:none;border:none;color:var(--text-primary);cursor:pointer;padding:6px;margin-right:4px} +.topbar-left{display:flex;align-items:center;flex:0 0 auto} +.libreportal-logo{display:flex;align-items:center;gap:10px;text-decoration:none;color:var(--text-primary);font-weight:700;font-size:1.05rem} +.libreportal-logo img{width:32px;height:32px} +.libreportal-logo b{color:var(--accent);font-weight:700} +.mobile-drawer{display:flex;align-items:center;flex:1;justify-content:space-between;gap:12px;min-width:0;margin-left:24px} +.topbar-nav{display:flex;gap:8px;align-items:center} +.topbar-nav .nav-item{background:rgba(var(--text-rgb),.10);border:1px solid rgba(var(--text-rgb),.20);border-radius:8px; + padding:10px 16px;font-size:14px;font-weight:500;color:var(--text-muted);text-decoration:none;display:flex;align-items:center;gap:8px; + transition:all .2s ease;cursor:pointer;min-height:42px;white-space:nowrap} +.topbar-nav .nav-item:hover{background:rgba(var(--text-rgb),.20);transform:translateY(-1px);color:var(--text-primary)} +.topbar-nav .nav-item.nav-active{background:var(--accent);color:var(--text-primary);border-color:var(--accent)} +.topbar-nav .nav-item.nav-active:hover{background:var(--accent-hover);border-color:var(--accent-hover)} +.topbar-nav .nav-item svg{width:16px;height:16px} +.topbar-controls{display:flex;align-items:center;gap:12px} +.tb-ghost{display:inline-flex;align-items:center;gap:6px;padding:8px 14px;border:1px solid var(--border);border-radius:8px; + background:rgba(var(--text-rgb),.05);color:var(--text-secondary);text-decoration:none;font-size:.85rem;font-weight:600;transition:all .2s} +.tb-ghost:hover{background:rgba(var(--text-rgb),.12);color:var(--text-primary)} +.tb-cta{display:inline-flex;align-items:center;gap:6px;padding:8px 18px;border-radius:8px;background:var(--accent); + color:var(--text-on-accent);text-decoration:none;font-size:.85rem;font-weight:700;transition:all .2s} +.tb-cta:hover{background:#22dbff;transform:translateY(-1px);box-shadow:0 8px 22px -8px rgba(var(--accent-rgb),.6)} +@media(max-width:768px){ + .topbar{padding:0 12px;gap:8px;justify-content:flex-start} + .mobile-menu-toggle{display:flex;align-items:center} + .mobile-drawer{position:fixed;top:60px;left:0;width:100vw;height:calc(100vh - 60px);flex-direction:column;align-items:stretch; + justify-content:flex-start;gap:0;padding:16px;margin-left:0;background:var(--surface-bg-solid);border-right:1px solid var(--border); + box-shadow:6px 0 24px rgba(0,0,0,.35);overflow-y:auto;transform:translateX(-100%);transition:transform .3s ease;z-index:101} + .mobile-drawer.mobile-open{transform:translateX(0)} + .mobile-drawer .topbar-nav{flex-direction:column;align-items:stretch;gap:6px;width:100%} + .mobile-drawer .topbar-nav .nav-item{width:100%;justify-content:flex-start} + .mobile-drawer .topbar-controls{flex-direction:column;align-items:stretch;gap:8px;width:100%;margin-top:auto;padding-top:16px; + border-top:1px solid rgba(var(--text-rgb),.12)} + .mobile-drawer .topbar-controls a{width:100%;justify-content:center} +} + +/* ============ hero ============ */ +header.hero{min-height:100vh;display:flex;flex-direction:column;justify-content:center;align-items:center; + text-align:center;padding:140px clamp(18px,5vw,48px) 90px;position:relative} +.badge{display:inline-flex;align-items:center;gap:8px;font-family:var(--font-mono);font-size:.72rem; + letter-spacing:.14em;text-transform:uppercase;color:var(--accent);padding:7px 15px;border-radius:999px; + border:1px solid rgba(var(--accent-rgb),.35);background:var(--accent-soft);margin-bottom:30px} +.badge .dot{width:6px;height:6px;border-radius:50%;background:var(--accent);box-shadow:0 0 10px var(--accent);animation:pulse 2.4s ease-in-out infinite} +@keyframes pulse{0%,100%{opacity:.4}50%{opacity:1}} +.hero-logo{width:64px;height:64px;margin-bottom:22px;filter:drop-shadow(0 6px 24px rgba(var(--accent-rgb),.45))} +h1{font-size:clamp(2.6rem,7vw,5.2rem);line-height:1.03;letter-spacing:-.03em;font-weight:800;max-width:15ch} +.sub{margin:26px auto 0;max-width:50ch;font-size:clamp(1.02rem,2vw,1.2rem);color:var(--text-secondary);font-weight:400} + +/* install portal */ +.portal{margin:46px auto 0;width:min(660px,100%);position:relative} +.portal::before{content:"";position:absolute;inset:-26px;border-radius:28px;z-index:-1; + background:radial-gradient(60% 80% at 50% 50%, rgba(var(--accent-rgb),.5), transparent 70%);filter:blur(22px);animation:breathe 6s ease-in-out infinite} +@keyframes breathe{0%,100%{opacity:.45;transform:scale(.98)}50%{opacity:.8;transform:scale(1.03)}} +.term{border:1px solid var(--card-border);border-radius:16px;overflow:hidden; + background:linear-gradient(155deg, rgba(255,255,255,.10) 0%, rgba(0,212,255,.06) 100%); + backdrop-filter:blur(12px);-webkit-backdrop-filter:blur(12px);box-shadow:var(--card-shadow-hover)} +.term-bar{display:flex;align-items:center;gap:7px;padding:12px 16px;border-bottom:1px solid var(--border-subtle);background:var(--console-bg)} +.term-bar i{width:11px;height:11px;border-radius:50%;display:block} +.term-bar i:nth-child(1){background:#ff6b5e}.term-bar i:nth-child(2){background:#ffc107}.term-bar i:nth-child(3){background:var(--accent)} +.term-bar em{margin-left:auto;font-family:var(--font-mono);font-size:.72rem;color:var(--text-faint);font-style:normal} +.term-body{display:flex;align-items:center;gap:14px;padding:22px 20px} +.term-body code{font-family:var(--font-mono);font-size:clamp(.82rem,2.3vw,1.02rem);color:var(--text-primary); + white-space:nowrap;overflow-x:auto;flex:1;text-align:left;scrollbar-width:none} +.term-body code::-webkit-scrollbar{display:none} +.term-body code .p{color:var(--accent)} +.term-body code .u{color:#e6f3ff} +.copy{flex:0 0 auto;display:inline-flex;align-items:center;gap:7px;font-family:var(--font-sans);font-weight:700; + font-size:.84rem;color:var(--text-on-accent);background:var(--accent);border:none;border-radius:9px;padding:9px 15px;cursor:pointer; + transition:transform .15s,background .25s,box-shadow .25s} +.copy:hover{transform:translateY(-1px);background:#22dbff;box-shadow:0 8px 22px -8px rgba(var(--accent-rgb),.7)} +.copy.done{background:#28a745;color:#fff} +.portal-foot{display:flex;align-items:center;justify-content:center;gap:16px;margin-top:16px;flex-wrap:wrap;font-size:.86rem;color:var(--text-faint)} +.portal-foot a{color:var(--text-secondary);text-decoration:none;border-bottom:1px dashed rgba(255,255,255,.3);transition:color .2s,border-color .2s} +.portal-foot a:hover{color:var(--accent);border-color:var(--accent)} +.hint{font-family:var(--font-mono);font-size:.78rem} +.scroll-cue{position:absolute;bottom:28px;left:50%;transform:translateX(-50%);color:var(--text-faint); + font-family:var(--font-mono);font-size:.66rem;letter-spacing:.2em;text-transform:uppercase; + display:flex;flex-direction:column;align-items:center;gap:8px;animation:bob 2.6s ease-in-out infinite} +.scroll-cue::after{content:"";width:1px;height:34px;background:linear-gradient(var(--text-faint),transparent)} +@keyframes bob{0%,100%{transform:translateX(-50%) translateY(0)}50%{transform:translateX(-50%) translateY(8px)}} + +/* ============ features ============ */ +.feat-grid{display:grid;grid-template-columns:repeat(3,1fr);gap:18px;margin-top:54px} +.card{border-radius:16px;padding:28px 26px;position:relative;overflow:hidden;transition:transform .4s var(--ease),border-color .4s,box-shadow .4s} +.card:hover{transform:translateY(-6px);border-color:rgba(var(--accent-rgb),.45);box-shadow:var(--card-shadow-hover)} +.card .ico{width:44px;height:44px;border-radius:12px;display:grid;place-items:center;margin-bottom:18px; + background:var(--accent-soft);border:1px solid rgba(var(--accent-rgb),.30)} +.card .ico svg{width:22px;height:22px;stroke:var(--accent);fill:none;stroke-width:1.6} +.card h3{font-size:1.24rem;margin-bottom:8px;font-weight:700;letter-spacing:-.01em} +.card p{color:var(--text-secondary);font-size:.96rem} + +/* ============ apps (data-driven) ============ */ +.apps-section{text-align:center} +.app-filters{display:flex;flex-wrap:wrap;justify-content:center;gap:10px;margin-top:40px} +.chip{font-family:var(--font-mono);font-size:.76rem;letter-spacing:.04em;padding:8px 15px;border-radius:999px; + border:1px solid var(--border);background:rgba(255,255,255,.05);color:var(--text-secondary);cursor:pointer;transition:all .2s} +.chip:hover{color:var(--text-primary);border-color:rgba(var(--accent-rgb),.45)} +.chip.active{background:var(--accent);color:var(--text-on-accent);border-color:var(--accent);font-weight:600} +.chip .n{opacity:.55;margin-left:6px} +.app-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(248px,1fr));gap:16px;margin-top:34px;text-align:left} +.app-card{border-radius:14px;padding:20px;display:flex;flex-direction:column;gap:11px;transition:transform .3s var(--ease),border-color .3s,box-shadow .3s} +.app-card.hidden{display:none} +.app-card:hover{transform:translateY(-4px);border-color:rgba(var(--accent-rgb),.4);box-shadow:var(--card-shadow-hover)} +.app-card-head{display:flex;align-items:center;gap:12px} +.app-ico{width:42px;height:42px;border-radius:10px;background:rgba(255,255,255,.06);padding:6px;object-fit:contain;border:1px solid var(--border-subtle);flex:0 0 auto} +.app-card h3{font-size:1.06rem;font-weight:700;line-height:1.2} +.app-cat{font-family:var(--font-mono);font-size:.64rem;letter-spacing:.1em;text-transform:uppercase;color:var(--accent)} +.app-card p{font-size:.9rem;color:var(--text-secondary);margin:0} +.app-count{margin-top:26px;color:var(--text-faint);font-size:.85rem;font-family:var(--font-mono)} + +/* ============ connect / courier ============ */ +.connect{display:grid;grid-template-columns:1.05fr .95fr;gap:60px;align-items:center} +.pill{display:inline-flex;align-items:center;gap:8px;font-family:var(--font-mono);font-size:.72rem; + letter-spacing:.1em;text-transform:uppercase;color:var(--text-secondary);padding:7px 14px;border-radius:999px; + border:1px solid var(--border);background:rgba(255,255,255,.04);margin-top:24px} +.connect ul{list-style:none;margin-top:26px;display:flex;flex-direction:column;gap:15px} +.connect li{display:flex;gap:13px;color:var(--text-secondary);font-size:1rem} +.connect li svg{flex:0 0 auto;width:21px;height:21px;stroke:var(--accent);fill:none;stroke-width:1.6;margin-top:3px} +.connect li b{color:var(--text-primary);font-weight:700} +.vault{position:relative;aspect-ratio:1;display:grid;place-items:center} +.vault svg{width:min(360px,82%);height:auto;overflow:visible} +.orbit{transform-origin:center;animation:spin 26s linear infinite} +@keyframes spin{to{transform:rotate(360deg)}} + +/* ============ promise ============ */ +.promise{text-align:center} +.pledges{display:grid;grid-template-columns:repeat(4,1fr);gap:16px;margin-top:52px} +.pledge{border-radius:14px;padding:28px 18px} +.pledge .big{font-size:2.4rem;font-weight:800;margin-bottom:8px;line-height:1} +.pledge p{font-size:.92rem;color:var(--text-secondary)} +.promise .link{display:inline-block;margin-top:40px;color:var(--accent);text-decoration:none;font-weight:700; + border-bottom:1px solid rgba(var(--accent-rgb),.45);padding-bottom:2px;transition:color .2s,border-color .2s} +.promise .link:hover{color:#fff;border-color:#fff} + +/* ============ final ============ */ +.final{text-align:center;padding:clamp(90px,14vh,160px) 0} +.final h2{max-width:18ch;margin-inline:auto} +.final .portal{margin-top:42px} + +/* ============ footer ============ */ +footer{border-top:1px solid var(--border-subtle);padding:54px 0 70px;margin-top:40px;background:rgba(10,16,32,.55);backdrop-filter:blur(8px)} +.foot-grid{display:flex;justify-content:space-between;gap:40px;flex-wrap:wrap;align-items:flex-start} +.foot-grid .libreportal-logo{margin-bottom:14px} +.foot-col h4{font-size:.74rem;letter-spacing:.16em;text-transform:uppercase;color:var(--text-faint);margin-bottom:14px;font-family:var(--font-mono)} +.foot-col a{display:block;color:var(--text-secondary);text-decoration:none;font-size:.95rem;margin-bottom:9px;transition:color .2s} +.foot-col a:hover{color:var(--accent)} +.colophon{margin-top:46px;padding-top:26px;border-top:1px solid var(--border-subtle); + display:flex;justify-content:space-between;gap:18px;flex-wrap:wrap;color:var(--text-faint);font-size:.86rem} +.colophon a{color:var(--text-secondary);text-decoration:none} +.colophon a:hover{color:var(--accent)} + +/* ============ reveal ============ */ +.reveal{opacity:0;transform:translateY(28px);transition:opacity .9s var(--ease),transform .9s var(--ease)} +.reveal.in{opacity:1;transform:none} +.stagger>*{opacity:0;transform:translateY(20px);transition:opacity .7s var(--ease),transform .7s var(--ease)} +.stagger.in>*{opacity:1;transform:none} +.stagger.in>*:nth-child(2){transition-delay:.07s}.stagger.in>*:nth-child(3){transition-delay:.14s} +.stagger.in>*:nth-child(4){transition-delay:.21s}.stagger.in>*:nth-child(5){transition-delay:.28s}.stagger.in>*:nth-child(6){transition-delay:.35s} +.h-anim{opacity:0;transform:translateY(24px);animation:rise .95s var(--ease) forwards} +.d1{animation-delay:.05s}.d2{animation-delay:.16s}.d3{animation-delay:.28s}.d4{animation-delay:.4s}.d5{animation-delay:.52s} +@keyframes rise{to{opacity:1;transform:none}} + +/* ============ responsive ============ */ +@media(max-width:860px){ + .feat-grid{grid-template-columns:1fr 1fr} + .connect{grid-template-columns:1fr;gap:36px} + .vault{order:-1;max-width:320px;margin-inline:auto} + .pledges{grid-template-columns:1fr 1fr} +} +@media(max-width:540px){ + .feat-grid{grid-template-columns:1fr} + .term-body{flex-direction:column;align-items:stretch} + .copy{justify-content:center} +} +@media(prefers-reduced-motion:reduce){ + *{animation:none!important;transition:none!important} + .reveal,.stagger>*,.h-anim{opacity:1;transform:none} +} diff --git a/site/src/index.njk b/site/src/index.njk new file mode 100644 index 0000000..8815eef --- /dev/null +++ b/site/src/index.njk @@ -0,0 +1,162 @@ +--- +layout: layout.njk +--- + +
+ {{ site.version }} · early days, no telemetry ever + +

Your own private corner
of the universe

+

Self-host the apps you actually rely on — on your own server. + One command brings up a whole platform, and your data never leaves your orbit.

+ +
+
+
~ one command, your whole server
+
+ bash <(curl -fsSL {{ site.installUrl }}) init + +
+
+
+ curl·to·shell, on a privacy tool? read it first → + · + or clone the repo +
+
+ +
explore
+
+ + +
+ What comes online +

A whole platform, handled for you

+

No yak-shaving. LibrePortal wires up the boring, fiddly infrastructure so you can run the good stuff.

+ +
+
+
+

One-click apps

+

Nextcloud, Vaultwarden, Jellyfin, Gitea and dozens more — picked from a menu, deployed clean.

+
+
+
+

Traefik + auto-SSL

+

A reverse proxy with automatic Let's Encrypt certificates. HTTPS everywhere, configured for you.

+
+
+
+

Rootless Docker

+

Containers run without root and with sane security defaults — CrowdSec on guard out of the box.

+
+
+
+

A real dashboard

+

Install, configure, back up and monitor everything from a clean web UI. No SSH archaeology.

+
+
+
+

Optional VPN routing

+

Send any app's traffic through gluetun. Keep the things that should be private, private.

+
+
+
+

No telemetry

+

Nothing phones home. No accounts required to self-host. The software is yours, entirely.

+
+
+
+ + +
+ The constellation +

Dozens of apps, one sky

+

Pulled straight from the repo — {{ apps.length }} apps you can light up from the dashboard, and switch off just as easily.

+ +
+ + {%- for c in categories %} + + {%- endfor %} +
+ +
+ {%- for app in apps %} + {% include "app-card.njk" %} + {%- endfor %} +
+

{{ apps.length }} apps

+
+ + +
+
+
+ LibrePortal Connect · optional +

We're the courier.
You hold the key.

+

Reaching your server from your phone and keeping off-site backups are the fiddly bits. + Connect handles them — but works like a courier carrying a sealed box.

+
    +
  • Reach it anywhere. No port-forwarding, no exposing your box to the internet.
  • +
  • Encrypted off-site backups. We only ever hold a locked box — you keep the only key.
  • +
  • We never run your apps. Your data and keys never leave your machine.
  • +
+ every part is free to self-host, too — you pay only for convenience +
+
+ +
+
+
+ + +
+ The promise +

Free software, and it stays that way

+

In plain language, so you can hold us to it.

+
+
100%

Every feature, free to self-host. Forever. No crippled edition.

+
0

Trackers. No telemetry, no phoning home, no accounts to run it.

+

Feature paywalls. You never pay to unlock — only for convenience.

+

What's open stays open. No rug-pulls, ever. AGPLv3.

+
+ Read the full Promise → +
+ + +
+ Ready when you are +

Light up your corner
of the universe.

+
+
+
~
+
+ bash <(curl -fsSL {{ site.installUrl }}) init + +
+
+
+