redirecter for ao3 that adds opengraph metadata

Compare changes

Choose any two refs to compare.

Changed files
+1641 -284
cypress
src
app
api
series
[seriesId]
preview
works
[workId]
chapters
[chapterId]
preview
generator
locked
series
[seriesId]
works
[workId]
icons
lib
+345 -34
bun.lock
···
"@fontsource-variable/bricolage-grotesque": "^5.2.10",
"@fontsource/stack-sans-notch": "^5.2.1",
"@fujocoded/ao3.js": "^0.22.1",
-
"@hono/vite-build": "^1.7.0",
"@hono/vite-dev-server": "^0.23.0",
-
"bun": "^1.3.2",
"fauxdom": "^1.2.2",
-
"hono": "^4.10.4",
-
"next": "16.0.2",
"react": "19.2.0",
"react-dom": "19.2.0",
},
"devDependencies": {
"@biomejs/biome": "2.2.0",
"@types/bun": "latest",
},
},
},
···
"@biomejs/cli-win32-x64": ["@biomejs/cli-win32-x64@2.2.0", "", { "os": "win32", "cpu": "x64" }, "sha512-Nawu5nHjP/zPKTIryh2AavzTc/KEg4um/MxWdXW0A6P/RZOyIpa7+QSjeXwAwX/utJGaCoXRPWtF3m5U/bB3Ww=="],
"@emnapi/runtime": ["@emnapi/runtime@1.7.0", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-oAYoQnCYaQZKVS53Fq23ceWMRxq5EhQsE0x0RdQ55jT7wagMu5k+fS39v1fiSLrtrLQlXwVINenqhLMtTrV/1Q=="],
"@fontsource-variable/bricolage-grotesque": ["@fontsource-variable/bricolage-grotesque@5.2.10", "", {}, "sha512-5EDsCqgGpKVcJWE4sg9ydli+t5WM97mISYw5lla/Ev4z71FwXh1oN0YUU8xjkRW9+wBCGD9R+ntAvI8G4bUFJg=="],
···
"@hono/node-server": ["@hono/node-server@1.19.6", "", { "peerDependencies": { "hono": "^4" } }, "sha512-Shz/KjlIeAhfiuE93NDKVdZ7HdBVLQAfdbaXEaoAVO3ic9ibRSLGIQGkcBbFyuLr+7/1D5ZCINM8B+6IvXeMtw=="],
-
"@hono/vite-build": ["@hono/vite-build@1.7.0", "", { "peerDependencies": { "hono": "*" } }, "sha512-L73WBed5teC7DHTzXYkho83POYYluD2rTkbT76FJuqpfXPgTW/PsbIa8O0YcCETE3VUoh584fe6vuLAM5ctjww=="],
"@hono/vite-dev-server": ["@hono/vite-dev-server@0.23.0", "", { "dependencies": { "@hono/node-server": "^1.14.2", "minimatch": "^9.0.3" }, "peerDependencies": { "hono": "*", "miniflare": "*", "wrangler": "*" }, "optionalPeers": ["miniflare", "wrangler"] }, "sha512-tHV86xToed9Up0j/dubQW2PDP4aYNFDSfQrjcV6Ra7bqCGrxhtg/zZBmbgSco3aTxKOEPzDXKK+6seAAfsbIXw=="],
···
"@img/sharp-win32-x64": ["@img/sharp-win32-x64@0.34.5", "", { "os": "win32", "cpu": "x64" }, "sha512-+29YMsqY2/9eFEiW93eqWnuLcWcufowXewwSNIT6UwZdUUCrM3oFjMWH/Z6/TMmb4hlFenmfAVbpWeup2jryCw=="],
-
"@next/env": ["@next/env@16.0.2", "", {}, "sha512-V2e9ITU6Ts9kxtTBX60qtWlKV+AeBNlz/hgAt0gkGA8aPgX27cRLjp7OEUMzYq4cY0QzOkOQ4CI/8IJh6kW/iw=="],
-
"@next/swc-darwin-arm64": ["@next/swc-darwin-arm64@16.0.2", "", { "os": "darwin", "cpu": "arm64" }, "sha512-E6rxUdkZX5sZjLduXphiMuRJAmvsxWi5IivD0kRLLX5cjNLOs2PjlSyda+dtT3iqE6vxaRGV3oQMnQiJU8F+Ig=="],
-
"@next/swc-darwin-x64": ["@next/swc-darwin-x64@16.0.2", "", { "os": "darwin", "cpu": "x64" }, "sha512-QNXdjXVFtb35vImDJtXqYlhq8A2mHLroqD8q4WCwO+IVnVoQshhcEVWJlP9UB/dOC6Wh782BbTHqGzKQwlCSkQ=="],
-
"@next/swc-linux-arm64-gnu": ["@next/swc-linux-arm64-gnu@16.0.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-dM9yEB35GZAW3r+w88iGEz7OkJjSYSd4pKyl4KwSXx8cLWMpWaX1WW42dCAKXCWWQhVUXUZAEx38yfpEZ1/IJg=="],
-
"@next/swc-linux-arm64-musl": ["@next/swc-linux-arm64-musl@16.0.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-hiNysPK1VeK5MGNmuKLnj3Y4lkaffvAlXin404QpxYkNCBms/Bk0msZHey5lUNq8FV50PY6I9CgY+c/NK+xeLg=="],
-
"@next/swc-linux-x64-gnu": ["@next/swc-linux-x64-gnu@16.0.2", "", { "os": "linux", "cpu": "x64" }, "sha512-hAhhobw4tHOCzZ5sm5W/EsQPxS3NbZl6rqzmA0GTV9etE8sPHmsV6OopP12TeeoXA/NjXKD2mcz8hcVWLe4jkg=="],
-
"@next/swc-linux-x64-musl": ["@next/swc-linux-x64-musl@16.0.2", "", { "os": "linux", "cpu": "x64" }, "sha512-s0LUsoeRky95aTS6IfYnJOn6F5kbs+gjiVUQK0JmsJ/ZCXaply20kDoJ8/zHwMz5cyOVg7GrQJdMvyO9FLD9Bw=="],
-
"@next/swc-win32-arm64-msvc": ["@next/swc-win32-arm64-msvc@16.0.2", "", { "os": "win32", "cpu": "arm64" }, "sha512-TMWE1h44d0WRyq0yQI/0W5A7nZUoiwE2Sdg43wt2Q1IoadU5Ky00G3cJ2mSnbetwL7+eFyM7BQgx+Fonpz6T8w=="],
-
"@next/swc-win32-x64-msvc": ["@next/swc-win32-x64-msvc@16.0.2", "", { "os": "win32", "cpu": "x64" }, "sha512-+8SqzDhau/PNsWdcagnoz6ltOM9IcsqagdTFsEELNOty0+lNh5hwO5oUFForPOywTbM+d3tPLo5m20VdEBDf3Q=="],
-
"@oven/bun-darwin-aarch64": ["@oven/bun-darwin-aarch64@1.3.2", "", { "os": "darwin", "cpu": "arm64" }, "sha512-licBDIbbLP5L5/S0+bwtJynso94XD3KyqSP48K59Sq7Mude6C7dR5ZujZm4Ut4BwZqUFfNOfYNMWBU5nlL7t1A=="],
-
"@oven/bun-darwin-x64": ["@oven/bun-darwin-x64@1.3.2", "", { "os": "darwin", "cpu": "x64" }, "sha512-hn8lLzsYyyh6ULo2E8v2SqtrWOkdQKJwapeVy1rDw7juTTeHY3KDudGWf4mVYteC9riZU6HD88Fn3nGwyX0eIg=="],
-
"@oven/bun-darwin-x64-baseline": ["@oven/bun-darwin-x64-baseline@1.3.2", "", { "os": "darwin", "cpu": "x64" }, "sha512-UHxdtbyxdtNJUNcXtIrjx3Lmq8ji3KywlXtIHV/0vn9A8W5mulqOcryqUWMFVH9JTIIzmNn6Q/qVmXHTME63Ww=="],
-
"@oven/bun-linux-aarch64": ["@oven/bun-linux-aarch64@1.3.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-5uZzxzvHU/z+3cZwN/A0H8G+enQ+9FkeJVZkE2fwK2XhiJZFUGAuWajCpy7GepvOWlqV7VjPaKi2+Qmr4IX7nQ=="],
-
"@oven/bun-linux-aarch64-musl": ["@oven/bun-linux-aarch64-musl@1.3.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-OD9DYkjes7WXieBn4zQZGXWhRVZhIEWMDGCetZ3H4vxIuweZ++iul/CNX5jdpNXaJ17myb1ROMvmRbrqW44j3w=="],
-
"@oven/bun-linux-x64": ["@oven/bun-linux-x64@1.3.2", "", { "os": "linux", "cpu": "x64" }, "sha512-EoEuRP9bxAxVKuvi6tZ0ZENjueP4lvjz0mKsMzdG0kwg/2apGKiirH1l0RIcdmvfDGGuDmNiv/XBpkoXq1x8ug=="],
-
"@oven/bun-linux-x64-baseline": ["@oven/bun-linux-x64-baseline@1.3.2", "", { "os": "linux", "cpu": "x64" }, "sha512-m9Ov9YH8KjRLui87eNtQQFKVnjGsNk3xgbrR9c8d2FS3NfZSxmVjSeBvEsDjzNf1TXLDriHb/NYOlpiMf/QzDg=="],
-
"@oven/bun-linux-x64-musl": ["@oven/bun-linux-x64-musl@1.3.2", "", { "os": "linux", "cpu": "x64" }, "sha512-3TuOsRVoG8K+soQWRo+Cp5ACpRs6rTFSu5tAqc/6WrqwbNWmqjov/eWJPTgz3gPXnC7uNKVG7RxxAmV8r2EYTQ=="],
-
"@oven/bun-linux-x64-musl-baseline": ["@oven/bun-linux-x64-musl-baseline@1.3.2", "", { "os": "linux", "cpu": "x64" }, "sha512-q8Hto8hcpofPJjvuvjuwyYvhOaAzPw1F5vRUUeOJDmDwZ4lZhANFM0rUwchMzfWUJCD6jg8/EVQ8MiixnZWU0A=="],
-
"@oven/bun-windows-x64": ["@oven/bun-windows-x64@1.3.2", "", { "os": "win32", "cpu": "x64" }, "sha512-nZJUa5NprPYQ4Ii4cMwtP9PzlJJTp1XhxJ+A9eSn1Jfr6YygVWyN2KLjenyI93IcuBouBAaepDAVZZjH2lFBhg=="],
-
"@oven/bun-windows-x64-baseline": ["@oven/bun-windows-x64-baseline@1.3.2", "", { "os": "win32", "cpu": "x64" }, "sha512-s00T99MjB+xLOWq+t+wVaVBrry+oBOZNiTJijt+bmkp/MJptYS3FGvs7a+nkjLNzoNDoWQcXgKew6AaHES37Bg=="],
-
"@swc/helpers": ["@swc/helpers@0.5.15", "", { "dependencies": { "tslib": "^2.8.0" } }, "sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g=="],
-
"@types/bun": ["@types/bun@1.3.2", "", { "dependencies": { "bun-types": "1.3.2" } }, "sha512-t15P7k5UIgHKkxwnMNkJbWlh/617rkDGEdSsDbu+qNHTaz9SKf7aC8fiIlUdD5RPpH6GEkP0cK7WlvmrEBRtWg=="],
-
"@types/node": ["@types/node@24.10.1", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ=="],
-
"@types/react": ["@types/react@19.2.4", "", { "dependencies": { "csstype": "^3.0.2" } }, "sha512-tBFxBp9Nfyy5rsmefN+WXc1JeW/j2BpBHFdLZbEVfs9wn3E3NRFxwV0pJg8M1qQAexFpvz73hJXFofV0ZAu92A=="],
"balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="],
"boolbase": ["boolbase@1.0.0", "", {}, "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww=="],
"brace-expansion": ["brace-expansion@2.0.2", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ=="],
-
"bun": ["bun@1.3.2", "", { "optionalDependencies": { "@oven/bun-darwin-aarch64": "1.3.2", "@oven/bun-darwin-x64": "1.3.2", "@oven/bun-darwin-x64-baseline": "1.3.2", "@oven/bun-linux-aarch64": "1.3.2", "@oven/bun-linux-aarch64-musl": "1.3.2", "@oven/bun-linux-x64": "1.3.2", "@oven/bun-linux-x64-baseline": "1.3.2", "@oven/bun-linux-x64-musl": "1.3.2", "@oven/bun-linux-x64-musl-baseline": "1.3.2", "@oven/bun-windows-x64": "1.3.2", "@oven/bun-windows-x64-baseline": "1.3.2" }, "os": [ "linux", "win32", "darwin", ], "cpu": [ "x64", "arm64", ], "bin": { "bun": "bin/bun.exe", "bunx": "bin/bunx.exe" } }, "sha512-x75mPJiEfhO1j4Tfc65+PtW6ZyrAB6yTZInydnjDZXF9u9PRAnr6OK3v0Q9dpDl0dxRHkXlYvJ8tteJxc8t4Sw=="],
-
"bun-types": ["bun-types@1.3.2", "", { "dependencies": { "@types/node": "*" }, "peerDependencies": { "@types/react": "^19" } }, "sha512-i/Gln4tbzKNuxP70OWhJRZz1MRfvqExowP7U6JKoI8cntFrtxg7RJK3jvz7wQW54UuvNC8tbKHHri5fy74FVqg=="],
"caniuse-lite": ["caniuse-lite@1.0.30001754", "", {}, "sha512-x6OeBXueoAceOmotzx3PO4Zpt4rzpeIFsSr6AAePTZxSkXiYDUmpypEl7e2+8NCd9bD7bXjqyef8CJYPC1jfxg=="],
"cheerio": ["cheerio@1.1.2", "", { "dependencies": { "cheerio-select": "^2.1.0", "dom-serializer": "^2.0.0", "domhandler": "^5.0.3", "domutils": "^3.2.2", "encoding-sniffer": "^0.2.1", "htmlparser2": "^10.0.0", "parse5": "^7.3.0", "parse5-htmlparser2-tree-adapter": "^7.1.0", "parse5-parser-stream": "^7.1.2", "undici": "^7.12.0", "whatwg-mimetype": "^4.0.0" } }, "sha512-IkxPpb5rS/d1IiLbHMgfPuS0FgiWTtFIm/Nj+2woXDLTZ7fOT2eqzgYbdMlLweqlHbsZjxEChoVK+7iph7jyQg=="],
"cheerio-select": ["cheerio-select@2.1.0", "", { "dependencies": { "boolbase": "^1.0.0", "css-select": "^5.1.0", "css-what": "^6.1.0", "domelementtype": "^2.3.0", "domhandler": "^5.0.3", "domutils": "^3.0.1" } }, "sha512-9v9kG0LvzrlcungtnJtpGNxY+fzECQKhK4EGJX2vByejiMX84MFNQw4UxPJl3bFbTMw+Dfs37XaIkCwTZfLh4g=="],
"client-only": ["client-only@0.0.1", "", {}, "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA=="],
"css-select": ["css-select@5.2.2", "", { "dependencies": { "boolbase": "^1.0.0", "css-what": "^6.1.0", "domhandler": "^5.0.2", "domutils": "^3.0.1", "nth-check": "^2.0.1" } }, "sha512-TizTzUddG/xYLA3NXodFM0fSbNizXjOKhqiQQwvhlspadZokn1KDy0NZFS0wuEubIYAV5/c1/lAr0TaaFXEXzw=="],
"css-what": ["css-what@6.2.2", "", {}, "sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA=="],
-
"csstype": ["csstype@3.1.3", "", {}, "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw=="],
"debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="],
"detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="],
···
"domutils": ["domutils@3.2.2", "", { "dependencies": { "dom-serializer": "^2.0.0", "domelementtype": "^2.3.0", "domhandler": "^5.0.3" } }, "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw=="],
"encoding-sniffer": ["encoding-sniffer@0.2.1", "", { "dependencies": { "iconv-lite": "^0.6.3", "whatwg-encoding": "^3.1.1" } }, "sha512-5gvq20T6vfpekVtqrYQsSCFZ1wEg5+wW0/QaZMWkFr6BqD3NfKs0rLCx4rrVlSWJeZb5NBJgVLswK/w2MWU+Gw=="],
"entities": ["entities@4.5.0", "", {}, "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw=="],
"fauxdom": ["fauxdom@1.2.2", "", {}, "sha512-Xoj9VPhIx9p9wU1CncWaKqgm5e+lnTqi5nYsQIfALEUKu++99Pj/BMkggH6mYjWaBeVWBxeV8xZTJauTXGiUDw=="],
"globrex": ["globrex@0.1.2", "", {}, "sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg=="],
-
"hono": ["hono@4.10.5", "", {}, "sha512-h/MXuTkoAK8NG1EfDp0jI1YLf6yGdDnfkebRO2pwEh5+hE3RAJFXkCsnD0vamSiARK4ZrB6MY+o3E/hCnOyHrQ=="],
"htmlparser2": ["htmlparser2@10.0.0", "", { "dependencies": { "domelementtype": "^2.3.0", "domhandler": "^5.0.3", "domutils": "^3.2.1", "entities": "^6.0.0" } }, "sha512-TwAZM+zE5Tq3lrEHvOlvwgj1XLWQCtaaibSN11Q+gGBAS7Y1uZSWwXXRe4iF6OXnaq1riyQAPFOBtYc77Mxq0g=="],
"iconv-lite": ["iconv-lite@0.6.3", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw=="],
"minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="],
"ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="],
"nanoid": ["nanoid@3.3.11", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w=="],
-
"next": ["next@16.0.2", "", { "dependencies": { "@next/env": "16.0.2", "@swc/helpers": "0.5.15", "caniuse-lite": "^1.0.30001579", "postcss": "8.4.31", "styled-jsx": "5.1.6" }, "optionalDependencies": { "@next/swc-darwin-arm64": "16.0.2", "@next/swc-darwin-x64": "16.0.2", "@next/swc-linux-arm64-gnu": "16.0.2", "@next/swc-linux-arm64-musl": "16.0.2", "@next/swc-linux-x64-gnu": "16.0.2", "@next/swc-linux-x64-musl": "16.0.2", "@next/swc-win32-arm64-msvc": "16.0.2", "@next/swc-win32-x64-msvc": "16.0.2", "sharp": "^0.34.4" }, "peerDependencies": { "@opentelemetry/api": "^1.1.0", "@playwright/test": "^1.51.1", "babel-plugin-react-compiler": "*", "react": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", "react-dom": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", "sass": "^1.3.0" }, "optionalPeers": ["@opentelemetry/api", "@playwright/test", "babel-plugin-react-compiler", "sass"], "bin": { "next": "dist/bin/next" } }, "sha512-zL8+UBf+xUIm8zF0vYGJYJMYDqwaBrRRe7S0Kob6zo9Kf+BdqFLEECMI+B6cNIcoQ+el9XM2fvUExwhdDnXjtw=="],
"nth-check": ["nth-check@2.1.1", "", { "dependencies": { "boolbase": "^1.0.0" } }, "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w=="],
"parse5": ["parse5@7.3.0", "", { "dependencies": { "entities": "^6.0.0" } }, "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw=="],
"parse5-htmlparser2-tree-adapter": ["parse5-htmlparser2-tree-adapter@7.1.0", "", { "dependencies": { "domhandler": "^5.0.3", "parse5": "^7.0.0" } }, "sha512-ruw5xyKs6lrpo9x9rCZqZZnIUntICjQAd0Wsmp396Ul9lN/h+ifgVV1x1gZHi8euej6wTfpqX8j+BFQxF0NS/g=="],
"parse5-parser-stream": ["parse5-parser-stream@7.1.2", "", { "dependencies": { "parse5": "^7.0.0" } }, "sha512-JyeQc9iwFLn5TbvvqACIF/VXG6abODeB3Fwmv/TGdLk2LfbWkaySGY72at4+Ty7EkPZj854u4CrICqNk2qIbow=="],
"picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="],
"postcss": ["postcss@8.4.31", "", { "dependencies": { "nanoid": "^3.3.6", "picocolors": "^1.0.0", "source-map-js": "^1.0.2" } }, "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ=="],
"react": ["react@19.2.0", "", {}, "sha512-tmbWg6W31tQLeB5cdIBOicJDJRR2KzXsV7uSK9iNfLWQ5bIZfxuPEHp7M8wiHyHnn0DD1i7w3Zmin0FtkrwoCQ=="],
"react-dom": ["react-dom@19.2.0", "", { "dependencies": { "scheduler": "^0.27.0" }, "peerDependencies": { "react": "^19.2.0" } }, "sha512-UlbRu4cAiGaIewkPyiRGJk0imDN2T3JjieT6spoL2UeSf5od4n5LB/mQ4ejmxhCFT1tYe8IvaFulzynWovsEFQ=="],
"safer-buffer": ["safer-buffer@2.1.2", "", {}, "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="],
"scheduler": ["scheduler@0.27.0", "", {}, "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q=="],
···
"sharp": ["sharp@0.34.5", "", { "dependencies": { "@img/colour": "^1.0.0", "detect-libc": "^2.1.2", "semver": "^7.7.3" }, "optionalDependencies": { "@img/sharp-darwin-arm64": "0.34.5", "@img/sharp-darwin-x64": "0.34.5", "@img/sharp-libvips-darwin-arm64": "1.2.4", "@img/sharp-libvips-darwin-x64": "1.2.4", "@img/sharp-libvips-linux-arm": "1.2.4", "@img/sharp-libvips-linux-arm64": "1.2.4", "@img/sharp-libvips-linux-ppc64": "1.2.4", "@img/sharp-libvips-linux-riscv64": "1.2.4", "@img/sharp-libvips-linux-s390x": "1.2.4", "@img/sharp-libvips-linux-x64": "1.2.4", "@img/sharp-libvips-linuxmusl-arm64": "1.2.4", "@img/sharp-libvips-linuxmusl-x64": "1.2.4", "@img/sharp-linux-arm": "0.34.5", "@img/sharp-linux-arm64": "0.34.5", "@img/sharp-linux-ppc64": "0.34.5", "@img/sharp-linux-riscv64": "0.34.5", "@img/sharp-linux-s390x": "0.34.5", "@img/sharp-linux-x64": "0.34.5", "@img/sharp-linuxmusl-arm64": "0.34.5", "@img/sharp-linuxmusl-x64": "0.34.5", "@img/sharp-wasm32": "0.34.5", "@img/sharp-win32-arm64": "0.34.5", "@img/sharp-win32-ia32": "0.34.5", "@img/sharp-win32-x64": "0.34.5" } }, "sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg=="],
"source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="],
"styled-jsx": ["styled-jsx@5.1.6", "", { "dependencies": { "client-only": "0.0.1" }, "peerDependencies": { "react": ">= 16.8.0 || 17.x.x || ^18.0.0-0 || ^19.0.0-0" } }, "sha512-qSVyDTeMotdvQYoHWLNGwRFJHC+i+ZvdBRYosOFgC+Wg1vx4frN2/RG/NA7SYqqvKNLf39P2LSRA2pu6n0XYZA=="],
"tsconfck": ["tsconfck@3.1.6", "", { "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"], "bin": { "tsconfck": "bin/tsconfck.js" } }, "sha512-ks6Vjr/jEw0P1gmOVwutM3B7fWxoWBL2KRDb1JfqGVawBmO5UsvmWOQFGHBPl5yxYz4eERr19E6L7NMv+Fej4w=="],
"tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
"undici": ["undici@7.16.0", "", {}, "sha512-QEg3HPMll0o3t2ourKwOeUAZ159Kn9mx5pnzHRQO8+Wixmh88YdZRiIwat0iNzNNXn0yoEtXJqFpyW7eM8BV7g=="],
"undici-types": ["undici-types@7.16.0", "", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="],
"vite-tsconfig-paths": ["vite-tsconfig-paths@5.1.4", "", { "dependencies": { "debug": "^4.1.1", "globrex": "^0.1.2", "tsconfck": "^3.0.3" }, "peerDependencies": { "vite": "*" }, "optionalPeers": ["vite"] }, "sha512-cYj0LRuLV2c2sMqhqhGpaO3LretdtMn/BVX4cPLanIZuwwrkVl+lK84E/miEXkCHWXuq65rhNN4rXsBcOB3S4w=="],
"whatwg-encoding": ["whatwg-encoding@3.1.1", "", { "dependencies": { "iconv-lite": "0.6.3" } }, "sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ=="],
"whatwg-mimetype": ["whatwg-mimetype@4.0.0", "", {}, "sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg=="],
"htmlparser2/entities": ["entities@6.0.1", "", {}, "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g=="],
"parse5/entities": ["entities@6.0.1", "", {}, "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g=="],
}
···
"@fontsource-variable/bricolage-grotesque": "^5.2.10",
"@fontsource/stack-sans-notch": "^5.2.1",
"@fujocoded/ao3.js": "^0.22.1",
+
"@hono/vite-build": "^1.8.0",
"@hono/vite-dev-server": "^0.23.0",
"fauxdom": "^1.2.2",
+
"hono": "^4.10.8",
+
"next": "16.0.7",
"react": "19.2.0",
"react-dom": "19.2.0",
},
"devDependencies": {
"@biomejs/biome": "2.2.0",
"@types/bun": "latest",
+
"baseline-browser-mapping": "^2.9.5",
+
"cypress": "^15.7.1",
},
},
},
···
"@biomejs/cli-win32-x64": ["@biomejs/cli-win32-x64@2.2.0", "", { "os": "win32", "cpu": "x64" }, "sha512-Nawu5nHjP/zPKTIryh2AavzTc/KEg4um/MxWdXW0A6P/RZOyIpa7+QSjeXwAwX/utJGaCoXRPWtF3m5U/bB3Ww=="],
+
"@cypress/request": ["@cypress/request@3.0.9", "", { "dependencies": { "aws-sign2": "~0.7.0", "aws4": "^1.8.0", "caseless": "~0.12.0", "combined-stream": "~1.0.6", "extend": "~3.0.2", "forever-agent": "~0.6.1", "form-data": "~4.0.4", "http-signature": "~1.4.0", "is-typedarray": "~1.0.0", "isstream": "~0.1.2", "json-stringify-safe": "~5.0.1", "mime-types": "~2.1.19", "performance-now": "^2.1.0", "qs": "6.14.0", "safe-buffer": "^5.1.2", "tough-cookie": "^5.0.0", "tunnel-agent": "^0.6.0", "uuid": "^8.3.2" } }, "sha512-I3l7FdGRXluAS44/0NguwWlO83J18p0vlr2FYHrJkWdNYhgVoiYo61IXPqaOsL+vNxU1ZqMACzItGK3/KKDsdw=="],
+
+
"@cypress/xvfb": ["@cypress/xvfb@1.2.4", "", { "dependencies": { "debug": "^3.1.0", "lodash.once": "^4.1.1" } }, "sha512-skbBzPggOVYCbnGgV+0dmBdW/s77ZkAOXIC1knS8NagwDjBrNC1LuXtQJeiN6l+m7lzmHtaoUw/ctJKdqkG57Q=="],
+
"@emnapi/runtime": ["@emnapi/runtime@1.7.0", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-oAYoQnCYaQZKVS53Fq23ceWMRxq5EhQsE0x0RdQ55jT7wagMu5k+fS39v1fiSLrtrLQlXwVINenqhLMtTrV/1Q=="],
"@fontsource-variable/bricolage-grotesque": ["@fontsource-variable/bricolage-grotesque@5.2.10", "", {}, "sha512-5EDsCqgGpKVcJWE4sg9ydli+t5WM97mISYw5lla/Ev4z71FwXh1oN0YUU8xjkRW9+wBCGD9R+ntAvI8G4bUFJg=="],
···
"@hono/node-server": ["@hono/node-server@1.19.6", "", { "peerDependencies": { "hono": "^4" } }, "sha512-Shz/KjlIeAhfiuE93NDKVdZ7HdBVLQAfdbaXEaoAVO3ic9ibRSLGIQGkcBbFyuLr+7/1D5ZCINM8B+6IvXeMtw=="],
+
"@hono/vite-build": ["@hono/vite-build@1.8.0", "", { "peerDependencies": { "hono": "*" } }, "sha512-T/w0Tuv8VV6FgjNnw2aW6LXtRfc5tOhaO54tKCRv4Zqc1tP3w6JyqTFtzJFFDvmY0xTxHS+Pj+ZPLhzFsdiFzQ=="],
"@hono/vite-dev-server": ["@hono/vite-dev-server@0.23.0", "", { "dependencies": { "@hono/node-server": "^1.14.2", "minimatch": "^9.0.3" }, "peerDependencies": { "hono": "*", "miniflare": "*", "wrangler": "*" }, "optionalPeers": ["miniflare", "wrangler"] }, "sha512-tHV86xToed9Up0j/dubQW2PDP4aYNFDSfQrjcV6Ra7bqCGrxhtg/zZBmbgSco3aTxKOEPzDXKK+6seAAfsbIXw=="],
···
"@img/sharp-win32-x64": ["@img/sharp-win32-x64@0.34.5", "", { "os": "win32", "cpu": "x64" }, "sha512-+29YMsqY2/9eFEiW93eqWnuLcWcufowXewwSNIT6UwZdUUCrM3oFjMWH/Z6/TMmb4hlFenmfAVbpWeup2jryCw=="],
+
"@next/env": ["@next/env@16.0.7", "", {}, "sha512-gpaNgUh5nftFKRkRQGnVi5dpcYSKGcZZkQffZ172OrG/XkrnS7UBTQ648YY+8ME92cC4IojpI2LqTC8sTDhAaw=="],
+
"@next/swc-darwin-arm64": ["@next/swc-darwin-arm64@16.0.7", "", { "os": "darwin", "cpu": "arm64" }, "sha512-LlDtCYOEj/rfSnEn/Idi+j1QKHxY9BJFmxx7108A6D8K0SB+bNgfYQATPk/4LqOl4C0Wo3LACg2ie6s7xqMpJg=="],
+
"@next/swc-darwin-x64": ["@next/swc-darwin-x64@16.0.7", "", { "os": "darwin", "cpu": "x64" }, "sha512-rtZ7BhnVvO1ICf3QzfW9H3aPz7GhBrnSIMZyr4Qy6boXF0b5E3QLs+cvJmg3PsTCG2M1PBoC+DANUi4wCOKXpA=="],
+
"@next/swc-linux-arm64-gnu": ["@next/swc-linux-arm64-gnu@16.0.7", "", { "os": "linux", "cpu": "arm64" }, "sha512-mloD5WcPIeIeeZqAIP5c2kdaTa6StwP4/2EGy1mUw8HiexSHGK/jcM7lFuS3u3i2zn+xH9+wXJs6njO7VrAqww=="],
+
"@next/swc-linux-arm64-musl": ["@next/swc-linux-arm64-musl@16.0.7", "", { "os": "linux", "cpu": "arm64" }, "sha512-+ksWNrZrthisXuo9gd1XnjHRowCbMtl/YgMpbRvFeDEqEBd523YHPWpBuDjomod88U8Xliw5DHhekBC3EOOd9g=="],
+
"@next/swc-linux-x64-gnu": ["@next/swc-linux-x64-gnu@16.0.7", "", { "os": "linux", "cpu": "x64" }, "sha512-4WtJU5cRDxpEE44Ana2Xro1284hnyVpBb62lIpU5k85D8xXxatT+rXxBgPkc7C1XwkZMWpK5rXLXTh9PFipWsA=="],
+
"@next/swc-linux-x64-musl": ["@next/swc-linux-x64-musl@16.0.7", "", { "os": "linux", "cpu": "x64" }, "sha512-HYlhqIP6kBPXalW2dbMTSuB4+8fe+j9juyxwfMwCe9kQPPeiyFn7NMjNfoFOfJ2eXkeQsoUGXg+O2SE3m4Qg2w=="],
+
"@next/swc-win32-arm64-msvc": ["@next/swc-win32-arm64-msvc@16.0.7", "", { "os": "win32", "cpu": "arm64" }, "sha512-EviG+43iOoBRZg9deGauXExjRphhuYmIOJ12b9sAPy0eQ6iwcPxfED2asb/s2/yiLYOdm37kPaiZu8uXSYPs0Q=="],
+
"@next/swc-win32-x64-msvc": ["@next/swc-win32-x64-msvc@16.0.7", "", { "os": "win32", "cpu": "x64" }, "sha512-gniPjy55zp5Eg0896qSrf3yB1dw4F/3s8VK1ephdsZZ129j2n6e1WqCbE2YgcKhW9hPB9TVZENugquWJD5x0ug=="],
+
"@swc/helpers": ["@swc/helpers@0.5.15", "", { "dependencies": { "tslib": "^2.8.0" } }, "sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g=="],
+
"@types/bun": ["@types/bun@1.3.4", "", { "dependencies": { "bun-types": "1.3.4" } }, "sha512-EEPTKXHP+zKGPkhRLv+HI0UEX8/o+65hqARxLy8Ov5rIxMBPNTjeZww00CIihrIQGEQBYg+0roO5qOnS/7boGA=="],
+
"@types/node": ["@types/node@24.10.1", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ=="],
+
+
"@types/sinonjs__fake-timers": ["@types/sinonjs__fake-timers@8.1.1", "", {}, "sha512-0kSuKjAS0TrGLJ0M/+8MaFkGsQhZpB6pxOmvS3K8FYI72K//YmdfoW9X2qPsAKh1mkwxGD5zib9s1FIFed6E8g=="],
+
+
"@types/sizzle": ["@types/sizzle@2.3.10", "", {}, "sha512-TC0dmN0K8YcWEAEfiPi5gJP14eJe30TTGjkvek3iM/1NdHHsdCA/Td6GvNndMOo/iSnIsZ4HuuhrYPDAmbxzww=="],
+
+
"@types/tmp": ["@types/tmp@0.2.6", "", {}, "sha512-chhaNf2oKHlRkDGt+tiKE2Z5aJ6qalm7Z9rlLdBwmOiAAf09YQvvoLXjWK4HWPF1xU/fqvMgfNfpVoBscA/tKA=="],
+
+
"@types/yauzl": ["@types/yauzl@2.10.3", "", { "dependencies": { "@types/node": "*" } }, "sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q=="],
+
+
"aggregate-error": ["aggregate-error@3.1.0", "", { "dependencies": { "clean-stack": "^2.0.0", "indent-string": "^4.0.0" } }, "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA=="],
+
"ansi-colors": ["ansi-colors@4.1.3", "", {}, "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw=="],
+
"ansi-escapes": ["ansi-escapes@4.3.2", "", { "dependencies": { "type-fest": "^0.21.3" } }, "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ=="],
+
"ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
+
"ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="],
+
"arch": ["arch@2.2.0", "", {}, "sha512-Of/R0wqp83cgHozfIYLbBMnej79U/SVGOOyuB3VVFv1NRM/PSFMK12x9KVtiYzJqmnU5WR2qp0Z5rHb7sWGnFQ=="],
+
"asn1": ["asn1@0.2.6", "", { "dependencies": { "safer-buffer": "~2.1.0" } }, "sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ=="],
+
"assert-plus": ["assert-plus@1.0.0", "", {}, "sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw=="],
+
"astral-regex": ["astral-regex@2.0.0", "", {}, "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ=="],
+
"asynckit": ["asynckit@0.4.0", "", {}, "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="],
+
"at-least-node": ["at-least-node@1.0.0", "", {}, "sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg=="],
+
"aws-sign2": ["aws-sign2@0.7.0", "", {}, "sha512-08kcGqnYf/YmjoRhfxyu+CLxBjUtHLXLXX/vUfx9l2LYzG3c1m61nrpyFUZI6zeS+Li/wWMMidD9KgrqtGq3mA=="],
+
"aws4": ["aws4@1.13.2", "", {}, "sha512-lHe62zvbTB5eEABUVi/AwVh0ZKY9rMMDhmm+eeyuuUQbQ3+J+fONVQOZyj+DdrvD4BY33uYniyRJ4UJIaSKAfw=="],
"balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="],
+
"base64-js": ["base64-js@1.5.1", "", {}, "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA=="],
+
+
"baseline-browser-mapping": ["baseline-browser-mapping@2.9.5", "", { "bin": { "baseline-browser-mapping": "dist/cli.js" } }, "sha512-D5vIoztZOq1XM54LUdttJVc96ggEsIfju2JBvht06pSzpckp3C7HReun67Bghzrtdsq9XdMGbSSB3v3GhMNmAA=="],
+
+
"bcrypt-pbkdf": ["bcrypt-pbkdf@1.0.2", "", { "dependencies": { "tweetnacl": "^0.14.3" } }, "sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w=="],
+
+
"blob-util": ["blob-util@2.0.2", "", {}, "sha512-T7JQa+zsXXEa6/8ZhHcQEW1UFfVM49Ts65uBkFL6fz2QmrElqmbajIDJvuA0tEhRe5eIjpV9ZF+0RfZR9voJFQ=="],
+
+
"bluebird": ["bluebird@3.7.2", "", {}, "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg=="],
+
"boolbase": ["boolbase@1.0.0", "", {}, "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww=="],
"brace-expansion": ["brace-expansion@2.0.2", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ=="],
+
"buffer": ["buffer@5.7.1", "", { "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.1.13" } }, "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ=="],
+
+
"buffer-crc32": ["buffer-crc32@0.2.13", "", {}, "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ=="],
+
+
"bun-types": ["bun-types@1.3.4", "", { "dependencies": { "@types/node": "*" } }, "sha512-5ua817+BZPZOlNaRgGBpZJOSAQ9RQ17pkwPD0yR7CfJg+r8DgIILByFifDTa+IPDDxzf5VNhtNlcKqFzDgJvlQ=="],
+
+
"cachedir": ["cachedir@2.4.0", "", {}, "sha512-9EtFOZR8g22CL7BWjJ9BUx1+A/djkofnyW3aOXZORNW2kxoUpx2h+uN2cOqwPmFhnpVmxg+KW2OjOSgChTEvsQ=="],
+
+
"call-bind-apply-helpers": ["call-bind-apply-helpers@1.0.2", "", { "dependencies": { "es-errors": "^1.3.0", "function-bind": "^1.1.2" } }, "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ=="],
+
"call-bound": ["call-bound@1.0.4", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.2", "get-intrinsic": "^1.3.0" } }, "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg=="],
"caniuse-lite": ["caniuse-lite@1.0.30001754", "", {}, "sha512-x6OeBXueoAceOmotzx3PO4Zpt4rzpeIFsSr6AAePTZxSkXiYDUmpypEl7e2+8NCd9bD7bXjqyef8CJYPC1jfxg=="],
+
"caseless": ["caseless@0.12.0", "", {}, "sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw=="],
+
+
"chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="],
+
"cheerio": ["cheerio@1.1.2", "", { "dependencies": { "cheerio-select": "^2.1.0", "dom-serializer": "^2.0.0", "domhandler": "^5.0.3", "domutils": "^3.2.2", "encoding-sniffer": "^0.2.1", "htmlparser2": "^10.0.0", "parse5": "^7.3.0", "parse5-htmlparser2-tree-adapter": "^7.1.0", "parse5-parser-stream": "^7.1.2", "undici": "^7.12.0", "whatwg-mimetype": "^4.0.0" } }, "sha512-IkxPpb5rS/d1IiLbHMgfPuS0FgiWTtFIm/Nj+2woXDLTZ7fOT2eqzgYbdMlLweqlHbsZjxEChoVK+7iph7jyQg=="],
"cheerio-select": ["cheerio-select@2.1.0", "", { "dependencies": { "boolbase": "^1.0.0", "css-select": "^5.1.0", "css-what": "^6.1.0", "domelementtype": "^2.3.0", "domhandler": "^5.0.3", "domutils": "^3.0.1" } }, "sha512-9v9kG0LvzrlcungtnJtpGNxY+fzECQKhK4EGJX2vByejiMX84MFNQw4UxPJl3bFbTMw+Dfs37XaIkCwTZfLh4g=="],
+
"ci-info": ["ci-info@4.3.1", "", {}, "sha512-Wdy2Igu8OcBpI2pZePZ5oWjPC38tmDVx5WKUXKwlLYkA0ozo85sLsLvkBbBn/sZaSCMFOGZJ14fvW9t5/d7kdA=="],
+
+
"clean-stack": ["clean-stack@2.2.0", "", {}, "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A=="],
+
+
"cli-cursor": ["cli-cursor@3.1.0", "", { "dependencies": { "restore-cursor": "^3.1.0" } }, "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw=="],
+
+
"cli-table3": ["cli-table3@0.6.1", "", { "dependencies": { "string-width": "^4.2.0" }, "optionalDependencies": { "colors": "1.4.0" } }, "sha512-w0q/enDHhPLq44ovMGdQeeDLvwxwavsJX7oQGYt/LrBlYsyaxyDnp6z3QzFut/6kLLKnlcUVJLrpB7KBfgG/RA=="],
+
+
"cli-truncate": ["cli-truncate@2.1.0", "", { "dependencies": { "slice-ansi": "^3.0.0", "string-width": "^4.2.0" } }, "sha512-n8fOixwDD6b/ObinzTrp1ZKFzbgvKZvuz/TvejnLn1aQfC6r52XEx85FmuC+3HI+JM7coBRXUvNqEU2PHVrHpg=="],
+
"client-only": ["client-only@0.0.1", "", {}, "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA=="],
+
"color-convert": ["color-convert@2.0.1", "", { "dependencies": { "color-name": "~1.1.4" } }, "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ=="],
+
+
"color-name": ["color-name@1.1.4", "", {}, "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="],
+
+
"colorette": ["colorette@2.0.20", "", {}, "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w=="],
+
+
"colors": ["colors@1.4.0", "", {}, "sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA=="],
+
+
"combined-stream": ["combined-stream@1.0.8", "", { "dependencies": { "delayed-stream": "~1.0.0" } }, "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg=="],
+
+
"commander": ["commander@6.2.1", "", {}, "sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA=="],
+
+
"common-tags": ["common-tags@1.8.2", "", {}, "sha512-gk/Z852D2Wtb//0I+kRFNKKE9dIIVirjoqPoA1wJU+XePVXZfGeBpk45+A1rKO4Q43prqWBNY/MiIeRLbPWUaA=="],
+
+
"core-util-is": ["core-util-is@1.0.2", "", {}, "sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ=="],
+
+
"cross-spawn": ["cross-spawn@7.0.6", "", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA=="],
+
"css-select": ["css-select@5.2.2", "", { "dependencies": { "boolbase": "^1.0.0", "css-what": "^6.1.0", "domhandler": "^5.0.2", "domutils": "^3.0.1", "nth-check": "^2.0.1" } }, "sha512-TizTzUddG/xYLA3NXodFM0fSbNizXjOKhqiQQwvhlspadZokn1KDy0NZFS0wuEubIYAV5/c1/lAr0TaaFXEXzw=="],
"css-what": ["css-what@6.2.2", "", {}, "sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA=="],
+
"cypress": ["cypress@15.7.1", "", { "dependencies": { "@cypress/request": "^3.0.9", "@cypress/xvfb": "^1.2.4", "@types/sinonjs__fake-timers": "8.1.1", "@types/sizzle": "^2.3.2", "@types/tmp": "^0.2.3", "arch": "^2.2.0", "blob-util": "^2.0.2", "bluebird": "^3.7.2", "buffer": "^5.7.1", "cachedir": "^2.3.0", "chalk": "^4.1.0", "ci-info": "^4.1.0", "cli-cursor": "^3.1.0", "cli-table3": "0.6.1", "commander": "^6.2.1", "common-tags": "^1.8.0", "dayjs": "^1.10.4", "debug": "^4.3.4", "enquirer": "^2.3.6", "eventemitter2": "6.4.7", "execa": "4.1.0", "executable": "^4.1.1", "extract-zip": "2.0.1", "figures": "^3.2.0", "fs-extra": "^9.1.0", "hasha": "5.2.2", "is-installed-globally": "~0.4.0", "listr2": "^3.8.3", "lodash": "^4.17.21", "log-symbols": "^4.0.0", "minimist": "^1.2.8", "ospath": "^1.2.2", "pretty-bytes": "^5.6.0", "process": "^0.11.10", "proxy-from-env": "1.0.0", "request-progress": "^3.0.0", "supports-color": "^8.1.1", "systeminformation": "5.27.7", "tmp": "~0.2.4", "tree-kill": "1.2.2", "untildify": "^4.0.0", "yauzl": "^2.10.0" }, "bin": { "cypress": "bin/cypress" } }, "sha512-U3sYnJ+Cnpgr6IPycxsznTg//mGVXfPGeGV+om7VQCyp5XyVkhG4oPr3X3hTq1+OB0Om0O5DxusYmt7cbvwqMQ=="],
+
+
"dashdash": ["dashdash@1.14.1", "", { "dependencies": { "assert-plus": "^1.0.0" } }, "sha512-jRFi8UDGo6j+odZiEpjazZaWqEal3w/basFjQHQEwVtZJGDpxbH1MeYluwCS8Xq5wmLJooDlMgvVarmWfGM44g=="],
+
+
"dayjs": ["dayjs@1.11.19", "", {}, "sha512-t5EcLVS6QPBNqM2z8fakk/NKel+Xzshgt8FFKAn+qwlD1pzZWxh0nVCrvFK7ZDb6XucZeF9z8C7CBWTRIVApAw=="],
"debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="],
+
+
"delayed-stream": ["delayed-stream@1.0.0", "", {}, "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ=="],
"detect-libc": ["detect-libc@2.1.2", "", {}, "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ=="],
···
"domutils": ["domutils@3.2.2", "", { "dependencies": { "dom-serializer": "^2.0.0", "domelementtype": "^2.3.0", "domhandler": "^5.0.3" } }, "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw=="],
+
"dunder-proto": ["dunder-proto@1.0.1", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-errors": "^1.3.0", "gopd": "^1.2.0" } }, "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A=="],
+
+
"ecc-jsbn": ["ecc-jsbn@0.1.2", "", { "dependencies": { "jsbn": "~0.1.0", "safer-buffer": "^2.1.0" } }, "sha512-eh9O+hwRHNbG4BLTjEl3nw044CkGm5X6LoaCf7LPp7UU8Qrt47JYNi6nPX8xjW97TKGKm1ouctg0QSpZe9qrnw=="],
+
+
"emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="],
+
"encoding-sniffer": ["encoding-sniffer@0.2.1", "", { "dependencies": { "iconv-lite": "^0.6.3", "whatwg-encoding": "^3.1.1" } }, "sha512-5gvq20T6vfpekVtqrYQsSCFZ1wEg5+wW0/QaZMWkFr6BqD3NfKs0rLCx4rrVlSWJeZb5NBJgVLswK/w2MWU+Gw=="],
+
"end-of-stream": ["end-of-stream@1.4.5", "", { "dependencies": { "once": "^1.4.0" } }, "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg=="],
+
+
"enquirer": ["enquirer@2.4.1", "", { "dependencies": { "ansi-colors": "^4.1.1", "strip-ansi": "^6.0.1" } }, "sha512-rRqJg/6gd538VHvR3PSrdRBb/1Vy2YfzHqzvbhGIQpDRKIa4FgV/54b5Q1xYSxOOwKvjXweS26E0Q+nAMwp2pQ=="],
+
"entities": ["entities@4.5.0", "", {}, "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw=="],
+
+
"es-define-property": ["es-define-property@1.0.1", "", {}, "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g=="],
+
+
"es-errors": ["es-errors@1.3.0", "", {}, "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw=="],
+
+
"es-object-atoms": ["es-object-atoms@1.1.1", "", { "dependencies": { "es-errors": "^1.3.0" } }, "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA=="],
+
+
"es-set-tostringtag": ["es-set-tostringtag@2.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "get-intrinsic": "^1.2.6", "has-tostringtag": "^1.0.2", "hasown": "^2.0.2" } }, "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA=="],
+
+
"escape-string-regexp": ["escape-string-regexp@1.0.5", "", {}, "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg=="],
+
+
"eventemitter2": ["eventemitter2@6.4.7", "", {}, "sha512-tYUSVOGeQPKt/eC1ABfhHy5Xd96N3oIijJvN3O9+TsC28T5V9yX9oEfEK5faP0EFSNVOG97qtAS68GBrQB2hDg=="],
+
+
"execa": ["execa@4.1.0", "", { "dependencies": { "cross-spawn": "^7.0.0", "get-stream": "^5.0.0", "human-signals": "^1.1.1", "is-stream": "^2.0.0", "merge-stream": "^2.0.0", "npm-run-path": "^4.0.0", "onetime": "^5.1.0", "signal-exit": "^3.0.2", "strip-final-newline": "^2.0.0" } }, "sha512-j5W0//W7f8UxAn8hXVnwG8tLwdiUy4FJLcSupCg6maBYZDpyBvTApK7KyuI4bKj8KOh1r2YH+6ucuYtJv1bTZA=="],
+
+
"executable": ["executable@4.1.1", "", { "dependencies": { "pify": "^2.2.0" } }, "sha512-8iA79xD3uAch729dUG8xaaBBFGaEa0wdD2VkYLFHwlqosEj/jT66AzcreRDSgV7ehnNLBW2WR5jIXwGKjVdTLg=="],
+
+
"extend": ["extend@3.0.2", "", {}, "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g=="],
+
+
"extract-zip": ["extract-zip@2.0.1", "", { "dependencies": { "debug": "^4.1.1", "get-stream": "^5.1.0", "yauzl": "^2.10.0" }, "optionalDependencies": { "@types/yauzl": "^2.9.1" }, "bin": { "extract-zip": "cli.js" } }, "sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg=="],
+
+
"extsprintf": ["extsprintf@1.3.0", "", {}, "sha512-11Ndz7Nv+mvAC1j0ktTa7fAb0vLyGGX+rMHNBYQviQDGU0Hw7lhctJANqbPhu9nV9/izT/IntTgZ7Im/9LJs9g=="],
"fauxdom": ["fauxdom@1.2.2", "", {}, "sha512-Xoj9VPhIx9p9wU1CncWaKqgm5e+lnTqi5nYsQIfALEUKu++99Pj/BMkggH6mYjWaBeVWBxeV8xZTJauTXGiUDw=="],
+
"fd-slicer": ["fd-slicer@1.1.0", "", { "dependencies": { "pend": "~1.2.0" } }, "sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g=="],
+
+
"figures": ["figures@3.2.0", "", { "dependencies": { "escape-string-regexp": "^1.0.5" } }, "sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg=="],
+
+
"forever-agent": ["forever-agent@0.6.1", "", {}, "sha512-j0KLYPhm6zeac4lz3oJ3o65qvgQCcPubiyotZrXqEaG4hNagNYO8qdlUrX5vwqv9ohqeT/Z3j6+yW067yWWdUw=="],
+
+
"form-data": ["form-data@4.0.5", "", { "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", "es-set-tostringtag": "^2.1.0", "hasown": "^2.0.2", "mime-types": "^2.1.12" } }, "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w=="],
+
+
"fs-extra": ["fs-extra@9.1.0", "", { "dependencies": { "at-least-node": "^1.0.0", "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", "universalify": "^2.0.0" } }, "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ=="],
+
+
"function-bind": ["function-bind@1.1.2", "", {}, "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA=="],
+
+
"get-intrinsic": ["get-intrinsic@1.3.0", "", { "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" } }, "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ=="],
+
+
"get-proto": ["get-proto@1.0.1", "", { "dependencies": { "dunder-proto": "^1.0.1", "es-object-atoms": "^1.0.0" } }, "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g=="],
+
+
"get-stream": ["get-stream@5.2.0", "", { "dependencies": { "pump": "^3.0.0" } }, "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA=="],
+
+
"getpass": ["getpass@0.1.7", "", { "dependencies": { "assert-plus": "^1.0.0" } }, "sha512-0fzj9JxOLfJ+XGLhR8ze3unN0KZCgZwiSSDz168VERjK8Wl8kVSdcu2kspd4s4wtAa1y/qrVRiAA0WclVsu0ng=="],
+
+
"global-dirs": ["global-dirs@3.0.1", "", { "dependencies": { "ini": "2.0.0" } }, "sha512-NBcGGFbBA9s1VzD41QXDG+3++t9Mn5t1FpLdhESY6oKY4gYTFpX4wO3sqGUa0Srjtbfj3szX0RnemmrVRUdULA=="],
+
"globrex": ["globrex@0.1.2", "", {}, "sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg=="],
+
"gopd": ["gopd@1.2.0", "", {}, "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg=="],
+
+
"graceful-fs": ["graceful-fs@4.2.11", "", {}, "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="],
+
+
"has-flag": ["has-flag@4.0.0", "", {}, "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="],
+
+
"has-symbols": ["has-symbols@1.1.0", "", {}, "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ=="],
+
+
"has-tostringtag": ["has-tostringtag@1.0.2", "", { "dependencies": { "has-symbols": "^1.0.3" } }, "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw=="],
+
+
"hasha": ["hasha@5.2.2", "", { "dependencies": { "is-stream": "^2.0.0", "type-fest": "^0.8.0" } }, "sha512-Hrp5vIK/xr5SkeN2onO32H0MgNZ0f17HRNH39WfL0SYUNOTZ5Lz1TJ8Pajo/87dYGEFlLMm7mIc/k/s6Bvz9HQ=="],
+
+
"hasown": ["hasown@2.0.2", "", { "dependencies": { "function-bind": "^1.1.2" } }, "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ=="],
+
+
"hono": ["hono@4.10.8", "", {}, "sha512-DDT0A0r6wzhe8zCGoYOmMeuGu3dyTAE40HHjwUsWFTEy5WxK1x2WDSsBPlEXgPbRIFY6miDualuUDbasPogIww=="],
"htmlparser2": ["htmlparser2@10.0.0", "", { "dependencies": { "domelementtype": "^2.3.0", "domhandler": "^5.0.3", "domutils": "^3.2.1", "entities": "^6.0.0" } }, "sha512-TwAZM+zE5Tq3lrEHvOlvwgj1XLWQCtaaibSN11Q+gGBAS7Y1uZSWwXXRe4iF6OXnaq1riyQAPFOBtYc77Mxq0g=="],
+
"http-signature": ["http-signature@1.4.0", "", { "dependencies": { "assert-plus": "^1.0.0", "jsprim": "^2.0.2", "sshpk": "^1.18.0" } }, "sha512-G5akfn7eKbpDN+8nPS/cb57YeA1jLTVxjpCj7tmm3QKPdyDy7T+qSC40e9ptydSWvkwjSXw1VbkpyEm39ukeAg=="],
+
+
"human-signals": ["human-signals@1.1.1", "", {}, "sha512-SEQu7vl8KjNL2eoGBLF3+wAjpsNfA9XMlXAYj/3EdaNfAlxKthD1xjEQfGOUhllCGGJVNY34bRr6lPINhNjyZw=="],
+
"iconv-lite": ["iconv-lite@0.6.3", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw=="],
+
"ieee754": ["ieee754@1.2.1", "", {}, "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="],
+
+
"indent-string": ["indent-string@4.0.0", "", {}, "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg=="],
+
+
"ini": ["ini@2.0.0", "", {}, "sha512-7PnF4oN3CvZF23ADhA5wRaYEQpJ8qygSkbtTXWBeXWXmEVRXK+1ITciHWwHhsjv1TmW0MgacIv6hEi5pX5NQdA=="],
+
+
"is-fullwidth-code-point": ["is-fullwidth-code-point@3.0.0", "", {}, "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="],
+
+
"is-installed-globally": ["is-installed-globally@0.4.0", "", { "dependencies": { "global-dirs": "^3.0.0", "is-path-inside": "^3.0.2" } }, "sha512-iwGqO3J21aaSkC7jWnHP/difazwS7SFeIqxv6wEtLU8Y5KlzFTjyqcSIT0d8s4+dDhKytsk9PJZ2BkS5eZwQRQ=="],
+
+
"is-path-inside": ["is-path-inside@3.0.3", "", {}, "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ=="],
+
+
"is-stream": ["is-stream@2.0.1", "", {}, "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg=="],
+
+
"is-typedarray": ["is-typedarray@1.0.0", "", {}, "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA=="],
+
+
"is-unicode-supported": ["is-unicode-supported@0.1.0", "", {}, "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw=="],
+
+
"isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="],
+
+
"isstream": ["isstream@0.1.2", "", {}, "sha512-Yljz7ffyPbrLpLngrMtZ7NduUgVvi6wG9RJ9IUcyCd59YQ911PBJphODUcbOVbqYfxe1wuYf/LJ8PauMRwsM/g=="],
+
+
"jsbn": ["jsbn@0.1.1", "", {}, "sha512-UVU9dibq2JcFWxQPA6KCqj5O42VOmAY3zQUfEKxU0KpTGXwNoCjkX1e13eHNvw/xPynt6pU0rZ1htjWTNTSXsg=="],
+
+
"json-schema": ["json-schema@0.4.0", "", {}, "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA=="],
+
+
"json-stringify-safe": ["json-stringify-safe@5.0.1", "", {}, "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA=="],
+
+
"jsonfile": ["jsonfile@6.2.0", "", { "dependencies": { "universalify": "^2.0.0" }, "optionalDependencies": { "graceful-fs": "^4.1.6" } }, "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg=="],
+
+
"jsprim": ["jsprim@2.0.2", "", { "dependencies": { "assert-plus": "1.0.0", "extsprintf": "1.3.0", "json-schema": "0.4.0", "verror": "1.10.0" } }, "sha512-gqXddjPqQ6G40VdnI6T6yObEC+pDNvyP95wdQhkWkg7crHH3km5qP1FsOXEkzEQwnz6gz5qGTn1c2Y52wP3OyQ=="],
+
+
"listr2": ["listr2@3.14.0", "", { "dependencies": { "cli-truncate": "^2.1.0", "colorette": "^2.0.16", "log-update": "^4.0.0", "p-map": "^4.0.0", "rfdc": "^1.3.0", "rxjs": "^7.5.1", "through": "^2.3.8", "wrap-ansi": "^7.0.0" }, "peerDependencies": { "enquirer": ">= 2.3.0 < 3" }, "optionalPeers": ["enquirer"] }, "sha512-TyWI8G99GX9GjE54cJ+RrNMcIFBfwMPxc3XTFiAYGN4s10hWROGtOg7+O6u6LE3mNkyld7RSLE6nrKBvTfcs3g=="],
+
+
"lodash": ["lodash@4.17.21", "", {}, "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="],
+
+
"lodash.once": ["lodash.once@4.1.1", "", {}, "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg=="],
+
+
"log-symbols": ["log-symbols@4.1.0", "", { "dependencies": { "chalk": "^4.1.0", "is-unicode-supported": "^0.1.0" } }, "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg=="],
+
+
"log-update": ["log-update@4.0.0", "", { "dependencies": { "ansi-escapes": "^4.3.0", "cli-cursor": "^3.1.0", "slice-ansi": "^4.0.0", "wrap-ansi": "^6.2.0" } }, "sha512-9fkkDevMefjg0mmzWFBW8YkFP91OrizzkW3diF7CpG+S2EYdy4+TVfGwz1zeF8x7hCx1ovSPTOE9Ngib74qqUg=="],
+
+
"math-intrinsics": ["math-intrinsics@1.1.0", "", {}, "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g=="],
+
+
"merge-stream": ["merge-stream@2.0.0", "", {}, "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w=="],
+
+
"mime-db": ["mime-db@1.52.0", "", {}, "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg=="],
+
+
"mime-types": ["mime-types@2.1.35", "", { "dependencies": { "mime-db": "1.52.0" } }, "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw=="],
+
+
"mimic-fn": ["mimic-fn@2.1.0", "", {}, "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg=="],
+
"minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="],
+
"minimist": ["minimist@1.2.8", "", {}, "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA=="],
+
"ms": ["ms@2.1.3", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="],
"nanoid": ["nanoid@3.3.11", "", { "bin": { "nanoid": "bin/nanoid.cjs" } }, "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w=="],
+
"next": ["next@16.0.7", "", { "dependencies": { "@next/env": "16.0.7", "@swc/helpers": "0.5.15", "caniuse-lite": "^1.0.30001579", "postcss": "8.4.31", "styled-jsx": "5.1.6" }, "optionalDependencies": { "@next/swc-darwin-arm64": "16.0.7", "@next/swc-darwin-x64": "16.0.7", "@next/swc-linux-arm64-gnu": "16.0.7", "@next/swc-linux-arm64-musl": "16.0.7", "@next/swc-linux-x64-gnu": "16.0.7", "@next/swc-linux-x64-musl": "16.0.7", "@next/swc-win32-arm64-msvc": "16.0.7", "@next/swc-win32-x64-msvc": "16.0.7", "sharp": "^0.34.4" }, "peerDependencies": { "@opentelemetry/api": "^1.1.0", "@playwright/test": "^1.51.1", "babel-plugin-react-compiler": "*", "react": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", "react-dom": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", "sass": "^1.3.0" }, "optionalPeers": ["@opentelemetry/api", "@playwright/test", "babel-plugin-react-compiler", "sass"], "bin": { "next": "dist/bin/next" } }, "sha512-3mBRJyPxT4LOxAJI6IsXeFtKfiJUbjCLgvXO02fV8Wy/lIhPvP94Fe7dGhUgHXcQy4sSuYwQNcOLhIfOm0rL0A=="],
+
+
"npm-run-path": ["npm-run-path@4.0.1", "", { "dependencies": { "path-key": "^3.0.0" } }, "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw=="],
"nth-check": ["nth-check@2.1.1", "", { "dependencies": { "boolbase": "^1.0.0" } }, "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w=="],
+
"object-inspect": ["object-inspect@1.13.4", "", {}, "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew=="],
+
+
"once": ["once@1.4.0", "", { "dependencies": { "wrappy": "1" } }, "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w=="],
+
+
"onetime": ["onetime@5.1.2", "", { "dependencies": { "mimic-fn": "^2.1.0" } }, "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg=="],
+
+
"ospath": ["ospath@1.2.2", "", {}, "sha512-o6E5qJV5zkAbIDNhGSIlyOhScKXgQrSRMilfph0clDfM0nEnBOlKlH4sWDmG95BW/CvwNz0vmm7dJVtU2KlMiA=="],
+
+
"p-map": ["p-map@4.0.0", "", { "dependencies": { "aggregate-error": "^3.0.0" } }, "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ=="],
+
"parse5": ["parse5@7.3.0", "", { "dependencies": { "entities": "^6.0.0" } }, "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw=="],
"parse5-htmlparser2-tree-adapter": ["parse5-htmlparser2-tree-adapter@7.1.0", "", { "dependencies": { "domhandler": "^5.0.3", "parse5": "^7.0.0" } }, "sha512-ruw5xyKs6lrpo9x9rCZqZZnIUntICjQAd0Wsmp396Ul9lN/h+ifgVV1x1gZHi8euej6wTfpqX8j+BFQxF0NS/g=="],
"parse5-parser-stream": ["parse5-parser-stream@7.1.2", "", { "dependencies": { "parse5": "^7.0.0" } }, "sha512-JyeQc9iwFLn5TbvvqACIF/VXG6abODeB3Fwmv/TGdLk2LfbWkaySGY72at4+Ty7EkPZj854u4CrICqNk2qIbow=="],
+
"path-key": ["path-key@3.1.1", "", {}, "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="],
+
+
"pend": ["pend@1.2.0", "", {}, "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg=="],
+
+
"performance-now": ["performance-now@2.1.0", "", {}, "sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow=="],
+
"picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="],
+
+
"pify": ["pify@2.3.0", "", {}, "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog=="],
"postcss": ["postcss@8.4.31", "", { "dependencies": { "nanoid": "^3.3.6", "picocolors": "^1.0.0", "source-map-js": "^1.0.2" } }, "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ=="],
+
"pretty-bytes": ["pretty-bytes@5.6.0", "", {}, "sha512-FFw039TmrBqFK8ma/7OL3sDz/VytdtJr044/QUJtH0wK9lb9jLq9tJyIxUwtQJHwar2BqtiA4iCWSwo9JLkzFg=="],
+
+
"process": ["process@0.11.10", "", {}, "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A=="],
+
+
"proxy-from-env": ["proxy-from-env@1.0.0", "", {}, "sha512-F2JHgJQ1iqwnHDcQjVBsq3n/uoaFL+iPW/eAeL7kVxy/2RrWaN4WroKjjvbsoRtv0ftelNyC01bjRhn/bhcf4A=="],
+
+
"pump": ["pump@3.0.3", "", { "dependencies": { "end-of-stream": "^1.1.0", "once": "^1.3.1" } }, "sha512-todwxLMY7/heScKmntwQG8CXVkWUOdYxIvY2s0VWAAMh/nd8SoYiRaKjlr7+iCs984f2P8zvrfWcDDYVb73NfA=="],
+
+
"qs": ["qs@6.14.0", "", { "dependencies": { "side-channel": "^1.1.0" } }, "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w=="],
+
"react": ["react@19.2.0", "", {}, "sha512-tmbWg6W31tQLeB5cdIBOicJDJRR2KzXsV7uSK9iNfLWQ5bIZfxuPEHp7M8wiHyHnn0DD1i7w3Zmin0FtkrwoCQ=="],
"react-dom": ["react-dom@19.2.0", "", { "dependencies": { "scheduler": "^0.27.0" }, "peerDependencies": { "react": "^19.2.0" } }, "sha512-UlbRu4cAiGaIewkPyiRGJk0imDN2T3JjieT6spoL2UeSf5od4n5LB/mQ4ejmxhCFT1tYe8IvaFulzynWovsEFQ=="],
+
"request-progress": ["request-progress@3.0.0", "", { "dependencies": { "throttleit": "^1.0.0" } }, "sha512-MnWzEHHaxHO2iWiQuHrUPBi/1WeBf5PkxQqNyNvLl9VAYSdXkP8tQ3pBSeCPD+yw0v0Aq1zosWLz0BdeXpWwZg=="],
+
+
"restore-cursor": ["restore-cursor@3.1.0", "", { "dependencies": { "onetime": "^5.1.0", "signal-exit": "^3.0.2" } }, "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA=="],
+
+
"rfdc": ["rfdc@1.4.1", "", {}, "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA=="],
+
+
"rxjs": ["rxjs@7.8.2", "", { "dependencies": { "tslib": "^2.1.0" } }, "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA=="],
+
+
"safe-buffer": ["safe-buffer@5.2.1", "", {}, "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="],
+
"safer-buffer": ["safer-buffer@2.1.2", "", {}, "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="],
"scheduler": ["scheduler@0.27.0", "", {}, "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q=="],
···
"sharp": ["sharp@0.34.5", "", { "dependencies": { "@img/colour": "^1.0.0", "detect-libc": "^2.1.2", "semver": "^7.7.3" }, "optionalDependencies": { "@img/sharp-darwin-arm64": "0.34.5", "@img/sharp-darwin-x64": "0.34.5", "@img/sharp-libvips-darwin-arm64": "1.2.4", "@img/sharp-libvips-darwin-x64": "1.2.4", "@img/sharp-libvips-linux-arm": "1.2.4", "@img/sharp-libvips-linux-arm64": "1.2.4", "@img/sharp-libvips-linux-ppc64": "1.2.4", "@img/sharp-libvips-linux-riscv64": "1.2.4", "@img/sharp-libvips-linux-s390x": "1.2.4", "@img/sharp-libvips-linux-x64": "1.2.4", "@img/sharp-libvips-linuxmusl-arm64": "1.2.4", "@img/sharp-libvips-linuxmusl-x64": "1.2.4", "@img/sharp-linux-arm": "0.34.5", "@img/sharp-linux-arm64": "0.34.5", "@img/sharp-linux-ppc64": "0.34.5", "@img/sharp-linux-riscv64": "0.34.5", "@img/sharp-linux-s390x": "0.34.5", "@img/sharp-linux-x64": "0.34.5", "@img/sharp-linuxmusl-arm64": "0.34.5", "@img/sharp-linuxmusl-x64": "0.34.5", "@img/sharp-wasm32": "0.34.5", "@img/sharp-win32-arm64": "0.34.5", "@img/sharp-win32-ia32": "0.34.5", "@img/sharp-win32-x64": "0.34.5" } }, "sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg=="],
+
"shebang-command": ["shebang-command@2.0.0", "", { "dependencies": { "shebang-regex": "^3.0.0" } }, "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA=="],
+
+
"shebang-regex": ["shebang-regex@3.0.0", "", {}, "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A=="],
+
+
"side-channel": ["side-channel@1.1.0", "", { "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" } }, "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw=="],
+
+
"side-channel-list": ["side-channel-list@1.0.0", "", { "dependencies": { "es-errors": "^1.3.0", "object-inspect": "^1.13.3" } }, "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA=="],
+
+
"side-channel-map": ["side-channel-map@1.0.1", "", { "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", "get-intrinsic": "^1.2.5", "object-inspect": "^1.13.3" } }, "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA=="],
+
+
"side-channel-weakmap": ["side-channel-weakmap@1.0.2", "", { "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" } }, "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A=="],
+
+
"signal-exit": ["signal-exit@3.0.7", "", {}, "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ=="],
+
+
"slice-ansi": ["slice-ansi@3.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "astral-regex": "^2.0.0", "is-fullwidth-code-point": "^3.0.0" } }, "sha512-pSyv7bSTC7ig9Dcgbw9AuRNUb5k5V6oDudjZoMBSr13qpLBG7tB+zgCkARjq7xIUgdz5P1Qe8u+rSGdouOOIyQ=="],
+
"source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="],
+
"sshpk": ["sshpk@1.18.0", "", { "dependencies": { "asn1": "~0.2.3", "assert-plus": "^1.0.0", "bcrypt-pbkdf": "^1.0.0", "dashdash": "^1.12.0", "ecc-jsbn": "~0.1.1", "getpass": "^0.1.1", "jsbn": "~0.1.0", "safer-buffer": "^2.0.2", "tweetnacl": "~0.14.0" }, "bin": { "sshpk-conv": "bin/sshpk-conv", "sshpk-sign": "bin/sshpk-sign", "sshpk-verify": "bin/sshpk-verify" } }, "sha512-2p2KJZTSqQ/I3+HX42EpYOa2l3f8Erv8MWKsy2I9uf4wA7yFIkXRffYdsx86y6z4vHtV8u7g+pPlr8/4ouAxsQ=="],
+
+
"string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="],
+
+
"strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
+
+
"strip-final-newline": ["strip-final-newline@2.0.0", "", {}, "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA=="],
+
"styled-jsx": ["styled-jsx@5.1.6", "", { "dependencies": { "client-only": "0.0.1" }, "peerDependencies": { "react": ">= 16.8.0 || 17.x.x || ^18.0.0-0 || ^19.0.0-0" } }, "sha512-qSVyDTeMotdvQYoHWLNGwRFJHC+i+ZvdBRYosOFgC+Wg1vx4frN2/RG/NA7SYqqvKNLf39P2LSRA2pu6n0XYZA=="],
+
"supports-color": ["supports-color@8.1.1", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q=="],
+
+
"systeminformation": ["systeminformation@5.27.7", "", { "os": "!aix", "bin": { "systeminformation": "lib/cli.js" } }, "sha512-saaqOoVEEFaux4v0K8Q7caiauRwjXC4XbD2eH60dxHXbpKxQ8kH9Rf7Jh+nryKpOUSEFxtCdBlSUx0/lO6rwRg=="],
+
+
"throttleit": ["throttleit@1.0.1", "", {}, "sha512-vDZpf9Chs9mAdfY046mcPt8fg5QSZr37hEH4TXYBnDF+izxgrbRGUAAaBvIk/fJm9aOFCGFd1EsNg5AZCbnQCQ=="],
+
+
"through": ["through@2.3.8", "", {}, "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg=="],
+
+
"tldts": ["tldts@6.1.86", "", { "dependencies": { "tldts-core": "^6.1.86" }, "bin": { "tldts": "bin/cli.js" } }, "sha512-WMi/OQ2axVTf/ykqCQgXiIct+mSQDFdH2fkwhPwgEwvJ1kSzZRiinb0zF2Xb8u4+OqPChmyI6MEu4EezNJz+FQ=="],
+
+
"tldts-core": ["tldts-core@6.1.86", "", {}, "sha512-Je6p7pkk+KMzMv2XXKmAE3McmolOQFdxkKw0R8EYNr7sELW46JqnNeTX8ybPiQgvg1ymCoF8LXs5fzFaZvJPTA=="],
+
+
"tmp": ["tmp@0.2.5", "", {}, "sha512-voyz6MApa1rQGUxT3E+BK7/ROe8itEx7vD8/HEvt4xwXucvQ5G5oeEiHkmHZJuBO21RpOf+YYm9MOivj709jow=="],
+
+
"tough-cookie": ["tough-cookie@5.1.2", "", { "dependencies": { "tldts": "^6.1.32" } }, "sha512-FVDYdxtnj0G6Qm/DhNPSb8Ju59ULcup3tuJxkFb5K8Bv2pUXILbf0xZWU8PX8Ov19OXljbUyveOFwRMwkXzO+A=="],
+
+
"tree-kill": ["tree-kill@1.2.2", "", { "bin": { "tree-kill": "cli.js" } }, "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A=="],
+
"tsconfck": ["tsconfck@3.1.6", "", { "peerDependencies": { "typescript": "^5.0.0" }, "optionalPeers": ["typescript"], "bin": { "tsconfck": "bin/tsconfck.js" } }, "sha512-ks6Vjr/jEw0P1gmOVwutM3B7fWxoWBL2KRDb1JfqGVawBmO5UsvmWOQFGHBPl5yxYz4eERr19E6L7NMv+Fej4w=="],
"tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
+
"tunnel-agent": ["tunnel-agent@0.6.0", "", { "dependencies": { "safe-buffer": "^5.0.1" } }, "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w=="],
+
+
"tweetnacl": ["tweetnacl@0.14.5", "", {}, "sha512-KXXFFdAbFXY4geFIwoyNK+f5Z1b7swfXABfL7HXCmoIWMKU3dmS26672A4EeQtDzLKy7SXmfBu51JolvEKwtGA=="],
+
+
"type-fest": ["type-fest@0.8.1", "", {}, "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA=="],
+
"undici": ["undici@7.16.0", "", {}, "sha512-QEg3HPMll0o3t2ourKwOeUAZ159Kn9mx5pnzHRQO8+Wixmh88YdZRiIwat0iNzNNXn0yoEtXJqFpyW7eM8BV7g=="],
"undici-types": ["undici-types@7.16.0", "", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="],
+
"universalify": ["universalify@2.0.1", "", {}, "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw=="],
+
+
"untildify": ["untildify@4.0.0", "", {}, "sha512-KK8xQ1mkzZeg9inewmFVDNkg3l5LUhoq9kN6iWYB/CC9YMG8HA+c1Q8HwDe6dEX7kErrEVNVBO3fWsVq5iDgtw=="],
+
+
"uuid": ["uuid@8.3.2", "", { "bin": { "uuid": "dist/bin/uuid" } }, "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg=="],
+
+
"verror": ["verror@1.10.0", "", { "dependencies": { "assert-plus": "^1.0.0", "core-util-is": "1.0.2", "extsprintf": "^1.2.0" } }, "sha512-ZZKSmDAEFOijERBLkmYfJ+vmk3w+7hOLYDNkRCuRuMJGEmqYNCNLyBBFwWKVMhfwaEF3WOd0Zlw86U/WC/+nYw=="],
+
"vite-tsconfig-paths": ["vite-tsconfig-paths@5.1.4", "", { "dependencies": { "debug": "^4.1.1", "globrex": "^0.1.2", "tsconfck": "^3.0.3" }, "peerDependencies": { "vite": "*" }, "optionalPeers": ["vite"] }, "sha512-cYj0LRuLV2c2sMqhqhGpaO3LretdtMn/BVX4cPLanIZuwwrkVl+lK84E/miEXkCHWXuq65rhNN4rXsBcOB3S4w=="],
"whatwg-encoding": ["whatwg-encoding@3.1.1", "", { "dependencies": { "iconv-lite": "0.6.3" } }, "sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ=="],
"whatwg-mimetype": ["whatwg-mimetype@4.0.0", "", {}, "sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg=="],
+
"which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="],
+
+
"wrap-ansi": ["wrap-ansi@7.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q=="],
+
+
"wrappy": ["wrappy@1.0.2", "", {}, "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="],
+
+
"yauzl": ["yauzl@2.10.0", "", { "dependencies": { "buffer-crc32": "~0.2.3", "fd-slicer": "~1.1.0" } }, "sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g=="],
+
+
"@cypress/xvfb/debug": ["debug@3.2.7", "", { "dependencies": { "ms": "^2.1.1" } }, "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ=="],
+
+
"ansi-escapes/type-fest": ["type-fest@0.21.3", "", {}, "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w=="],
+
+
"chalk/supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="],
+
"htmlparser2/entities": ["entities@6.0.1", "", {}, "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g=="],
+
+
"log-update/slice-ansi": ["slice-ansi@4.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "astral-regex": "^2.0.0", "is-fullwidth-code-point": "^3.0.0" } }, "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ=="],
+
+
"log-update/wrap-ansi": ["wrap-ansi@6.2.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA=="],
"parse5/entities": ["entities@6.0.1", "", {}, "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g=="],
}
+56
cypress/component/generator.cy.js
···
···
+
import baseFonts from '../../src/lib/baseFonts'
+
import titleFonts from '../../src/lib/titleFonts'
+
import themes from '../../src/lib/themes'
+
import ao3CanonicalUrls from "../../src/lib/ao3Canonical"
+
import Page from "../../src/app/generator/page"
+
import styles from "../../src/app/generator/page.module.css"
+
+
describe('<Page />', () => {
+
it('displays an alphabetized list of base font options using the list in the baseFont lib', () => {
+
cy.mount('<Page />')
+
cy.get('select#baseFont > option').should('have.length', Object.keys(baseFonts).length)
+
const sortedFontKeys = Object.keys(baseFonts).sort()
+
const sortedFontValues = Object.values(baseFonts).sort((a, b) => {
+
if (a.displayName > b.displayName) {
+
return 1;
+
}
+
return -1;
+
})
+
cy.get('select#baseFont > option').each(($opt, i) => {
+
expect($opt).to.contain(sortedFontValues[i].displayName)
+
expect($opt).to.have.value(sortedFontKeys[i])
+
})
+
})
+
+
it('displays an alphabetized list of title font options using the list in the titleFont lib', () => {
+
cy.mount('<Page />')
+
cy.get('select#titleFont > option').should('have.length', Object.keys(titleFonts).length)
+
const sortedTitleFontKeys = Object.keys(titleFonts).sort()
+
const sortedTitleFontValues = Object.values(titleFonts).sort((a, b) => {
+
if (a.displayName > b.displayName) {
+
return 1
+
}
+
return -1
+
})
+
cy.get('select#titleFont > option').each(($opt, i) => {
+
expect($opt).to.contain(sortedTitleFontValues[i].displayName)
+
expect($opt).to.have.value(sortedTitleFontKeys[i])
+
})
+
})
+
+
it('displays an alphabetized list of themes using the list in the themes lib', () => {
+
cy.mount('<Page />')
+
cy.get('select#theme > option').should('have.length', Object.keys(themes).length)
+
const sortedThemeKeys = Object.keys(themes).sort()
+
const sortedThemeValues = Object.values(themes).sort((a, b) => {
+
if (a.name > b.name) {
+
return 1
+
}
+
return -1
+
})
+
cy.get('select#theme > option').each(($opt, i) => {
+
expect($opt).to.contain(sortedThemeValues[i].name)
+
expect($opt).to.have.value(sortedThemeKeys[i])
+
})
+
})
+
})
cypress/e2e/app.cy.js

This is a binary file and will not be displayed.

+16
cypress/e2e/navigation.cy.js
···
···
+
describe('Navigation', () => {
+
it('should navigate to the generator page', () => {
+
// Start from the index page
+
cy.visit('http://localhost:3001')
+
+
// Find a link with an href attribute containing "generator" and click it
+
cy.get('a[href*="/generator"]').click()
+
+
// The new url should include "/generator"
+
cy.url().should('include', '/generator')
+
+
// The new page should contain an form with the ID "geerator"
+
cy.get('form').should('have.id', 'generator')
+
+
})
+
})
+14
cypress/support/component-index.html
···
···
+
<html lang="en">
+
<head>
+
</head>
+
<body>
+
<div id="page">
+
<h1>
+
<a href="/">fixAO3</a>
+
<a href="/generator" id="generator-link">โœจ generator โœจ</a>
+
</h1>
+
</div>
+
<script data-goatcounter="https://fixao3.goatcounter.com/count"
+
async src="//gc.zgo.at/count.js"></script>
+
</body>
+
</html>
+3
cypress/support/component.js
···
···
+
import '@fontsource-variable/bricolage-grotesque'
+
import '@fontsource/stack-sans-notch'
+
import "../../src/app/globals.css"
cypress/support/e2e.cy.js

This is a binary file and will not be displayed.

+15
cypress.config.js
···
···
+
import { defineConfig } from 'cypress'
+
+
export default defineConfig({
+
e2e: {
+
setupNodeEvents(on, config) {},
+
supportFile: false
+
},
+
component: {
+
supportFile: false,
+
devServer: {
+
framework: 'next',
+
bundler: 'webpack',
+
},
+
},
+
})
-3
next.config.mjs
···
/** @type {import('next').NextConfig} */
const nextConfig = {
/* config options here */
-
turbopack: {
-
root: './'
-
}
};
export default nextConfig;
···
/** @type {import('next').NextConfig} */
const nextConfig = {
/* config options here */
};
export default nextConfig;
+8 -5
package.json
···
"build": "--bun next build",
"start": "--bun next start",
"lint": "--bun biome check",
-
"format": "--bun biome format --write"
},
"dependencies": {
"@fontsource-variable/bricolage-grotesque": "^5.2.10",
"@fontsource/stack-sans-notch": "^5.2.1",
"@fujocoded/ao3.js": "^0.22.1",
-
"@hono/vite-build": "^1.7.0",
"@hono/vite-dev-server": "^0.23.0",
"fauxdom": "^1.2.2",
-
"hono": "^4.10.4",
-
"next": "16.0.2",
"react": "19.2.0",
"react-dom": "19.2.0"
},
"devDependencies": {
"@biomejs/biome": "2.2.0",
-
"@types/bun": "latest"
}
}
···
"build": "--bun next build",
"start": "--bun next start",
"lint": "--bun biome check",
+
"format": "--bun biome format --write",
+
"cypress:open": "cypress open"
},
"dependencies": {
"@fontsource-variable/bricolage-grotesque": "^5.2.10",
"@fontsource/stack-sans-notch": "^5.2.1",
"@fujocoded/ao3.js": "^0.22.1",
+
"@hono/vite-build": "^1.8.0",
"@hono/vite-dev-server": "^0.23.0",
"fauxdom": "^1.2.2",
+
"hono": "^4.10.8",
+
"next": "16.0.7",
"react": "19.2.0",
"react-dom": "19.2.0"
},
"devDependencies": {
"@biomejs/biome": "2.2.0",
+
"@types/bun": "latest",
+
"baseline-browser-mapping": "^2.9.5",
+
"cypress": "^15.7.1"
}
}
src/app/api/series/[seriesId]/preview/route.js

This is a binary file and will not be displayed.

+12 -1
src/app/api/series/[seriesId]/route.js
···
import { getSeries } from "@fujocoded/ao3.js"
-
export async function GET(_req, ctx) {
const { seriesId } = await ctx.params
const series = await getSeries({seriesId: seriesId})
return Response.json(series)
}
···
import { getSeries } from "@fujocoded/ao3.js"
+
import { setArchiveBaseUrl, resetArchiveBaseUrl } from "@fujocoded/ao3.js/urls"
+
export const dynamic = 'force-static'
+
+
export async function GET(req, ctx) {
const { seriesId } = await ctx.params
+
const { archive } = await req.nextUrl.searchParams
+
console.log(seriesId)
+
console.log(archive)
+
const domain = await req.nextUrl.hostname
+
const subdomain = domain.split(".").length > 2 ? domain.split(".")[0] : null
+
if (subdomain) setArchiveBaseUrl('https://'+subdomain)
+
if (archive) setArchiveBaseUrl(archive)
const series = await getSeries({seriesId: seriesId})
+
if (archive || subdomain) resetArchiveBaseUrl()
return Response.json(series)
}
src/app/api/works/[workId]/chapters/[chapterId]/preview/route.js

This is a binary file and will not be displayed.

+12 -1
src/app/api/works/[workId]/chapters/[chapterId]/route.js
···
import { getWork } from "@fujocoded/ao3.js"
-
export async function GET(_req, ctx) {
const { workId, chapterId } = await ctx.params
const work = await getWork({workId: workId, chapterId: chapterId})
return Response.json(work)
}
···
import { getWork } from "@fujocoded/ao3.js"
+
import { setArchiveBaseUrl, resetArchiveBaseUrl } from "@fujocoded/ao3.js/urls"
+
import siteMap from "@/lib/siteMap.js"
+
+
export const dynamic = 'force-static'
+
export async function GET(req, ctx) {
const { workId, chapterId } = await ctx.params
+
const params = await req.nextUrl.searchParams
+
const domain = await req.nextUrl.hostname
+
const subdomain = domain.split(".").length > 2 ? domain.split(".")[0] : null
+
const archive = params ? params.get('archive') : null
+
if (subdomain) setArchiveBaseUrl('https://'+siteMap[subdomain])
+
if (archive) setArchiveBaseUrl(archive)
const work = await getWork({workId: workId, chapterId: chapterId})
+
if (archive || subdomain) resetArchiveBaseUrl()
return Response.json(work)
}
+9 -1
src/app/api/works/[workId]/route.js
···
import { getWork } from "@fujocoded/ao3.js"
-
export async function GET(_req, ctx) {
const { workId } = await ctx.params
const work = await getWork({workId: workId})
return Response.json(work)
}
···
import { getWork } from "@fujocoded/ao3.js"
+
import { setArchiveBaseUrl, getArchiveBaseUrl, resetArchiveBaseUrl } from "@fujocoded/ao3.js/urls"
+
export async function GET(req, ctx) {
const { workId } = await ctx.params
+
const params = await req.nextUrl.searchParams
+
const domain = await req.nextUrl.hostname
+
const subdomain = (domain.split(".").length > 2) ? domain.split(".")[0] : null
+
const archive = params ? params.get('archive') : null
+
if (subdomain) setArchiveBaseUrl('https://'+siteMap[subdomain])
+
if (archive) setArchiveBaseUrl(archive)
const work = await getWork({workId: workId})
+
if (archive || subdomain) resetArchiveBaseUrl()
return Response.json(work)
}
+48 -35
src/app/generator/page.js
···
"use client"
import { useEffect, useState } from "react"
import themes from "@/lib/themes.js"
import baseFonts from "@/lib/baseFonts.js"
import titleFonts from "@/lib/titleFonts.js"
-
import styles from "./page.module.css"
export default function Generator() {
const [url, setUrl] = useState('')
const [workData, setWorkData] = useState(null)
const [addr, setAddr] = useState('')
const [imgData, setImgData] = useState(null)
-
const [props, setProps] = useState({
-
theme: 'ao3',
-
baseFont: 'bricolagegrotesque',
-
titleFont: 'stacksansnotch',
-
category: true,
-
rating: true,
-
warnings: false,
-
charTags: false,
-
relTags: false,
-
freeTags: false,
-
summary: true,
-
wordcount: true,
-
chapters: true,
-
postedAt: true,
-
updatedAt: false,
-
summaryType: 'basic',
-
customSummary: ''
-
})
const updateProp = (name, value) => {
const newProps = props
···
}
const updateData = async () => {
const workMatch = /\/works\/(?<workId>[0-9]+)(?:\/chapters\/(?<chapterId>[0-9]+))?$/
const seriesMatch = /\/series\/(?<seriesId>[0-9]+)$/
if (workMatch.test(url)) {
const match = url.match(workMatch)
-
const resp = match.groups.chapterId ? await fetch(`/api/works/${match.groups.workId}/chapters/${match.groups.chapterId}`) : await fetch(`/api/works/${match.groups.workId}`)
const data = await resp.json()
setAddr(match.groups.chapterId ? `works/${match.groups.workId}/chapters/${match.groups.chapterId}` : `works/${match.groups.workId}`)
setWorkData(data)
} else if (seriesMatch.test(url)) {
const match = url.match(seriesMatch)
-
const resp = await fetch(`/api/series/${match.groups.seriesId}`)
const data = await resp.json()
setAddr(`series/${match.groups.seriesId}`)
setWorkData(data)
···
useEffect(() => {
const fn = async () => {
if (!addr) return;
const params = new URLSearchParams(props)
-
const image = await fetch(`/${addr}/preview?${params.toString()}`)
if (image.status !== 200) return;
const imageBlob = await image.blob()
const reader = new FileReader()
···
<li><label><input type="checkbox" name="features[]" value="updatedAt" defaultChecked={props.updatedAt} onChange={e => updateProp(e.target.value, e.target.checked)} /> Updated Date</label></li>
</ul>
</div>
-
<div className="input-field">
-
<label htmlFor="summaryOptions">Summary Type</label>
-
<ul>
-
<li><label><input type="radio" name="summaryType" value="basic" defaultChecked={props.summaryType === 'basic'} onChange={e => updateProp(e.target.name, e.target.value)} /> Story Summary</label></li>
-
<li><label><input type="radio" name="summaryType" defaultChecked={props.summaryType === 'chapter'} value="chapter" onChange={e => updateProp(e.target.name, e.target.value)} /> Chapter Summary (if available)</label></li>
-
<li><label><input type="radio" name="summaryType" defaultChecked={props.summaryType === 'custom'} value="custom" onChange={e => updateProp(e.target.name, e.target.value)} /> Custom Summary</label></li>
-
</ul>
-
{props.summaryType === 'custom' && (
-
<div className="input-field">
-
<label htmlFor="customSummary">Custom Summary</label>
-
<textarea name="customSummary" id="customSummary" onChange={e => updateProp(e.target.name, e.target.value)}></textarea>
-
</div>
-
)}
</div>
</div>
</details>
···
"use client"
import { useEffect, useState } from "react"
+
import { setArchiveBaseUrl, resetArchiveBaseUrl } from "@fujocoded/ao3.js/urls"
import themes from "@/lib/themes.js"
import baseFonts from "@/lib/baseFonts.js"
import titleFonts from "@/lib/titleFonts.js"
+
import defaults from "@/lib/ogdefaults.js"
+
import ao3CanonicalUrls from "@/lib/ao3Canonical.js"
export default function Generator() {
const [url, setUrl] = useState('')
const [workData, setWorkData] = useState(null)
const [addr, setAddr] = useState('')
const [imgData, setImgData] = useState(null)
+
const [props, setProps] = useState(defaults)
+
const [domain, setDomain] = useState('')
const updateProp = (name, value) => {
const newProps = props
···
}
const updateData = async () => {
+
if (url === '') return
const workMatch = /\/works\/(?<workId>[0-9]+)(?:\/chapters\/(?<chapterId>[0-9]+))?$/
const seriesMatch = /\/series\/(?<seriesId>[0-9]+)$/
+
const baseurl = /(?<domain>https:\/\/[a-z0-9\-\.]+)\//
+
const domainMatch = url.match(baseurl)
+
if (!domainMatch) return
+
setDomain(domainMatch.groups.domain)
+
const domainParam = domain && !ao3CanonicalUrls.includes(domain) ? `?archive=${domain}` : ''
if (workMatch.test(url)) {
const match = url.match(workMatch)
+
const resp = match.groups.chapterId ? await fetch(`/api/works/${match.groups.workId}/chapters/${match.groups.chapterId}${domainParam}`) : await fetch(`/api/works/${match.groups.workId}${domainParam}`)
+
if (!resp) return
const data = await resp.json()
setAddr(match.groups.chapterId ? `works/${match.groups.workId}/chapters/${match.groups.chapterId}` : `works/${match.groups.workId}`)
setWorkData(data)
} else if (seriesMatch.test(url)) {
const match = url.match(seriesMatch)
+
const resp = await fetch(`/api/series/${match.groups.seriesId}${domainParam}`)
+
if (!resp) return
const data = await resp.json()
setAddr(`series/${match.groups.seriesId}`)
setWorkData(data)
···
useEffect(() => {
const fn = async () => {
if (!addr) return;
+
if (workData.locked) {
+
const image = await fetch('/locked')
+
if (image.status !== 200) return;
+
const imageBlob = await image.blob()
+
const reader = new FileReader()
+
reader.onloadend = () => {
+
setImgData(reader.result)
+
}
+
return
+
}
const params = new URLSearchParams(props)
+
const image = await fetch(`/${addr}/preview?${params.toString()}&archive=${domain}`)
if (image.status !== 200) return;
const imageBlob = await image.blob()
const reader = new FileReader()
···
<li><label><input type="checkbox" name="features[]" value="updatedAt" defaultChecked={props.updatedAt} onChange={e => updateProp(e.target.value, e.target.checked)} /> Updated Date</label></li>
</ul>
</div>
+
<div className="col">
+
<div className="input-field">
+
<label htmlFor="displayOptions">Display Options</label>
+
<ul>
+
<li><label><input type="checkbox" name="uppercaseTitle" value="uppercaseTitle" defaultChecked={props.uppercaseTitle} onChange={e => updateProp(e.target.value, e.target.checked)} /> Uppercase Title?</label></li>
+
<li><label><input type="checkbox" name="uppercaseChapterName" value="uppercaseChapterName" defaultChecked={props.uppercaseChapterName} onChange={e => updateProp(e.target.value, e.target.checked)} /> Uppercase Chapter Name?</label></li>
+
</ul>
+
</div>
+
<div className="input-field">
+
<label htmlFor="summaryOptions">Summary Type</label>
+
<ul>
+
<li><label><input type="radio" name="summaryType" value="basic" defaultChecked={props.summaryType === 'basic'} onChange={e => updateProp(e.target.name, e.target.value)} /> Story Summary</label></li>
+
<li><label><input type="radio" name="summaryType" defaultChecked={props.summaryType === 'chapter'} value="chapter" onChange={e => updateProp(e.target.name, e.target.value)} /> Chapter Summary (if available)</label></li>
+
<li><label><input type="radio" name="summaryType" defaultChecked={props.summaryType === 'custom'} value="custom" onChange={e => updateProp(e.target.name, e.target.value)} /> Custom Summary</label></li>
+
</ul>
+
{props.summaryType === 'custom' && (
+
<div className="input-field">
+
<label htmlFor="customSummary">Custom Summary</label>
+
<textarea name="customSummary" id="customSummary" onBlur={e => updateProp(e.target.name, e.target.value)}></textarea>
+
</div>
+
)}
+
</div>
</div>
</div>
</details>
+13
src/app/locked/route.js
···
···
+
import OGImageLocked from "@/lib/ogimagelocked.js"
+
+
export const size = {
+
width: 1600,
+
height: 900,
+
}
+
+
export const contentType = 'image/png'
+
+
export async function GET(req, _ctx) {
+
const props = await req.nextUrl.searchParams
+
return OGImageLocked({theme: props.has('theme') ? props.get('theme') : process.env.DEFAULT_THEME})
+
}
+4 -22
src/app/series/[seriesId]/opengraph-image.jsx
···
import { getSeries } from "@fujocoded/ao3.js"
import sanitizeData from "@/lib/sanitizeData.js"
import OGImage from "@/lib/ogimage.js"
import baseFonts from "@/lib/baseFonts.js"
import titleFonts from "@/lib/titleFonts.js"
export const size = {
width: 1600,
height: 900,
}
export const alt = 'fixAO3'
-
export const contentType = 'image/webp'
-
const defaults = new URLSearchParams({
-
theme: 'ao3',
-
baseFont: 'bricolagegrotesque',
-
titleFont: 'stacksansnotch',
-
category: true,
-
rating: true,
-
warnings: false,
-
charTags: false,
-
relTags: false,
-
freeTags: false,
-
summary: true,
-
wordcount: true,
-
chapters: true,
-
postedAt: true,
-
updatedAt: false,
-
summaryType: 'basic',
-
customSummary: ''
-
})
-
export default async function Image({params, searchParams}) {
const { seriesId } = await params
const addr = `series/${seriesId}`
const data = await getSeries({seriesId: seriesId})
-
const imageParams = await sanitizeData({type: 'series', data: data, props: defaults})
const theme = imageParams.theme
-
console.log(theme)
const baseFont = baseFonts[imageParams.baseFont].displayName
const titleFont = titleFonts[imageParams.titleFont].displayName
const opts = imageParams.opts
···
import { getSeries } from "@fujocoded/ao3.js"
import sanitizeData from "@/lib/sanitizeData.js"
import OGImage from "@/lib/ogimage.js"
+
import OGImageLocked from "@/lib/ogimagelocked.js"
import baseFonts from "@/lib/baseFonts.js"
import titleFonts from "@/lib/titleFonts.js"
+
import defaults from "@/lib/ogdefaults.js"
export const size = {
width: 1600,
height: 900,
}
export const alt = 'fixAO3'
export const contentType = 'image/webp'
export default async function Image({params, searchParams}) {
const { seriesId } = await params
const addr = `series/${seriesId}`
const data = await getSeries({seriesId: seriesId})
+
if (data.locked) return OGImageLocked({theme: theme})
+
const imageParams = await sanitizeData({type: 'series', data: data, props: new URLSearchParams(defaults)})
const theme = imageParams.theme
const baseFont = baseFonts[imageParams.baseFont].displayName
const titleFont = titleFonts[imageParams.titleFont].displayName
const opts = imageParams.opts
+7 -4
src/app/series/[seriesId]/page.js
···
import { getSeries } from "@fujocoded/ao3.js"
export async function generateMetadata({ params, _searchParams }, _parent) {
// read route params
···
const fandoms = series.works.map(w => w.fandoms).reduce((a,b) => { return a.concat(b) }).filter((f, i, arr) => arr.indexOf(f) === i)
const fandomString = fandoms.length > 4 ? fandoms.slice(0, 4).join(", ")+" (+"+(fandoms.length - 4)+")" : (fandoms.length > 1 ? fandoms.slice(0, -1).join(", ")+" & "+fandoms.slice(-1)[0] : fandoms[0])
const title = `${series.name} by ${authorString} - ${fandomString} (${series.workCount} works)`
-
const description = `${series.description.replace("<br />", "\n").replace(/<[^>]>/g, "")}`
const addr = `series/${seriesId}`
return {
title: title,
description: description,
-
openGraph: {
-
description: description
-
}
}
}
···
import { getSeries } from "@fujocoded/ao3.js"
+
import DOM from "fauxdom"
export async function generateMetadata({ params, _searchParams }, _parent) {
// read route params
···
const fandoms = series.works.map(w => w.fandoms).reduce((a,b) => { return a.concat(b) }).filter((f, i, arr) => arr.indexOf(f) === i)
const fandomString = fandoms.length > 4 ? fandoms.slice(0, 4).join(", ")+" (+"+(fandoms.length - 4)+")" : (fandoms.length > 1 ? fandoms.slice(0, -1).join(", ")+" & "+fandoms.slice(-1)[0] : fandoms[0])
const title = `${series.name} by ${authorString} - ${fandomString} (${series.workCount} works)`
+
const summaryDOM = new DOM(summary, {decodeEntities: true});
+
const description = summaryDOM.innerHTML.replace(/\<br(?: \/)?\>/g, "\n").replace(
+
/(<([^>]+)>)/ig,
+
"",
+
)
const addr = `series/${seriesId}`
return {
title: title,
description: description,
+
metadataBase: new URL('https://'+process.env.DOMAIN)
}
}
+10 -4
src/app/series/[seriesId]/preview/route.js
···
import { getSeries } from "@fujocoded/ao3.js"
import sanitizeData from "@/lib/sanitizeData.js"
import OGImage from "@/lib/ogimage.js"
import baseFonts from "@/lib/baseFonts.js"
import titleFonts from "@/lib/titleFonts.js"
export const size = {
width: 1600,
height: 900,
}
-
export const contentType = 'image/webp'
export async function GET(req, ctx) {
const { seriesId } = await ctx.params
-
const props = await req.nextUrl.searchParams
const addr = `series/${seriesId}`
-
const data = await getSeries({seriesId: seriesId})
-
const imageParams = await sanitizeData({type: 'series', data: data, props: props})
const theme = imageParams.theme
const baseFont = baseFonts[imageParams.baseFont].displayName
const titleFont = titleFonts[imageParams.titleFont].displayName
···
import { getSeries } from "@fujocoded/ao3.js"
+
import { setArchiveBaseUrl, resetArchiveBaseUrl } from "@fujocoded/ao3.js/urls"
+
import querystring from 'node:querystring'
import sanitizeData from "@/lib/sanitizeData.js"
import OGImage from "@/lib/ogimage.js"
import baseFonts from "@/lib/baseFonts.js"
import titleFonts from "@/lib/titleFonts.js"
+
import ao3CanonicalUrls from "@/lib/ao3Canonical.js"
export const size = {
width: 1600,
height: 900,
}
+
export const contentType = 'image/png'
export async function GET(req, ctx) {
const { seriesId } = await ctx.params
+
const p = await req.nextUrl.searchParams
+
const props = querystring.parse(p.toString())
const addr = `series/${seriesId}`
+
const domainParam = p && p.has('archive') && !ao3CanonicalUrls.includes(p.get('archive')) ? `?archive=${p.get('archive')}` : ''
+
const work = await fetch(`http://${process.env.DOMAIN}/api/series/${seriesId}${domainParam}`)
+
const data = await work.json()
+
const imageParams = await sanitizeData({type: 'series', data: data, props: p})
const theme = imageParams.theme
const baseFont = baseFonts[imageParams.baseFont].displayName
const titleFont = titleFonts[imageParams.titleFont].displayName
+4 -21
src/app/works/[workId]/chapters/[chapterId]/opengraph-image.jsx
···
import { getWork } from "@fujocoded/ao3.js"
import sanitizeData from "@/lib/sanitizeData.js"
import OGImage from "@/lib/ogimage.js"
import baseFonts from "@/lib/baseFonts.js"
import titleFonts from "@/lib/titleFonts.js"
export const size = {
width: 1600,
height: 900,
}
export const alt = 'fixAO3'
-
export const contentType = 'image/webp'
-
const defaults = new URLSearchParams({
-
theme: 'ao3',
-
baseFont: 'bricolagegrotesque',
-
titleFont: 'stacksansnotch',
-
category: true,
-
rating: true,
-
warnings: false,
-
charTags: false,
-
relTags: false,
-
freeTags: false,
-
summary: true,
-
wordcount: true,
-
chapters: true,
-
postedAt: true,
-
updatedAt: false,
-
summaryType: 'basic',
-
customSummary: ''
-
})
-
export default async function Image({params, searchParams}) {
const { workId, chapterId } = await params
const addr = `works/${workId}/chapters/${chapterId}`
const data = await getWork({workId: workId, chapterId: chapterId})
-
const imageParams = await sanitizeData({type: 'work', data: data, props: defaults})
const theme = imageParams.theme
const baseFont = baseFonts[imageParams.baseFont].displayName
const titleFont = titleFonts[imageParams.titleFont].displayName
···
import { getWork } from "@fujocoded/ao3.js"
import sanitizeData from "@/lib/sanitizeData.js"
import OGImage from "@/lib/ogimage.js"
+
import OGImageLocked from "@/lib/ogimagelocked.js"
import baseFonts from "@/lib/baseFonts.js"
import titleFonts from "@/lib/titleFonts.js"
+
import defaults from "@/lib/ogdefaults.js"
export const size = {
width: 1600,
height: 900,
}
export const alt = 'fixAO3'
export const contentType = 'image/webp'
export default async function Image({params, searchParams}) {
const { workId, chapterId } = await params
const addr = `works/${workId}/chapters/${chapterId}`
const data = await getWork({workId: workId, chapterId: chapterId})
+
if (data.locked) return OGImageLocked({theme: process.env.DEFAULT_THEME})
+
const imageParams = await sanitizeData({type: 'work', data: data, props: new URLSearchParams(defaults)})
const theme = imageParams.theme
const baseFont = baseFonts[imageParams.baseFont].displayName
const titleFont = titleFonts[imageParams.titleFont].displayName
+27 -8
src/app/works/[workId]/chapters/[chapterId]/page.js
···
import { getWork } from "@fujocoded/ao3.js"
-
export async function generateMetadata({ params, _searchParams }, _parent) {
// read route params
const { workId, chapterId } = await params
-
const work = await getWork({workId: workId, chapterId: chapterId})
const authors = work.authors.map((a) => {
if (a.anonymous) {
return "Anonymous"
···
const fandoms = work.fandoms
const fandomString = fandoms.length > 4 ? fandoms.slice(0, 4).join(", ")+" (+"+(fandoms.length - 4)+")" : (fandoms.length > 1 ? fandoms.slice(0, -1).join(", ")+" & "+fandoms.slice(-1)[0] : fandoms[0])
const title = `${work.title} by ${authorString}, Chapter ${work.chapterInfo.index}${work.chapterInfo.name ? ": "+work.chapterInfo.name : ''} - ${fandomString}`
-
const summary = work.chapterInfo && work.chapterInfo.summary ? work.chapterInfo.summary : work.summary
-
const description = `${summary.replace("<br />", "\n").replace(/<[^>]>/g, "")}`
const addr = `works/${workId}/chapters/${chapterId}`
return {
title: title,
description: description,
-
openGraph: {
-
description: description
-
}
}
}
···
const { workId, chapterId } = await params
return (
<div dangerouslySetInnerHTML={{__html: `<script type="text/javascript">
-
window.location.replace("https://archiveofourown.org/works/${workId}/chapters/${chapterId}");
</script>`}}></div>
)
}
···
import { getWork } from "@fujocoded/ao3.js"
+
import DOM from "fauxdom"
+
import siteMap from "@/lib/siteMap.js"
+
export async function generateMetadata({ params, _searchParams }, parent) {
// read route params
const { workId, chapterId } = await params
+
const parentData = await parent;
+
const base = parentData.metadataBase.replace('https://', '').replace('/', '')
+
const subdomain = base.split(".").length > 0 ? base.split(".")[0] : null
+
const archive = subdomain && Object.keys(siteMap).includes(subdomain) ? siteMap[subdomain] : null
+
const domainParam = archive ? `?archive=https://${archive}` : ''
+
const data = await fetch(`http://${process.env.DOMAIN}/api/works/${workId}/chapters/${chapterId}${domainParam}`)
+
const work = await data.json()
+
if (work.locked) {
+
return {
+
title: 'Locked Work',
+
description: 'This work is locked to the public. Log in to see it!',
+
metadataBase: new URL('https://'+archive)
+
}
+
}
+
const parentWorkData = await fetch(`http://${process.env.DOMAIN}/api/works/${workId}`)
+
const parentWork = await parentWorkData.json()
const authors = work.authors.map((a) => {
if (a.anonymous) {
return "Anonymous"
···
const fandoms = work.fandoms
const fandomString = fandoms.length > 4 ? fandoms.slice(0, 4).join(", ")+" (+"+(fandoms.length - 4)+")" : (fandoms.length > 1 ? fandoms.slice(0, -1).join(", ")+" & "+fandoms.slice(-1)[0] : fandoms[0])
const title = `${work.title} by ${authorString}, Chapter ${work.chapterInfo.index}${work.chapterInfo.name ? ": "+work.chapterInfo.name : ''} - ${fandomString}`
+
const summary = work.chapterInfo && work.chapterInfo.summary ? work.chapterInfo.summary : parentWork.summary
+
const summaryDOM = new DOM(summary, {decodeEntities: true});
+
const description = summaryDOM.innerHTML.replace(/\<br(?: \/)?\>/g, "\n").replace(
+
/(<([^>]+)>)/ig,
+
"",
+
)
const addr = `works/${workId}/chapters/${chapterId}`
return {
title: title,
description: description,
+
metadataBase: new URL('https://'+base)
}
}
···
const { workId, chapterId } = await params
return (
<div dangerouslySetInnerHTML={{__html: `<script type="text/javascript">
+
window.location.replace("https://archiveofourown.org/works/${workId}");
</script>`}}></div>
)
}
+9 -4
src/app/works/[workId]/chapters/[chapterId]/preview/route.js
···
-
import { getWork } from "@fujocoded/ao3.js"
import sanitizeData from "@/lib/sanitizeData.js"
import OGImage from "@/lib/ogimage.js"
import baseFonts from "@/lib/baseFonts.js"
import titleFonts from "@/lib/titleFonts.js"
export const size = {
width: 1600,
height: 900,
}
-
export const contentType = 'image/webp'
export async function GET(req, ctx) {
const { workId, chapterId } = await ctx.params
const props = await req.nextUrl.searchParams
-
const addr = `works/${workId}/chapters/${chapterId}`
-
const data = await getWork({workId: workId, chapterId: chapterId})
const imageParams = await sanitizeData({type: 'work', data: data, props: props})
const theme = imageParams.theme
const baseFont = baseFonts[imageParams.baseFont].displayName
···
+
import querystring from 'node:querystring'
import sanitizeData from "@/lib/sanitizeData.js"
import OGImage from "@/lib/ogimage.js"
import baseFonts from "@/lib/baseFonts.js"
import titleFonts from "@/lib/titleFonts.js"
+
import siteMap from "@/lib/siteMap.js"
+
import ao3CanonicalUrls from "@/lib/ao3Canonical.js"
export const size = {
width: 1600,
height: 900,
}
+
export const contentType = 'image/png'
export async function GET(req, ctx) {
const { workId, chapterId } = await ctx.params
const props = await req.nextUrl.searchParams
+
const addr = `${props && props.has('archive') ? props.get('archive').replace("https://", "")+"/" : 'archiveofourown.org/'}works/${workId}/chapters/${chapterId}`
+
const domainParam = props && props.has('archive') && !ao3CanonicalUrls.includes(props.get('archive')) ? `?archive=${props.get('archive')}` : ''
+
const subdomain = props && props.has('archive') && Object.values(siteMap).includes(props.get('archive')) ? Object.keys(siteMap)[Object.values(siteMap).indexOf(props.get('archive'))]+'.' : ''
+
const work = await fetch(`http://${subdomain}${process.env.DOMAIN}/api/works/${workId}${domainParam}`)
+
const data = await work.json()
const imageParams = await sanitizeData({type: 'work', data: data, props: props})
const theme = imageParams.theme
const baseFont = baseFonts[imageParams.baseFont].displayName
+5 -22
src/app/works/[workId]/opengraph-image.jsx
···
import { getWork } from "@fujocoded/ao3.js"
import sanitizeData from "@/lib/sanitizeData.js"
import OGImage from "@/lib/ogimage.js"
import baseFonts from "@/lib/baseFonts.js"
import titleFonts from "@/lib/titleFonts.js"
export const size = {
width: 1600,
height: 900,
}
export const alt = 'fixAO3'
-
export const contentType = 'image/webp'
-
const defaults = new URLSearchParams({
-
theme: 'ao3',
-
baseFont: 'bricolagegrotesque',
-
titleFont: 'stacksansnotch',
-
category: true,
-
rating: true,
-
warnings: false,
-
charTags: false,
-
relTags: false,
-
freeTags: false,
-
summary: true,
-
wordcount: true,
-
chapters: true,
-
postedAt: true,
-
updatedAt: false,
-
summaryType: 'basic',
-
customSummary: ''
-
})
-
-
export default async function Image({params}) {
const { workId } = await params
const addr = `works/${workId}`
const data = await getWork({workId: workId})
-
const imageParams = await sanitizeData({type: 'work', data: data, props: defaults})
const theme = imageParams.theme
const baseFont = baseFonts[imageParams.baseFont].displayName
const titleFont = titleFonts[imageParams.titleFont].displayName
···
import { getWork } from "@fujocoded/ao3.js"
import sanitizeData from "@/lib/sanitizeData.js"
import OGImage from "@/lib/ogimage.js"
+
import OGImageLocked from "@/lib/ogimagelocked.js"
import baseFonts from "@/lib/baseFonts.js"
import titleFonts from "@/lib/titleFonts.js"
+
import defaults from "@/lib/ogdefaults.js"
export const size = {
width: 1600,
height: 900,
}
export const alt = 'fixAO3'
export const contentType = 'image/webp'
+
export default async function Image({params, searchParams}) {
const { workId } = await params
const addr = `works/${workId}`
const data = await getWork({workId: workId})
+
if (data.locked) return OGImageLocked({theme: process.env.DEFAULT_THEME})
+
const imageParams = await sanitizeData({type: 'work', data: data, props: new URLSearchParams(defaults)})
const theme = imageParams.theme
const baseFont = baseFonts[imageParams.baseFont].displayName
const titleFont = titleFonts[imageParams.titleFont].displayName
+22 -8
src/app/works/[workId]/page.js
···
import { getWork } from "@fujocoded/ao3.js"
-
export async function generateMetadata({ params, _searchParams }, _parent) {
// read route params
const { workId, chapterId } = await params
const work = await getWork({workId: workId, chapterId: chapterId})
const authors = work.authors.map((a) => {
if (a.anonymous) {
return "Anonymous"
···
const fandomString = fandoms.length > 4 ? fandoms.slice(0, 4).join(", ")+" (+"+(fandoms.length - 4)+")" : (fandoms.length > 1 ? fandoms.slice(0, -1).join(", ")+" & "+fandoms.slice(-1)[0] : fandoms[0])
const title = `${work.title} by ${authorString} - ${fandomString}`
const summary = work.summary
-
const description = `${summary.replace("<br />", "\n").replace(/<[^>]>/g, "")}`
const addr = `works/${workId}`
return {
-
title: title,
-
description: description,
-
openGraph: {
-
description: description
-
}
}
}
···
const { workId, chapterId } = await params
return (
<div dangerouslySetInnerHTML={{__html: `<script type="text/javascript">
-
window.location.replace("https://archiveofourown.org/works/${workId}");
</script>`}}></div>
)
}
···
import { getWork } from "@fujocoded/ao3.js"
+
import DOM from "fauxdom"
+
export async function generateMetadata({ params, _searchParams }, parent) {
// read route params
const { workId, chapterId } = await params
+
const p = await parent
+
console.log(p)
const work = await getWork({workId: workId, chapterId: chapterId})
+
if (work.locked) {
+
return {
+
title: 'Locked Work',
+
description: 'This work is locked to the public. Log in to see it!',
+
metadataBase: new URL('https://'+process.env.DOMAIN)
+
}
+
}
const authors = work.authors.map((a) => {
if (a.anonymous) {
return "Anonymous"
···
const fandomString = fandoms.length > 4 ? fandoms.slice(0, 4).join(", ")+" (+"+(fandoms.length - 4)+")" : (fandoms.length > 1 ? fandoms.slice(0, -1).join(", ")+" & "+fandoms.slice(-1)[0] : fandoms[0])
const title = `${work.title} by ${authorString} - ${fandomString}`
const summary = work.summary
+
const summaryDOM = new DOM(summary, {decodeEntities: true});
+
const description = summaryDOM.innerHTML.replace(/\<br(?: \/)?\>/g, "\n").replace(
+
/(<([^>]+)>)/ig,
+
"",
+
)
const addr = `works/${workId}`
+
const parentData = await parent;
+
const base = parentData.metadataBase.replace('https://', '').replace('/', '')
return {
+
title: title ? title : 'Locked Work',
+
description: description ? description : 'This but this work is locked to the public. Log in to see it!',
+
metadataBase: new URL('https://'+base)
}
}
···
const { workId, chapterId } = await params
return (
<div dangerouslySetInnerHTML={{__html: `<script type="text/javascript">
+
window.location.replace("https://archiveofourown.org/works/${workId}/chapters/${chapterId}");
</script>`}}></div>
)
}
+6 -4
src/app/works/[workId]/preview/route.js
···
-
import { getWork } from "@fujocoded/ao3.js"
import sanitizeData from "@/lib/sanitizeData.js"
import OGImage from "@/lib/ogimage.js"
import baseFonts from "@/lib/baseFonts.js"
···
height: 900,
}
-
export const contentType = 'image/webp'
export async function GET(req, ctx) {
const { workId } = await ctx.params
const props = await req.nextUrl.searchParams
-
const addr = `works/${workId}`
-
const data = await getWork({workId: workId})
const imageParams = await sanitizeData({type: 'work', data: data, props: props})
const theme = imageParams.theme
const baseFont = baseFonts[imageParams.baseFont].displayName
const titleFont = titleFonts[imageParams.titleFont].displayName
···
import sanitizeData from "@/lib/sanitizeData.js"
import OGImage from "@/lib/ogimage.js"
import baseFonts from "@/lib/baseFonts.js"
···
height: 900,
}
+
export const contentType = 'image/png'
export async function GET(req, ctx) {
const { workId } = await ctx.params
const props = await req.nextUrl.searchParams
+
const addr = `${props && props.has('archive') ? props.get('archive').replace("https://", "")+"/" : 'archiveofourown.org/'}works/${workId}`
+
const domainParam = props && props.has('archive') ? `?archive=${props.get('archive')}` : ''
+
const work = await fetch(`http://${process.env.DOMAIN}/api/works/${workId}${domainParam}`)
+
const data = await work.json()
const imageParams = await sanitizeData({type: 'work', data: data, props: props})
+
console.log(imageParams)
const theme = imageParams.theme
const baseFont = baseFonts[imageParams.baseFont].displayName
const titleFont = titleFonts[imageParams.titleFont].displayName
+8
src/icons/lock.js
···
···
+
export default function Lock ({bg, fg, width, height}) {
+
return (
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 640" width={width} height={height}>
+
<ellipse cx="320" cy="320" rx="320" ry="320" fill={bg} />
+
<path d="M256 160L256 224L384 224L384 160C384 124.7 355.3 96 320 96C284.7 96 256 124.7 256 160zM192 224L192 160C192 89.3 249.3 32 320 32C390.7 32 448 89.3 448 160L448 224C483.3 224 512 252.7 512 288L512 512C512 547.3 483.3 576 448 576L192 576C156.7 576 128 547.3 128 512L128 288C128 252.7 156.7 224 192 224z" fill={fg} />
+
</svg>
+
)
+
}
+7
src/lib/ao3Canonical.js
···
···
+
const ao3CanonicalUrls = [
+
"https://archiveofourown.org",
+
"https://ao3.org",
+
"https://archive.transformativeworks.org"
+
]
+
+
export default ao3CanonicalUrls
+22
src/lib/ogdefaults.js
···
···
+
const defaults = {
+
theme: 'ao3',
+
baseFont: 'bricolagegrotesque',
+
titleFont: 'stacksansnotch',
+
category: true,
+
rating: true,
+
warnings: false,
+
charTags: false,
+
relTags: false,
+
freeTags: false,
+
summary: true,
+
wordcount: true,
+
chapters: true,
+
postedAt: true,
+
updatedAt: false,
+
uppercaseTitle: false,
+
uppercaseChapterName: false,
+
summaryType: 'basic',
+
customSummary: ''
+
}
+
+
export default defaults
+49 -39
src/lib/ogimage.js
···
import NoWarnings from "@/icons/nowarnings.js"
import Warnings from "@/icons/warnings.js"
import ChoseNotToWarn from "@/icons/chosenottowarn.js"
export default async function OGImage ({ theme, baseFont, titleFont, image, addr, opts }) {
return new ImageResponse(
(
<div
···
}}
>
<div
style={{
-
textTransform: "uppercase",
display: "flex",
justifyContent: "center",
-
gap: 10,
-
color: theme.accent,
-
alignItems: "center"
-
}}
>
-
<div
-
style={{
-
display: "flex"
-
}}
-
>
-
{image.topLine}
-
</div>
-
-
{image.props.get('rating') === 'true' && image.rating === 'E' && (<Explicit fg={theme.background} bg={theme.accent} width={28} height={28} />)}
-
{image.props.get('rating') === 'true' && image.rating === 'M' && (<Mature fg={theme.background} bg={theme.accent} width={28} height={28} />)}
-
{image.props.get('rating') === 'true' && image.rating === 'T' && (<Teen fg={theme.background} bg={theme.accent} width={28} height={28} />)}
-
{image.props.get('rating') === 'true' && image.rating === 'G' && (<General fg={theme.background} bg={theme.accent} width={28} height={28} />)}
-
{image.props.get('rating') === 'true' && image.rating === 'NR' && (<NotRated fg={theme.background} bg={theme.accent} width={28} height={28} />)}
-
{image.props.get('warnings') === 'true' && image.warning === 'NW' && (<NoWarnings fg={theme.background} bg={theme.accent2} width={28} height={28} />)}
-
{image.props.get('warnings') === 'true' && image.warning === 'CNTW' && (<ChoseNotToWarn fg={theme.background} bg={theme.accent2} width={28} height={28} />)}
-
{image.props.get('warnings') === 'true' && image.warning === 'W' && (<Warnings fg={theme.background} bg={theme.accent2} width={28} height={28} />)}
-
{image.props.get('category') === 'true' && image.category === 'F' && (<Yuri fg={theme.background} bg={theme.accent3} width={28} height={28} />)}
-
{image.props.get('category') === 'true' && image.category === 'M' && (<Yaoi fg={theme.background} bg={theme.accent3} width={28} height={28} />)}
-
{image.props.get('category') === 'true' && image.category === 'FM' && (<Het fg={theme.background} bg={theme.accent3} width={28} height={28} />)}
-
{image.props.get('category') === 'true' && image.category === 'G' && (<Gen fg={theme.background} bg={theme.accent3} width={28} height={28} />)}
-
{image.props.get('category') === 'true' && image.category === 'MX' && (<MultiShip fg={theme.background} bg={theme.accent3} width={28} height={28} />)}
-
{image.props.get('category') === 'true' && image.category === 'O' && (<OtherShip fg={theme.background} bg={theme.accent3} width={28} height={28} />)}
</div>
<div
style={{
fontSize: 54,
justifyContent: "center",
fontFamily: titleFont,
fontWeight: "bold",
-
color: theme.color
}}
>
{image.titleLine}
···
>
{`by ${image.authorLine}`}
</div>
-
<div
style={{
fontStyle: "italic",
fontSize: 36,
fontFamily: titleFont,
display: "flex",
justifyContent: "center",
-
color: theme.color
}}
>
{image.chapterLine}
-
</div>
</div>
<div
style={{
···
alignItems: "flex-end"
}}
>
-
{image.props.get("charTags") === 'true' && (<div
style={{
display: "flex",
flexWrap: "wrap",
···
<span
style={{
backgroundColor: theme.accent2,
-
color: theme.descBackground,
padding: "3px 5px",
borderRadius: 5
}}
···
</span>
))}
</div>)}
-
{image.props.get("relTags") === 'true' && (<div
style={{
display: "flex",
flexWrap: "wrap",
···
<span
style={{
backgroundColor: theme.accent3,
-
color: theme.descBackground,
padding: "3px 5px",
borderRadius: 5
}}
···
</span>
))}
</div>)}
-
{image.props.get("freeTags") === 'true' && (<div
style={{
display: "flex",
flexWrap: "wrap",
···
<span
style={{
backgroundColor: theme.accent4,
-
color: theme.descBackground,
padding: "3px 5px",
borderRadius: 5
}}
···
</span>
))}
</div>)}
-
{image.props.get("summary") === 'true' && (<div
style={{
display: "flex",
flexDirection: "column",
···
color: theme.accent2
}}
>
-
{image.props.get("wordcount") === 'true' && `${image.words} words โ€ข `}{(image.props.get("chapters") === 'true' && image.chapterCount !== null) && `${image.chapterCount} chapters โ€ข `}{image.props.get("postedAt") === 'true' && `posted on ${image.postedAt} โ€ข `}{image.props.get("updatedAt") === 'true' && `updated on ${image.updatedAt} โ€ข `}{process.env.ARCHIVE}/{addr}
</div>
</div>
</div>
···
import NoWarnings from "@/icons/nowarnings.js"
import Warnings from "@/icons/warnings.js"
import ChoseNotToWarn from "@/icons/chosenottowarn.js"
+
import { checkItem, checkToggle } from '@/lib/propUtils.js'
export default async function OGImage ({ theme, baseFont, titleFont, image, addr, opts }) {
+
console.log(image)
return new ImageResponse(
(
<div
···
}}
>
<div
+
style={{
+
textTransform: "uppercase",
+
display: "flex",
+
justifyContent: "center",
+
color: theme.accent,
+
alignItems: "center",
+
display: "flex",
+
justifyContent: "center",
+
alignItems: "center",
+
textAlign: "center"
+
}}
+
>
+
{image.topLine}
+
</div>
+
<div
style={{
display: "flex",
justifyContent: "center",
+
alignItems: "center",
+
gap: 10
+
}}
>
+
{checkToggle('rating', image.props) && image.rating === 'E' && (<Explicit fg={theme.accentColor} bg={theme.accent} width={28} height={28} />)}
+
{checkToggle('rating', image.props) && image.rating === 'M' && (<Mature fg={theme.accentColor} bg={theme.accent} width={28} height={28} />)}
+
{checkToggle('rating', image.props) && image.rating === 'T' && (<Teen fg={theme.accentColor} bg={theme.accent} width={28} height={28} />)}
+
{checkToggle('rating', image.props) && image.rating === 'G' && (<General fg={theme.accentColor} bg={theme.accent} width={28} height={28} />)}
+
{checkToggle('rating', image.props) && image.rating === 'NR' && (<NotRated fg={theme.accentColor} bg={theme.accent} width={28} height={28} />)}
+
{checkToggle('warnings', image.props) && image.warning === 'NW' && (<NoWarnings fg={theme.accent2Color} bg={theme.accent2} width={28} height={28} />)}
+
{checkToggle('warnings', image.props) && image.warning === 'CNTW' && (<ChoseNotToWarn fg={theme.accent2Color} bg={theme.accent2} width={28} height={28} />)}
+
{checkToggle('warnings', image.props) && image.warning === 'W' && (<Warnings fg={theme.accent2Color} bg={theme.accent2} width={28} height={28} />)}
+
{checkToggle('category', image.props) && image.category === 'F' && (<Yuri fg={theme.accent3Color} bg={theme.accent3} width={28} height={28} />)}
+
{checkToggle('category', image.props)&& image.category === 'M' && (<Yaoi fg={theme.accent3Color} bg={theme.accent3} width={28} height={28} />)}
+
{checkToggle('category', image.props) && image.category === 'FM' && (<Het fg={theme.accent3Color} bg={theme.accent3} width={28} height={28} />)}
+
{checkToggle('category', image.props) && image.category === 'G' && (<Gen fg={theme.accent3Color} bg={theme.accent3} width={28} height={28} />)}
+
{checkToggle('category', image.props) && image.category === 'MX' && (<MultiShip fg={theme.accent3Color} bg={theme.accent3} width={28} height={28} />)}
+
{checkToggle('category', image.props) && image.category === 'O' && (<OtherShip fg={theme.accent3Color} bg={theme.accent3} width={28} height={28} />)}
</div>
<div
style={{
fontSize: 54,
justifyContent: "center",
+
textAlign: "center",
fontFamily: titleFont,
fontWeight: "bold",
+
color: theme.color,
+
textTransform: (image.props.uppercaseTitle ? 'uppercase' : 'none')
}}
>
{image.titleLine}
···
>
{`by ${image.authorLine}`}
</div>
+
{image.chapterLine !== '' && (<div
style={{
fontStyle: "italic",
fontSize: 36,
fontFamily: titleFont,
display: "flex",
justifyContent: "center",
+
color: theme.color,
+
textTransform: (image.props.uppercaseChapterName ? 'uppercase' : 'none')
}}
>
{image.chapterLine}
+
</div>)}
</div>
<div
style={{
···
alignItems: "flex-end"
}}
>
+
{checkToggle('charTags', image.props) && (<div
style={{
display: "flex",
flexWrap: "wrap",
···
<span
style={{
backgroundColor: theme.accent2,
+
color: theme.accent2Color,
padding: "3px 5px",
borderRadius: 5
}}
···
</span>
))}
</div>)}
+
{checkToggle('relTags', image.props) && (<div
style={{
display: "flex",
flexWrap: "wrap",
···
<span
style={{
backgroundColor: theme.accent3,
+
color: theme.accent3Color,
padding: "3px 5px",
borderRadius: 5
}}
···
</span>
))}
</div>)}
+
{checkToggle('freeTags', image.props) && (<div
style={{
display: "flex",
flexWrap: "wrap",
···
<span
style={{
backgroundColor: theme.accent4,
+
color: theme.accent4Color,
padding: "3px 5px",
borderRadius: 5
}}
···
</span>
))}
</div>)}
+
{checkToggle('summary', image.props) && (<div
style={{
display: "flex",
flexDirection: "column",
···
color: theme.accent2
}}
>
+
{checkToggle('wordcount', image.props) && `${image.words} words โ€ข `}{(checkToggle('chapters', image.props) && image.chapterCount !== null) && `${image.chapterCount} chapters โ€ข `}{checkToggle('postedAt', image.props) && `posted on ${image.postedAt} โ€ข `}{checkToggle('updatedAt', image.props) && `updated on ${image.updatedAt} โ€ข `}{addr}
</div>
</div>
</div>
+27
src/lib/ogimagelocked.js
···
···
+
import { ImageResponse } from "next/og"
+
import themes from "@/lib/themes.js"
+
import Lock from "@/icons/lock.js"
+
+
export default async function OGImageLocked ({ theme }) {
+
const themeData = themes[theme]
+
return new ImageResponse(
+
(
+
<div
+
style={{
+
display: "flex",
+
flexDirection: "column",
+
justifyContent: "center",
+
alignItems: "center",
+
color: themeData.color,
+
backgroundColor: themeData.background,
+
fontSize: 24,
+
padding: 20,
+
width: "100%",
+
height: "100%",
+
}}
+
>
+
<Lock bg={themeData.background} fg={themeData.color} width={480} height={480} />
+
</div>
+
)
+
)
+
}
+7
src/lib/propUtils.js
···
···
+
export function checkItem (key, props) {
+
return props.has(key) ? props.get(key) !== '' : false
+
}
+
+
export function checkToggle (key, props) {
+
return props.has(key) ? props.get(key) === 'true' : false
+
}
+51 -20
src/lib/sanitizeData.js
···
-
import { getWork } from "@fujocoded/ao3.js"
import DOM from "fauxdom"
import { readFile } from 'node:fs/promises'
import { join } from 'node:path'
import themes from '@/lib/themes.js'
import baseFonts from '@/lib/baseFonts.js'
import titleFonts from '@/lib/titleFonts.js'
-
const getHighestRating = async (works) => {
const ratings = await Promise.all(works.map(async (w) => {
-
const work = await getWork({workId: w.id})
return work.rating
}))
if (ratings.includes("Not Rated")) {
···
return "G"
}
-
const getHighestWarning = async (works) => {
const warnings = await Promise.all(works.map(async (w) => {
-
const work = await getWork({workId: w.id})
return work.tags.warnings
}))
const warningsUnique = warnings.reduce((a, b) => { return a.concat(b) }).filter((w, i) => { return i === warnings.indexOf(w) })
···
return "W"
}
-
const getCategory = async (works) => {
const categories = await Promise.all(works.map(async (w) => {
-
const work = await getWork({workId: w.id})
return work.category
}))
const categoriesJoined = categories.reduce((a, b) => { return a.concat(b) })
···
return "MX"
}
export default async function sanitizeData ({ type, data, props}) {
-
const baseFont = props.has('baseFont') ? props.get('baseFont') : process.env.DEFAULT_BASE_FONT
const baseFontData = baseFonts[baseFont]
-
const titleFont = props.has('titleFont') ? props.get('titleFont') : process.env.DEFAULT_TITLE_FONT
const titleFontData = titleFonts[titleFont]
-
const themeData = props.has('theme') ? themes[props.get('theme')] : themes[process.env.DEFAULT_THEME]
-
const parentWork = type === 'work' && data.chapterInfo ? await getWork({workId: data.id}) : null
const bfs = await Promise.all(baseFontData.defs.map(async (bf) => {
return {
name: baseFontData.displayName,
···
return a.username
})
: []
-
const rating = type === 'work' ? await getHighestRating([data]) : await getHighestRating(data.works)
-
const warning = type === 'work' ? await getHighestWarning([data]) : await getHighestWarning(data.works)
-
const category = type === 'work' ? await getCategory([data]) : await getCategory(data.works)
-
const authorString = authorsFormatted.length > 1
? authorsFormatted.slice(0, -1).join(", ") + " & " +
authorsFormatted.slice(-1)[0]
-
: authorsFormatted[0]
const summaryContent = type === 'work'
-
? (props.get('summaryType') === 'chapter' && data.chapterInfo && data.chapterInfo.summary ? data.chapterInfo.summary : (props.get('summaryType') === 'custom' && props.has('customSummary') ? props.get('customSummary') : (data.summary ? data.summary : (parentWork ? parentWork.summary : ''))))
-
: (props.get('summaryType') === 'custom' && props.has('customSummary') ? props.get('customSummary') : data.notes)
const formatter = new Intl.NumberFormat('en-US')
const words = formatter.format(data.words)
-
const summaryDOM = new DOM(summaryContent, {decodeEntities: true});
const summaryFormatted = summaryDOM.innerHTML.replace(/\<br(?: \/)?\>/g, "\n").replace(
/(<([^>]+)>)/ig,
"",
···
const freeTags = type === 'work' ? data.tags.additional : data.works.map(w => w.tags.additional).reduce((a, b) => { return b ? (a ? a.concat(b) : []) : (a ? a : []) }).filter((w, i) => { return i === data.works.indexOf(w) })
const warnings = type === 'work' ? data.tags.warnings : data.works.map(w => w.tags.warnings).reduce((a, b) => { return b ? (a ? a.concat(b) : []) : (a ? a : []) }).filter((w, i) => { return i === data.works.indexOf(w) })
-
return {
topLine: fandomString,
titleLine: titleString,
authorLine: authorString,
···
fonts: bfs.concat(tfs)
}
}
}
···
import DOM from "fauxdom"
import { readFile } from 'node:fs/promises'
+
import querystring from 'node:querystring'
import { join } from 'node:path'
import themes from '@/lib/themes.js'
import baseFonts from '@/lib/baseFonts.js'
import titleFonts from '@/lib/titleFonts.js'
+
import { checkItem, checkToggle } from '@/lib/propUtils.js'
+
const getWork = async (workId, archive = null) => {
+
const domainParam = (archive && archive !== process.env.ARCHIVE) ? `?archive=${archive}` : ''
+
const data = await fetch(`http://${process.env.DOMAIN}/api/works/${workId}${domainParam}`)
+
const work = await data.json()
+
return work
+
}
+
+
const getHighestRating = async (works, archive = null) => {
const ratings = await Promise.all(works.map(async (w) => {
+
const work = await getWork(w.id, archive)
return work.rating
}))
if (ratings.includes("Not Rated")) {
···
return "G"
}
+
const getHighestWarning = async (works, archive = null) => {
const warnings = await Promise.all(works.map(async (w) => {
+
const work = await getWork(w.id, archive)
return work.tags.warnings
}))
const warningsUnique = warnings.reduce((a, b) => { return a.concat(b) }).filter((w, i) => { return i === warnings.indexOf(w) })
···
return "W"
}
+
const getCategory = async (works, archive = null) => {
const categories = await Promise.all(works.map(async (w) => {
+
const work = await getWork(w.id, archive)
return work.category
}))
const categoriesJoined = categories.reduce((a, b) => { return a.concat(b) })
···
return "MX"
}
+
const sanitizeProps = (props) => {
+
let propsParsed = {}
+
Object.keys(props).forEach((pr) => {
+
if (props[pr] === 'true') {
+
propsParsed[pr] = true
+
return
+
} else if (props[pr] === 'false') {
+
propsParsed[pr] = false
+
return
+
} else if (typeof parseInt(props[pr]) === 'Number') {
+
propsParsed[pr] = parseInt(props[pr])
+
return
+
}
+
propsParsed[pr] = props[pr]
+
})
+
return propsParsed
+
}
+
export default async function sanitizeData ({ type, data, props}) {
+
const archive = props && checkItem('archive', props) ? props.get('archive') : process.env.ARCHIVE
+
console.log(props)
+
const baseFont = checkItem('baseFont', props) ? props.get('baseFont') : process.env.DEFAULT_BASE_FONT
const baseFontData = baseFonts[baseFont]
+
const titleFont = checkItem('titleFont', props) ? props.get('titleFont') : process.env.DEFAULT_TITLE_FONT
const titleFontData = titleFonts[titleFont]
+
const archClean = checkItem('archive', props) ? props.get('archive').replace("https://", '').replace('/', '') : null
+
const theme = checkItem('theme', props) ? props.get('theme') : (checkItem('archive', props) && !["ao3.org", "archiveofourown.org", "archive.transformativeworks.org"].includes(archClean) && Object.values(siteMap).includes(archClean) ? Object.keys(siteMap)[Object.values(siteMap).indexOf(archClean)] : process.env.DEFAULT_THEME)
+
const themeData = themes[theme]
+
const parentWork = type === 'work' && data.chapterInfo ? await getWork(data.id, archive) : null
const bfs = await Promise.all(baseFontData.defs.map(async (bf) => {
return {
name: baseFontData.displayName,
···
return a.username
})
: []
+
const rating = type === 'work' ? data.rating : await getHighestRating(data.works, archive)
+
const warning = type === 'work' ? await getHighestWarning([data], archive) : await getHighestWarning(data.works, archive)
+
const category = type === 'work' ? await getCategory([data], archive) : await getCategory(data.works, archive)
+
const authorString = (authorsFormatted.length > 1
? authorsFormatted.slice(0, -1).join(", ") + " & " +
authorsFormatted.slice(-1)[0]
+
: authorsFormatted[0])
const summaryContent = type === 'work'
+
? (props.get('summaryType') === 'chapter' && data.chapterInfo && data.chapterInfo.summary ? data.chapterInfo.summary : (props.get('summaryType') === 'custom' && props.get('customSummary') !== '' ? props.get('customSummary') : (data.summary ? data.summary : (parentWork ? parentWork.summary : ''))))
+
: (props.get('summaryType') === 'custom' && props.get('customSummary') !== '' ? props.get('customSummary') : data.notes)
const formatter = new Intl.NumberFormat('en-US')
const words = formatter.format(data.words)
+
const summaryDOM = new DOM(summaryContent, {decodeEntities: true})
const summaryFormatted = summaryDOM.innerHTML.replace(/\<br(?: \/)?\>/g, "\n").replace(
/(<([^>]+)>)/ig,
"",
···
const freeTags = type === 'work' ? data.tags.additional : data.works.map(w => w.tags.additional).reduce((a, b) => { return b ? (a ? a.concat(b) : []) : (a ? a : []) }).filter((w, i) => { return i === data.works.indexOf(w) })
const warnings = type === 'work' ? data.tags.warnings : data.works.map(w => w.tags.warnings).reduce((a, b) => { return b ? (a ? a.concat(b) : []) : (a ? a : []) }).filter((w, i) => { return i === data.works.indexOf(w) })
+
const ret = {
topLine: fandomString,
titleLine: titleString,
authorLine: authorString,
···
fonts: bfs.concat(tfs)
}
}
+
return ret;
}
+9
src/lib/siteMap.js
···
···
+
const siteMap = {
+
superlove: 'superlove.sayitditto.net',
+
sunset: 'sunset.femslash.club',
+
squidgeworld: 'squidgeworld.org',
+
cfaarchive: 'cfaarchive.org',
+
adastra: 'adastrafanfic.com'
+
}
+
+
export default siteMap
+816 -18
src/lib/themes.js
···
descBackground: '#FFFFFF',
descColor: '#000000',
accent: '#FFFFFF',
-
accent2: '#990000',
-
accent3: '#009900',
-
accent4: '#000099'
},
softEra: {
name: 'Soft Era',
···
descBackground: '#F9F5F5',
descColor: '#414141',
accent: '#DB90A7',
accent2: '#EEAABE',
accent3: '#82B4E3',
-
accent4: '#a29acb'
},
wildCherry: {
name: 'Wild Cherry',
···
descBackground: '#FFFFFF',
descColor: '#2B1F32',
accent: '#E15D97',
accent2: '#0AACC5',
accent3: '#FFB86C',
-
accent4: '#35BA66'
},
rosePine: {
name: 'Rosรฉ Pine',
···
descBackground: '#1f1d2e',
descColor: '#e0def4',
accent: '#eb6f92',
-
accent2: '#31748f',
accent3: '#f6c177',
-
accent4: '#c4a7e7'
},
rosePineDawn: {
name: 'Rosรฉ Pine Dawn',
···
descBackground: '#fffaf3',
descColor: '#575279',
accent: '#eb6f92',
accent2: '#286983',
accent3: '#ea9d34',
-
accent4: '#907aa9'
},
rosePineMoon: {
name: 'Rosรฉ Pine Moon',
···
descBackground: '#2a273f',
descColor: '#e0def4',
accent: '#b4637a',
-
accent2: '#3e8fb0',
accent3: '#f6c177',
-
accent4: '#c4a7e7'
},
solarizedLight: {
name: 'Solarized Light',
···
descBackground: '#eee8d5',
descColor: '#002b36',
accent: '#d33682',
accent2: '#2aa198',
accent3: '#859900',
-
accent4: '#6c71c4'
},
solarizedDark: {
name: 'Solarized Dark',
···
descBackground: '#073642',
descColor: '#fdf6e3',
accent: '#d33682',
accent2: '#2aa198',
accent3: '#859900',
-
accent4: '#6c71c4'
},
squidgeworld: {
name: 'Squidgeworld',
···
color: '#f5f5dc',
descBackground: '#f5f5dc',
descColor: '#2a2a2a',
-
accent: '#fece3f',
accent2: '#818D4C',
accent3: '#6D7A34',
-
accent4: '#556121'
},
superlove: {
name: 'Superlove',
···
color: '#ffffff',
descBackground: '#FFFFFF',
descColor: '#2a2a2a',
-
accent: '#F9E4E6',
accent2: '#a33961',
accent3: '#87254A',
-
accent4: '#6A1133'
},
catppuccinMocha: {
name: 'Catppuccin Mocha',
···
descBackground: '#313244',
descColor: '#bac2de',
accent: '#f5e0dc',
accent2: '#cba6f7',
accent3: '#fab387',
-
accent4: '#89dceb'
},
catppuccinLatte: {
name: 'Catppuccin Latte',
···
descBackground: '#ccd0da',
descColor: '#5c5f77',
accent: '#dc8a78',
accent2: '#8839ef',
accent3: '#fe640b',
-
accent4: '#04a5e5'
}
}
···
descBackground: '#FFFFFF',
descColor: '#000000',
accent: '#FFFFFF',
+
accentColor: '#990000',
+
accent2: '#CC9999',
+
accent2Color: '#990000',
+
accent3: '#EEBBBB',
+
accent3Color: '#990000',
+
accent4: '#FFDDDD',
+
accent4Color: '#990000'
},
softEra: {
name: 'Soft Era',
···
descBackground: '#F9F5F5',
descColor: '#414141',
accent: '#DB90A7',
+
accentColor: '#F9F5F5',
accent2: '#EEAABE',
+
accent2Color: '#F9F5F5',
accent3: '#82B4E3',
+
accent3Color: '#F9F5F5',
+
accent4: '#a29acb',
+
accent4Color: '#F9F5F5',
},
wildCherry: {
name: 'Wild Cherry',
···
descBackground: '#FFFFFF',
descColor: '#2B1F32',
accent: '#E15D97',
+
accentColor: '#FFFFFF',
accent2: '#0AACC5',
+
accent2Color: '#FFFFFF',
accent3: '#FFB86C',
+
accent3Color: '#2B1F32',
+
accent4: '#35BA66',
+
accent4Color: '#FFFFFF',
},
rosePine: {
name: 'Rosรฉ Pine',
···
descBackground: '#1f1d2e',
descColor: '#e0def4',
accent: '#eb6f92',
+
accentColor: '#191724',
+
accent2: '#9ccfd8',
+
accent2Color: '#191724',
accent3: '#f6c177',
+
accent3Color: '#191724',
+
accent4: '#c4a7e7',
+
accent4Color: '#191724',
},
rosePineDawn: {
name: 'Rosรฉ Pine Dawn',
···
descBackground: '#fffaf3',
descColor: '#575279',
accent: '#eb6f92',
+
accentColor: '#faf4ed',
accent2: '#286983',
+
accent2Color: '#faf4ed',
accent3: '#ea9d34',
+
accent3Color: '#faf4ed',
+
accent4: '#907aa9',
+
accent4Color: '#faf4ed',
},
rosePineMoon: {
name: 'Rosรฉ Pine Moon',
···
descBackground: '#2a273f',
descColor: '#e0def4',
accent: '#b4637a',
+
accentColor: '#232136',
+
accent2: '#9ccfd8',
+
accent2Color: '#232136',
accent3: '#f6c177',
+
accent3Color: '#232136',
+
accent4: '#c4a7e7',
+
accent4Color: '#232136',
},
solarizedLight: {
name: 'Solarized Light',
···
descBackground: '#eee8d5',
descColor: '#002b36',
accent: '#d33682',
+
accentColor: '#fdf6e3',
accent2: '#2aa198',
+
accent2Color: '#fdf6e3',
accent3: '#859900',
+
accent3Color: '#fdf6e3',
+
accent4: '#6c71c4',
+
accent4Color: '#fdf6e3',
},
solarizedDark: {
name: 'Solarized Dark',
···
descBackground: '#073642',
descColor: '#fdf6e3',
accent: '#d33682',
+
accentColor: '#002b36',
accent2: '#2aa198',
+
accent2Color: '#002b36',
accent3: '#859900',
+
accent3Color: '#002b36',
+
accent4: '#6c71c4',
+
accent4Color: '#002b36',
},
squidgeworld: {
name: 'Squidgeworld',
···
color: '#f5f5dc',
descBackground: '#f5f5dc',
descColor: '#2a2a2a',
+
accent: '#FFC825',
+
accentColor: '#f5f5dc',
accent2: '#818D4C',
+
accent2Color: '#f5f5dc',
accent3: '#6D7A34',
+
accent3Color: '#f5f5dc',
+
accent4: '#556121',
+
accent4Color: '#f5f5dc',
},
superlove: {
name: 'Superlove',
···
color: '#ffffff',
descBackground: '#FFFFFF',
descColor: '#2a2a2a',
+
accent: '#F4C4C5',
+
accentColor: '#ffffff',
accent2: '#a33961',
+
accent2Color: '#ffffff',
accent3: '#87254A',
+
accent3Color: '#ffffff',
+
accent4: '#6A1133',
+
accent4Color: '#ffffff',
},
catppuccinMocha: {
name: 'Catppuccin Mocha',
···
descBackground: '#313244',
descColor: '#bac2de',
accent: '#f5e0dc',
+
accentColor: '#1e1e2e',
accent2: '#cba6f7',
+
accent2Color: '#1e1e2e',
accent3: '#fab387',
+
accent3Color: '#1e1e2e',
+
accent4: '#89dceb',
+
accent4Color: '#1e1e2e',
},
catppuccinLatte: {
name: 'Catppuccin Latte',
···
descBackground: '#ccd0da',
descColor: '#5c5f77',
accent: '#dc8a78',
+
accentColor: '#FFFFFF',
accent2: '#8839ef',
+
accent2Color: '#eff1f5',
accent3: '#fe640b',
+
accent3Color: '#eff1f5',
+
accent4: '#04a5e5',
+
accent4Color: '#eff1f5',
+
},
+
atelierCave: {
+
name: 'Atelier Cave',
+
background: '#19171c',
+
color: '#655F6D',
+
descBackground: '#19171c',
+
descColor: '#E2DFE7',
+
accent: '#BE4678',
+
accentColor: '#E2DFE7',
+
accent2: '#2A9292',
+
accent2Color: '#E2DFE7',
+
accent3: '#AA573C',
+
accent3Color: '#E2DFE7',
+
accent4: '#A06E3B',
+
accent4Color: '#E2DFE7'
+
},
+
atelierDune: {
+
name: 'Atelier Dune',
+
background: '#20201d',
+
color: '#7D7A68',
+
descBackground: '#20201d',
+
descColor: '#7D7A68',
+
accent: '#D73737',
+
accentColor: '#FEFBEC',
+
accent2: '#60AC39',
+
accent2Color: '#FEFBEC',
+
accent3: '#AE9513',
+
accent3Color: '#FEFBEC',
+
accent4: '#6684E1',
+
accent4Color: '#FEFBEC'
+
},
+
atelierEstuary: {
+
name: 'Atelier Estuary',
+
background: '#22221b',
+
color: '#6C6B5A',
+
descBackground: '#22221b',
+
descColor: '#E7E6DF',
+
accent: '#BA6236',
+
accentColor: '#F4F3EC',
+
accent2: '#7D9726',
+
accent2Color: '#F4F3EC',
+
accent3: '#AE7313',
+
accent3Color: '#F4F3EC',
+
accent4: '#36A166',
+
accent4Color: '#F4F3EC'
+
},
+
atelierForest: {
+
name: 'Atelier Forest',
+
background: '#1b1918',
+
color: '#766E6B',
+
descBackground: '#1b1918',
+
descColor: '#F1EFEE',
+
accent: '#F22C40',
+
accentColor: '#F1EFEE',
+
accent2: '#7B9726',
+
accent2Color: '#F1EFEE',
+
accent3: '#DF5320',
+
accent3Color: '#F1EFEE',
+
accent4: '#407EE7',
+
accent4Color: '#F1EFEE'
+
},
+
atelierHeath: {
+
name: 'Atelier Heath',
+
background: '#1b1918',
+
color: '#776977',
+
descBackground: '#1b1918',
+
descColor: '#F7F3F7',
+
accent: '#CA402B',
+
accentColor: '#F7F3F7',
+
accent2: '#918B3B',
+
accent2Color: '#F7F3F7',
+
accent3: '#BB8A35',
+
accent3Color: '#F7F3F7',
+
accent4: '#516AEC',
+
accent4Color: '#F7F3F7'
+
},
+
atelierLakeside: {
+
name: 'Atelier Lakeside',
+
background: '#161b1d',
+
color: '#5A7B8C',
+
descBackground: '#161b1d',
+
descColor: '#EBF8FF',
+
accent: '#D22D72',
+
accentColor: '#EBF8FF',
+
accent2: '#568C3B',
+
accent2Color: '#EBF8FF',
+
accent3: '#935C25',
+
accent3Color: '#EBF8FF',
+
accent4: '#257FAD',
+
accent4Color: '#EBF8FF'
+
},
+
atelierPlateau: {
+
name: 'Atelier Plateau',
+
background: '#1b1818',
+
color: '#655D5D',
+
descBackground: '#1b1818',
+
descColor: '#F4ECEC',
+
accent: '#CA4949',
+
accentColor: '#F4ECEC',
+
accent2: '#4B8B8B',
+
accent2Color: '#F4ECEC',
+
accent3: '#A06E3B',
+
accent3Color: '#F4ECEC',
+
accent4: '#7272CA',
+
accent4Color: '#F4ECEC'
+
},
+
atelierSavanna: {
+
name: 'Atelier Savanna',
+
background: '#171c19',
+
color: '#5F6D64',
+
descBackground: '#1b1818',
+
descColor: '#ECF4EE',
+
accent: '#B16139',
+
accentColor: '#ECF4EE',
+
accent2: '#489963',
+
accent2Color: '#ECF4EE',
+
accent3: '#A07E3B',
+
accent3Color: '#ECF4EE',
+
accent4: '#478C90',
+
accent4Color: '#ECF4EE'
+
},
+
atelierSeaside: {
+
name: 'Atelier Seaside',
+
background: '#131513',
+
color: '#687D68',
+
descBackground: '#131513',
+
descColor: '#CFE8CF',
+
accent: '#E6193C',
+
accentColor: '#CFE8CF',
+
accent2: '#29A329',
+
accent2Color: '#CFE8CF',
+
accent3: '#98981B',
+
accent3Color: '#CFE8CF',
+
accent4: '#3D62F5',
+
accent4Color: '#CFE8CF'
+
},
+
atelierSulphurpool: {
+
name: 'Atelier Sulphurpool',
+
background: '#202746',
+
color: '#6B7394',
+
descBackground: '#202746',
+
descColor: '#DFE2F1',
+
accent: '#C94922',
+
accentColor: '#DFE2F1',
+
accent2: '#AC9739',
+
accent2Color: '#DFE2F1',
+
accent3: '#C08B30',
+
accent3Color: '#DFE2F1',
+
accent4: '#3D8FD1',
+
accent4Color: '#DFE2F1'
+
},
+
ayaka: {
+
name: 'Ayaka',
+
background: '#36283d',
+
color: '#FFFEFE',
+
descBackground: '#36283d',
+
descColor: '#FFFEFE',
+
accent: '#71ADE9',
+
accentColor: '#FFFEFE',
+
accent2: '#AB8CAE',
+
accent2Color: '#FFFEFE',
+
accent3: '#E59DB1',
+
accent3Color: '#FFFEFE',
+
accent4: '#8BB8E9',
+
accent4Color: '#FFFEFE'
+
},
+
ayuMirage: {
+
name: 'Ayu Mirage',
+
background: '#1F2430',
+
color: '#CBCCC6',
+
descBackground: '#1F2430',
+
descColor: '#CBCCC6',
+
accent: '#FF3333',
+
accentColor: '#1F2430',
+
accent2: '#BAE67E',
+
accent2Color: '#1F2430',
+
accent3: '#FFA759',
+
accent3Color: '#1F2430',
+
accent4: '#73D0FF',
+
accent4Color: '#1F2430'
+
},
+
base2ToneCave: {
+
name: 'Base2Tone Cave',
+
background: '#222021',
+
color: '#9f999b',
+
descBackground: '#2f2d2e',
+
descColor: '#ffebf2',
+
accent: '#936c7a',
+
accentColor: '#ffebf2',
+
accent2: '#cca133',
+
accent2Color: '#ffebf2',
+
accent3: '#d27998',
+
accent3Color: '#ffebf2',
+
accent4: '#706b6d',
+
accent4Color: '#ffebf2'
+
},
+
base2ToneDesert: {
+
name: 'Base2Tone Desert',
+
background: '#292724',
+
color: '#ada594',
+
descBackground: '#3d3a34',
+
descColor: '#f2ead9',
+
accent: '#816f4b',
+
accentColor: '#f2ead9',
+
accent2: '#ec9255',
+
accent2Color: '#f2ead9',
+
accent3: '#957e50',
+
accent3Color: '#f2ead9',
+
accent4: '#615c51',
+
accent4Color: '#f2ead9'
+
},
+
base2ToneDrawbridge: {
+
name: 'Base2Tone Drawbridge',
+
background: '#1b1f32',
+
color: '#9094a7',
+
descBackground: '#252a41',
+
descColor: '#9094a7',
+
accent: '#627af4',
+
accentColor: '#e1e6ff',
+
accent2: '#5cbcd6',
+
accent2Color: '#e1e6ff',
+
accent3: '#8b9efd',
+
accent3Color: '#e1e6ff',
+
accent4: '#444b6f',
+
accent4Color: '#e1e6ff'
+
},
+
base2ToneEarth: {
+
name: 'Base2Tone Earth',
+
background: '#322d29',
+
color: '#b5a9a1',
+
descBackground: '#3f3a37',
+
descColor: '#fff3eb',
+
accent: '#e6b84d',
+
accentColor: '#fff3eb',
+
accent2: '#d9b154',
+
accent2Color: '#fff3eb',
+
accent3: '#816d5f',
+
accent3Color: '#fff3eb',
+
accent4: '#b5a9a1',
+
accent4Color: '#fff3eb'
+
},
+
base2ToneEvening: {
+
name: 'Base2Tone Evening',
+
background: '#2a2734',
+
color: '#a4a1b5',
+
descBackground: '#363342',
+
descColor: '#eeebff',
+
accent: '#8a75f5',
+
accentColor: '#eeebff',
+
accent2: '#ffad5c',
+
accent2Color: '#eeebff',
+
accent3: '#afa0fe',
+
accent3Color: '#eeebff',
+
accent4: '#a4a1b5',
+
accent4Color: '#eeebff'
+
},
+
base2ToneField: {
+
name: 'Base2Tone Field',
+
background: '#18201e',
+
color: '#8ea4a0',
+
descBackground: '#242e2c',
+
descColor: '#a8fff1',
+
accent: '#0fbda0',
+
accentColor: '#242e2c',
+
accent2: '#3be381',
+
accent2Color: '#242e2c',
+
accent3: '#40ddc3',
+
accent3Color: '#242e2c',
+
accent4: '#8ea4a0',
+
accent4Color: '#242e2c'
+
},
+
base2ToneForest: {
+
name: 'Base2Tone Forest',
+
background: '#2a2d2a',
+
color: '#a1b5a1',
+
descBackground: '#353b35',
+
descColor: '#f0fff0',
+
accent: '#5c705c',
+
accentColor: '#f0fff0',
+
accent2: '#b1c44f',
+
accent2Color: '#f0fff0',
+
accent3: '#687d68',
+
accent3Color: '#f0fff0',
+
accent4: '#8fae8f',
+
accent4Color: '#f0fff0'
+
},
+
base2ToneLavender: {
+
name: 'Base2Tone Lavender',
+
background: '#201d2a',
+
color: '#9992b0',
+
descBackground: '#2c2839',
+
descColor: '#efebff',
+
accent: '#9375f5',
+
accentColor: '#efebff',
+
accent2: '#d294ff',
+
accent2Color: '#efebff',
+
accent3: '#b5a0fe',
+
accent3Color: '#efebff',
+
accent4: '#9992b0',
+
accent4Color: '#efebff'
+
},
+
base2ToneMall: {
+
name: 'Base2Tone Mall',
+
background: '#1e1e1f',
+
color: '#97959d',
+
descBackground: '#2b2b2c',
+
descColor: '#f4f0ff',
+
accent: '#a17efc',
+
accentColor: '#f4f0ff',
+
accent2: '#75bfff',
+
accent2Color: '#f4f0ff',
+
accent3: '#c5adff',
+
accent3Color: '#f4f0ff',
+
accent4: '#97959d',
+
accent4Color: '#f4f0ff'
+
},
+
base2ToneMotel: {
+
name: 'Base2Tone Motel',
+
background: '#242323',
+
color: '#a5979a',
+
descBackground: '#373434',
+
descColor: '#f0dbdf',
+
accent: '#956f76',
+
accentColor: '#f0dbdf',
+
accent2: '#f8917c',
+
accent2Color: '#f0dbdf',
+
accent3: '#a7868b',
+
accent3Color: '#f0dbdf',
+
accent4: '#a5979a',
+
accent4Color: '#f0dbdf'
+
},
+
base2TonePool: {
+
name: 'Base2Tone Pool',
+
background: '#2a2433',
+
color: '#9a90a7',
+
descBackground: '#372f42',
+
descColor: '#f3ebff',
+
accent: '#aa75f5',
+
accentColor: '#f3ebff',
+
accent2: '#f87972',
+
accent2Color: '#f3ebff',
+
accent3: '#c7a0fe',
+
accent3Color: '#f3ebff',
+
accent4: '#9a90a7',
+
accent4Color: '#f3ebff'
+
},
+
base2TonePorch: {
+
name: 'Base2Tone Porch',
+
background: '#221e24',
+
color: '#9f95a3',
+
descBackground: '#302a32',
+
descColor: '#f2e3f7',
+
accent: '#9466a3',
+
accentColor: '#f2e3f7',
+
accent2: '#f39b68',
+
accent2Color: '#f2e3f7',
+
accent3: '#a77cb6',
+
accent3Color: '#f2e3f7',
+
accent4: '#9f95a3',
+
accent4Color: '#f2e3f7'
+
},
+
base2ToneSpace: {
+
name: 'Base2Tone Space',
+
background: '#24242e',
+
color: '#a1a1b5',
+
descBackground: '#333342',
+
descColor: '#cecee3',
+
accent: '#7676f4',
+
accentColor: '#ebebff',
+
accent2: '#f37b3f',
+
accent2Color: '#ebebff',
+
accent3: '#fe8c52',
+
accent3Color: '#ebebff',
+
accent4: '#737391',
+
accent4Color: '#ebebff'
+
},
+
base2ToneSuburb: {
+
name: 'Base2Tone Suburb',
+
background: '#1e202f',
+
color: '#4f5472',
+
descBackground: '#292c3d',
+
descColor: '#ebedff',
+
accent: '#7586f5',
+
accentColor: '#ebedff',
+
accent2: '#fe81b5',
+
accent2Color: '#ebedff',
+
accent3: '#fb6fa9',
+
accent3Color: '#ebedff',
+
accent4: '#5b6080',
+
accent4Color: '#ebedff'
+
},
+
blueDolphin: {
+
name: "Blue Dolphin",
+
background: '#006984',
+
color: '#A3F7FF',
+
descBackground: '#006984',
+
descColor: '#FFFFFF',
+
accent: '#FF8288',
+
accentColor: '#292D3E',
+
accent2: '#B4E88D',
+
accent2Color: '#292D3E',
+
accent3: '#F4D69F',
+
accent3Color: '#292D3E',
+
accent4: '#82AAFF',
+
accent4Color: '#292D3E'
+
},
+
borland: {
+
name: "Borland",
+
background: '#0000a4',
+
color: '#FFFFB6',
+
descBackground: '#0000a4',
+
descColor: '#FFFFB6',
+
accent: '#FF6C60',
+
accentColor: '#292D3E',
+
accent2: '#B4E88D',
+
accent2Color: '#292D3E',
+
accent3: '#F4D69F',
+
accent3Color: '#292D3E',
+
accent4: '#82AAFF',
+
accent4Color: '#292D3E'
+
},
+
butrin: {
+
name: 'Butrin',
+
background: '#4b3b3c',
+
color: '#F2F2F2',
+
descBackground: '#4b3b3c',
+
descColor: '#F2F2F2',
+
accent: '#F2B1B1',
+
accentColor: '#4b3b3c',
+
accent2: '#B2D8B2',
+
accent2Color: '#4b3b3c',
+
accent3: '#87CEFA',
+
accent3Color: '#4b3b3c',
+
accent4: '#D8BFD8',
+
accent4Color: '#4b3b3c'
+
},
+
gooey: {
+
name: "Gooey",
+
background: '#000009',
+
color: '#FFFFFF',
+
descBackground: '#1F222D',
+
descColor: '#FFFFFF',
+
accent: '#BB4F6C',
+
accentColor: '#FFFFFF',
+
accent2: '#C65E3D',
+
accent2Color: '#FFFFFF',
+
accent3: '#72CCAE',
+
accent3Color: '#FFFFFF',
+
accent4: '#58B6CA',
+
accent4Color: '#FFFFFF'
+
},
+
gruvbox: {
+
name: 'Gruvbox',
+
background: '#FBF1C7',
+
color: '#4f5472',
+
descBackground: '#FBF1C7',
+
descColor: '#3C3836',
+
accent: '#9D0006',
+
accentColor: '#4F4F4F',
+
accent2: '#A8FF60',
+
accent2Color: '#4F4F4F',
+
accent3: '#96CBFE',
+
accent3Color: '#4F4F4F',
+
accent4: '#FF73FD',
+
accent4Color: '#4F4F4F'
+
},
+
gruvboxDark: {
+
name: 'Gruvbox Dark',
+
background: '#282828',
+
color: '#7C6F64',
+
descBackground: '#282828',
+
descColor: '#ebedff',
+
accent: '#FB4934',
+
accentColor: '#282828',
+
accent2: '#B8BB26',
+
accent2Color: '#282828',
+
accent3: '#FABD2F',
+
accent3Color: '#282828',
+
accent4: '#83A598',
+
accent4Color: '#282828'
+
},
+
monoAmber: {
+
name: "Mono Amber",
+
background: '#2b1900',
+
color: '#FF9400',
+
descBackground: '#2b1900',
+
descColor: '#FF9400',
+
accent: '#FF9400',
+
accentColor: '#402500',
+
accent2: '#FF9400',
+
accent2Color: '#402500',
+
accent3: '#FF9400',
+
accent3Color: '#402500',
+
accent4: '#FF9400',
+
accent4Color: '#402500'
+
},
+
monoCyan: {
+
name: "Mono Cyan",
+
background: '#00222b',
+
color: '#00CCFF',
+
descBackground: '#00222b',
+
descColor: '#00CCFF',
+
accent: '#00CCFF',
+
accentColor: '#003340',
+
accent2: '#00CCFF',
+
accent2Color: '#003340',
+
accent3: '#00CCFF',
+
accent3Color: '#003340',
+
accent4: '#00CCFF',
+
accent4Color: '#003340'
+
},
+
monoGreen: {
+
name: "Mono Green",
+
background: '#022b00',
+
color: '#0BFF00',
+
descBackground: '#022b00',
+
descColor: '#0BFF00',
+
accent: '#0BFF00',
+
accentColor: '#034000',
+
accent2: '#0BFF00',
+
accent2Color: '#034000',
+
accent3: '#0BFF00',
+
accent3Color: '#034000',
+
accent4: '#0BFF00',
+
accent4Color: '#034000'
+
},
+
monoRed: {
+
name: "Mono Red",
+
background: '#2b0c00',
+
color: '#FF3600',
+
descBackground: '#2b0c00',
+
descColor: '#FF3600',
+
accent: '#FF3600',
+
accentColor: '#401200',
+
accent2: '#FF3600',
+
accent2Color: '#401200',
+
accent3: '#FF3600',
+
accent3Color: '#401200',
+
accent4: '#FF3600',
+
accent4Color: '#401200'
+
},
+
monoWhite: {
+
name: "Mono White",
+
background: '#262626',
+
color: '#FAFAFA',
+
descBackground: '#262626',
+
descColor: '#FAFAFA',
+
accent: '#FAFAFA',
+
accentColor: '#3B3B3B',
+
accent2: '#FAFAFA',
+
accent2Color: '#3B3B3B',
+
accent3: '#FAFAFA',
+
accent3Color: '#3B3B3B',
+
accent4: '#FAFAFA',
+
accent4Color: '#3B3B3B'
+
},
+
monoYellow: {
+
name: "Mono Yellow",
+
background: '#2b2400',
+
color: '#FFD300',
+
descBackground: '#2b2400',
+
descColor: '#FFD300',
+
accent: '#FFD300',
+
accentColor: '#403500',
+
accent2: '#FFD300',
+
accent2Color: '#403500',
+
accent3: '#FFD300',
+
accent3Color: '#403500',
+
accent4: '#FFD300',
+
accent4Color: '#403500'
+
},
+
seaShells: {
+
name: "Sea Shells",
+
background: '#09141b',
+
color: '#DEB88D',
+
descBackground: '#09141b',
+
descColor: '#FEE4CE',
+
accent: '#D15123',
+
accentColor: '#FEE4CE',
+
accent2: '#027C9B',
+
accent2Color: '#FEE4CE',
+
accent3: '#1E4950',
+
accent3Color: '#FEE4CE',
+
accent4: '#434B53',
+
accent4Color: '#FEE4CE'
+
},
+
seafoamPastel: {
+
name: "Seafoam Pastel",
+
background: '#243435',
+
color: '#E0E0E0',
+
descBackground: '#243435',
+
descColor: '#E0E0E0',
+
accent: '#825D4D',
+
accentColor: '#FEE4CE',
+
accent2: '#728C62',
+
accent2Color: '#FEE4CE',
+
accent3: '#ADA16D',
+
accent3Color: '#FEE4CE',
+
accent4: '#4D7B82',
+
accent4Color: '#FEE4CE'
+
},
+
seoul256: {
+
name: "Seoul 256",
+
background: '#3a3a3a',
+
color: '#e4e4e4',
+
descBackground: '#4e4e4e',
+
descColor: '#d0d0d0',
+
accent: '#d68787',
+
accentColor: '#4e4e4e',
+
accent2: '#87af87',
+
accent2Color: '#4e4e4e',
+
accent3: '#add4fb',
+
accent3Color: '#4e4e4e',
+
accent4: '#d7afaf',
+
accent4Color: '#4e4e4e'
+
},
+
seoul256Light: {
+
name: "Seoul 256 Light",
+
background: '#dadada',
+
color: '#4e4e4e',
+
descBackground: '#e4e4e4',
+
descColor: '#3a3a3a',
+
accent: '#870100',
+
accentColor: '#eeeeee',
+
accent2: '#d8865f',
+
accent2Color: '#eeeeee',
+
accent3: '#87025f',
+
accent3Color: '#eeeeee',
+
accent4: '#008787',
+
accent4Color: '#eeeeee'
+
},
+
shel: {
+
name: "Shel",
+
background: '#2C2423',
+
color: '#8FBAEC',
+
descBackground: '#2C2423',
+
descColor: '#F5EEEC',
+
accent: '#F588B9',
+
accentColor: '#F5EEEC',
+
accent2: '#AB6423',
+
accent2Color: '#F5EEEC',
+
accent3: '#2C64A2',
+
accent3Color: '#F5EEEC',
+
accent4: '#6C24A2',
+
accent4Color: '#F5EEEC'
+
},
+
slate: {
+
name: "Slate",
+
background: '#222222',
+
color: '#8CDFE0',
+
descBackground: '#222222',
+
descColor: '#E0E0E0',
+
accent: '#E2A8BF',
+
accentColor: '#222222',
+
accent2: '#81D778',
+
accent2Color: '#222222',
+
accent3: '#C4C9C0',
+
accent3Color: '#222222',
+
accent4: '#A481D3',
+
accent4Color: '#222222'
+
},
+
vaughn: {
+
name: "Vaughn",
+
background: '#25234F',
+
color: '#FFFFFF',
+
descBackground: '#25234F',
+
descColor: '#FFFFFF',
+
accent: '#60B48A',
+
accentColor: '#25234F',
+
accent2: '#DFAF8F',
+
accent2Color: '#25234F',
+
accent3: '#F08CC3',
+
accent3Color: '#25234F',
+
accent4: '#8CD0D3',
+
accent4Color: '#25234F'
+
},
+
warmNeon: {
+
name: "Warm Neon",
+
background: '#404040',
+
color: '#9CC090',
+
descBackground: '#404040',
+
descColor: '#FEFCFC',
+
accent: '#2ABBD4',
+
accentColor: '#FEFCFC',
+
accent2: '#39B13A',
+
accent2Color: '#FEFCFC',
+
accent3: '#4261C5',
+
accent3Color: '#FEFCFC',
+
accent4: '#F920FB',
+
accent4Color: '#FEFCFC'
+
},
+
website: {
+
name: "Website",
+
background: '#132f35',
+
color: '#ffd48f',
+
descBackground: '#183c44',
+
descColor: '#ffd48f',
+
accent: '#ff5757',
+
accentColor: '#235662',
+
accent2: '#ecff14',
+
accent2Color: '#235662',
+
accent3: '#4cbfff',
+
accent3Color: '#235662',
+
accent4: '#ff4cc2',
+
accent4Color: '#235662'
+
},
+
wryan: {
+
name: "Wryan",
+
background: '#101010',
+
color: '#899CA1',
+
descBackground: '#101010',
+
descColor: '#f0efd0',
+
accent: '#8C4665',
+
accentColor: '#C0C0C0',
+
accent2: '#287373',
+
accent2Color: '#C0C0C0',
+
accent3: '#7C7C99',
+
accent3Color: '#C0C0C0',
+
accent4: '#395573',
+
accent4Color: '#C0C0C0'
+
},
+
zenburn: {
+
name: "Zenburn",
+
background: '#3a3a3a',
+
color: '#f0efd0',
+
descBackground: '#333333',
+
descColor: '#f0efd0',
+
accent: '#cc9393',
+
accentColor: '#333333',
+
accent2: '#dfaf87',
+
accent2Color: '#333333',
+
accent3: '#efef87',
+
accent3Color: '#333333',
+
accent4: '#bca3a3',
+
accent4Color: '#333333'
+
},
+
sunset: {
+
name: "Sunset",
+
background: '#c757ab',
+
color: '#FFFFFF',
+
descBackground: '#FFFFFF',
+
descColor: '#2a2a2a',
+
accent: '#ff8c00',
+
accentColor: '#FFFFFF',
+
accent2: '#c5b95c',
+
accent2Color: '#FFFFFF',
+
accent3: '#ff69b4',
+
accent3Color: '#FFFFFF',
+
accent4: '#000069',
+
accent4Color: '#FFFFFF'
}
}
-30
src/lib/titleFonts.js
···
weight: 700
}
]
-
},
-
datalegreyathin: {
-
displayName: 'Datalegreya Thin',
-
defs: [
-
{
-
path: '/fonts/Datalegreya-Thin.otf',
-
style: 'normal',
-
weight: 400
-
}
-
]
-
},
-
datalegreyadot: {
-
displayName: 'Datalegreya Dot',
-
defs: [
-
{
-
path: '/fonts/Datalegreya-Dot.otf',
-
style: 'normal',
-
weight: 400
-
}
-
]
-
},
-
datalegreyagradient: {
-
displayName: 'Datalegreya Gradient',
-
defs: [
-
{
-
path: '/fonts/Datalegreya-Gradient.otf',
-
style: 'normal',
-
weight: 400
-
}
-
]
}
}
···
weight: 700
}
]
}
}