diff --git a/.gitignore b/.gitignore index c288ba2..cb3a08f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ .claude/ node_modules/ output/book.html +output/resized/ output/cover.html output/book-meta.json # Interior PDF is large (~100 MB with placeholder pages) — excluded from git. diff --git a/images/01/scene-1.png b/images/01/scene-1.png index 6e13ff3..2547a4f 100644 Binary files a/images/01/scene-1.png and b/images/01/scene-1.png differ diff --git a/images/01/scene-2.png b/images/01/scene-2.png index 6f457c6..95d4734 100644 Binary files a/images/01/scene-2.png and b/images/01/scene-2.png differ diff --git a/images/01/scene-3.png b/images/01/scene-3.png index 8e15766..ebc2df2 100644 Binary files a/images/01/scene-3.png and b/images/01/scene-3.png differ diff --git a/images/01/scene-4.png b/images/01/scene-4.png index 87835bf..d859bb1 100644 Binary files a/images/01/scene-4.png and b/images/01/scene-4.png differ diff --git a/images/02/scene-1.png b/images/02/scene-1.png index 0ed397d..d1a4730 100644 Binary files a/images/02/scene-1.png and b/images/02/scene-1.png differ diff --git a/images/02/scene-2.png b/images/02/scene-2.png index 3c4f220..100ff2d 100644 Binary files a/images/02/scene-2.png and b/images/02/scene-2.png differ diff --git a/images/02/scene-3.png b/images/02/scene-3.png index 605f0ac..8d76098 100644 Binary files a/images/02/scene-3.png and b/images/02/scene-3.png differ diff --git a/images/02/scene-4.png b/images/02/scene-4.png index 5c8721a..8e89316 100644 Binary files a/images/02/scene-4.png and b/images/02/scene-4.png differ diff --git a/images/03/scene-1.png b/images/03/scene-1.png new file mode 100644 index 0000000..eed0d96 Binary files /dev/null and b/images/03/scene-1.png differ diff --git a/images/03/scene-2.png b/images/03/scene-2.png new file mode 100644 index 0000000..8aeefba Binary files /dev/null and b/images/03/scene-2.png differ diff --git a/images/03/scene-3.png b/images/03/scene-3.png new file mode 100644 index 0000000..fc5e284 Binary files /dev/null and b/images/03/scene-3.png differ diff --git a/images/03/scene-4.png b/images/03/scene-4.png new file mode 100644 index 0000000..36749d3 Binary files /dev/null and b/images/03/scene-4.png differ diff --git a/images/04/scene-1.png b/images/04/scene-1.png new file mode 100644 index 0000000..bb6dc71 Binary files /dev/null and b/images/04/scene-1.png differ diff --git a/images/04/scene-2.png b/images/04/scene-2.png new file mode 100644 index 0000000..7296c21 Binary files /dev/null and b/images/04/scene-2.png differ diff --git a/images/04/scene-3.png b/images/04/scene-3.png new file mode 100644 index 0000000..6901dd3 Binary files /dev/null and b/images/04/scene-3.png differ diff --git a/images/04/scene-4.png b/images/04/scene-4.png new file mode 100644 index 0000000..f03c046 Binary files /dev/null and b/images/04/scene-4.png differ diff --git a/images/05/scene-1.png b/images/05/scene-1.png new file mode 100644 index 0000000..9f5847d Binary files /dev/null and b/images/05/scene-1.png differ diff --git a/images/05/scene-2.png b/images/05/scene-2.png new file mode 100644 index 0000000..61f7fe4 Binary files /dev/null and b/images/05/scene-2.png differ diff --git a/images/05/scene-3.png b/images/05/scene-3.png new file mode 100644 index 0000000..58c6df0 Binary files /dev/null and b/images/05/scene-3.png differ diff --git a/images/05/scene-4.png b/images/05/scene-4.png new file mode 100644 index 0000000..6c0e053 Binary files /dev/null and b/images/05/scene-4.png differ diff --git a/images/06/scene-1.png b/images/06/scene-1.png new file mode 100644 index 0000000..b434610 Binary files /dev/null and b/images/06/scene-1.png differ diff --git a/images/06/scene-2.png b/images/06/scene-2.png new file mode 100644 index 0000000..eec06e5 Binary files /dev/null and b/images/06/scene-2.png differ diff --git a/images/06/scene-3.png b/images/06/scene-3.png new file mode 100644 index 0000000..2f20926 Binary files /dev/null and b/images/06/scene-3.png differ diff --git a/images/06/scene-4.png b/images/06/scene-4.png new file mode 100644 index 0000000..4c18b08 Binary files /dev/null and b/images/06/scene-4.png differ diff --git a/images/07/scene-1.png b/images/07/scene-1.png new file mode 100644 index 0000000..a630cbe Binary files /dev/null and b/images/07/scene-1.png differ diff --git a/images/07/scene-2.png b/images/07/scene-2.png new file mode 100644 index 0000000..971d47d Binary files /dev/null and b/images/07/scene-2.png differ diff --git a/images/07/scene-3.png b/images/07/scene-3.png new file mode 100644 index 0000000..78a37f0 Binary files /dev/null and b/images/07/scene-3.png differ diff --git a/images/07/scene-4.png b/images/07/scene-4.png new file mode 100644 index 0000000..9e57c78 Binary files /dev/null and b/images/07/scene-4.png differ diff --git a/images/08/scene-1.png b/images/08/scene-1.png new file mode 100644 index 0000000..bdb269e Binary files /dev/null and b/images/08/scene-1.png differ diff --git a/images/08/scene-2.png b/images/08/scene-2.png new file mode 100644 index 0000000..9066ae8 Binary files /dev/null and b/images/08/scene-2.png differ diff --git a/images/08/scene-3.png b/images/08/scene-3.png new file mode 100644 index 0000000..3b0ed8b Binary files /dev/null and b/images/08/scene-3.png differ diff --git a/images/08/scene-4.png b/images/08/scene-4.png new file mode 100644 index 0000000..a59974e Binary files /dev/null and b/images/08/scene-4.png differ diff --git a/images/09/scene-1.png b/images/09/scene-1.png new file mode 100644 index 0000000..faad42b Binary files /dev/null and b/images/09/scene-1.png differ diff --git a/images/09/scene-2.png b/images/09/scene-2.png new file mode 100644 index 0000000..c2b5fc1 Binary files /dev/null and b/images/09/scene-2.png differ diff --git a/images/09/scene-3.png b/images/09/scene-3.png new file mode 100644 index 0000000..b3d9b76 Binary files /dev/null and b/images/09/scene-3.png differ diff --git a/images/09/scene-4.png b/images/09/scene-4.png new file mode 100644 index 0000000..d983105 Binary files /dev/null and b/images/09/scene-4.png differ diff --git a/images/10/scene-1.png b/images/10/scene-1.png new file mode 100644 index 0000000..9ddb987 Binary files /dev/null and b/images/10/scene-1.png differ diff --git a/images/10/scene-2.png b/images/10/scene-2.png new file mode 100644 index 0000000..27b62a2 Binary files /dev/null and b/images/10/scene-2.png differ diff --git a/images/10/scene-3.png b/images/10/scene-3.png new file mode 100644 index 0000000..b9f2009 Binary files /dev/null and b/images/10/scene-3.png differ diff --git a/images/10/scene-4.png b/images/10/scene-4.png new file mode 100644 index 0000000..17e071e Binary files /dev/null and b/images/10/scene-4.png differ diff --git a/images/11/scene-1.png b/images/11/scene-1.png new file mode 100644 index 0000000..15b5f09 Binary files /dev/null and b/images/11/scene-1.png differ diff --git a/images/11/scene-2.png b/images/11/scene-2.png new file mode 100644 index 0000000..c15cf6c Binary files /dev/null and b/images/11/scene-2.png differ diff --git a/images/11/scene-3.png b/images/11/scene-3.png new file mode 100644 index 0000000..48f8c10 Binary files /dev/null and b/images/11/scene-3.png differ diff --git a/images/11/scene-4.png b/images/11/scene-4.png new file mode 100644 index 0000000..9b3eb10 Binary files /dev/null and b/images/11/scene-4.png differ diff --git a/images/12/scene-1.png b/images/12/scene-1.png new file mode 100644 index 0000000..276d05e Binary files /dev/null and b/images/12/scene-1.png differ diff --git a/images/12/scene-2.png b/images/12/scene-2.png new file mode 100644 index 0000000..be24f2d Binary files /dev/null and b/images/12/scene-2.png differ diff --git a/images/12/scene-3.png b/images/12/scene-3.png new file mode 100644 index 0000000..b445cf1 Binary files /dev/null and b/images/12/scene-3.png differ diff --git a/images/12/scene-4.png b/images/12/scene-4.png new file mode 100644 index 0000000..3e3d8a7 Binary files /dev/null and b/images/12/scene-4.png differ diff --git a/package.json b/package.json index ec52728..d05f6d2 100644 --- a/package.json +++ b/package.json @@ -24,6 +24,7 @@ "gray-matter": "^4.0.3", "marked": "^12.0.0", "nunjucks": "^3.2.4", - "puppeteer": "^24.42.0" + "puppeteer": "^24.42.0", + "sharp": "^0.34.5" } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index fb84194..3748b3e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -26,6 +26,9 @@ importers: puppeteer: specifier: ^24.42.0 version: 24.42.0 + sharp: + specifier: ^0.34.5 + version: 0.34.5 packages: @@ -37,12 +40,152 @@ packages: resolution: {integrity: sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==} engines: {node: '>=6.9.0'} + '@emnapi/runtime@1.10.0': + resolution: {integrity: sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==} + '@fontsource/cormorant-garamond@5.2.11': resolution: {integrity: sha512-5JjpN023lhA5soijgVT0BdRGzmlijm402ppjccMd6h+vRE0mX2lJnE+41UPfnlidrkV9/rCo1mf58WZlHnB0CA==} '@fontsource/lora@5.2.8': resolution: {integrity: sha512-AQlfsHw4TP1x/eb2IZ6VjQ70ctKa39m9JN9A4zlvDOeKYLrCs+GaYIEQ86Y6YfSPGHn01bErXkRcyktOW0LOPQ==} + '@img/colour@1.1.0': + resolution: {integrity: sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ==} + engines: {node: '>=18'} + + '@img/sharp-darwin-arm64@0.34.5': + resolution: {integrity: sha512-imtQ3WMJXbMY4fxb/Ndp6HBTNVtWCUI0WdobyheGf5+ad6xX8VIDO8u2xE4qc/fr08CKG/7dDseFtn6M6g/r3w==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm64] + os: [darwin] + + '@img/sharp-darwin-x64@0.34.5': + resolution: {integrity: sha512-YNEFAF/4KQ/PeW0N+r+aVVsoIY0/qxxikF2SWdp+NRkmMB7y9LBZAVqQ4yhGCm/H3H270OSykqmQMKLBhBJDEw==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [x64] + os: [darwin] + + '@img/sharp-libvips-darwin-arm64@1.2.4': + resolution: {integrity: sha512-zqjjo7RatFfFoP0MkQ51jfuFZBnVE2pRiaydKJ1G/rHZvnsrHAOcQALIi9sA5co5xenQdTugCvtb1cuf78Vf4g==} + cpu: [arm64] + os: [darwin] + + '@img/sharp-libvips-darwin-x64@1.2.4': + resolution: {integrity: sha512-1IOd5xfVhlGwX+zXv2N93k0yMONvUlANylbJw1eTah8K/Jtpi15KC+WSiaX/nBmbm2HxRM1gZ0nSdjSsrZbGKg==} + cpu: [x64] + os: [darwin] + + '@img/sharp-libvips-linux-arm64@1.2.4': + resolution: {integrity: sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw==} + cpu: [arm64] + os: [linux] + + '@img/sharp-libvips-linux-arm@1.2.4': + resolution: {integrity: sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A==} + cpu: [arm] + os: [linux] + + '@img/sharp-libvips-linux-ppc64@1.2.4': + resolution: {integrity: sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA==} + cpu: [ppc64] + os: [linux] + + '@img/sharp-libvips-linux-riscv64@1.2.4': + resolution: {integrity: sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA==} + cpu: [riscv64] + os: [linux] + + '@img/sharp-libvips-linux-s390x@1.2.4': + resolution: {integrity: sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ==} + cpu: [s390x] + os: [linux] + + '@img/sharp-libvips-linux-x64@1.2.4': + resolution: {integrity: sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw==} + cpu: [x64] + os: [linux] + + '@img/sharp-libvips-linuxmusl-arm64@1.2.4': + resolution: {integrity: sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw==} + cpu: [arm64] + os: [linux] + + '@img/sharp-libvips-linuxmusl-x64@1.2.4': + resolution: {integrity: sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg==} + cpu: [x64] + os: [linux] + + '@img/sharp-linux-arm64@0.34.5': + resolution: {integrity: sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm64] + os: [linux] + + '@img/sharp-linux-arm@0.34.5': + resolution: {integrity: sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm] + os: [linux] + + '@img/sharp-linux-ppc64@0.34.5': + resolution: {integrity: sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [ppc64] + os: [linux] + + '@img/sharp-linux-riscv64@0.34.5': + resolution: {integrity: sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [riscv64] + os: [linux] + + '@img/sharp-linux-s390x@0.34.5': + resolution: {integrity: sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [s390x] + os: [linux] + + '@img/sharp-linux-x64@0.34.5': + resolution: {integrity: sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [x64] + os: [linux] + + '@img/sharp-linuxmusl-arm64@0.34.5': + resolution: {integrity: sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm64] + os: [linux] + + '@img/sharp-linuxmusl-x64@0.34.5': + resolution: {integrity: sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [x64] + os: [linux] + + '@img/sharp-wasm32@0.34.5': + resolution: {integrity: sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [wasm32] + + '@img/sharp-win32-arm64@0.34.5': + resolution: {integrity: sha512-WQ3AgWCWYSb2yt+IG8mnC6Jdk9Whs7O0gxphblsLvdhSpSTtmu69ZG1Gkb6NuvxsNACwiPV6cNSZNzt0KPsw7g==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm64] + os: [win32] + + '@img/sharp-win32-ia32@0.34.5': + resolution: {integrity: sha512-FV9m/7NmeCmSHDD5j4+4pNI8Cp3aW+JvLoXcTUo0IqyjSfAZJ8dIUmijx1qaJsIiU+Hosw6xM5KijAWRJCSgNg==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [ia32] + os: [win32] + + '@img/sharp-win32-x64@0.34.5': + resolution: {integrity: sha512-+29YMsqY2/9eFEiW93eqWnuLcWcufowXewwSNIT6UwZdUUCrM3oFjMWH/Z6/TMmb4hlFenmfAVbpWeup2jryCw==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [x64] + os: [win32] + '@puppeteer/browsers@2.13.0': resolution: {integrity: sha512-46BZJYJjc/WwmKjsvDFykHtXrtomsCIrwYQPOP7VfMJoZY2bsDF9oROBABR3paDjDcmkUye1Pb1BqdcdiipaWA==} engines: {node: '>=18'} @@ -191,6 +334,10 @@ packages: resolution: {integrity: sha512-TllpMR/t0M5sqCXfj85i4XaAzxmS5tVA16dqvdkMwGmzI+dXLXnw3J+3Vdv7VKw+ThlTMboK6i9rnZ6Nntj5CQ==} engines: {node: '>= 14'} + detect-libc@2.1.2: + resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} + engines: {node: '>=8'} + devtools-protocol@0.0.1595872: resolution: {integrity: sha512-kRfgp8vWVjBu/fbYCiVFiOqsCk3CrMKEo3WbgGT2NXK2dG7vawWPBljixajVgGK9II8rDO9G0oD0zLt3I1daRg==} @@ -405,6 +552,10 @@ packages: engines: {node: '>=10'} hasBin: true + sharp@0.34.5: + resolution: {integrity: sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + smart-buffer@4.2.0: resolution: {integrity: sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==} engines: {node: '>= 6.0.0', npm: '>= 3.0.0'} @@ -510,10 +661,111 @@ snapshots: '@babel/helper-validator-identifier@7.28.5': {} + '@emnapi/runtime@1.10.0': + dependencies: + tslib: 2.8.1 + optional: true + '@fontsource/cormorant-garamond@5.2.11': {} '@fontsource/lora@5.2.8': {} + '@img/colour@1.1.0': {} + + '@img/sharp-darwin-arm64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-darwin-arm64': 1.2.4 + optional: true + + '@img/sharp-darwin-x64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-darwin-x64': 1.2.4 + optional: true + + '@img/sharp-libvips-darwin-arm64@1.2.4': + optional: true + + '@img/sharp-libvips-darwin-x64@1.2.4': + optional: true + + '@img/sharp-libvips-linux-arm64@1.2.4': + optional: true + + '@img/sharp-libvips-linux-arm@1.2.4': + optional: true + + '@img/sharp-libvips-linux-ppc64@1.2.4': + optional: true + + '@img/sharp-libvips-linux-riscv64@1.2.4': + optional: true + + '@img/sharp-libvips-linux-s390x@1.2.4': + optional: true + + '@img/sharp-libvips-linux-x64@1.2.4': + optional: true + + '@img/sharp-libvips-linuxmusl-arm64@1.2.4': + optional: true + + '@img/sharp-libvips-linuxmusl-x64@1.2.4': + optional: true + + '@img/sharp-linux-arm64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linux-arm64': 1.2.4 + optional: true + + '@img/sharp-linux-arm@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linux-arm': 1.2.4 + optional: true + + '@img/sharp-linux-ppc64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linux-ppc64': 1.2.4 + optional: true + + '@img/sharp-linux-riscv64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linux-riscv64': 1.2.4 + optional: true + + '@img/sharp-linux-s390x@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linux-s390x': 1.2.4 + optional: true + + '@img/sharp-linux-x64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linux-x64': 1.2.4 + optional: true + + '@img/sharp-linuxmusl-arm64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linuxmusl-arm64': 1.2.4 + optional: true + + '@img/sharp-linuxmusl-x64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linuxmusl-x64': 1.2.4 + optional: true + + '@img/sharp-wasm32@0.34.5': + dependencies: + '@emnapi/runtime': 1.10.0 + optional: true + + '@img/sharp-win32-arm64@0.34.5': + optional: true + + '@img/sharp-win32-ia32@0.34.5': + optional: true + + '@img/sharp-win32-x64@0.34.5': + optional: true + '@puppeteer/browsers@2.13.0': dependencies: debug: 4.4.3 @@ -642,6 +894,8 @@ snapshots: escodegen: 2.1.0 esprima: 4.0.1 + detect-libc@2.1.2: {} + devtools-protocol@0.0.1595872: {} emoji-regex@8.0.0: {} @@ -883,6 +1137,37 @@ snapshots: semver@7.7.4: {} + sharp@0.34.5: + dependencies: + '@img/colour': 1.1.0 + detect-libc: 2.1.2 + semver: 7.7.4 + 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 + smart-buffer@4.2.0: {} socks-proxy-agent@8.0.5: diff --git a/scripts/build.js b/scripts/build.js index 62fb351..948220a 100644 --- a/scripts/build.js +++ b/scripts/build.js @@ -1,9 +1,10 @@ -import { readdir, readFile, writeFile, access } from 'fs/promises'; +import { readdir, readFile, writeFile, access, mkdir } from 'fs/promises'; import { join, resolve, dirname } from 'path'; import { fileURLToPath } from 'url'; import matter from 'gray-matter'; import { marked } from 'marked'; import nunjucks from 'nunjucks'; +import sharp from 'sharp'; const __dir = dirname(fileURLToPath(import.meta.url)); const root = resolve(__dir, '..'); @@ -11,12 +12,39 @@ const root = resolve(__dir, '..'); // Nunjucks: load templates relative to project root const env = nunjucks.configure(join(root, 'templates'), { autoescape: true }); +const PRINT_PX = 2550; // 8.5 in × 300 DPI + async function fileExists(path) { try { await access(path); return true; } catch { return false; } } +// Resize image to PRINT_PX on the long side, write to output/resized/ tree. +// Returns the resized file path (absolute). Skips resize if already ≤ PRINT_PX. +async function resizeForPrint(srcAbs, resizedDir) { + const rel = srcAbs.replace(root + '/', ''); // e.g. images/01/scene-1.png + const dest = join(resizedDir, rel); + const destDir = dirname(dest); + + await mkdir(destDir, { recursive: true }); + + const meta = await sharp(srcAbs).metadata(); + const maxDim = Math.max(meta.width || 0, meta.height || 0); + + if (maxDim <= PRINT_PX) { + // Already small enough — copy as-is + await sharp(srcAbs).toFile(dest); + } else { + await sharp(srcAbs) + .resize(PRINT_PX, PRINT_PX, { fit: 'inside', withoutEnlargement: true }) + .png({ compressionLevel: 6 }) + .toFile(dest); + } + return dest; +} + async function loadStories() { - const contentDir = join(root, 'content'); + const contentDir = join(root, 'content'); + const resizedDir = join(root, 'output', 'resized'); const files = (await readdir(contentDir)).filter(f => f.endsWith('.md')).sort(); const stories = []; @@ -27,18 +55,24 @@ async function loadStories() { if (data.type === 'front-matter') continue; - // Split body by --- into individual scene texts const sceneTexts = content.split(/\n---\n/).map(t => t.trim()).filter(Boolean); const scenes = await Promise.all( (data.scenes || []).map(async (scene, i) => { - const imagePath = join(root, scene.image); - const imageExists = await fileExists(imagePath); + const srcAbs = join(root, scene.image); + const imageExists = await fileExists(srcAbs); + + let imageSrc = `../${scene.image}`; + if (imageExists) { + await resizeForPrint(srcAbs, resizedDir); + // Relative path from output/book.html to output/resized/ + imageSrc = `resized/${scene.image}`; + } + return { ...scene, imageExists, - // Relative path from output/book.html back to project root - image: `../${scene.image}`, + image: imageSrc, html: marked.parse(sceneTexts[i] || ''), }; })