diff --git a/package-lock.json b/package-lock.json index 52d104a..d5fa89b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -19,6 +19,7 @@ "next-themes": "^0.4.4", "react": "^18.2.0", "react-dom": "^18.2.0", + "sharp": "^0.33.2", "tailwind-merge": "^2.6.0", "tailwindcss-animate": "^1.0.7", "zod": "^3.22.4" @@ -50,6 +51,16 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/@emnapi/runtime": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.3.1.tgz", + "integrity": "sha512-kEBmG8KyqtxJZv+ygbEim+KCGtIq1fC22Ms3S4ziXmYKm8uyoLX0MHONVKwp+9opg390VaKRNt4a7A9NwmpNhw==", + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, "node_modules/@eslint-community/eslint-utils": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.1.tgz", @@ -189,6 +200,367 @@ "dev": true, "license": "BSD-3-Clause" }, + "node_modules/@img/sharp-darwin-arm64": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.33.5.tgz", + "integrity": "sha512-UT4p+iz/2H4twwAoLCqfA9UH5pI6DggwKEGuaPy7nCVQ8ZsiY5PIcrRvD1DzuY3qYL07NtIQcWnBSY/heikIFQ==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-arm64": "1.0.4" + } + }, + "node_modules/@img/sharp-darwin-x64": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.33.5.tgz", + "integrity": "sha512-fyHac4jIc1ANYGRDxtiqelIbdWkIuQaI84Mv45KvGRRxSAa7o7d1ZKAOBaYbnepLC1WqxfpimdeWfvqqSGwR2Q==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-x64": "1.0.4" + } + }, + "node_modules/@img/sharp-libvips-darwin-arm64": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.0.4.tgz", + "integrity": "sha512-XblONe153h0O2zuFfTAbQYAX2JhYmDHeWikp1LM9Hul9gVPjFY427k6dFEcOL72O01QxQsWi761svJ/ev9xEDg==", + "cpu": [ + "arm64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "darwin" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-darwin-x64": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.0.4.tgz", + "integrity": "sha512-xnGR8YuZYfJGmWPvmlunFaWJsb9T/AO2ykoP3Fz/0X5XV2aoYBPkX6xqCQvUTKKiLddarLaxpzNe+b1hjeWHAQ==", + "cpu": [ + "x64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "darwin" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-arm": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.0.5.tgz", + "integrity": "sha512-gvcC4ACAOPRNATg/ov8/MnbxFDJqf/pDePbBnuBDcjsI8PssmjoKMAz4LtLaVi+OnSb5FK/yIOamqDwGmXW32g==", + "cpu": [ + "arm" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-arm64": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.0.4.tgz", + "integrity": "sha512-9B+taZ8DlyyqzZQnoeIvDVR/2F4EbMepXMc/NdVbkzsJbzkUjhXv/70GQJ7tdLA4YJgNP25zukcxpX2/SueNrA==", + "cpu": [ + "arm64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-s390x": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.0.4.tgz", + "integrity": "sha512-u7Wz6ntiSSgGSGcjZ55im6uvTrOxSIS8/dgoVMoiGE9I6JAfU50yH5BoDlYA1tcuGS7g/QNtetJnxA6QEsCVTA==", + "cpu": [ + "s390x" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-x64": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.0.4.tgz", + "integrity": "sha512-MmWmQ3iPFZr0Iev+BAgVMb3ZyC4KeFc3jFxnNbEPas60e1cIfevbtuyf9nDGIzOaW9PdnDciJm+wFFaTlj5xYw==", + "cpu": [ + "x64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linuxmusl-arm64": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.0.4.tgz", + "integrity": "sha512-9Ti+BbTYDcsbp4wfYib8Ctm1ilkugkA/uscUn6UXK1ldpC1JjiXbLfFZtRlBhjPZ5o1NCLiDbg8fhUPKStHoTA==", + "cpu": [ + "arm64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linuxmusl-x64": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.0.4.tgz", + "integrity": "sha512-viYN1KX9m+/hGkJtvYYp+CCLgnJXwiQB39damAO7WMdKWlIhmYTfHjwSbQeUK/20vY154mwezd9HflVFM1wVSw==", + "cpu": [ + "x64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-linux-arm": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.33.5.tgz", + "integrity": "sha512-JTS1eldqZbJxjvKaAkxhZmBqPRGmxgu+qFKSInv8moZ2AmT5Yib3EQ1c6gp493HvrvV8QgdOXdyaIBrhvFhBMQ==", + "cpu": [ + "arm" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm": "1.0.5" + } + }, + "node_modules/@img/sharp-linux-arm64": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.33.5.tgz", + "integrity": "sha512-JMVv+AMRyGOHtO1RFBiJy/MBsgz0x4AWrT6QoEVVTyh1E39TrCUpTRI7mx9VksGX4awWASxqCYLCV4wBZHAYxA==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm64": "1.0.4" + } + }, + "node_modules/@img/sharp-linux-s390x": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.33.5.tgz", + "integrity": "sha512-y/5PCd+mP4CA/sPDKl2961b+C9d+vPAveS33s6Z3zfASk2j5upL6fXVPZi7ztePZ5CuH+1kW8JtvxgbuXHRa4Q==", + "cpu": [ + "s390x" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-s390x": "1.0.4" + } + }, + "node_modules/@img/sharp-linux-x64": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.33.5.tgz", + "integrity": "sha512-opC+Ok5pRNAzuvq1AG0ar+1owsu842/Ab+4qvU879ippJBHvyY5n2mxF1izXqkPYlGuP/M556uh53jRLJmzTWA==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-x64": "1.0.4" + } + }, + "node_modules/@img/sharp-linuxmusl-arm64": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.33.5.tgz", + "integrity": "sha512-XrHMZwGQGvJg2V/oRSUfSAfjfPxO+4DkiRh6p2AFjLQztWUuY/o8Mq0eMQVIY7HJ1CDQUJlxGGZRw1a5bqmd1g==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-arm64": "1.0.4" + } + }, + "node_modules/@img/sharp-linuxmusl-x64": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.33.5.tgz", + "integrity": "sha512-WT+d/cgqKkkKySYmqoZ8y3pxx7lx9vVejxW/W4DOFMYVSkErR+w7mf2u8m/y4+xHe7yY9DAXQMWQhpnMuFfScw==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-x64": "1.0.4" + } + }, + "node_modules/@img/sharp-wasm32": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.33.5.tgz", + "integrity": "sha512-ykUW4LVGaMcU9lu9thv85CbRMAwfeadCJHRsg2GmeRa/cJxsVY9Rbd57JcMxBkKHag5U/x7TSBpScF4U8ElVzg==", + "cpu": [ + "wasm32" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT", + "optional": true, + "dependencies": { + "@emnapi/runtime": "^1.2.0" + }, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-ia32": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.33.5.tgz", + "integrity": "sha512-T36PblLaTwuVJ/zw/LaH0PdZkRz5rd3SmMHX8GSmR7vtNSP5Z6bQkExdSK7xGWyxLw4sUknBuugTelgw2faBbQ==", + "cpu": [ + "ia32" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-x64": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.33.5.tgz", + "integrity": "sha512-MpY/o8/8kj+EcnxwvrP4aTJSWw/aZ7JIGR4aBeZkZw5B7/Jn+tY9/VNwtcoGmdT7GfggGIU4kygOMSbYnOrAbg==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, "node_modules/@isaacs/cliui": { "version": "8.0.2", "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", @@ -2044,6 +2416,19 @@ "node": ">=6" } }, + "node_modules/color": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/color/-/color-4.2.3.tgz", + "integrity": "sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==", + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1", + "color-string": "^1.9.0" + }, + "engines": { + "node": ">=12.5.0" + } + }, "node_modules/color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", @@ -2062,6 +2447,16 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "license": "MIT" }, + "node_modules/color-string": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz", + "integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==", + "license": "MIT", + "dependencies": { + "color-name": "^1.0.0", + "simple-swizzle": "^0.2.2" + } + }, "node_modules/commander": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", @@ -2233,6 +2628,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/detect-libc": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.3.tgz", + "integrity": "sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==", + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, "node_modules/detect-node-es": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/detect-node-es/-/detect-node-es-1.1.0.tgz", @@ -3607,6 +4011,12 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-arrayish": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz", + "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==", + "license": "MIT" + }, "node_modules/is-async-function": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-async-function/-/is-async-function-2.1.1.tgz", @@ -5332,7 +5742,6 @@ "version": "7.7.1", "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz", "integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==", - "dev": true, "license": "ISC", "bin": { "semver": "bin/semver.js" @@ -5390,6 +5799,45 @@ "node": ">= 0.4" } }, + "node_modules/sharp": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.33.5.tgz", + "integrity": "sha512-haPVm1EkS9pgvHrQ/F3Xy+hgcuMV0Wm9vfIBSiwZ05k+xgb0PkBQpGsAA/oWdDobNaZTH5ppvHtzCFbnSEwHVw==", + "hasInstallScript": true, + "license": "Apache-2.0", + "dependencies": { + "color": "^4.2.3", + "detect-libc": "^2.0.3", + "semver": "^7.6.3" + }, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-darwin-arm64": "0.33.5", + "@img/sharp-darwin-x64": "0.33.5", + "@img/sharp-libvips-darwin-arm64": "1.0.4", + "@img/sharp-libvips-darwin-x64": "1.0.4", + "@img/sharp-libvips-linux-arm": "1.0.5", + "@img/sharp-libvips-linux-arm64": "1.0.4", + "@img/sharp-libvips-linux-s390x": "1.0.4", + "@img/sharp-libvips-linux-x64": "1.0.4", + "@img/sharp-libvips-linuxmusl-arm64": "1.0.4", + "@img/sharp-libvips-linuxmusl-x64": "1.0.4", + "@img/sharp-linux-arm": "0.33.5", + "@img/sharp-linux-arm64": "0.33.5", + "@img/sharp-linux-s390x": "0.33.5", + "@img/sharp-linux-x64": "0.33.5", + "@img/sharp-linuxmusl-arm64": "0.33.5", + "@img/sharp-linuxmusl-x64": "0.33.5", + "@img/sharp-wasm32": "0.33.5", + "@img/sharp-win32-ia32": "0.33.5", + "@img/sharp-win32-x64": "0.33.5" + } + }, "node_modules/shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", @@ -5499,6 +5947,15 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/simple-swizzle": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz", + "integrity": "sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==", + "license": "MIT", + "dependencies": { + "is-arrayish": "^0.3.1" + } + }, "node_modules/slash": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", diff --git a/package.json b/package.json index dda9673..483c5f5 100644 --- a/package.json +++ b/package.json @@ -6,7 +6,8 @@ "dev": "next dev", "build": "next build", "start": "next start", - "lint": "next lint" + "lint": "next lint", + "icons": "node scripts/generate-icons.js" }, "dependencies": { "@radix-ui/react-dialog": "^1.0.5", @@ -22,7 +23,8 @@ "react-dom": "^18.2.0", "tailwind-merge": "^2.6.0", "tailwindcss-animate": "^1.0.7", - "zod": "^3.22.4" + "zod": "^3.22.4", + "sharp": "^0.33.2" }, "devDependencies": { "@types/node": "^20.11.16", diff --git a/public/images/icons/icon-128x128.png b/public/images/icons/icon-128x128.png new file mode 100644 index 0000000..77789fa Binary files /dev/null and b/public/images/icons/icon-128x128.png differ diff --git a/public/images/icons/icon-144x144.png b/public/images/icons/icon-144x144.png new file mode 100644 index 0000000..266cadd Binary files /dev/null and b/public/images/icons/icon-144x144.png differ diff --git a/public/images/icons/icon-152x152.png b/public/images/icons/icon-152x152.png new file mode 100644 index 0000000..a438c33 Binary files /dev/null and b/public/images/icons/icon-152x152.png differ diff --git a/public/images/icons/icon-192x192.png b/public/images/icons/icon-192x192.png new file mode 100644 index 0000000..1df2ca0 Binary files /dev/null and b/public/images/icons/icon-192x192.png differ diff --git a/public/images/icons/icon-384x384.png b/public/images/icons/icon-384x384.png new file mode 100644 index 0000000..beb271f Binary files /dev/null and b/public/images/icons/icon-384x384.png differ diff --git a/public/images/icons/icon-512x512.png b/public/images/icons/icon-512x512.png new file mode 100644 index 0000000..90ccb41 Binary files /dev/null and b/public/images/icons/icon-512x512.png differ diff --git a/public/images/icons/icon-72x72.png b/public/images/icons/icon-72x72.png new file mode 100644 index 0000000..3e26ccc Binary files /dev/null and b/public/images/icons/icon-72x72.png differ diff --git a/public/images/icons/icon-96x96.png b/public/images/icons/icon-96x96.png new file mode 100644 index 0000000..bf276ac Binary files /dev/null and b/public/images/icons/icon-96x96.png differ diff --git a/public/manifest.json b/public/manifest.json index 9bce1ea..09ad56e 100644 --- a/public/manifest.json +++ b/public/manifest.json @@ -1,17 +1,103 @@ { "name": "StripStream", "short_name": "StripStream", - "description": "A modern web reader for Komga", + "description": "Votre bibliothèque numérique pour lire vos BD, mangas et comics préférés", "start_url": "/", "display": "standalone", + "orientation": "portrait", "background_color": "#0F172A", "theme_color": "#4F46E5", + "categories": ["books", "entertainment", "reading"], "icons": [ { - "src": "/favicon.svg", - "sizes": "32x32", - "type": "image/svg+xml", + "src": "/images/icons/icon-72x72.png", + "sizes": "72x72", + "type": "image/png", + "purpose": "any maskable" + }, + { + "src": "/images/icons/icon-96x96.png", + "sizes": "96x96", + "type": "image/png", + "purpose": "any maskable" + }, + { + "src": "/images/icons/icon-128x128.png", + "sizes": "128x128", + "type": "image/png", + "purpose": "any maskable" + }, + { + "src": "/images/icons/icon-144x144.png", + "sizes": "144x144", + "type": "image/png", + "purpose": "any maskable" + }, + { + "src": "/images/icons/icon-152x152.png", + "sizes": "152x152", + "type": "image/png", + "purpose": "any maskable" + }, + { + "src": "/images/icons/icon-192x192.png", + "sizes": "192x192", + "type": "image/png", + "purpose": "any maskable" + }, + { + "src": "/images/icons/icon-384x384.png", + "sizes": "384x384", + "type": "image/png", + "purpose": "any maskable" + }, + { + "src": "/images/icons/icon-512x512.png", + "sizes": "512x512", + "type": "image/png", "purpose": "any maskable" } - ] + ], + "screenshots": [ + { + "src": "/images/screenshots/home.png", + "sizes": "1280x720", + "type": "image/png", + "platform": "wide", + "label": "Page d'accueil de StripStream" + }, + { + "src": "/images/screenshots/reader.png", + "sizes": "1280x720", + "type": "image/png", + "platform": "wide", + "label": "Lecteur de BD StripStream" + } + ], + "shortcuts": [ + { + "name": "Accueil", + "url": "/", + "icons": [ + { + "src": "/images/icons/home.png", + "sizes": "96x96", + "type": "image/png" + } + ] + }, + { + "name": "Bibliothèques", + "url": "/libraries", + "icons": [ + { + "src": "/images/icons/library.png", + "sizes": "96x96", + "type": "image/png" + } + ] + } + ], + "related_applications": [], + "prefer_related_applications": false } diff --git a/public/offline.html b/public/offline.html new file mode 100644 index 0000000..6de462a --- /dev/null +++ b/public/offline.html @@ -0,0 +1,62 @@ + + + + + + Hors ligne - StripStream + + + +
+

Vous êtes hors ligne

+

+ Il semble que vous n'ayez pas de connexion internet. Certaines fonctionnalités de + StripStream peuvent ne pas être disponibles en mode hors ligne. +

+ +
+ + diff --git a/public/sw.js b/public/sw.js new file mode 100644 index 0000000..6ada58b --- /dev/null +++ b/public/sw.js @@ -0,0 +1,58 @@ +const CACHE_NAME = "stripstream-cache-v1"; +const OFFLINE_PAGE = "/offline.html"; + +const STATIC_ASSETS = [ + "/", + "/offline.html", + "/manifest.json", + "/favicon.svg", + "/images/icons/icon-192x192.png", + "/images/icons/icon-512x512.png", +]; + +// Installation du service worker +self.addEventListener("install", (event) => { + event.waitUntil( + caches.open(CACHE_NAME).then((cache) => { + return cache.addAll(STATIC_ASSETS); + }) + ); + self.skipWaiting(); +}); + +// Activation et nettoyage des anciens caches +self.addEventListener("activate", (event) => { + event.waitUntil( + caches.keys().then((cacheNames) => { + return Promise.all( + cacheNames.filter((name) => name !== CACHE_NAME).map((name) => caches.delete(name)) + ); + }) + ); + self.clients.claim(); +}); + +// Stratégie de cache : Network First avec fallback sur le cache +self.addEventListener("fetch", (event) => { + // Ne pas intercepter les requêtes vers l'API + if (event.request.url.includes("/api/")) { + return; + } + + event.respondWith( + fetch(event.request) + .then((response) => { + // Mettre en cache la nouvelle réponse + const responseClone = response.clone(); + caches.open(CACHE_NAME).then((cache) => { + cache.put(event.request, responseClone); + }); + return response; + }) + .catch(async () => { + const cache = await caches.open(CACHE_NAME); + const cachedResponse = await cache.match(event.request); + return cachedResponse || cache.match(OFFLINE_PAGE); + }) + ); +}); diff --git a/scripts/generate-icons.js b/scripts/generate-icons.js new file mode 100644 index 0000000..c307daf --- /dev/null +++ b/scripts/generate-icons.js @@ -0,0 +1,30 @@ +const sharp = require("sharp"); +const fs = require("fs").promises; +const path = require("path"); + +const sizes = [72, 96, 128, 144, 152, 192, 384, 512]; +const inputSvg = path.join(__dirname, "../public/favicon.svg"); +const outputDir = path.join(__dirname, "../public/images/icons"); + +async function generateIcons() { + try { + // Créer le dossier de sortie s'il n'existe pas + await fs.mkdir(outputDir, { recursive: true }); + + // Générer les icônes pour chaque taille + for (const size of sizes) { + const outputPath = path.join(outputDir, `icon-${size}x${size}.png`); + + await sharp(inputSvg).resize(size, size).png().toFile(outputPath); + + console.log(`✓ Icône ${size}x${size} générée`); + } + + console.log("\n✨ Toutes les icônes ont été générées avec succès !"); + } catch (error) { + console.error("Erreur lors de la génération des icônes:", error); + process.exit(1); + } +} + +generateIcons(); diff --git a/src/components/layout/ClientLayout.tsx b/src/components/layout/ClientLayout.tsx index 4e19875..8d7238c 100644 --- a/src/components/layout/ClientLayout.tsx +++ b/src/components/layout/ClientLayout.tsx @@ -4,6 +4,7 @@ import { ThemeProvider } from "next-themes"; import { useState, useEffect } from "react"; import { Header } from "@/components/layout/Header"; import { Sidebar } from "@/components/layout/Sidebar"; +import { InstallPWA } from "../ui/InstallPWA"; import { Toaster } from "@/components/ui/toaster"; export default function ClientLayout({ children }: { children: React.ReactNode }) { @@ -34,12 +35,27 @@ export default function ClientLayout({ children }: { children: React.ReactNode } }; }, [isSidebarOpen]); + useEffect(() => { + // Enregistrer le service worker + if ("serviceWorker" in navigator) { + navigator.serviceWorker + .register("/sw.js") + .then((registration) => { + console.log("Service Worker enregistré avec succès:", registration); + }) + .catch((error) => { + console.error("Erreur lors de l'enregistrement du Service Worker:", error); + }); + } + }, []); + return (
setIsSidebarOpen(!isSidebarOpen)} />
{children}
+
diff --git a/src/components/ui/InstallPWA.tsx b/src/components/ui/InstallPWA.tsx new file mode 100644 index 0000000..6e12fd5 --- /dev/null +++ b/src/components/ui/InstallPWA.tsx @@ -0,0 +1,88 @@ +"use client"; + +import { useEffect, useState } from "react"; +import { Download } from "lucide-react"; + +interface BeforeInstallPromptEvent extends Event { + prompt: () => Promise; + userChoice: Promise<{ outcome: "accepted" | "dismissed" }>; +} + +export function InstallPWA() { + const [deferredPrompt, setDeferredPrompt] = useState(null); + const [isInstallable, setIsInstallable] = useState(false); + const [isIOS, setIsIOS] = useState(false); + + useEffect(() => { + // Détecter si c'est un appareil iOS + const isIOSDevice = /iPad|iPhone|iPod/.test(navigator.userAgent) && !window.MSStream; + setIsIOS(isIOSDevice); + + const handleBeforeInstallPrompt = (e: Event) => { + e.preventDefault(); + setDeferredPrompt(e as BeforeInstallPromptEvent); + setIsInstallable(true); + }; + + window.addEventListener("beforeinstallprompt", handleBeforeInstallPrompt); + + // Vérifier si l'app est déjà installée + if (window.matchMedia("(display-mode: standalone)").matches) { + setIsInstallable(false); + } + + return () => { + window.removeEventListener("beforeinstallprompt", handleBeforeInstallPrompt); + }; + }, []); + + const handleInstallClick = async () => { + if (!deferredPrompt) return; + + try { + await deferredPrompt.prompt(); + const { outcome } = await deferredPrompt.userChoice; + + if (outcome === "accepted") { + setIsInstallable(false); + } + } catch (error) { + console.error("Erreur lors de l'installation:", error); + } + + setDeferredPrompt(null); + }; + + if (!isInstallable && !isIOS) return null; + + return ( +
+
+
+ +
+

Installer StripStream

+

+ {isIOS + ? "Ajoutez StripStream à votre écran d'accueil pour une meilleure expérience" + : "Installez StripStream pour un accès rapide et une meilleure expérience"} +

+ {isIOS ? ( +
+ Appuyez sur Partager puis{" "} + Sur l'écran d'accueil +
+ ) : ( + + )} +
+
+
+
+ ); +}