redirecter for ao3 that adds opengraph metadata

Compare changes

Choose any two refs to compare.

+2 -1
.env.example
···
SITENAME=fixAO3
-
DESCRIPTION=Unofficial AO3 embed prettifier for social media
+
DESCRIPTION="Unofficial AO3 embed prettifier for social media"
DOMAIN=localhost:3000
+
ARCHIVE=archiveofourown.org
DEFAULT_THEME=ao3
DEFAULT_BASE_FONT=bricolagegrotesque
DEFAULT_TITLE_FONT=stacksansnotch
+11 -3
README.md
···
you too can self-host if you want to change the visuals or just have a different redirection url or something. the main things you need installed are:
-
- ImageMagick (at least 6.9), built from source with pkg-config, pangocairo, libpng-dev, libwebp-dev. this is kind of a pain in the ass but it is in fact essential
- Bun
- Probably some process manager like PM2 (you will also need Node.js for PM2)
- A web server that allows reverse proxying (I use Caddy)
How to run:
-
- `bun main.jsx` will run the application. if you're running it through pm2, do `pm2 start --name=fixao3 bun -- main.jsx`
+
- copy .env.example to .env, and update the variables in there to be whatever you want them to be. the lists of existing themes and fonts can be found in the src/lib folder, for setting defaults
+
- `bun --bun next start` will run the application. if you're running it through pm2, do `pm2 start --name=fixao3 bun -- --bun next start`
- set up a reverse proxy on your domain or subdomain to localhost:3000
-
- it should, theoretically work (installing imagemagick from source is the hard part. wheeze)
+
- it should, theoretically work (installing imagemagick from source is the hard part. wheeze)
+
+
How to customize:
+
+
- stuff like site name, meta description, etc. are set in site variables. right now the ARCHIVE var is only used for what to display on the bottom of cards because I'm not sure how AO3.js supports other archives apart from that it does.
+
- basically all the styles are in src/app/globals.css
+
- add fonts to src/lib/baseFonts.js and src/lib/titleFonts.js, and themes to src/lib/themes.js (titleFonts already grabs all the baseFonts as long as you don't remove `...baseFonts` from the array)
+
- icons are svg components stored in src/icons if you don't like my icon choices
+
- add font files to the fonts folder. they have to be either ttf or otf (sorry, no woff/woff2, next/og hates those as it turns out)
+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-build": "^1.8.0",
"@hono/vite-dev-server": "^0.23.0",
-
"bun": "^1.3.2",
"fauxdom": "^1.2.2",
-
"hono": "^4.10.4",
-
"next": "16.0.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.7.0", "", { "peerDependencies": { "hono": "*" } }, "sha512-L73WBed5teC7DHTzXYkho83POYYluD2rTkbT76FJuqpfXPgTW/PsbIa8O0YcCETE3VUoh584fe6vuLAM5ctjww=="],
+
"@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.2", "", {}, "sha512-V2e9ITU6Ts9kxtTBX60qtWlKV+AeBNlz/hgAt0gkGA8aPgX27cRLjp7OEUMzYq4cY0QzOkOQ4CI/8IJh6kW/iw=="],
+
"@next/env": ["@next/env@16.0.7", "", {}, "sha512-gpaNgUh5nftFKRkRQGnVi5dpcYSKGcZZkQffZ172OrG/XkrnS7UBTQ648YY+8ME92cC4IojpI2LqTC8sTDhAaw=="],
-
"@next/swc-darwin-arm64": ["@next/swc-darwin-arm64@16.0.2", "", { "os": "darwin", "cpu": "arm64" }, "sha512-E6rxUdkZX5sZjLduXphiMuRJAmvsxWi5IivD0kRLLX5cjNLOs2PjlSyda+dtT3iqE6vxaRGV3oQMnQiJU8F+Ig=="],
+
"@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.2", "", { "os": "darwin", "cpu": "x64" }, "sha512-QNXdjXVFtb35vImDJtXqYlhq8A2mHLroqD8q4WCwO+IVnVoQshhcEVWJlP9UB/dOC6Wh782BbTHqGzKQwlCSkQ=="],
+
"@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.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-dM9yEB35GZAW3r+w88iGEz7OkJjSYSd4pKyl4KwSXx8cLWMpWaX1WW42dCAKXCWWQhVUXUZAEx38yfpEZ1/IJg=="],
+
"@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.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-hiNysPK1VeK5MGNmuKLnj3Y4lkaffvAlXin404QpxYkNCBms/Bk0msZHey5lUNq8FV50PY6I9CgY+c/NK+xeLg=="],
+
"@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.2", "", { "os": "linux", "cpu": "x64" }, "sha512-hAhhobw4tHOCzZ5sm5W/EsQPxS3NbZl6rqzmA0GTV9etE8sPHmsV6OopP12TeeoXA/NjXKD2mcz8hcVWLe4jkg=="],
+
"@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.2", "", { "os": "linux", "cpu": "x64" }, "sha512-s0LUsoeRky95aTS6IfYnJOn6F5kbs+gjiVUQK0JmsJ/ZCXaply20kDoJ8/zHwMz5cyOVg7GrQJdMvyO9FLD9Bw=="],
+
"@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.2", "", { "os": "win32", "cpu": "arm64" }, "sha512-TMWE1h44d0WRyq0yQI/0W5A7nZUoiwE2Sdg43wt2Q1IoadU5Ky00G3cJ2mSnbetwL7+eFyM7BQgx+Fonpz6T8w=="],
+
"@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.2", "", { "os": "win32", "cpu": "x64" }, "sha512-+8SqzDhau/PNsWdcagnoz6ltOM9IcsqagdTFsEELNOty0+lNh5hwO5oUFForPOywTbM+d3tPLo5m20VdEBDf3Q=="],
+
"@next/swc-win32-x64-msvc": ["@next/swc-win32-x64-msvc@16.0.7", "", { "os": "win32", "cpu": "x64" }, "sha512-gniPjy55zp5Eg0896qSrf3yB1dw4F/3s8VK1ephdsZZ129j2n6e1WqCbE2YgcKhW9hPB9TVZENugquWJD5x0ug=="],
-
"@oven/bun-darwin-aarch64": ["@oven/bun-darwin-aarch64@1.3.2", "", { "os": "darwin", "cpu": "arm64" }, "sha512-licBDIbbLP5L5/S0+bwtJynso94XD3KyqSP48K59Sq7Mude6C7dR5ZujZm4Ut4BwZqUFfNOfYNMWBU5nlL7t1A=="],
+
"@swc/helpers": ["@swc/helpers@0.5.15", "", { "dependencies": { "tslib": "^2.8.0" } }, "sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g=="],
-
"@oven/bun-darwin-x64": ["@oven/bun-darwin-x64@1.3.2", "", { "os": "darwin", "cpu": "x64" }, "sha512-hn8lLzsYyyh6ULo2E8v2SqtrWOkdQKJwapeVy1rDw7juTTeHY3KDudGWf4mVYteC9riZU6HD88Fn3nGwyX0eIg=="],
+
"@types/bun": ["@types/bun@1.3.4", "", { "dependencies": { "bun-types": "1.3.4" } }, "sha512-EEPTKXHP+zKGPkhRLv+HI0UEX8/o+65hqARxLy8Ov5rIxMBPNTjeZww00CIihrIQGEQBYg+0roO5qOnS/7boGA=="],
-
"@oven/bun-darwin-x64-baseline": ["@oven/bun-darwin-x64-baseline@1.3.2", "", { "os": "darwin", "cpu": "x64" }, "sha512-UHxdtbyxdtNJUNcXtIrjx3Lmq8ji3KywlXtIHV/0vn9A8W5mulqOcryqUWMFVH9JTIIzmNn6Q/qVmXHTME63Ww=="],
+
"@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=="],
-
"@oven/bun-linux-aarch64": ["@oven/bun-linux-aarch64@1.3.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-5uZzxzvHU/z+3cZwN/A0H8G+enQ+9FkeJVZkE2fwK2XhiJZFUGAuWajCpy7GepvOWlqV7VjPaKi2+Qmr4IX7nQ=="],
+
"ansi-colors": ["ansi-colors@4.1.3", "", {}, "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw=="],
-
"@oven/bun-linux-aarch64-musl": ["@oven/bun-linux-aarch64-musl@1.3.2", "", { "os": "linux", "cpu": "arm64" }, "sha512-OD9DYkjes7WXieBn4zQZGXWhRVZhIEWMDGCetZ3H4vxIuweZ++iul/CNX5jdpNXaJ17myb1ROMvmRbrqW44j3w=="],
+
"ansi-escapes": ["ansi-escapes@4.3.2", "", { "dependencies": { "type-fest": "^0.21.3" } }, "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ=="],
-
"@oven/bun-linux-x64": ["@oven/bun-linux-x64@1.3.2", "", { "os": "linux", "cpu": "x64" }, "sha512-EoEuRP9bxAxVKuvi6tZ0ZENjueP4lvjz0mKsMzdG0kwg/2apGKiirH1l0RIcdmvfDGGuDmNiv/XBpkoXq1x8ug=="],
+
"ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
-
"@oven/bun-linux-x64-baseline": ["@oven/bun-linux-x64-baseline@1.3.2", "", { "os": "linux", "cpu": "x64" }, "sha512-m9Ov9YH8KjRLui87eNtQQFKVnjGsNk3xgbrR9c8d2FS3NfZSxmVjSeBvEsDjzNf1TXLDriHb/NYOlpiMf/QzDg=="],
+
"ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="],
-
"@oven/bun-linux-x64-musl": ["@oven/bun-linux-x64-musl@1.3.2", "", { "os": "linux", "cpu": "x64" }, "sha512-3TuOsRVoG8K+soQWRo+Cp5ACpRs6rTFSu5tAqc/6WrqwbNWmqjov/eWJPTgz3gPXnC7uNKVG7RxxAmV8r2EYTQ=="],
+
"arch": ["arch@2.2.0", "", {}, "sha512-Of/R0wqp83cgHozfIYLbBMnej79U/SVGOOyuB3VVFv1NRM/PSFMK12x9KVtiYzJqmnU5WR2qp0Z5rHb7sWGnFQ=="],
-
"@oven/bun-linux-x64-musl-baseline": ["@oven/bun-linux-x64-musl-baseline@1.3.2", "", { "os": "linux", "cpu": "x64" }, "sha512-q8Hto8hcpofPJjvuvjuwyYvhOaAzPw1F5vRUUeOJDmDwZ4lZhANFM0rUwchMzfWUJCD6jg8/EVQ8MiixnZWU0A=="],
+
"asn1": ["asn1@0.2.6", "", { "dependencies": { "safer-buffer": "~2.1.0" } }, "sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ=="],
-
"@oven/bun-windows-x64": ["@oven/bun-windows-x64@1.3.2", "", { "os": "win32", "cpu": "x64" }, "sha512-nZJUa5NprPYQ4Ii4cMwtP9PzlJJTp1XhxJ+A9eSn1Jfr6YygVWyN2KLjenyI93IcuBouBAaepDAVZZjH2lFBhg=="],
+
"assert-plus": ["assert-plus@1.0.0", "", {}, "sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw=="],
-
"@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=="],
+
"astral-regex": ["astral-regex@2.0.0", "", {}, "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ=="],
-
"@swc/helpers": ["@swc/helpers@0.5.15", "", { "dependencies": { "tslib": "^2.8.0" } }, "sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g=="],
+
"asynckit": ["asynckit@0.4.0", "", {}, "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="],
-
"@types/bun": ["@types/bun@1.3.2", "", { "dependencies": { "bun-types": "1.3.2" } }, "sha512-t15P7k5UIgHKkxwnMNkJbWlh/617rkDGEdSsDbu+qNHTaz9SKf7aC8fiIlUdD5RPpH6GEkP0cK7WlvmrEBRtWg=="],
+
"at-least-node": ["at-least-node@1.0.0", "", {}, "sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg=="],
-
"@types/node": ["@types/node@24.10.1", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ=="],
+
"aws-sign2": ["aws-sign2@0.7.0", "", {}, "sha512-08kcGqnYf/YmjoRhfxyu+CLxBjUtHLXLXX/vUfx9l2LYzG3c1m61nrpyFUZI6zeS+Li/wWMMidD9KgrqtGq3mA=="],
-
"@types/react": ["@types/react@19.2.4", "", { "dependencies": { "csstype": "^3.0.2" } }, "sha512-tBFxBp9Nfyy5rsmefN+WXc1JeW/j2BpBHFdLZbEVfs9wn3E3NRFxwV0pJg8M1qQAexFpvz73hJXFofV0ZAu92A=="],
+
"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=="],
-
"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=="],
+
"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=="],
-
"bun-types": ["bun-types@1.3.2", "", { "dependencies": { "@types/node": "*" }, "peerDependencies": { "@types/react": "^19" } }, "sha512-i/Gln4tbzKNuxP70OWhJRZz1MRfvqExowP7U6JKoI8cntFrtxg7RJK3jvz7wQW54UuvNC8tbKHHri5fy74FVqg=="],
+
"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=="],
-
"csstype": ["csstype@3.1.3", "", {}, "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw=="],
+
"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=="],
-
"hono": ["hono@4.10.5", "", {}, "sha512-h/MXuTkoAK8NG1EfDp0jI1YLf6yGdDnfkebRO2pwEh5+hE3RAJFXkCsnD0vamSiARK4ZrB6MY+o3E/hCnOyHrQ=="],
+
"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.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=="],
+
"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;
+8 -5
package.json
···
"build": "--bun next build",
"start": "--bun next start",
"lint": "--bun biome check",
-
"format": "--bun biome format --write"
+
"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.7.0",
+
"@hono/vite-build": "^1.8.0",
"@hono/vite-dev-server": "^0.23.0",
"fauxdom": "^1.2.2",
-
"hono": "^4.10.4",
-
"next": "16.0.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"
+
"@types/bun": "latest",
+
"baseline-browser-mapping": "^2.9.5",
+
"cypress": "^15.7.1"
}
}
src/app/android-chrome-192x192.png

This is a binary file and will not be displayed.

src/app/android-chrome-512x512.png

This is a binary file and will not be displayed.

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"
+
import { setArchiveBaseUrl, resetArchiveBaseUrl } from "@fujocoded/ao3.js/urls"
-
export async function GET(_req, ctx) {
+
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"
+
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) {
+
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"
+
import { setArchiveBaseUrl, getArchiveBaseUrl, resetArchiveBaseUrl } from "@fujocoded/ao3.js/urls"
-
export async function GET(_req, ctx) {
+
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)
}
src/app/apple-touch-icon.png

This is a binary file and will not be displayed.

src/app/favicon-16x16.png

This is a binary file and will not be displayed.

src/app/favicon-32x32.png

This is a binary file and will not be displayed.

src/app/favicon.ico

This is a binary file and will not be displayed.

+48 -35
src/app/generator/page.js
···
"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 styles from "./page.module.css"
+
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({
-
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 [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}`) : await fetch(`/api/works/${match.groups.workId}`)
+
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}`)
+
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()}`)
+
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="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 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>
+4 -1
src/app/layout.js
···
export const metadata = {
title: process.env.SITENAME,
-
description: process.env.DESCRIPTION
+
description: process.env.DESCRIPTION,
+
metadataBase: new URL('https://'+process.env.DOMAIN),
};
export const viewport = "width=device-width, initial-scale=1.0"
···
</h1>
{children}
</div>
+
<script data-goatcounter="https://fixao3.goatcounter.com/count"
+
async src="//gc.zgo.at/count.js"></script>
</body>
</html>
)
+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})
+
}
+54
src/app/opengraph-image.jsx
···
+
import { ImageResponse } from "next/og"
+
import { readFile } from 'node:fs/promises'
+
import { join } from 'node:path'
+
+
export const size = {
+
width: 1600,
+
height: 900,
+
}
+
export const alt = 'fixAO3'
+
+
export const contentType = 'image/webp'
+
+
export default async function Image() {
+
return new ImageResponse(
+
(
+
<div
+
style={{
+
backgroundColor: '#990000',
+
color: '#FFFFFF',
+
display: 'flex',
+
alignItems: 'center',
+
justifyContent: 'center',
+
width: '100%',
+
height: '100%',
+
fontFamily: 'Stack Sans Notch',
+
fontSize: 288,
+
fontWeight: 700
+
}}
+
>
+
{process.env.SITENAME}
+
</div>
+
),
+
{
+
fonts: [
+
{
+
name: 'Stack Sans Notch',
+
data: await readFile(
+
join(process.cwd(), '/fonts/StackSansNotch-Regular.ttf')
+
),
+
style: 'normal',
+
weight: 400
+
},
+
{
+
name: 'Stack Sans Notch',
+
data: await readFile(
+
join(process.cwd(), '/fonts/StackSansNotch-Bold.ttf')
+
),
+
style: 'normal',
+
weight: 700
+
}
+
]
+
}
+
)
+
}
+6 -2
src/app/page.js
···
It'll automatically pull in your fic's metadata and set up a
redirect. Easy as that!
</p>
+
<h2>How is my data used?</h2>
+
<p>No data is stored on my servers; it's all just fetched from AO3 in realtime. If this starts eating all my bandwidth I might look into caching resources but also I don't want to deal with a database. Data is only fetched when requested (either by linking a url somewhere that uses opengraph embeds, or by using the card generator).</p>
+
<p>A limited amount of non-identifying data is collected for statistical purposes so I know, like, how much use the site is getting, where people are hearing about it from, etc. If you don't want your data collected, just block goatcounter.com with your ad/scriptblocker of choice and you will be functionally invisible to me. Spooky...</p>
<h2>Support fixAO3</h2>
<p>
If you found this useful and would like to support further
development, you can tip me on{" "}
<a href="https://ko-fi.com/veryroundbird" target="_blank">
Ko-fi
-
</a>! This work would also be much, much harder and more annoying
+
</a>! The server/domain costs are maybe like, $40/yr at the moment which is not much, but also I'm an underemployed freelancer doing this for fun, so I appreciate people chipping in if you find it useful!</p>
+
<p>This work would also be much, much harder and more annoying
without being able to build off of Fujocoded's{" "}
<a href="https://github.com/fujocoded/ao3.js" target="_blank">
AO3.js
-
</a>. For questions, comments, job offers, and other inquiries,
+
</a> (give them your support too!). For questions, comments, job offers, and other inquiries,
I'm{" "}
<a
href="https://bsky.app/profile/veryroundbird.house"
+27
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 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
+
return OGImage({theme: theme, baseFont: baseFont, titleFont: titleFont, image: imageParams, addr: addr, opts: opts})
+
}
+7 -5
src/app/series/[seriesId]/page.js
···
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 description = `${series.description.replace("<br />", "\n").replace(/<[^>]>/g, "")}`
+
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,
-
openGraph: {
-
description: description,
-
images: [`/${addr}/preview`]
-
}
+
metadataBase: new URL('https://'+process.env.DOMAIN)
}
}
+10 -4
src/app/series/[seriesId]/preview/route.js
···
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/webp'
+
export const contentType = 'image/png'
export async function GET(req, ctx) {
const { seriesId } = await ctx.params
-
const props = await req.nextUrl.searchParams
+
const p = await req.nextUrl.searchParams
+
const props = querystring.parse(p.toString())
const addr = `series/${seriesId}`
-
const data = await getSeries({seriesId: seriesId})
-
const imageParams = await sanitizeData({type: 'series', data: data, props: props})
+
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
+27
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 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
+
const opts = imageParams.opts
+
return OGImage({theme: theme, baseFont: baseFont, titleFont: titleFont, image: imageParams, addr: addr, opts: opts})
+
}
+27 -9
src/app/works/[workId]/chapters/[chapterId]/page.js
···
import { getWork } from "@fujocoded/ao3.js"
+
import DOM from "fauxdom"
+
import siteMap from "@/lib/siteMap.js"
-
export async function generateMetadata({ params, _searchParams }, _parent) {
+
export async function generateMetadata({ params, _searchParams }, parent) {
// read route params
const { workId, chapterId } = await params
-
const work = await getWork({workId: workId, chapterId: chapterId})
+
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 : work.summary
-
const description = `${summary.replace("<br />", "\n").replace(/<[^>]>/g, "")}`
+
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,
-
openGraph: {
-
description: description,
-
images: [`/${addr}/preview`]
-
}
+
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}");
+
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 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/webp'
+
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 = `works/${workId}/chapters/${chapterId}`
-
const data = await getWork({workId: workId, chapterId: chapterId})
+
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
+27
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 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
+
const opts = imageParams.opts
+
return OGImage({theme: theme, baseFont: baseFont, titleFont: titleFont, image: imageParams, addr: addr, opts: opts})
+
}
+22 -9
src/app/works/[workId]/page.js
···
import { getWork } from "@fujocoded/ao3.js"
+
import DOM from "fauxdom"
-
export async function generateMetadata({ params, _searchParams }, _parent) {
+
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 description = `${summary.replace("<br />", "\n").replace(/<[^>]>/g, "")}`
+
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,
-
description: description,
-
openGraph: {
-
description: description,
-
images: [`/${addr}/preview`]
-
}
+
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}");
+
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 const contentType = 'image/png'
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 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
+48 -39
src/lib/ogimage.js
···
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)
···
}}
>
<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={{
-
textTransform: "uppercase",
display: "flex",
justifyContent: "center",
-
gap: 10,
-
color: theme.accent,
-
alignItems: "center"
-
}}
+
alignItems: "center",
+
gap: 10
+
}}
>
-
<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} />)}
+
{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} />)}
-
{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} />)}
+
{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} />)}
-
{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} />)}
+
{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
+
color: theme.color,
+
textTransform: (image.props.uppercaseTitle ? 'uppercase' : 'none')
}}
>
{image.titleLine}
···
>
{`by ${image.authorLine}`}
</div>
-
<div
+
{image.chapterLine !== '' && (<div
style={{
fontStyle: "italic",
fontSize: 36,
fontFamily: titleFont,
display: "flex",
justifyContent: "center",
-
color: theme.color
+
color: theme.color,
+
textTransform: (image.props.uppercaseChapterName ? 'uppercase' : 'none')
}}
>
{image.chapterLine}
-
</div>
+
</div>)}
</div>
<div
style={{
···
alignItems: "flex-end"
}}
>
-
{image.props.get("charTags") === 'true' && (<div
+
{checkToggle('charTags', image.props) && (<div
style={{
display: "flex",
flexWrap: "wrap",
···
<span
style={{
backgroundColor: theme.accent2,
-
color: theme.descBackground,
+
color: theme.accent2Color,
padding: "3px 5px",
borderRadius: 5
}}
···
</span>
))}
</div>)}
-
{image.props.get("relTags") === 'true' && (<div
+
{checkToggle('relTags', image.props) && (<div
style={{
display: "flex",
flexWrap: "wrap",
···
<span
style={{
backgroundColor: theme.accent3,
-
color: theme.descBackground,
+
color: theme.accent3Color,
padding: "3px 5px",
borderRadius: 5
}}
···
</span>
))}
</div>)}
-
{image.props.get("freeTags") === 'true' && (<div
+
{checkToggle('freeTags', image.props) && (<div
style={{
display: "flex",
flexWrap: "wrap",
···
<span
style={{
backgroundColor: theme.accent4,
-
color: theme.descBackground,
+
color: theme.accent4Color,
padding: "3px 5px",
borderRadius: 5
}}
···
</span>
))}
</div>)}
-
{image.props.get("summary") === 'true' && (<div
+
{checkToggle('summary', image.props) && (<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} โ€ข `}https://archiveofourown.org/{addr}
+
{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 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 getHighestRating = async (works) => {
+
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({workId: w.id})
+
const work = await getWork(w.id, archive)
return work.rating
}))
if (ratings.includes("Not Rated")) {
···
return "G"
}
-
const getHighestWarning = async (works) => {
+
const getHighestWarning = async (works, archive = null) => {
const warnings = await Promise.all(works.map(async (w) => {
-
const work = await getWork({workId: w.id})
+
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) => {
+
const getCategory = async (works, archive = null) => {
const categories = await Promise.all(works.map(async (w) => {
-
const work = await getWork({workId: w.id})
+
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 baseFont = props.has('baseFont') ? props.get('baseFont') : process.env.DEFAULT_BASE_FONT
+
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 = props.has('titleFont') ? props.get('titleFont') : process.env.DEFAULT_TITLE_FONT
+
const titleFont = checkItem('titleFont', props) ? 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 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' ? 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
+
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]
+
: 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)
+
? (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 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 {
+
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'
+
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',
-
accent4: '#a29acb'
+
accent3Color: '#F9F5F5',
+
accent4: '#a29acb',
+
accent4Color: '#F9F5F5',
},
wildCherry: {
name: 'Wild Cherry',
···
descBackground: '#FFFFFF',
descColor: '#2B1F32',
accent: '#E15D97',
+
accentColor: '#FFFFFF',
accent2: '#0AACC5',
+
accent2Color: '#FFFFFF',
accent3: '#FFB86C',
-
accent4: '#35BA66'
+
accent3Color: '#2B1F32',
+
accent4: '#35BA66',
+
accent4Color: '#FFFFFF',
},
rosePine: {
name: 'Rosรฉ Pine',
···
descBackground: '#1f1d2e',
descColor: '#e0def4',
accent: '#eb6f92',
-
accent2: '#31748f',
+
accentColor: '#191724',
+
accent2: '#9ccfd8',
+
accent2Color: '#191724',
accent3: '#f6c177',
-
accent4: '#c4a7e7'
+
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',
-
accent4: '#907aa9'
+
accent3Color: '#faf4ed',
+
accent4: '#907aa9',
+
accent4Color: '#faf4ed',
},
rosePineMoon: {
name: 'Rosรฉ Pine Moon',
···
descBackground: '#2a273f',
descColor: '#e0def4',
accent: '#b4637a',
-
accent2: '#3e8fb0',
+
accentColor: '#232136',
+
accent2: '#9ccfd8',
+
accent2Color: '#232136',
accent3: '#f6c177',
-
accent4: '#c4a7e7'
+
accent3Color: '#232136',
+
accent4: '#c4a7e7',
+
accent4Color: '#232136',
},
solarizedLight: {
name: 'Solarized Light',
···
descBackground: '#eee8d5',
descColor: '#002b36',
accent: '#d33682',
+
accentColor: '#fdf6e3',
accent2: '#2aa198',
+
accent2Color: '#fdf6e3',
accent3: '#859900',
-
accent4: '#6c71c4'
+
accent3Color: '#fdf6e3',
+
accent4: '#6c71c4',
+
accent4Color: '#fdf6e3',
},
solarizedDark: {
name: 'Solarized Dark',
···
descBackground: '#073642',
descColor: '#fdf6e3',
accent: '#d33682',
+
accentColor: '#002b36',
accent2: '#2aa198',
+
accent2Color: '#002b36',
accent3: '#859900',
-
accent4: '#6c71c4'
+
accent3Color: '#002b36',
+
accent4: '#6c71c4',
+
accent4Color: '#002b36',
},
squidgeworld: {
name: 'Squidgeworld',
···
color: '#f5f5dc',
descBackground: '#f5f5dc',
descColor: '#2a2a2a',
-
accent: '#fece3f',
+
accent: '#FFC825',
+
accentColor: '#f5f5dc',
accent2: '#818D4C',
+
accent2Color: '#f5f5dc',
accent3: '#6D7A34',
-
accent4: '#556121'
+
accent3Color: '#f5f5dc',
+
accent4: '#556121',
+
accent4Color: '#f5f5dc',
},
superlove: {
name: 'Superlove',
···
color: '#ffffff',
descBackground: '#FFFFFF',
descColor: '#2a2a2a',
-
accent: '#F9E4E6',
+
accent: '#F4C4C5',
+
accentColor: '#ffffff',
accent2: '#a33961',
+
accent2Color: '#ffffff',
accent3: '#87254A',
-
accent4: '#6A1133'
+
accent3Color: '#ffffff',
+
accent4: '#6A1133',
+
accent4Color: '#ffffff',
},
catppuccinMocha: {
name: 'Catppuccin Mocha',
···
descBackground: '#313244',
descColor: '#bac2de',
accent: '#f5e0dc',
+
accentColor: '#1e1e2e',
accent2: '#cba6f7',
+
accent2Color: '#1e1e2e',
accent3: '#fab387',
-
accent4: '#89dceb'
+
accent3Color: '#1e1e2e',
+
accent4: '#89dceb',
+
accent4Color: '#1e1e2e',
},
catppuccinLatte: {
name: 'Catppuccin Latte',
···
descBackground: '#ccd0da',
descColor: '#5c5f77',
accent: '#dc8a78',
+
accentColor: '#FFFFFF',
accent2: '#8839ef',
+
accent2Color: '#eff1f5',
accent3: '#fe640b',
-
accent4: '#04a5e5'
+
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
-
}
-
]
}
}