diff --git a/.gitignore b/.gitignore index 14a6e94..8d39da1 100644 --- a/.gitignore +++ b/.gitignore @@ -5,6 +5,8 @@ fotospiel-tenant-app /public/build /public/hot /public/storage +/public/guest-sw.js +/public/guest.webmanifest /resources/js/actions /resources/js/routes /resources/js/wayfinder diff --git a/package-lock.json b/package-lock.json index b4ebc0d..b09574c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -100,6 +100,7 @@ "prettier": "^3.7.4", "shadcn": "^3.5.2", "typescript-eslint": "^8.49.0", + "vite-plugin-pwa": "^1.2.0", "vitest": "^2.1.9" }, "optionalDependencies": { @@ -281,7 +282,6 @@ "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.28.5.tgz", "integrity": "sha512-N1EhvLtHzOvj7QQOUCCS3NrPJP8c5W6ZXCHDn7Yialuy1iu4r5EmIYkXlKNqT99Ciw+W0mDqWoR6HWMZlFP3hw==", "license": "MIT", - "peer": true, "dependencies": { "@babel/helper-annotate-as-pure": "^7.27.3", "regexpu-core": "^6.3.1", @@ -294,6 +294,44 @@ "@babel/core": "^7.0.0" } }, + "node_modules/@babel/helper-define-polyfill-provider": { + "version": "0.6.5", + "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.6.5.tgz", + "integrity": "sha512-uJnGFcPsWQK8fvjgGP5LZUZZsYGIoPeRjSF5PGwrelYgq7Q15/Ft9NGFp1zglwgIv//W0uG4BevRuSJRyylZPg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-compilation-targets": "^7.27.2", + "@babel/helper-plugin-utils": "^7.27.1", + "debug": "^4.4.1", + "lodash.debounce": "^4.0.8", + "resolve": "^1.22.10" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/@babel/helper-define-polyfill-provider/node_modules/resolve": { + "version": "1.22.11", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.11.tgz", + "integrity": "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-core-module": "^2.16.1", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/@babel/helper-globals": { "version": "7.28.0", "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", @@ -367,6 +405,24 @@ "node": ">=6.9.0" } }, + "node_modules/@babel/helper-remap-async-to-generator": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.27.1.tgz", + "integrity": "sha512-7fiA521aVw8lSPeI4ZOD3vRFkoqkJcS+z4hFo82bFSH/2tNd6eJ5qCVMS5OzDmZh/kaHQeBaeyxK6wljcPtveA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.27.1", + "@babel/helper-wrap-function": "^7.27.1", + "@babel/traverse": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, "node_modules/@babel/helper-replace-supers": { "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.27.1.tgz", @@ -424,6 +480,21 @@ "node": ">=6.9.0" } }, + "node_modules/@babel/helper-wrap-function": { + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.28.3.tgz", + "integrity": "sha512-zdf983tNfLZFletc0RRXYrHrucBEg95NIFMkn6K9dbeMYnsgHaSBGcQqdsCSStG2PYwRre0Qc2NNSCXbG+xc6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.27.2", + "@babel/traverse": "^7.28.3", + "@babel/types": "^7.28.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/@babel/helpers": { "version": "7.28.4", "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.4.tgz", @@ -452,6 +523,103 @@ "node": ">=6.0.0" } }, + "node_modules/@babel/plugin-bugfix-firefox-class-in-computed-class-key": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-firefox-class-in-computed-class-key/-/plugin-bugfix-firefox-class-in-computed-class-key-7.28.5.tgz", + "integrity": "sha512-87GDMS3tsmMSi/3bWOte1UblL+YUTFMV8SZPZ2eSEL17s74Cw/l63rR6NmGVKMYW2GYi85nE+/d6Hw5N0bEk2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/traverse": "^7.28.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-bugfix-safari-class-field-initializer-scope": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-class-field-initializer-scope/-/plugin-bugfix-safari-class-field-initializer-scope-7.27.1.tgz", + "integrity": "sha512-qNeq3bCKnGgLkEXUuFry6dPlGfCdQNZbn7yUAPCInwAJHMU7THJfrBSozkcWq5sNM6RcF3S8XyQL2A52KNR9IA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.27.1.tgz", + "integrity": "sha512-g4L7OYun04N1WyqMNjldFwlfPCLVkgB54A/YCXICZYBsvJJE3kByKv9c9+R/nAfmIfjl2rKYLNyMHboYbZaWaA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.27.1.tgz", + "integrity": "sha512-oO02gcONcD5O1iTLi/6frMJBIwWEHceWGSGqrpCmEL8nogiS6J9PBlE48CaK20/Jx1LuRml9aDftLgdjXT8+Cw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1", + "@babel/plugin-transform-optional-chaining": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.13.0" + } + }, + "node_modules/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": { + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/-/plugin-bugfix-v8-static-class-fields-redefine-readonly-7.28.3.tgz", + "integrity": "sha512-b6YTX108evsvE4YgWyQ921ZAFFQm3Bn+CA3+ZXlNVnPhx+UfsVURoPjfGAPCjBgrqo30yX/C2nZGX96DxvR9Iw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/traverse": "^7.28.3" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-proposal-private-property-in-object": { + "version": "7.21.0-placeholder-for-preset-env.2", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.0-placeholder-for-preset-env.2.tgz", + "integrity": "sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, "node_modules/@babel/plugin-syntax-async-generators": { "version": "7.8.4", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", @@ -507,12 +675,27 @@ "@babel/core": "^7.0.0-0" } }, + "node_modules/@babel/plugin-syntax-import-assertions": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.27.1.tgz", + "integrity": "sha512-UT/Jrhw57xg4ILHLFnzFpPDlMbcdEicaAtjPQpbj9wa8T4r5KVWCimHcL/460g8Ht0DMxDyjsLgiWSkVjnwPFg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, "node_modules/@babel/plugin-syntax-import-attributes": { "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.27.1.tgz", "integrity": "sha512-oFT0FrKHgF53f4vOsZGi2Hh3I35PfSmVs4IBFLFj4dnafP+hIWDLg3VyKmUHfLoLHlyxY4C7DGtmHuJgn+IGww==", "license": "MIT", - "peer": true, "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, @@ -689,12 +872,96 @@ "@babel/core": "^7.0.0-0" } }, + "node_modules/@babel/plugin-syntax-unicode-sets-regex": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-unicode-sets-regex/-/plugin-syntax-unicode-sets-regex-7.18.6.tgz", + "integrity": "sha512-727YkEAPwSIQTv5im8QHz3upqp92JTWhidIC81Tdx4VJYIte/VndKf1qKrfnnhPLiPghStWfvC/iFaMCQu7Nqg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.18.6", + "@babel/helper-plugin-utils": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, "node_modules/@babel/plugin-transform-arrow-functions": { "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.27.1.tgz", "integrity": "sha512-8Z4TGic6xW70FKThA5HYEKKyBpOOsucTOD1DjU3fZxDg+K3zBJcXMFnt/4yQiZnf5+MiOMSXQ9PaEK/Ilh1DeA==", "license": "MIT", - "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-async-generator-functions": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.28.0.tgz", + "integrity": "sha512-BEOdvX4+M765icNPZeidyADIvQ1m1gmunXufXxvRESy/jNNyfovIqUyE7MVgGBjWktCoJlzvFA1To2O4ymIO3Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-remap-async-to-generator": "^7.27.1", + "@babel/traverse": "^7.28.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-async-to-generator": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.27.1.tgz", + "integrity": "sha512-NREkZsZVJS4xmTr8qzE5y8AfIPqsdQfRuUiLRTEzb7Qii8iFWCyDKaUV2c0rCuh4ljDZ98ALHP/PetiBV2nddA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-remap-async-to-generator": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-block-scoped-functions": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.27.1.tgz", + "integrity": "sha512-cnqkuOtZLapWYZUYM5rVIdv1nXYuFVIltZ6ZJ7nIj585QsjKM5dhL2Fu/lICXZ1OyIAFc7Qy+bvDAtTXqGrlhg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-block-scoping": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.28.5.tgz", + "integrity": "sha512-45DmULpySVvmq9Pj3X9B+62Xe+DJGov27QravQJU1LLcapR6/10i+gYVAucGGJpHBp5mYxIMK4nDAT/QDLr47g==", + "dev": true, + "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, @@ -710,7 +977,6 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.27.1.tgz", "integrity": "sha512-D0VcalChDMtuRvJIu3U/fwWjf8ZMykz5iZsg77Nuj821vCKI3zCyRLwRdWbsuJ/uRwZhZ002QtCqIkwC/ZkvbA==", "license": "MIT", - "peer": true, "dependencies": { "@babel/helper-create-class-features-plugin": "^7.27.1", "@babel/helper-plugin-utils": "^7.27.1" @@ -722,12 +988,28 @@ "@babel/core": "^7.0.0-0" } }, + "node_modules/@babel/plugin-transform-class-static-block": { + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.28.3.tgz", + "integrity": "sha512-LtPXlBbRoc4Njl/oh1CeD/3jC+atytbnf/UqLoqTDcEYGUPj022+rvfkbDYieUrSj3CaV4yHDByPE+T2HwfsJg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.28.3", + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.12.0" + } + }, "node_modules/@babel/plugin-transform-classes": { "version": "7.28.4", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.28.4.tgz", "integrity": "sha512-cFOlhIYPBv/iBoc+KS3M6et2XPtbT2HiCRfBXWtfpc9OAyostldxIf9YAYB6ypURBBbx+Qv6nyrLzASfJe+hBA==", "license": "MIT", - "peer": true, "dependencies": { "@babel/helper-annotate-as-pure": "^7.27.3", "@babel/helper-compilation-targets": "^7.27.2", @@ -743,6 +1025,271 @@ "@babel/core": "^7.0.0-0" } }, + "node_modules/@babel/plugin-transform-computed-properties": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.27.1.tgz", + "integrity": "sha512-lj9PGWvMTVksbWiDT2tW68zGS/cyo4AkZ/QTp0sQT0mjPopCmrSkzxeXkznjqBxzDI6TclZhOJbBmbBLjuOZUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/template": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-destructuring": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.28.5.tgz", + "integrity": "sha512-Kl9Bc6D0zTUcFUvkNuQh4eGXPKKNDOJQXVyyM4ZAQPMveniJdxi8XMJwLo+xSoW3MIq81bD33lcUe9kZpl0MCw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/traverse": "^7.28.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-dotall-regex": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.27.1.tgz", + "integrity": "sha512-gEbkDVGRvjj7+T1ivxrfgygpT7GUd4vmODtYpbs0gZATdkX8/iSnOtZSxiZnsgm1YjTgjI6VKBGSJJevkrclzw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-duplicate-keys": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.27.1.tgz", + "integrity": "sha512-MTyJk98sHvSs+cvZ4nOauwTTG1JeonDjSGvGGUNHreGQns+Mpt6WX/dVzWBHgg+dYZhkC4X+zTDfkTU+Vy9y7Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-duplicate-named-capturing-groups-regex": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-named-capturing-groups-regex/-/plugin-transform-duplicate-named-capturing-groups-regex-7.27.1.tgz", + "integrity": "sha512-hkGcueTEzuhB30B3eJCbCYeCaaEQOmQR0AdvzpD4LoN0GXMWzzGSuRrxR2xTnCrvNbVwK9N6/jQ92GSLfiZWoQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-transform-dynamic-import": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.27.1.tgz", + "integrity": "sha512-MHzkWQcEmjzzVW9j2q8LGjwGWpG2mjwaaB0BNQwst3FIjqsg8Ct/mIZlvSPJvfi9y2AC8mi/ktxbFVL9pZ1I4A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-explicit-resource-management": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-explicit-resource-management/-/plugin-transform-explicit-resource-management-7.28.0.tgz", + "integrity": "sha512-K8nhUcn3f6iB+P3gwCv/no7OdzOZQcKchW6N389V6PD8NUWKZHzndOd9sPDVbMoBsbmjMqlB4L9fm+fEFNVlwQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/plugin-transform-destructuring": "^7.28.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-exponentiation-operator": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.28.5.tgz", + "integrity": "sha512-D4WIMaFtwa2NizOp+dnoFjRez/ClKiC2BqqImwKd1X28nqBtZEyCYJ2ozQrrzlxAFrcrjxo39S6khe9RNDlGzw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-export-namespace-from": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.27.1.tgz", + "integrity": "sha512-tQvHWSZ3/jH2xuq/vZDy0jNn+ZdXJeM8gHvX4lnJmsc3+50yPlWdZXIc5ay+umX+2/tJIqHqiEqcJvxlmIvRvQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-for-of": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.27.1.tgz", + "integrity": "sha512-BfbWFFEJFQzLCQ5N8VocnCtA8J1CLkNTe2Ms2wocj75dd6VpiqS5Z5quTYcUoo4Yq+DN0rtikODccuv7RU81sw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-function-name": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.27.1.tgz", + "integrity": "sha512-1bQeydJF9Nr1eBCMMbC+hdwmRlsv5XYOMu03YSWFwNs0HsAmtSxxF1fyuYPqemVldVyFmlCU7w8UE14LupUSZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-compilation-targets": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/traverse": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-json-strings": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.27.1.tgz", + "integrity": "sha512-6WVLVJiTjqcQauBhn1LkICsR2H+zm62I3h9faTDKt1qP4jn2o72tSvqMwtGFKGTpojce0gJs+76eZ2uCHRZh0Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-literals": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.27.1.tgz", + "integrity": "sha512-0HCFSepIpLTkLcsi86GG3mTUzxV5jpmbv97hTETW3yzrAij8aqlD36toB1D0daVFJM8NK6GvKO0gslVQmm+zZA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-logical-assignment-operators": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.28.5.tgz", + "integrity": "sha512-axUuqnUTBuXyHGcJEVVh9pORaN6wC5bYfE7FGzPiaWa3syib9m7g+/IT/4VgCOe2Upef43PHzeAvcrVek6QuuA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-member-expression-literals": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.27.1.tgz", + "integrity": "sha512-hqoBX4dcZ1I33jCSWcXrP+1Ku7kdqXf1oeah7ooKOIiAdKQ+uqftgCFNOSzA5AMS2XIHEYeGFg4cKRCdpxzVOQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-amd": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.27.1.tgz", + "integrity": "sha512-iCsytMg/N9/oFq6n+gFTvUYDZQOMK5kEdeYxmxt91fcJGycfxVP9CnrxoliM0oumFERba2i8ZtwRUCMhvP1LnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-transforms": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, "node_modules/@babel/plugin-transform-modules-commonjs": { "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.27.1.tgz", @@ -759,12 +1306,149 @@ "@babel/core": "^7.0.0-0" } }, + "node_modules/@babel/plugin-transform-modules-systemjs": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.28.5.tgz", + "integrity": "sha512-vn5Jma98LCOeBy/KpeQhXcV2WZgaRUtjwQmjoBuLNlOmkg0fB5pdvYVeWRYI69wWKwK2cD1QbMiUQnoujWvrew==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-transforms": "^7.28.3", + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-validator-identifier": "^7.28.5", + "@babel/traverse": "^7.28.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-modules-umd": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.27.1.tgz", + "integrity": "sha512-iQBE/xC5BV1OxJbp6WG7jq9IWiD+xxlZhLrdwpPkTX3ydmXdvoCpyfJN7acaIBZaOqTfr76pgzqBJflNbeRK+w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-transforms": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-named-capturing-groups-regex": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.27.1.tgz", + "integrity": "sha512-SstR5JYy8ddZvD6MhV0tM/j16Qds4mIpJTOd1Yu9J9pJjH93bxHECF7pgtc28XvkzTD6Pxcm/0Z73Hvk7kb3Ng==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-transform-new-target": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.27.1.tgz", + "integrity": "sha512-f6PiYeqXQ05lYq3TIfIDu/MtliKUbNwkGApPUvyo6+tc7uaR4cPjPe7DFPr15Uyycg2lZU6btZ575CuQoYh7MQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, "node_modules/@babel/plugin-transform-nullish-coalescing-operator": { "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.27.1.tgz", "integrity": "sha512-aGZh6xMo6q9vq1JGcw58lZ1Z0+i0xB2x0XaauNIUXd6O1xXc3RwoWEBlsTQrY4KQ9Jf0s5rgD6SiNkaUdJegTA==", "license": "MIT", - "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-numeric-separator": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.27.1.tgz", + "integrity": "sha512-fdPKAcujuvEChxDBJ5c+0BTaS6revLV7CJL08e4m3de8qJfNIuCc2nc7XJYOjBoTMJeqSmwXJ0ypE14RCjLwaw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-object-rest-spread": { + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.28.4.tgz", + "integrity": "sha512-373KA2HQzKhQCYiRVIRr+3MjpCObqzDlyrM6u4I201wL8Mp2wHf7uB8GhDwis03k2ti8Zr65Zyyqs1xOxUF/Ew==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-compilation-targets": "^7.27.2", + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/plugin-transform-destructuring": "^7.28.0", + "@babel/plugin-transform-parameters": "^7.27.7", + "@babel/traverse": "^7.28.4" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-object-super": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.27.1.tgz", + "integrity": "sha512-SFy8S9plRPbIcxlJ8A6mT/CxFdJx/c04JEctz4jf8YZaVS2px34j7NXRrlGlHkN/M2gnpL37ZpGRGVFLd3l8Ng==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-replace-supers": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-optional-catch-binding": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.27.1.tgz", + "integrity": "sha512-txEAEKzYrHEX4xSZN4kJ+OfKXFVSWKB2ZxM9dpcE3wT7smwkNmXo5ORRlVzMVdJbD+Q8ILTgSD7959uj+3Dm3Q==", + "dev": true, + "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, @@ -780,7 +1464,6 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.27.1.tgz", "integrity": "sha512-BQmKPPIuc8EkZgNKsv0X4bPmOoayeu4F1YCwx2/CfmDSXDbp7GnzlUH+/ul5VGfRg1AoFPsrIThlEBj2xb4CAg==", "license": "MIT", - "peer": true, "dependencies": { "@babel/helper-plugin-utils": "^7.27.1", "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1" @@ -792,6 +1475,73 @@ "@babel/core": "^7.0.0-0" } }, + "node_modules/@babel/plugin-transform-parameters": { + "version": "7.27.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.27.7.tgz", + "integrity": "sha512-qBkYTYCb76RRxUM6CcZA5KRu8K4SM8ajzVeUgVdMVO9NN9uI/GaVmBg/WKJJGnNokV9SY8FxNOVWGXzqzUidBg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-private-methods": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.27.1.tgz", + "integrity": "sha512-10FVt+X55AjRAYI9BrdISN9/AQWHqldOeZDUoLyif1Kn05a56xVBXb8ZouL8pZ9jem8QpXaOt8TS7RHUIS+GPA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-private-property-in-object": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.27.1.tgz", + "integrity": "sha512-5J+IhqTi1XPa0DXF83jYOaARrX+41gOewWbkPyjMNRDqgOCqdffGh8L3f/Ek5utaEBZExjSAzcyjmV9SSAWObQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.27.1", + "@babel/helper-create-class-features-plugin": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-property-literals": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.27.1.tgz", + "integrity": "sha512-oThy3BCuCha8kDZ8ZkgOg2exvPYUlprMukKQXI1r1pJ47NCvxfkEy8vK+r/hT9nF0Aa4H1WUPZZjHTFtAhGfmQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, "node_modules/@babel/plugin-transform-react-jsx": { "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.27.1.tgz", @@ -841,12 +1591,93 @@ "@babel/core": "^7.0.0-0" } }, + "node_modules/@babel/plugin-transform-regenerator": { + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.28.4.tgz", + "integrity": "sha512-+ZEdQlBoRg9m2NnzvEeLgtvBMO4tkFBw5SQIUgLICgTrumLoU7lr+Oghi6km2PFj+dbUt2u1oby2w3BDO9YQnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-regexp-modifiers": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regexp-modifiers/-/plugin-transform-regexp-modifiers-7.27.1.tgz", + "integrity": "sha512-TtEciroaiODtXvLZv4rmfMhkCv8jx3wgKpL68PuiPh2M4fvz5jhsA7697N1gMvkvr/JTF13DrFYyEbY9U7cVPA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/plugin-transform-reserved-words": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.27.1.tgz", + "integrity": "sha512-V2ABPHIJX4kC7HegLkYoDpfg9PVmuWy/i6vUM5eGK22bx4YVFD3M5F0QQnWQoDs6AGsUWTVOopBiMFQgHaSkVw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, "node_modules/@babel/plugin-transform-shorthand-properties": { "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.27.1.tgz", "integrity": "sha512-N/wH1vcn4oYawbJ13Y/FxcQrWk63jhfNa7jef0ih7PHSIHX2LB7GWE1rkPrOnka9kwMxb6hMl19p7lidA+EHmQ==", "license": "MIT", - "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-spread": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.27.1.tgz", + "integrity": "sha512-kpb3HUqaILBJcRFVhFUs6Trdd4mkrzcGXss+6/mxUd273PfbWqSDHRzMT2234gIg2QYfAjvXLSquP1xECSg09Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-sticky-regex": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.27.1.tgz", + "integrity": "sha512-lhInBO5bi/Kowe2/aLdBAawijx+q1pQzicSgnkB6dUPc1+RC8QmJHKf2OjvU+NZWitguJHEaEmbV6VWEouT58g==", + "dev": true, + "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, @@ -862,7 +1693,22 @@ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.27.1.tgz", "integrity": "sha512-fBJKiV7F2DxZUkg5EtHKXQdbsbURW3DZKQUWphDum0uRP6eHGGa/He9mc0mypL680pb+e/lDIthRohlv8NCHkg==", "license": "MIT", - "peer": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-typeof-symbol": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.27.1.tgz", + "integrity": "sha512-RiSILC+nRJM7FY5srIyc4/fGIwUhyDuuBSdWn4y6yT6gm652DpCHZjIipgn6B7MQ1ITOUnAKWixEUjQRIBIcLw==", + "dev": true, + "license": "MIT", "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, @@ -892,12 +1738,28 @@ "@babel/core": "^7.0.0-0" } }, - "node_modules/@babel/plugin-transform-unicode-regex": { + "node_modules/@babel/plugin-transform-unicode-escapes": { "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.27.1.tgz", - "integrity": "sha512-xvINq24TRojDuyt6JGtHmkVkrfVV3FPT16uytxImLeBZqW3/H52yN+kM1MGuyPkIQxrzKwPHs5U/MP3qKyzkGw==", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.27.1.tgz", + "integrity": "sha512-Ysg4v6AmF26k9vpfFuTZg8HRfVWzsh1kVfowA23y9j/Gu6dOuahdUVhkLqpObp3JIv27MLSii6noRnuKN8H0Mg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-property-regex": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.27.1.tgz", + "integrity": "sha512-uW20S39PnaTImxp39O5qFlHLS9LJEmANjMG7SxIhap8rCHqu0Ik+tLEPX5DKmHn6CsWQ7j3lix2tFOa5YtL12Q==", + "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@babel/helper-create-regexp-features-plugin": "^7.27.1", "@babel/helper-plugin-utils": "^7.27.1" @@ -909,6 +1771,156 @@ "@babel/core": "^7.0.0-0" } }, + "node_modules/@babel/plugin-transform-unicode-regex": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.27.1.tgz", + "integrity": "sha512-xvINq24TRojDuyt6JGtHmkVkrfVV3FPT16uytxImLeBZqW3/H52yN+kM1MGuyPkIQxrzKwPHs5U/MP3qKyzkGw==", + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-unicode-sets-regex": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.27.1.tgz", + "integrity": "sha512-EtkOujbc4cgvb0mlpQefi4NTPBzhSIevblFevACNLUspmrALgmEBdL/XfnyyITfd8fKBZrZys92zOWcik7j9Tw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-create-regexp-features-plugin": "^7.27.1", + "@babel/helper-plugin-utils": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/preset-env": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.28.5.tgz", + "integrity": "sha512-S36mOoi1Sb6Fz98fBfE+UZSpYw5mJm0NUHtIKrOuNcqeFauy1J6dIvXm2KRVKobOSaGq4t/hBXdN4HGU3wL9Wg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.28.5", + "@babel/helper-compilation-targets": "^7.27.2", + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-validator-option": "^7.27.1", + "@babel/plugin-bugfix-firefox-class-in-computed-class-key": "^7.28.5", + "@babel/plugin-bugfix-safari-class-field-initializer-scope": "^7.27.1", + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.27.1", + "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.27.1", + "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": "^7.28.3", + "@babel/plugin-proposal-private-property-in-object": "7.21.0-placeholder-for-preset-env.2", + "@babel/plugin-syntax-import-assertions": "^7.27.1", + "@babel/plugin-syntax-import-attributes": "^7.27.1", + "@babel/plugin-syntax-unicode-sets-regex": "^7.18.6", + "@babel/plugin-transform-arrow-functions": "^7.27.1", + "@babel/plugin-transform-async-generator-functions": "^7.28.0", + "@babel/plugin-transform-async-to-generator": "^7.27.1", + "@babel/plugin-transform-block-scoped-functions": "^7.27.1", + "@babel/plugin-transform-block-scoping": "^7.28.5", + "@babel/plugin-transform-class-properties": "^7.27.1", + "@babel/plugin-transform-class-static-block": "^7.28.3", + "@babel/plugin-transform-classes": "^7.28.4", + "@babel/plugin-transform-computed-properties": "^7.27.1", + "@babel/plugin-transform-destructuring": "^7.28.5", + "@babel/plugin-transform-dotall-regex": "^7.27.1", + "@babel/plugin-transform-duplicate-keys": "^7.27.1", + "@babel/plugin-transform-duplicate-named-capturing-groups-regex": "^7.27.1", + "@babel/plugin-transform-dynamic-import": "^7.27.1", + "@babel/plugin-transform-explicit-resource-management": "^7.28.0", + "@babel/plugin-transform-exponentiation-operator": "^7.28.5", + "@babel/plugin-transform-export-namespace-from": "^7.27.1", + "@babel/plugin-transform-for-of": "^7.27.1", + "@babel/plugin-transform-function-name": "^7.27.1", + "@babel/plugin-transform-json-strings": "^7.27.1", + "@babel/plugin-transform-literals": "^7.27.1", + "@babel/plugin-transform-logical-assignment-operators": "^7.28.5", + "@babel/plugin-transform-member-expression-literals": "^7.27.1", + "@babel/plugin-transform-modules-amd": "^7.27.1", + "@babel/plugin-transform-modules-commonjs": "^7.27.1", + "@babel/plugin-transform-modules-systemjs": "^7.28.5", + "@babel/plugin-transform-modules-umd": "^7.27.1", + "@babel/plugin-transform-named-capturing-groups-regex": "^7.27.1", + "@babel/plugin-transform-new-target": "^7.27.1", + "@babel/plugin-transform-nullish-coalescing-operator": "^7.27.1", + "@babel/plugin-transform-numeric-separator": "^7.27.1", + "@babel/plugin-transform-object-rest-spread": "^7.28.4", + "@babel/plugin-transform-object-super": "^7.27.1", + "@babel/plugin-transform-optional-catch-binding": "^7.27.1", + "@babel/plugin-transform-optional-chaining": "^7.28.5", + "@babel/plugin-transform-parameters": "^7.27.7", + "@babel/plugin-transform-private-methods": "^7.27.1", + "@babel/plugin-transform-private-property-in-object": "^7.27.1", + "@babel/plugin-transform-property-literals": "^7.27.1", + "@babel/plugin-transform-regenerator": "^7.28.4", + "@babel/plugin-transform-regexp-modifiers": "^7.27.1", + "@babel/plugin-transform-reserved-words": "^7.27.1", + "@babel/plugin-transform-shorthand-properties": "^7.27.1", + "@babel/plugin-transform-spread": "^7.27.1", + "@babel/plugin-transform-sticky-regex": "^7.27.1", + "@babel/plugin-transform-template-literals": "^7.27.1", + "@babel/plugin-transform-typeof-symbol": "^7.27.1", + "@babel/plugin-transform-unicode-escapes": "^7.27.1", + "@babel/plugin-transform-unicode-property-regex": "^7.27.1", + "@babel/plugin-transform-unicode-regex": "^7.27.1", + "@babel/plugin-transform-unicode-sets-regex": "^7.27.1", + "@babel/preset-modules": "0.1.6-no-external-plugins", + "babel-plugin-polyfill-corejs2": "^0.4.14", + "babel-plugin-polyfill-corejs3": "^0.13.0", + "babel-plugin-polyfill-regenerator": "^0.6.5", + "core-js-compat": "^3.43.0", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/preset-env/node_modules/@babel/plugin-transform-optional-chaining": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.28.5.tgz", + "integrity": "sha512-N6fut9IZlPnjPwgiQkXNhb+cT8wQKFlJNqcZkWlcTqkcqx6/kU4ynGmLFoa4LViBSirn05YAwk+sQBbPfxtYzQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.27.1", + "@babel/helper-skip-transparent-expression-wrappers": "^7.27.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/preset-modules": { + "version": "0.1.6-no-external-plugins", + "resolved": "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.6-no-external-plugins.tgz", + "integrity": "sha512-HrcgcIESLm9aIR842yhJ5RWan/gebQUJ6E/E5+rf0y9o6oj7w0Br+sWuL6kEQ/o/AdfvR1Je9jG18/gnpwjEyA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/types": "^7.4.4", + "esutils": "^2.0.2" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0 || ^8.0.0-0 <8.0.0" + } + }, "node_modules/@babel/preset-typescript": { "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.27.1.tgz", @@ -2720,7 +3732,6 @@ "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.11.tgz", "integrity": "sha512-ZMp1V8ZFcPG5dIWnQLr3NSI1MiCU7UETdS/A0G8V/XWHvJv3ZsFqutJn1Y5RPmAPX6F3BiE397OqveU/9NCuIA==", "license": "MIT", - "peer": true, "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.25" @@ -5031,6 +6042,118 @@ "integrity": "sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==", "license": "MIT" }, + "node_modules/@rollup/plugin-node-resolve": { + "version": "15.3.1", + "resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-15.3.1.tgz", + "integrity": "sha512-tgg6b91pAybXHJQMAAwW9VuWBO6Thi+q7BCNARLwSqlmsHz0XYURtGvh/AuwSADXSI4h/2uHbs7s4FzlZDGSGA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@rollup/pluginutils": "^5.0.1", + "@types/resolve": "1.20.2", + "deepmerge": "^4.2.2", + "is-module": "^1.0.0", + "resolve": "^1.22.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^2.78.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/@rollup/plugin-node-resolve/node_modules/resolve": { + "version": "1.22.11", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.11.tgz", + "integrity": "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-core-module": "^2.16.1", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/@rollup/plugin-terser": { + "version": "0.4.4", + "resolved": "https://registry.npmjs.org/@rollup/plugin-terser/-/plugin-terser-0.4.4.tgz", + "integrity": "sha512-XHeJC5Bgvs8LfukDwWZp7yeqin6ns8RTl2B9avbejt6tZqsqvVoWI7ZTQrcNsfKEDWBTnTxM8nMDkO2IFFbd0A==", + "dev": true, + "license": "MIT", + "dependencies": { + "serialize-javascript": "^6.0.1", + "smob": "^1.0.0", + "terser": "^5.17.4" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^2.0.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/@rollup/pluginutils": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.3.0.tgz", + "integrity": "sha512-5EdhGZtnu3V88ces7s53hhfK5KSASnJZv8Lulpc04cWO3REESroJXg73DFsOmgbU2BhwV0E20bu2IDZb3VKW4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "estree-walker": "^2.0.2", + "picomatch": "^4.0.2" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/@rollup/pluginutils/node_modules/estree-walker": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@rollup/pluginutils/node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, "node_modules/@rollup/rollup-android-arm-eabi": { "version": "4.53.3", "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.53.3.tgz", @@ -5796,6 +6919,29 @@ "node": ">=12.16" } }, + "node_modules/@surma/rollup-plugin-off-main-thread": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/@surma/rollup-plugin-off-main-thread/-/rollup-plugin-off-main-thread-2.2.3.tgz", + "integrity": "sha512-lR8q/9W7hZpMWweNiAKU7NQerBnzQQLvi8qnTDU/fxItPhtZVMbPV3lbCwjhIlNBe9Bbr5V+KHshvWmVSG9cxQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "ejs": "^3.1.6", + "json5": "^2.2.0", + "magic-string": "^0.25.0", + "string.prototype.matchall": "^4.0.6" + } + }, + "node_modules/@surma/rollup-plugin-off-main-thread/node_modules/magic-string": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.9.tgz", + "integrity": "sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "sourcemap-codec": "^1.4.8" + } + }, "node_modules/@swc/helpers": { "version": "0.5.17", "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.17.tgz", @@ -7975,6 +9121,13 @@ "@types/react": "^19.2.0" } }, + "node_modules/@types/resolve": { + "version": "1.20.2", + "resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.20.2.tgz", + "integrity": "sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/stack-utils": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.3.tgz", @@ -7989,6 +9142,13 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/trusted-types": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz", + "integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/yargs": { "version": "17.0.35", "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.35.tgz", @@ -8915,6 +10075,13 @@ "node": ">=8" } }, + "node_modules/async": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", + "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==", + "dev": true, + "license": "MIT" + }, "node_modules/async-function": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/async-function/-/async-function-1.0.0.tgz", @@ -8931,6 +10098,16 @@ "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", "license": "MIT" }, + "node_modules/at-least-node": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/at-least-node/-/at-least-node-1.0.0.tgz", + "integrity": "sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">= 4.0.0" + } + }, "node_modules/available-typed-arrays": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", @@ -9098,6 +10275,48 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, + "node_modules/babel-plugin-polyfill-corejs2": { + "version": "0.4.14", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.14.tgz", + "integrity": "sha512-Co2Y9wX854ts6U8gAAPXfn0GmAyctHuK8n0Yhfjd6t30g7yvKjspvvOo9yG+z52PZRgFErt7Ka2pYnXCjLKEpg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.27.7", + "@babel/helper-define-polyfill-provider": "^0.6.5", + "semver": "^6.3.1" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/babel-plugin-polyfill-corejs3": { + "version": "0.13.0", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.13.0.tgz", + "integrity": "sha512-U+GNwMdSFgzVmfhNm8GJUX88AadB3uo9KpJqS3FaqNIPKgySuvMb+bHPsOmmuWyIcuqZj/pzt1RUIUZns4y2+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-define-polyfill-provider": "^0.6.5", + "core-js-compat": "^3.43.0" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, + "node_modules/babel-plugin-polyfill-regenerator": { + "version": "0.6.5", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.6.5.tgz", + "integrity": "sha512-ISqQ2frbiNU9vIJkzg7dlPpznPZ4jOiUQ1uSmB0fEHeowtN3COYRsXr/xexn64NpU13P06jc/L5TgiJXOgrbEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-define-polyfill-provider": "^0.6.5" + }, + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" + } + }, "node_modules/babel-plugin-syntax-hermes-parser": { "version": "0.32.0", "resolved": "https://registry.npmjs.org/babel-plugin-syntax-hermes-parser/-/babel-plugin-syntax-hermes-parser-0.32.0.tgz", @@ -9358,8 +10577,7 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/bytes": { "version": "3.1.2", @@ -9858,6 +11076,16 @@ "node": ">=16" } }, + "node_modules/common-tags": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/common-tags/-/common-tags-1.8.2.tgz", + "integrity": "sha512-gk/Z852D2Wtb//0I+kRFNKKE9dIIVirjoqPoA1wJU+XePVXZfGeBpk45+A1rKO4Q43prqWBNY/MiIeRLbPWUaA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4.0.0" + } + }, "node_modules/commondir": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", @@ -9963,6 +11191,20 @@ "node": ">=6.6.0" } }, + "node_modules/core-js-compat": { + "version": "3.47.0", + "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.47.0.tgz", + "integrity": "sha512-IGfuznZ/n7Kp9+nypamBhvwdwLsW6KC8IOaURw2doAK5e98AG3acVLdh0woOnEqCfUtS+Vu882JE4k/DAm3ItQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "browserslist": "^4.28.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/core-js" + } + }, "node_modules/core-util-is": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", @@ -10034,6 +11276,16 @@ "node": ">= 8" } }, + "node_modules/crypto-random-string": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-2.0.0.tgz", + "integrity": "sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/css-in-js-utils": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/css-in-js-utils/-/css-in-js-utils-3.1.0.tgz", @@ -10474,6 +11726,22 @@ "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", "license": "MIT" }, + "node_modules/ejs": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.10.tgz", + "integrity": "sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "jake": "^10.8.5" + }, + "bin": { + "ejs": "bin/cli.js" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/electron-to-chromium": { "version": "1.5.267", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.267.tgz", @@ -11975,6 +13243,39 @@ "node": ">=16.0.0" } }, + "node_modules/filelist": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.4.tgz", + "integrity": "sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "minimatch": "^5.0.1" + } + }, + "node_modules/filelist/node_modules/brace-expansion": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/filelist/node_modules/minimatch": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/fill-range": { "version": "7.1.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", @@ -12489,6 +13790,13 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/get-own-enumerable-property-symbols": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/get-own-enumerable-property-symbols/-/get-own-enumerable-property-symbols-3.0.2.tgz", + "integrity": "sha512-I0UBV/XOz1XkIJHEUDMZAbzCThU/H8DxmSfmdGcKPnVhu2VfFqr34jr9777IyaTYvxjedWhqVIilEDsCdP5G6g==", + "dev": true, + "license": "ISC" + }, "node_modules/get-package-type": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", @@ -13162,6 +14470,13 @@ "url": "https://opencollective.com/express" } }, + "node_modules/idb": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/idb/-/idb-7.1.1.tgz", + "integrity": "sha512-gchesWBzyvGHRO9W8tzUWFDycow5gwjvFKfyV9FF32Y7F50yZMp7mP+T2mJIWFx49zicqyC4uefHM17o6xKIVQ==", + "dev": true, + "license": "ISC" + }, "node_modules/ieee754": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", @@ -13566,6 +14881,13 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-module": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-module/-/is-module-1.0.0.tgz", + "integrity": "sha512-51ypPSPCoTEIN9dy5Oy+h4pShgJmPCygKfyRCISBI+JoWT/2oJvK8QPxmwv7b/p239jXrm9M1mlQbyKJ5A152g==", + "dev": true, + "license": "MIT" + }, "node_modules/is-negated-glob": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-negated-glob/-/is-negated-glob-1.0.0.tgz", @@ -13978,6 +15300,24 @@ "@pkgjs/parseargs": "^0.11.0" } }, + "node_modules/jake": { + "version": "10.9.4", + "resolved": "https://registry.npmjs.org/jake/-/jake-10.9.4.tgz", + "integrity": "sha512-wpHYzhxiVQL+IV05BLE2Xn34zW1S223hvjtqk0+gsPrwd/8JNLXJgZZM/iPFsYc1xyphF+6M6EvdE5E9MBGkDA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "async": "^3.2.6", + "filelist": "^1.0.4", + "picocolors": "^1.1.1" + }, + "bin": { + "jake": "bin/cli.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/jest-environment-node": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-29.7.0.tgz", @@ -14483,6 +15823,13 @@ "dev": true, "license": "MIT" }, + "node_modules/json-schema": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.4.0.tgz", + "integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==", + "dev": true, + "license": "(AFL-2.1 OR BSD-3-Clause)" + }, "node_modules/json-schema-traverse": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", @@ -14521,6 +15868,16 @@ "graceful-fs": "^4.1.6" } }, + "node_modules/jsonpointer": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/jsonpointer/-/jsonpointer-5.0.1.tgz", + "integrity": "sha512-p/nXbhSEcu3pZRdkW1OfJhpsVtW1gd4Wa1fnQc9YLiTfAjn0312eMKimbdIQzuZl9aa9xUGaRlP9T/CJE/ditQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/jsx-ast-utils": { "version": "3.3.5", "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.5.tgz", @@ -14601,7 +15958,6 @@ "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", "license": "MIT", - "peer": true, "engines": { "node": ">=6" } @@ -14932,6 +16288,13 @@ "integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==", "license": "MIT" }, + "node_modules/lodash.debounce": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", + "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==", + "dev": true, + "license": "MIT" + }, "node_modules/lodash.merge": { "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", @@ -14939,6 +16302,13 @@ "dev": true, "license": "MIT" }, + "node_modules/lodash.sortby": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/lodash.sortby/-/lodash.sortby-4.7.0.tgz", + "integrity": "sha512-HDWXG8isMntAyRF5vZ7xKuEvOhT4AhlRt/3czTSjvGUxjYCBVRQY48ViDHyfYz9VIoBkW4TMGQNapx+l3RUwdA==", + "dev": true, + "license": "MIT" + }, "node_modules/lodash.throttle": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/lodash.throttle/-/lodash.throttle-4.1.1.tgz", @@ -16826,6 +18196,19 @@ "url": "https://github.com/prettier/prettier?sponsor=1" } }, + "node_modules/pretty-bytes": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-6.1.1.tgz", + "integrity": "sha512-mQUvGU6aUFQ+rNvTIAcZuWGRT9a6f6Yrg9bHs4ImKF+HZCEK+plBvnAZYSIQztknZF2qnzNtr6F8s0+IuptdlQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/pretty-format": { "version": "27.5.1", "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz", @@ -17037,6 +18420,16 @@ "integrity": "sha512-XadVyw0xE+oZ5FGApXsdswv96rOhStzKqL53uSe5UaTadABGkWIg1+DTx8kiZ/VqTZTBneoL0l65RcPe4W3ecw==", "license": "MIT" }, + "node_modules/randombytes": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "safe-buffer": "^5.1.0" + } + }, "node_modules/range-parser": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", @@ -17648,15 +19041,13 @@ "version": "1.4.2", "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", "integrity": "sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/regenerate-unicode-properties": { "version": "10.2.2", "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-10.2.2.tgz", "integrity": "sha512-m03P+zhBeQd1RGnYxrGyDAPpWX/epKirLrp8e3qevZdVkKtnCrjjWczIbYc8+xd6vcTStVlqfycTx1KR4LOr0g==", "license": "MIT", - "peer": true, "dependencies": { "regenerate": "^1.4.2" }, @@ -17697,7 +19088,6 @@ "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-6.4.0.tgz", "integrity": "sha512-0ghuzq67LI9bLXpOX/ISfve/Mq33a4aFRzoQYhnnok1JOFpmE/A2TBGkNVenOGEeSBCjIiWcc6MVOG5HEQv0sA==", "license": "MIT", - "peer": true, "dependencies": { "regenerate": "^1.4.2", "regenerate-unicode-properties": "^10.2.2", @@ -17714,15 +19104,13 @@ "version": "0.8.0", "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.8.0.tgz", "integrity": "sha512-RvwtGe3d7LvWiDQXeQw8p5asZUmfU1G/l6WbUXeHta7Y2PEIvBTwH6E2EfmYUK8pxcxEdEmaomqyp0vZZ7C+3Q==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/regjsparser": { "version": "0.13.0", "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.13.0.tgz", "integrity": "sha512-NZQZdC5wOE/H3UT28fVGL+ikOZcEzfMGk/c3iN9UGxzWHMa1op7274oyiUVrAG4B2EuFhus8SvkaYnhvW92p9Q==", "license": "BSD-2-Clause", - "peer": true, "dependencies": { "jsesc": "~3.1.0" }, @@ -18184,6 +19572,16 @@ "node": ">=0.10.0" } }, + "node_modules/serialize-javascript": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz", + "integrity": "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "randombytes": "^2.1.0" + } + }, "node_modules/serve-static": { "version": "1.16.3", "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.3.tgz", @@ -18613,6 +20011,13 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, + "node_modules/smob": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/smob/-/smob-1.5.0.tgz", + "integrity": "sha512-g6T+p7QO8npa+/hNx9ohv1E5pVCmWrVCUzUXJyLdMmftX6ER0oiWY/w9knEonLpnOp6b6FenKnMfR8gqwWdwig==", + "dev": true, + "license": "MIT" + }, "node_modules/sortobject": { "version": "4.17.0", "resolved": "https://registry.npmjs.org/sortobject/-/sortobject-4.17.0.tgz", @@ -18650,7 +20055,6 @@ "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", "license": "MIT", - "peer": true, "dependencies": { "buffer-from": "^1.0.0", "source-map": "^0.6.0" @@ -18661,11 +20065,18 @@ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", "license": "BSD-3-Clause", - "peer": true, "engines": { "node": ">=0.10.0" } }, + "node_modules/sourcemap-codec": { + "version": "1.4.8", + "resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz", + "integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==", + "deprecated": "Please use @jridgewell/sourcemap-codec instead", + "dev": true, + "license": "MIT" + }, "node_modules/sprintf-js": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", @@ -18996,6 +20407,16 @@ "node": ">=4" } }, + "node_modules/strip-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-comments/-/strip-comments-2.0.1.tgz", + "integrity": "sha512-ZprKx+bBLXv067WTCALv8SSz5l2+XhpYCsVtSqlMnkAXMWDq+/ekVbl1ghqP9rUHTzv6sm/DwCOiYutU/yp1fw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, "node_modules/strip-final-newline": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-4.0.0.tgz", @@ -19300,12 +20721,66 @@ "streamx": "^2.12.5" } }, + "node_modules/temp-dir": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/temp-dir/-/temp-dir-2.0.0.tgz", + "integrity": "sha512-aoBAniQmmwtcKp/7BzsH8Cxzv8OL736p7v1ihGb5e9DJ9kTwGWHrQrVB5+lfVDzfGrdRzXch+ig7LHaY1JTOrg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/tempy": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tempy/-/tempy-0.6.0.tgz", + "integrity": "sha512-G13vtMYPT/J8A4X2SjdtBTphZlrp1gKv6hZiOjw14RCWg6GbHuQBGtjlx75xLbYV/wEc0D7G5K4rxKP/cXk8Bw==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-stream": "^2.0.0", + "temp-dir": "^2.0.0", + "type-fest": "^0.16.0", + "unique-string": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/tempy/node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/tempy/node_modules/type-fest": { + "version": "0.16.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.16.0.tgz", + "integrity": "sha512-eaBzG6MxNzEn9kiwvtre90cXaNLkmadMWa1zQMs3XORCXNbsH/OewwbxC5ia9dCxIxnTAsSxXJaa/p5y8DlvJg==", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/terser": { "version": "5.44.1", "resolved": "https://registry.npmjs.org/terser/-/terser-5.44.1.tgz", "integrity": "sha512-t/R3R/n0MSwnnazuPpPNVO60LX0SKL45pyl9YlvxIdkH0Of7D5qM2EVe+yASRIlY5pZ73nclYJfNANGWPwFDZw==", "license": "BSD-2-Clause", - "peer": true, "dependencies": { "@jridgewell/source-map": "^0.3.3", "acorn": "^8.15.0", @@ -19323,8 +20798,7 @@ "version": "2.20.3", "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/test-exclude": { "version": "6.0.0", @@ -19847,7 +21321,6 @@ "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.1.tgz", "integrity": "sha512-dA8WbNeb2a6oQzAQ55YlT5vQAWGV9WXOsi3SskE3bcCdM0P4SDd+24zS/OCacdRq5BkdsRj9q3Pg6YyQoxIGqg==", "license": "MIT", - "peer": true, "engines": { "node": ">=4" } @@ -19857,7 +21330,6 @@ "resolved": "https://registry.npmjs.org/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-2.0.0.tgz", "integrity": "sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q==", "license": "MIT", - "peer": true, "dependencies": { "unicode-canonical-property-names-ecmascript": "^2.0.0", "unicode-property-aliases-ecmascript": "^2.0.0" @@ -19871,7 +21343,6 @@ "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.2.1.tgz", "integrity": "sha512-JQ84qTuMg4nVkx8ga4A16a1epI9H6uTXAknqxkGF/aFfRLw1xC/Bp24HNLaZhHSkWd3+84t8iXnp1J0kYcZHhg==", "license": "MIT", - "peer": true, "engines": { "node": ">=4" } @@ -19881,7 +21352,6 @@ "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.2.0.tgz", "integrity": "sha512-hpbDzxUY9BFwX+UeBnxv3Sh1q7HFxj48DTmXchNgRa46lO8uj3/1iEn3MiNUYTg1g9ctIqXCCERn8gYZhHC5lQ==", "license": "MIT", - "peer": true, "engines": { "node": ">=4" } @@ -19899,6 +21369,19 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/unique-string": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unique-string/-/unique-string-2.0.0.tgz", + "integrity": "sha512-uNaeirEPvpZWSgzwsPGtU2zVSTrn/8L5q/IexZmH0eH6SA73CmAA5U4GwORTxQAZs95TAXLNqeLoPPNO5gZfWg==", + "dev": true, + "license": "MIT", + "dependencies": { + "crypto-random-string": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/universalify": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", @@ -19939,6 +21422,17 @@ "url": "https://github.com/sponsors/kettanaito" } }, + "node_modules/upath": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/upath/-/upath-1.2.0.tgz", + "integrity": "sha512-aZwGpamFO61g3OlfT7OQCHqhGnW43ieH9WZeP7QxN/G/jS4jfqUkZxoryvJgVPEcrl5NL/ggHsSmLMHuH64Lhg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4", + "yarn": "*" + } + }, "node_modules/update-browserslist-db": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.2.tgz", @@ -20778,6 +22272,37 @@ "picomatch": "^2.3.1" } }, + "node_modules/vite-plugin-pwa": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/vite-plugin-pwa/-/vite-plugin-pwa-1.2.0.tgz", + "integrity": "sha512-a2xld+SJshT9Lgcv8Ji4+srFJL4k/1bVbd1x06JIkvecpQkwkvCncD1+gSzcdm3s+owWLpMJerG3aN5jupJEVw==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.3.6", + "pretty-bytes": "^6.1.1", + "tinyglobby": "^0.2.10", + "workbox-build": "^7.4.0", + "workbox-window": "^7.4.0" + }, + "engines": { + "node": ">=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "@vite-pwa/assets-generator": "^1.0.0", + "vite": "^3.1.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0", + "workbox-build": "^7.4.0", + "workbox-window": "^7.4.0" + }, + "peerDependenciesMeta": { + "@vite-pwa/assets-generator": { + "optional": true + } + } + }, "node_modules/vite/node_modules/fdir": { "version": "6.5.0", "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", @@ -21704,6 +23229,551 @@ "node": ">=0.10.0" } }, + "node_modules/workbox-background-sync": { + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/workbox-background-sync/-/workbox-background-sync-7.4.0.tgz", + "integrity": "sha512-8CB9OxKAgKZKyNMwfGZ1XESx89GryWTfI+V5yEj8sHjFH8MFelUwYXEyldEK6M6oKMmn807GoJFUEA1sC4XS9w==", + "dev": true, + "license": "MIT", + "dependencies": { + "idb": "^7.0.1", + "workbox-core": "7.4.0" + } + }, + "node_modules/workbox-broadcast-update": { + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/workbox-broadcast-update/-/workbox-broadcast-update-7.4.0.tgz", + "integrity": "sha512-+eZQwoktlvo62cI0b+QBr40v5XjighxPq3Fzo9AWMiAosmpG5gxRHgTbGGhaJv/q/MFVxwFNGh/UwHZ/8K88lA==", + "dev": true, + "license": "MIT", + "dependencies": { + "workbox-core": "7.4.0" + } + }, + "node_modules/workbox-build": { + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/workbox-build/-/workbox-build-7.4.0.tgz", + "integrity": "sha512-Ntk1pWb0caOFIvwz/hfgrov/OJ45wPEhI5PbTywQcYjyZiVhT3UrwwUPl6TRYbTm4moaFYithYnl1lvZ8UjxcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@apideck/better-ajv-errors": "^0.3.1", + "@babel/core": "^7.24.4", + "@babel/preset-env": "^7.11.0", + "@babel/runtime": "^7.11.2", + "@rollup/plugin-babel": "^5.2.0", + "@rollup/plugin-node-resolve": "^15.2.3", + "@rollup/plugin-replace": "^2.4.1", + "@rollup/plugin-terser": "^0.4.3", + "@surma/rollup-plugin-off-main-thread": "^2.2.3", + "ajv": "^8.6.0", + "common-tags": "^1.8.0", + "fast-json-stable-stringify": "^2.1.0", + "fs-extra": "^9.0.1", + "glob": "^11.0.1", + "lodash": "^4.17.20", + "pretty-bytes": "^5.3.0", + "rollup": "^2.79.2", + "source-map": "^0.8.0-beta.0", + "stringify-object": "^3.3.0", + "strip-comments": "^2.0.1", + "tempy": "^0.6.0", + "upath": "^1.2.0", + "workbox-background-sync": "7.4.0", + "workbox-broadcast-update": "7.4.0", + "workbox-cacheable-response": "7.4.0", + "workbox-core": "7.4.0", + "workbox-expiration": "7.4.0", + "workbox-google-analytics": "7.4.0", + "workbox-navigation-preload": "7.4.0", + "workbox-precaching": "7.4.0", + "workbox-range-requests": "7.4.0", + "workbox-recipes": "7.4.0", + "workbox-routing": "7.4.0", + "workbox-strategies": "7.4.0", + "workbox-streams": "7.4.0", + "workbox-sw": "7.4.0", + "workbox-window": "7.4.0" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/workbox-build/node_modules/@apideck/better-ajv-errors": { + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/@apideck/better-ajv-errors/-/better-ajv-errors-0.3.6.tgz", + "integrity": "sha512-P+ZygBLZtkp0qqOAJJVX4oX/sFo5JR3eBWwwuqHHhK0GIgQOKWrAfiAaWX0aArHkRWHMuggFEgAZNxVPwPZYaA==", + "dev": true, + "license": "MIT", + "dependencies": { + "json-schema": "^0.4.0", + "jsonpointer": "^5.0.0", + "leven": "^3.1.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "ajv": ">=8" + } + }, + "node_modules/workbox-build/node_modules/@rollup/plugin-babel": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/@rollup/plugin-babel/-/plugin-babel-5.3.1.tgz", + "integrity": "sha512-WFfdLWU/xVWKeRQnKmIAQULUI7Il0gZnBIH/ZFO069wYIfPu+8zrfp/KMW0atmELoRDq8FbiP3VCss9MhCut7Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.10.4", + "@rollup/pluginutils": "^3.1.0" + }, + "engines": { + "node": ">= 10.0.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0", + "@types/babel__core": "^7.1.9", + "rollup": "^1.20.0||^2.0.0" + }, + "peerDependenciesMeta": { + "@types/babel__core": { + "optional": true + } + } + }, + "node_modules/workbox-build/node_modules/@rollup/plugin-replace": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/@rollup/plugin-replace/-/plugin-replace-2.4.2.tgz", + "integrity": "sha512-IGcu+cydlUMZ5En85jxHH4qj2hta/11BHq95iHEyb2sbgiN0eCdzvUcHw5gt9pBL5lTi4JDYJ1acCoMGpTvEZg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@rollup/pluginutils": "^3.1.0", + "magic-string": "^0.25.7" + }, + "peerDependencies": { + "rollup": "^1.20.0 || ^2.0.0" + } + }, + "node_modules/workbox-build/node_modules/@rollup/pluginutils": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-3.1.0.tgz", + "integrity": "sha512-GksZ6pr6TpIjHm8h9lSQ8pi8BE9VeubNT0OMJ3B5uZJ8pz73NPiqOtCog/x2/QzM1ENChPKxMDhiQuRHsqc+lg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "0.0.39", + "estree-walker": "^1.0.1", + "picomatch": "^2.2.2" + }, + "engines": { + "node": ">= 8.0.0" + }, + "peerDependencies": { + "rollup": "^1.20.0||^2.0.0" + } + }, + "node_modules/workbox-build/node_modules/@types/estree": { + "version": "0.0.39", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.39.tgz", + "integrity": "sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw==", + "dev": true, + "license": "MIT" + }, + "node_modules/workbox-build/node_modules/ajv": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", + "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/workbox-build/node_modules/estree-walker": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-1.0.1.tgz", + "integrity": "sha512-1fMXF3YP4pZZVozF8j/ZLfvnR8NSIljt56UhbZ5PeeDmmGHpgpdwQt7ITlGvYaQukCvuBRMLEiKiYC+oeIg4cg==", + "dev": true, + "license": "MIT" + }, + "node_modules/workbox-build/node_modules/fs-extra": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", + "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "at-least-node": "^1.0.0", + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/workbox-build/node_modules/glob": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-11.1.0.tgz", + "integrity": "sha512-vuNwKSaKiqm7g0THUBu2x7ckSs3XJLXE+2ssL7/MfTGPLLcrJQ/4Uq1CjPTtO5cCIiRxqvN6Twy1qOwhL0Xjcw==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "foreground-child": "^3.3.1", + "jackspeak": "^4.1.1", + "minimatch": "^10.1.1", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^2.0.0" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/workbox-build/node_modules/is-obj": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-1.0.1.tgz", + "integrity": "sha512-l4RyHgRqGN4Y3+9JHVrNqO+tN0rV5My76uW5/nuO4K1b6vw5G8d/cmFjP9tRfEsdhZNt0IFdZuK/c2Vr4Nb+Qg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/workbox-build/node_modules/is-regexp": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-regexp/-/is-regexp-1.0.0.tgz", + "integrity": "sha512-7zjFAPO4/gwyQAAgRRmqeEeyIICSdmCqa3tsVHMdBzaXXRiqopZL4Cyghg/XulGWrtABTpbnYYzzIRffLkP4oA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/workbox-build/node_modules/jackspeak": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-4.1.1.tgz", + "integrity": "sha512-zptv57P3GpL+O0I7VdMJNBZCu+BPHVQUk55Ft8/QCJjTVxrnJHuVuX/0Bl2A6/+2oyR/ZMEuFKwmzqqZ/U5nPQ==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/workbox-build/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true, + "license": "MIT" + }, + "node_modules/workbox-build/node_modules/lru-cache": { + "version": "11.2.4", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.4.tgz", + "integrity": "sha512-B5Y16Jr9LB9dHVkh6ZevG+vAbOsNOYCX+sXvFWFu7B3Iz5mijW3zdbMyhsh8ANd2mSWBYdJgnqi+mL7/LrOPYg==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/workbox-build/node_modules/magic-string": { + "version": "0.25.9", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.9.tgz", + "integrity": "sha512-RmF0AsMzgt25qzqqLc1+MbHmhdx0ojF2Fvs4XnOqz2ZOBXzzkEwc/dJQZCYHAn7v1jbVOjAZfK8msRn4BxO4VQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "sourcemap-codec": "^1.4.8" + } + }, + "node_modules/workbox-build/node_modules/minimatch": { + "version": "10.1.1", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.1.1.tgz", + "integrity": "sha512-enIvLvRAFZYXJzkCYG5RKmPfrFArdLv+R+lbQ53BmIMLIry74bjKzX6iHAm8WYamJkhSSEabrWN5D97XnKObjQ==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/brace-expansion": "^5.0.0" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/workbox-build/node_modules/minipass": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/workbox-build/node_modules/path-scurry": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.1.tgz", + "integrity": "sha512-oWyT4gICAu+kaA7QWk/jvCHWarMKNs6pXOGWKDTr7cw4IGcUbW+PeTfbaQiLGheFRpjo6O9J0PmyMfQPjH71oA==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^11.0.0", + "minipass": "^7.1.2" + }, + "engines": { + "node": "20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/workbox-build/node_modules/pretty-bytes": { + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-5.6.0.tgz", + "integrity": "sha512-FFw039TmrBqFK8ma/7OL3sDz/VytdtJr044/QUJtH0wK9lb9jLq9tJyIxUwtQJHwar2BqtiA4iCWSwo9JLkzFg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/workbox-build/node_modules/rollup": { + "version": "2.79.2", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.79.2.tgz", + "integrity": "sha512-fS6iqSPZDs3dr/y7Od6y5nha8dW1YnbgtsyotCVvoFGKbERG++CVRFv1meyGDE1SNItQA8BrnCw7ScdAhRJ3XQ==", + "dev": true, + "license": "MIT", + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=10.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/workbox-build/node_modules/source-map": { + "version": "0.8.0-beta.0", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.8.0-beta.0.tgz", + "integrity": "sha512-2ymg6oRBpebeZi9UUNsgQ89bhx01TcTkmNTGnNO88imTmbSgy4nfujrgVEFKWpMTEGA11EDkTt7mqObTPdigIA==", + "deprecated": "The work that was done in this beta branch won't be included in future versions", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "whatwg-url": "^7.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/workbox-build/node_modules/stringify-object": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/stringify-object/-/stringify-object-3.3.0.tgz", + "integrity": "sha512-rHqiFh1elqCQ9WPLIC8I0Q/g/wj5J1eMkyoiD6eoQApWHP0FtlK7rqnhmabL5VUY9JQCcqwwvlOaSuutekgyrw==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "get-own-enumerable-property-symbols": "^3.0.0", + "is-obj": "^1.0.1", + "is-regexp": "^1.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/workbox-build/node_modules/tr46": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-1.0.1.tgz", + "integrity": "sha512-dTpowEjclQ7Kgx5SdBkqRzVhERQXov8/l9Ft9dVM9fmg0W0KQSVaXX9T4i6twCPNtYiZM53lpSSUAwJbFPOHxA==", + "dev": true, + "license": "MIT", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/workbox-build/node_modules/webidl-conversions": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-4.0.2.tgz", + "integrity": "sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==", + "dev": true, + "license": "BSD-2-Clause" + }, + "node_modules/workbox-build/node_modules/whatwg-url": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-7.1.0.tgz", + "integrity": "sha512-WUu7Rg1DroM7oQvGWfOiAK21n74Gg+T4elXEQYkOhtyLeWiJFoOGLXPKI/9gzIie9CtwVLm8wtw6YJdKyxSjeg==", + "dev": true, + "license": "MIT", + "dependencies": { + "lodash.sortby": "^4.7.0", + "tr46": "^1.0.1", + "webidl-conversions": "^4.0.2" + } + }, + "node_modules/workbox-cacheable-response": { + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/workbox-cacheable-response/-/workbox-cacheable-response-7.4.0.tgz", + "integrity": "sha512-0Fb8795zg/x23ISFkAc7lbWes6vbw34DGFIMw31cwuHPgDEC/5EYm6m/ZkylLX0EnEbbOyOCLjKgFS/Z5g0HeQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "workbox-core": "7.4.0" + } + }, + "node_modules/workbox-core": { + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/workbox-core/-/workbox-core-7.4.0.tgz", + "integrity": "sha512-6BMfd8tYEnN4baG4emG9U0hdXM4gGuDU3ectXuVHnj71vwxTFI7WOpQJC4siTOlVtGqCUtj0ZQNsrvi6kZZTAQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/workbox-expiration": { + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/workbox-expiration/-/workbox-expiration-7.4.0.tgz", + "integrity": "sha512-V50p4BxYhtA80eOvulu8xVfPBgZbkxJ1Jr8UUn0rvqjGhLDqKNtfrDfjJKnLz2U8fO2xGQJTx/SKXNTzHOjnHw==", + "dev": true, + "license": "MIT", + "dependencies": { + "idb": "^7.0.1", + "workbox-core": "7.4.0" + } + }, + "node_modules/workbox-google-analytics": { + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/workbox-google-analytics/-/workbox-google-analytics-7.4.0.tgz", + "integrity": "sha512-MVPXQslRF6YHkzGoFw1A4GIB8GrKym/A5+jYDUSL+AeJw4ytQGrozYdiZqUW1TPQHW8isBCBtyFJergUXyNoWQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "workbox-background-sync": "7.4.0", + "workbox-core": "7.4.0", + "workbox-routing": "7.4.0", + "workbox-strategies": "7.4.0" + } + }, + "node_modules/workbox-navigation-preload": { + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/workbox-navigation-preload/-/workbox-navigation-preload-7.4.0.tgz", + "integrity": "sha512-etzftSgdQfjMcfPgbfaZCfM2QuR1P+4o8uCA2s4rf3chtKTq/Om7g/qvEOcZkG6v7JZOSOxVYQiOu6PbAZgU6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "workbox-core": "7.4.0" + } + }, + "node_modules/workbox-precaching": { + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/workbox-precaching/-/workbox-precaching-7.4.0.tgz", + "integrity": "sha512-VQs37T6jDqf1rTxUJZXRl3yjZMf5JX/vDPhmx2CPgDDKXATzEoqyRqhYnRoxl6Kr0rqaQlp32i9rtG5zTzIlNg==", + "dev": true, + "license": "MIT", + "dependencies": { + "workbox-core": "7.4.0", + "workbox-routing": "7.4.0", + "workbox-strategies": "7.4.0" + } + }, + "node_modules/workbox-range-requests": { + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/workbox-range-requests/-/workbox-range-requests-7.4.0.tgz", + "integrity": "sha512-3Vq854ZNuP6Y0KZOQWLaLC9FfM7ZaE+iuQl4VhADXybwzr4z/sMmnLgTeUZLq5PaDlcJBxYXQ3U91V7dwAIfvw==", + "dev": true, + "license": "MIT", + "dependencies": { + "workbox-core": "7.4.0" + } + }, + "node_modules/workbox-recipes": { + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/workbox-recipes/-/workbox-recipes-7.4.0.tgz", + "integrity": "sha512-kOkWvsAn4H8GvAkwfJTbwINdv4voFoiE9hbezgB1sb/0NLyTG4rE7l6LvS8lLk5QIRIto+DjXLuAuG3Vmt3cxQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "workbox-cacheable-response": "7.4.0", + "workbox-core": "7.4.0", + "workbox-expiration": "7.4.0", + "workbox-precaching": "7.4.0", + "workbox-routing": "7.4.0", + "workbox-strategies": "7.4.0" + } + }, + "node_modules/workbox-routing": { + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/workbox-routing/-/workbox-routing-7.4.0.tgz", + "integrity": "sha512-C/ooj5uBWYAhAqwmU8HYQJdOjjDKBp9MzTQ+otpMmd+q0eF59K+NuXUek34wbL0RFrIXe/KKT+tUWcZcBqxbHQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "workbox-core": "7.4.0" + } + }, + "node_modules/workbox-strategies": { + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/workbox-strategies/-/workbox-strategies-7.4.0.tgz", + "integrity": "sha512-T4hVqIi5A4mHi92+5EppMX3cLaVywDp8nsyUgJhOZxcfSV/eQofcOA6/EMo5rnTNmNTpw0rUgjAI6LaVullPpg==", + "dev": true, + "license": "MIT", + "dependencies": { + "workbox-core": "7.4.0" + } + }, + "node_modules/workbox-streams": { + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/workbox-streams/-/workbox-streams-7.4.0.tgz", + "integrity": "sha512-QHPBQrey7hQbnTs5GrEVoWz7RhHJXnPT+12qqWM378orDMo5VMJLCkCM1cnCk+8Eq92lccx/VgRZ7WAzZWbSLg==", + "dev": true, + "license": "MIT", + "dependencies": { + "workbox-core": "7.4.0", + "workbox-routing": "7.4.0" + } + }, + "node_modules/workbox-sw": { + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/workbox-sw/-/workbox-sw-7.4.0.tgz", + "integrity": "sha512-ltU+Kr3qWR6BtbdlMnCjobZKzeV1hN+S6UvDywBrwM19TTyqA03X66dzw1tEIdJvQ4lYKkBFox6IAEhoSEZ8Xw==", + "dev": true, + "license": "MIT" + }, + "node_modules/workbox-window": { + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/workbox-window/-/workbox-window-7.4.0.tgz", + "integrity": "sha512-/bIYdBLAVsNR3v7gYGaV4pQW3M3kEPx5E8vDxGvxo6khTrGtSSCS7QiFKv9ogzBgZiy0OXLP9zO28U/1nF1mfw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/trusted-types": "^2.0.2", + "workbox-core": "7.4.0" + } + }, "node_modules/wrap-ansi": { "version": "6.2.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", diff --git a/package.json b/package.json index 123d2e6..ebd397e 100644 --- a/package.json +++ b/package.json @@ -39,6 +39,7 @@ "prettier": "^3.7.4", "shadcn": "^3.5.2", "typescript-eslint": "^8.49.0", + "vite-plugin-pwa": "^1.2.0", "vitest": "^2.1.9" }, "dependencies": { diff --git a/public/guest-sw.js b/public/guest-sw.js deleted file mode 100644 index 4e0f1b0..0000000 --- a/public/guest-sw.js +++ /dev/null @@ -1,66 +0,0 @@ -self.addEventListener('install', () => { - self.skipWaiting(); -}); - -self.addEventListener('activate', (event) => { - event.waitUntil(self.clients.claim()); -}); - -self.addEventListener('sync', (event) => { - if (event.tag === 'upload-queue') { - event.waitUntil( - (async () => { - const clients = await self.clients.matchAll({ includeUncontrolled: true, type: 'window' }); - clients.forEach((client) => client.postMessage({ type: 'sync-queue' })); - })() - ); - } -}); - -self.addEventListener('push', (event) => { - const payload = event.data?.json?.() ?? {}; - - event.waitUntil( - (async () => { - const title = payload.title ?? 'Neue Nachricht'; - const options = { - body: payload.body ?? '', - icon: '/icons/icon-192.png', - badge: '/icons/badge.png', - data: payload.data ?? {}, - }; - - await self.registration.showNotification(title, options); - - const clients = await self.clients.matchAll({ type: 'window', includeUncontrolled: true }); - clients.forEach((client) => client.postMessage({ type: 'guest-notification-refresh' })); - })() - ); -}); - -self.addEventListener('notificationclick', (event) => { - event.notification.close(); - const targetUrl = event.notification.data?.url || '/'; - - event.waitUntil( - self.clients.matchAll({ type: 'window', includeUncontrolled: true }).then((clientList) => { - for (const client of clientList) { - if ('focus' in client) { - client.navigate(targetUrl); - return client.focus(); - } - } - if (self.clients.openWindow) { - return self.clients.openWindow(targetUrl); - } - }) - ); -}); - -self.addEventListener('pushsubscriptionchange', (event) => { - event.waitUntil( - self.clients.matchAll({ type: 'window', includeUncontrolled: true }).then((clientList) => { - clientList.forEach((client) => client.postMessage({ type: 'push-subscription-change' })); - }) - ); -}); diff --git a/resources/js/guest/components/PullToRefresh.tsx b/resources/js/guest/components/PullToRefresh.tsx new file mode 100644 index 0000000..db8b443 --- /dev/null +++ b/resources/js/guest/components/PullToRefresh.tsx @@ -0,0 +1,149 @@ +import React from 'react'; +import { ArrowDown, Loader2 } from 'lucide-react'; +import { cn } from '@/lib/utils'; + +const MAX_PULL = 96; +const TRIGGER_PULL = 72; +const DAMPING = 0.55; + +type PullToRefreshProps = { + onRefresh: () => Promise | void; + disabled?: boolean; + className?: string; + pullLabel?: string; + releaseLabel?: string; + refreshingLabel?: string; + children: React.ReactNode; +}; + +export default function PullToRefresh({ + onRefresh, + disabled = false, + className, + pullLabel = 'Pull to refresh', + releaseLabel = 'Release to refresh', + refreshingLabel = 'Refreshing…', + children, +}: PullToRefreshProps) { + const containerRef = React.useRef(null); + const startYRef = React.useRef(null); + const pullDistanceRef = React.useRef(0); + const [pullDistance, setPullDistance] = React.useState(0); + const [dragging, setDragging] = React.useState(false); + const [refreshing, setRefreshing] = React.useState(false); + + const updatePull = React.useCallback((value: number) => { + pullDistanceRef.current = value; + setPullDistance(value); + }, []); + + React.useEffect(() => { + const container = containerRef.current; + if (!container || disabled) { + return; + } + + const handleStart = (event: TouchEvent) => { + if (refreshing || window.scrollY > 0) { + return; + } + startYRef.current = event.touches[0]?.clientY ?? null; + setDragging(true); + }; + + const handleMove = (event: TouchEvent) => { + if (refreshing || startYRef.current === null) { + return; + } + if (window.scrollY > 0) { + startYRef.current = null; + updatePull(0); + setDragging(false); + return; + } + const currentY = event.touches[0]?.clientY ?? 0; + const delta = currentY - startYRef.current; + if (delta <= 0) { + updatePull(0); + return; + } + event.preventDefault(); + const next = Math.min(MAX_PULL, delta * DAMPING); + updatePull(next); + }; + + const handleEnd = async () => { + if (startYRef.current === null) { + return; + } + startYRef.current = null; + setDragging(false); + + if (pullDistanceRef.current >= TRIGGER_PULL) { + setRefreshing(true); + updatePull(TRIGGER_PULL); + try { + await onRefresh(); + } finally { + setRefreshing(false); + updatePull(0); + } + return; + } + + updatePull(0); + }; + + container.addEventListener('touchstart', handleStart, { passive: true }); + container.addEventListener('touchmove', handleMove, { passive: false }); + container.addEventListener('touchend', handleEnd); + container.addEventListener('touchcancel', handleEnd); + + return () => { + container.removeEventListener('touchstart', handleStart); + container.removeEventListener('touchmove', handleMove); + container.removeEventListener('touchend', handleEnd); + container.removeEventListener('touchcancel', handleEnd); + }; + }, [disabled, onRefresh, refreshing, updatePull]); + + const progress = Math.min(pullDistance / TRIGGER_PULL, 1); + const ready = pullDistance >= TRIGGER_PULL; + const indicatorLabel = refreshing + ? refreshingLabel + : ready + ? releaseLabel + : pullLabel; + + return ( +
+
+
+ {refreshing ? ( + + ) : ( + + )} + {indicatorLabel} +
+
+ +
+ {children} +
+
+ ); +} diff --git a/resources/js/guest/components/PwaManager.tsx b/resources/js/guest/components/PwaManager.tsx new file mode 100644 index 0000000..2853ac1 --- /dev/null +++ b/resources/js/guest/components/PwaManager.tsx @@ -0,0 +1,77 @@ +import React from 'react'; +import { registerSW } from 'virtual:pwa-register'; +import { useTranslation } from '../i18n/useTranslation'; +import { useToast } from './ToastHost'; + +export default function PwaManager() { + const toast = useToast(); + const { t } = useTranslation(); + const toastRef = React.useRef(toast); + const tRef = React.useRef(t); + const updatePromptedRef = React.useRef(false); + + React.useEffect(() => { + toastRef.current = toast; + }, [toast]); + + React.useEffect(() => { + tRef.current = t; + }, [t]); + + React.useEffect(() => { + if (!('serviceWorker' in navigator)) { + return; + } + + const updateSW = registerSW({ + immediate: true, + onNeedRefresh() { + if (updatePromptedRef.current) { + return; + } + updatePromptedRef.current = true; + toastRef.current.push({ + text: tRef.current('common.updateAvailable'), + type: 'info', + durationMs: 0, + action: { + label: tRef.current('common.updateAction'), + onClick: () => updateSW(true), + }, + }); + }, + onOfflineReady() { + toastRef.current.push({ + text: tRef.current('common.offlineReady'), + type: 'success', + }); + }, + onRegisterError(error) { + console.warn('Guest PWA registration failed', error); + }, + }); + + const runQueue = () => { + void import('../queue/queue') + .then((m) => m.processQueue().catch(() => {})) + .catch(() => {}); + }; + + const handleMessage = (event: MessageEvent) => { + if (event.data?.type === 'sync-queue') { + runQueue(); + } + }; + + navigator.serviceWorker.addEventListener('message', handleMessage); + window.addEventListener('online', runQueue); + runQueue(); + + return () => { + navigator.serviceWorker.removeEventListener('message', handleMessage); + window.removeEventListener('online', runQueue); + }; + }, []); + + return null; +} diff --git a/resources/js/guest/components/RouteTransition.tsx b/resources/js/guest/components/RouteTransition.tsx new file mode 100644 index 0000000..f4c4c2e --- /dev/null +++ b/resources/js/guest/components/RouteTransition.tsx @@ -0,0 +1,100 @@ +import React from 'react'; +import { AnimatePresence, motion, useReducedMotion } from 'framer-motion'; +import { Outlet, useLocation, useNavigationType } from 'react-router-dom'; + +const TAB_SECTIONS = new Set(['home', 'tasks', 'achievements', 'gallery']); + +export function getTabKey(pathname: string): string | null { + const match = pathname.match(/^\/e\/[^/]+(?:\/([^/]+))?$/); + if (!match) { + return null; + } + + const section = match[1]; + if (!section) { + return 'home'; + } + + return TAB_SECTIONS.has(section) ? section : null; +} + +export function getTransitionKind(prevPath: string, nextPath: string): 'tab' | 'stack' { + const prevTab = getTabKey(prevPath); + const nextTab = getTabKey(nextPath); + + if (prevTab && nextTab && prevTab !== nextTab) { + return 'tab'; + } + + return 'stack'; +} + +export function isTransitionDisabled(pathname: string): boolean { + if (pathname.startsWith('/share/')) { + return true; + } + + return /^\/e\/[^/]+\/upload(?:\/|$)/.test(pathname); +} + +export default function RouteTransition({ children }: { children?: React.ReactNode }) { + const location = useLocation(); + const navigationType = useNavigationType(); + const prefersReducedMotion = useReducedMotion(); + const prevPathRef = React.useRef(location.pathname); + const prevPath = prevPathRef.current; + const direction = navigationType === 'POP' ? 'back' : 'forward'; + const kind = getTransitionKind(prevPath, location.pathname); + const disableTransitions = prefersReducedMotion + || isTransitionDisabled(prevPath) + || isTransitionDisabled(location.pathname); + + React.useEffect(() => { + prevPathRef.current = location.pathname; + }, [location.pathname]); + + const content = children ?? ; + + if (disableTransitions) { + return <>{content}; + } + + const stackVariants = { + enter: ({ direction }: { direction: 'forward' | 'back' }) => ({ + x: direction === 'back' ? -28 : 28, + opacity: 0, + }), + center: { x: 0, opacity: 1 }, + exit: ({ direction }: { direction: 'forward' | 'back' }) => ({ + x: direction === 'back' ? 28 : -28, + opacity: 0, + }), + }; + + const tabVariants = { + enter: { opacity: 0, scale: 0.985 }, + center: { opacity: 1, scale: 1 }, + exit: { opacity: 0, scale: 0.985 }, + }; + + const transition = kind === 'tab' + ? { duration: 0.18, ease: [0.22, 0.61, 0.36, 1] } + : { duration: 0.24, ease: [0.25, 0.8, 0.25, 1] }; + + return ( + + + {content} + + + ); +} diff --git a/resources/js/guest/components/ToastHost.tsx b/resources/js/guest/components/ToastHost.tsx index 9d8643a..6a2172a 100644 --- a/resources/js/guest/components/ToastHost.tsx +++ b/resources/js/guest/components/ToastHost.tsx @@ -1,29 +1,69 @@ // @ts-nocheck import React from 'react'; -type Toast = { id: number; text: string; type?: 'success'|'error' }; +type ToastAction = { label: string; onClick: () => void }; +type Toast = { + id: number; + text: string; + type?: 'success' | 'error' | 'info'; + action?: ToastAction; + durationMs?: number; +}; const Ctx = React.createContext<{ push: (t: Omit) => void } | null>(null); export function ToastProvider({ children }: { children: React.ReactNode }) { const [list, setList] = React.useState([]); const push = React.useCallback((t: Omit) => { const id = Date.now() + Math.random(); - setList((arr) => [...arr, { id, ...t }]); - setTimeout(() => setList((arr) => arr.filter((x) => x.id !== id)), 3000); + const durationMs = t.durationMs ?? 3000; + setList((arr) => [...arr, { id, ...t, durationMs }]); + if (durationMs > 0) { + setTimeout(() => setList((arr) => arr.filter((x) => x.id !== id)), durationMs); + } }, []); + const dismiss = React.useCallback((id: number) => { + setList((arr) => arr.filter((x) => x.id !== id)); + }, []); + const contextValue = React.useMemo(() => ({ push }), [push]); React.useEffect(() => { const onEvt = (e: CustomEvent>) => push(e.detail); window.addEventListener('guest-toast', onEvt); return () => window.removeEventListener('guest-toast', onEvt); }, [push]); return ( - + {children}
{list.map((t) => ( -
- {t.text} +
+
+ {t.text} + {t.action ? ( + + ) : null} +
))}
diff --git a/resources/js/guest/components/__tests__/PullToRefresh.test.tsx b/resources/js/guest/components/__tests__/PullToRefresh.test.tsx new file mode 100644 index 0000000..77ba281 --- /dev/null +++ b/resources/js/guest/components/__tests__/PullToRefresh.test.tsx @@ -0,0 +1,22 @@ +import React from 'react'; +import { describe, expect, it, vi } from 'vitest'; +import { render, screen } from '@testing-library/react'; +import PullToRefresh from '../PullToRefresh'; + +describe('PullToRefresh', () => { + it('renders children and labels', () => { + render( + +
Content
+
+ ); + + expect(screen.getByText('Content')).toBeInTheDocument(); + expect(screen.getByText('Pull')).toBeInTheDocument(); + }); +}); diff --git a/resources/js/guest/components/__tests__/RouteTransition.test.tsx b/resources/js/guest/components/__tests__/RouteTransition.test.tsx new file mode 100644 index 0000000..ec1d340 --- /dev/null +++ b/resources/js/guest/components/__tests__/RouteTransition.test.tsx @@ -0,0 +1,23 @@ +import { describe, expect, it } from 'vitest'; +import { getTabKey, getTransitionKind, isTransitionDisabled } from '../RouteTransition'; + +describe('RouteTransition helpers', () => { + it('detects top-level tabs', () => { + expect(getTabKey('/e/demo')).toBe('home'); + expect(getTabKey('/e/demo/tasks')).toBe('tasks'); + expect(getTabKey('/e/demo/achievements')).toBe('achievements'); + expect(getTabKey('/e/demo/gallery')).toBe('gallery'); + expect(getTabKey('/e/demo/tasks/123')).toBeNull(); + }); + + it('detects tab vs stack transitions', () => { + expect(getTransitionKind('/e/demo', '/e/demo/gallery')).toBe('tab'); + expect(getTransitionKind('/e/demo/tasks', '/e/demo/tasks/1')).toBe('stack'); + }); + + it('disables transitions for excluded routes', () => { + expect(isTransitionDisabled('/e/demo/upload')).toBe(true); + expect(isTransitionDisabled('/share/demo-photo')).toBe(true); + expect(isTransitionDisabled('/e/demo/gallery')).toBe(false); + }); +}); diff --git a/resources/js/guest/components/__tests__/ToastHost.test.tsx b/resources/js/guest/components/__tests__/ToastHost.test.tsx new file mode 100644 index 0000000..2194d08 --- /dev/null +++ b/resources/js/guest/components/__tests__/ToastHost.test.tsx @@ -0,0 +1,42 @@ +import React from 'react'; +import { fireEvent, render, screen } from '@testing-library/react'; +import { vi } from 'vitest'; +import { ToastProvider, useToast } from '../ToastHost'; + +function ToastTestHarness({ onAction }: { onAction: () => void }) { + const toast = useToast(); + + React.useEffect(() => { + toast.push({ + text: 'Update ready', + type: 'info', + durationMs: 0, + action: { + label: 'Reload', + onClick: onAction, + }, + }); + }, [toast, onAction]); + + return null; +} + +describe('ToastHost', () => { + it('renders action toasts and dismisses after action click', async () => { + const onAction = vi.fn(); + + render( + + + + ); + + expect(screen.getByText('Update ready')).toBeInTheDocument(); + const button = screen.getByRole('button', { name: 'Reload' }); + + fireEvent.click(button); + + expect(onAction).toHaveBeenCalledTimes(1); + expect(screen.queryByText('Update ready')).not.toBeInTheDocument(); + }); +}); diff --git a/resources/js/guest/guest-sw.ts b/resources/js/guest/guest-sw.ts new file mode 100644 index 0000000..bab3aea --- /dev/null +++ b/resources/js/guest/guest-sw.ts @@ -0,0 +1,157 @@ +/* eslint-disable no-restricted-globals */ +/// + +import { clientsClaim } from 'workbox-core'; +import { CacheableResponsePlugin } from 'workbox-cacheable-response'; +import { ExpirationPlugin } from 'workbox-expiration'; +import { precacheAndRoute, cleanupOutdatedCaches } from 'workbox-precaching'; +import { registerRoute } from 'workbox-routing'; +import { CacheFirst, NetworkFirst, StaleWhileRevalidate } from 'workbox-strategies'; + +declare const self: ServiceWorkerGlobalScope & { + __WB_MANIFEST: Array; +}; + +clientsClaim(); +precacheAndRoute(self.__WB_MANIFEST); +cleanupOutdatedCaches(); + +const isGuestNavigation = (pathname: string) => { + if (pathname === '/event') { + return true; + } + if (pathname.startsWith('/e/')) { + return true; + } + if (pathname.startsWith('/g/')) { + return true; + } + if (pathname.startsWith('/share/')) { + return true; + } + if (pathname.startsWith('/help')) { + return true; + } + if (pathname.startsWith('/legal')) { + return true; + } + if (pathname.startsWith('/settings')) { + return true; + } + return false; +}; + +registerRoute( + ({ request, url }) => + request.mode === 'navigate' && url.origin === self.location.origin && isGuestNavigation(url.pathname), + new NetworkFirst({ + cacheName: 'guest-pages', + networkTimeoutSeconds: 5, + plugins: [ + new CacheableResponsePlugin({ statuses: [0, 200] }), + new ExpirationPlugin({ maxEntries: 40, maxAgeSeconds: 60 * 60 * 24 * 7 }), + ], + }) +); + +registerRoute( + ({ request, url }) => + request.method === 'GET' && + url.origin === self.location.origin && + url.pathname.startsWith('/api/v1/'), + new NetworkFirst({ + cacheName: 'guest-api', + networkTimeoutSeconds: 6, + plugins: [ + new CacheableResponsePlugin({ statuses: [0, 200] }), + new ExpirationPlugin({ maxEntries: 80, maxAgeSeconds: 60 * 60 * 24 }), + ], + }) +); + +registerRoute( + ({ request, url }) => request.destination === 'image' && url.origin === self.location.origin, + new CacheFirst({ + cacheName: 'guest-images', + plugins: [ + new CacheableResponsePlugin({ statuses: [0, 200] }), + new ExpirationPlugin({ maxEntries: 200, maxAgeSeconds: 60 * 60 * 24 * 30 }), + ], + }) +); + +registerRoute( + ({ request, url }) => request.destination === 'font' && url.origin === self.location.origin, + new StaleWhileRevalidate({ + cacheName: 'guest-fonts', + plugins: [ + new CacheableResponsePlugin({ statuses: [0, 200] }), + new ExpirationPlugin({ maxEntries: 30, maxAgeSeconds: 60 * 60 * 24 * 365 }), + ], + }) +); + +self.addEventListener('message', (event) => { + if (event.data?.type === 'SKIP_WAITING') { + self.skipWaiting(); + } +}); + +self.addEventListener('sync', (event) => { + if (event.tag === 'upload-queue') { + event.waitUntil( + (async () => { + const clients = await self.clients.matchAll({ includeUncontrolled: true, type: 'window' }); + clients.forEach((client) => client.postMessage({ type: 'sync-queue' })); + })() + ); + } +}); + +self.addEventListener('push', (event) => { + const payload = event.data?.json?.() ?? {}; + + event.waitUntil( + (async () => { + const title = payload.title ?? 'Neue Nachricht'; + const options = { + body: payload.body ?? '', + icon: '/apple-touch-icon.png', + badge: '/apple-touch-icon.png', + data: payload.data ?? {}, + }; + + await self.registration.showNotification(title, options); + + const clients = await self.clients.matchAll({ type: 'window', includeUncontrolled: true }); + clients.forEach((client) => client.postMessage({ type: 'guest-notification-refresh' })); + })() + ); +}); + +self.addEventListener('notificationclick', (event) => { + event.notification.close(); + const targetUrl = event.notification.data?.url || '/'; + + event.waitUntil( + self.clients.matchAll({ type: 'window', includeUncontrolled: true }).then((clientList) => { + for (const client of clientList) { + if ('focus' in client) { + client.navigate(targetUrl); + return client.focus(); + } + } + if (self.clients.openWindow) { + return self.clients.openWindow(targetUrl); + } + }) + ); +}); + +self.addEventListener('pushsubscriptionchange', (event) => { + event.waitUntil( + self.clients.matchAll({ type: 'window', includeUncontrolled: true }).then((clientList) => { + clientList.forEach((client) => client.postMessage({ type: 'push-subscription-change' })); + }) + ); +}); diff --git a/resources/js/guest/i18n/messages.ts b/resources/js/guest/i18n/messages.ts index 8ee0e9d..1ae37f3 100644 --- a/resources/js/guest/i18n/messages.ts +++ b/resources/js/guest/i18n/messages.ts @@ -16,6 +16,13 @@ export const messages: Record = { de: { common: { hi: 'Hi', + refresh: 'Aktualisieren', + pullToRefresh: 'Zum Aktualisieren ziehen', + releaseToRefresh: 'Zum Aktualisieren loslassen', + refreshing: 'Aktualisiere...', + updateAvailable: 'Neue Version verfügbar.', + updateAction: 'Aktualisieren', + offlineReady: 'Offline bereit.', actions: { close: 'Schließen', loading: 'Lädt...', @@ -137,6 +144,7 @@ export const messages: Record = { home: { fallbackGuestName: 'Gast', welcomeLine: 'Willkommen {name}!', + swipeHint: 'Wische für mehr Aufgaben', introRotating: { 0: 'Hilf uns, diesen besonderen Tag mit deinen schönsten Momenten festzuhalten.', 1: 'Fang die Stimmung des Events ein und teile sie mit allen Gästen.', @@ -703,6 +711,13 @@ export const messages: Record = { en: { common: { hi: 'Hi', + refresh: 'Refresh', + pullToRefresh: 'Pull to refresh', + releaseToRefresh: 'Release to refresh', + refreshing: 'Refreshing...', + updateAvailable: 'A new version is available.', + updateAction: 'Update', + offlineReady: 'Offline ready.', actions: { close: 'Close', loading: 'Loading...', @@ -824,6 +839,7 @@ export const messages: Record = { home: { fallbackGuestName: 'Guest', welcomeLine: 'Welcome {name}!', + swipeHint: 'Swipe for more missions', introRotating: { 0: 'Help us capture this special day with your favourite moments.', 1: 'Capture the mood of the event and share it with everyone.', diff --git a/resources/js/guest/lib/__tests__/motion.test.ts b/resources/js/guest/lib/__tests__/motion.test.ts new file mode 100644 index 0000000..4a1413d --- /dev/null +++ b/resources/js/guest/lib/__tests__/motion.test.ts @@ -0,0 +1,41 @@ +import { describe, expect, it, vi } from 'vitest'; +import { FADE_SCALE, FADE_UP, STAGGER_FAST, getMotionContainerProps, getMotionItemProps, prefersReducedMotion } from '../motion'; + +describe('motion helpers', () => { + it('returns disabled props when motion is off', () => { + const props = getMotionContainerProps(false, STAGGER_FAST); + expect(props.initial).toBe(false); + }); + + it('returns variants when motion is on', () => { + const containerProps = getMotionContainerProps(true, STAGGER_FAST); + const itemProps = getMotionItemProps(true, FADE_UP); + expect(containerProps.initial).toBe('hidden'); + expect(containerProps.animate).toBe('show'); + expect(itemProps.variants).toBe(FADE_UP); + }); + + it('detects reduced motion preference safely', () => { + const original = window.matchMedia; + Object.defineProperty(window, 'matchMedia', { + configurable: true, + value: undefined, + }); + expect(prefersReducedMotion()).toBe(false); + + Object.defineProperty(window, 'matchMedia', { + configurable: true, + value: vi.fn().mockReturnValue({ matches: true }), + }); + expect(prefersReducedMotion()).toBe(true); + + Object.defineProperty(window, 'matchMedia', { + configurable: true, + value: original, + }); + }); + + it('exposes distinct base variants', () => { + expect(FADE_UP).not.toBe(FADE_SCALE); + }); +}); diff --git a/resources/js/guest/lib/motion.ts b/resources/js/guest/lib/motion.ts new file mode 100644 index 0000000..9d2c78d --- /dev/null +++ b/resources/js/guest/lib/motion.ts @@ -0,0 +1,58 @@ +import type { Variants } from 'framer-motion'; + +export const IOS_EASE = [0.22, 0.61, 0.36, 1] as const; +export const IOS_EASE_SOFT = [0.25, 0.8, 0.25, 1] as const; + +export const STAGGER_FAST: Variants = { + hidden: {}, + show: { + transition: { + staggerChildren: 0.06, + delayChildren: 0.04, + }, + }, +}; + +export const FADE_UP: Variants = { + hidden: { opacity: 0, y: 10 }, + show: { + opacity: 1, + y: 0, + transition: { + duration: 0.24, + ease: IOS_EASE, + }, + }, +}; + +export const FADE_SCALE: Variants = { + hidden: { opacity: 0, scale: 0.98 }, + show: { + opacity: 1, + scale: 1, + transition: { + duration: 0.22, + ease: IOS_EASE, + }, + }, +}; + +export function prefersReducedMotion(): boolean { + if (typeof window === 'undefined') { + return false; + } + + return Boolean(window.matchMedia?.('(prefers-reduced-motion: reduce)')?.matches); +} + +export function getMotionContainerProps(enabled: boolean, variants: Variants) { + if (!enabled) { + return { initial: false } as const; + } + + return { variants, initial: 'hidden', animate: 'show' } as const; +} + +export function getMotionItemProps(enabled: boolean, variants: Variants) { + return enabled ? { variants } : {}; +} diff --git a/resources/js/guest/main.tsx b/resources/js/guest/main.tsx index a6d3fbb..dda82c0 100644 --- a/resources/js/guest/main.tsx +++ b/resources/js/guest/main.tsx @@ -36,6 +36,7 @@ const appRoot = async () => { const { RouterProvider } = await import('react-router-dom'); const { router } = await import('./router'); const { ToastProvider } = await import('./components/ToastHost'); + const { default: PwaManager } = await import('./components/PwaManager'); const { LocaleProvider } = await import('./i18n/LocaleContext'); const { default: MatomoTracker } = await import('@/components/analytics/MatomoTracker'); const rawMatomo = (window as any).__MATOMO_GUEST__ as { enabled?: boolean; url?: string; siteId?: string } | undefined; @@ -47,21 +48,6 @@ const appRoot = async () => { } : undefined; - // Register a minimal service worker for background sync (best-effort) - if ('serviceWorker' in navigator) { - navigator.serviceWorker.register('/guest-sw.js').catch(() => {}); - navigator.serviceWorker.addEventListener('message', (evt) => { - if (evt.data?.type === 'sync-queue') { - import('./queue/queue').then((m) => m.processQueue().catch(() => {})); - } - }); - // Also attempt to process queue on load and when going online - import('./queue/queue').then((m) => m.processQueue().catch(() => {})); - window.addEventListener('online', () => { - import('./queue/queue').then((m) => m.processQueue().catch(() => {})); - }); - } - createRoot(rootEl).render( }> @@ -69,6 +55,7 @@ const appRoot = async () => { + }> diff --git a/resources/js/guest/pages/AchievementsPage.tsx b/resources/js/guest/pages/AchievementsPage.tsx index d902d6f..28864af 100644 --- a/resources/js/guest/pages/AchievementsPage.tsx +++ b/resources/js/guest/pages/AchievementsPage.tsx @@ -6,6 +6,7 @@ import { Button } from '@/components/ui/button'; import { Skeleton } from '@/components/ui/skeleton'; import { Alert, AlertDescription } from '@/components/ui/alert'; import { Separator } from '@/components/ui/separator'; +import { AnimatePresence, motion } from 'framer-motion'; import { AchievementBadge, AchievementsPayload, @@ -23,6 +24,8 @@ import type { LocaleCode } from '../i18n/messages'; import { localizeTaskLabel } from '../lib/localizeTaskLabel'; import { useEventData } from '../hooks/useEventData'; import { isTaskModeEnabled } from '../lib/engagement'; +import { FADE_SCALE, FADE_UP, STAGGER_FAST, getMotionContainerProps, getMotionItemProps, prefersReducedMotion } from '../lib/motion'; +import PullToRefresh from '../components/PullToRefresh'; const GENERIC_ERROR = 'GENERIC_ERROR'; @@ -356,172 +359,213 @@ export default function AchievementsPage() { const personalName = identity.hydrated && identity.name ? identity.name : undefined; - useEffect(() => { + const loadAchievements = React.useCallback(async (signal?: AbortSignal) => { if (!token) return; - const controller = new AbortController(); setLoading(true); setError(null); - fetchAchievements(token, { - guestName: personalName, - locale, - signal: controller.signal, - }) - .then((payload) => { - setData(payload); - if (!payload.personal) { - setActiveTab('event'); - } - }) - .catch((err) => { - if (err.name === 'AbortError') return; - console.error('Failed to load achievements', err); - setError(err.message || GENERIC_ERROR); - }) - .finally(() => setLoading(false)); + try { + const payload = await fetchAchievements(token, { + guestName: personalName, + locale, + signal, + }); + setData(payload); + if (!payload.personal) { + setActiveTab('event'); + } + } catch (err) { + if (err instanceof DOMException && err.name === 'AbortError') return; + console.error('Failed to load achievements', err); + setError(err instanceof Error ? err.message : GENERIC_ERROR); + } finally { + if (!signal?.aborted) { + setLoading(false); + } + } + }, [locale, personalName, token]); + + useEffect(() => { + const controller = new AbortController(); + void loadAchievements(controller.signal); return () => controller.abort(); - }, [token, personalName, locale]); + }, [loadAchievements]); const hasPersonal = Boolean(data?.personal); + const motionEnabled = !prefersReducedMotion(); + const containerMotion = getMotionContainerProps(motionEnabled, STAGGER_FAST); + const fadeUpMotion = getMotionItemProps(motionEnabled, FADE_UP); + const fadeScaleMotion = getMotionItemProps(motionEnabled, FADE_SCALE); + const tabMotion = motionEnabled + ? { variants: FADE_UP, initial: 'hidden', animate: 'show', exit: 'hidden' as const } + : {}; + const handleRefresh = React.useCallback(async () => { + await loadAchievements(); + }, [loadAchievements]); if (!token) { return null; } - return ( -
-
-
-
- -
-
-

{t('achievements.page.title')}

-

{t('achievements.page.subtitle')}

-
-
-
- - {loading && ( -
- - - -
- )} - - {!loading && error && ( - - - {error === GENERIC_ERROR ? t('achievements.page.loadError') : error} - - - - )} - - {!loading && !error && data && ( - <> -
- - - -
- - - - {activeTab === 'personal' && hasPersonal && data.personal && ( -
- - -
- - {t('achievements.personal.greeting', { name: data.personal.guestName || identity.name || t('achievements.leaderboard.guestFallback') })} - - - {t('achievements.personal.stats', { - photos: formatNumber(data.personal.photos), - tasks: formatNumber(data.personal.tasks), - likes: formatNumber(data.personal.likes), - })} - -
- -
-
- - -
- )} - - {activeTab === 'event' && ( -
- - -
- - + const tabContent = ( + <> + {activeTab === 'personal' && hasPersonal && data?.personal && ( +
+ + +
+ + {t('achievements.personal.greeting', { name: data.personal.guestName || identity.name || t('achievements.leaderboard.guestFallback') })} + + + {t('achievements.personal.stats', { + photos: formatNumber(data.personal.photos), + tasks: formatNumber(data.personal.tasks), + likes: formatNumber(data.personal.likes), + })} +
-
- )} + + + - {activeTab === 'feed' && ( - +
+ )} + + {activeTab === 'event' && data && ( +
+ + +
+ - )} - + +
+
)} -
+ + {activeTab === 'feed' && data && ( + + )} + + ); + + return ( + + + +
+
+ +
+
+

{t('achievements.page.title')}

+

{t('achievements.page.subtitle')}

+
+
+
+ + {loading && ( + + + + + + )} + + {!loading && error && ( + + + + {error === GENERIC_ERROR ? t('achievements.page.loadError') : error} + + + + + )} + + {!loading && !error && data && ( + <> + + + + + + + + + + + {motionEnabled ? ( + + + {tabContent} + + + ) : ( + {tabContent} + )} + + )} +
+
); } diff --git a/resources/js/guest/pages/GalleryPage.tsx b/resources/js/guest/pages/GalleryPage.tsx index 399bd57..04d0837 100644 --- a/resources/js/guest/pages/GalleryPage.tsx +++ b/resources/js/guest/pages/GalleryPage.tsx @@ -5,6 +5,7 @@ import { useParams, useSearchParams } from 'react-router-dom'; import { usePollGalleryDelta } from '../polling/usePollGalleryDelta'; import FiltersBar, { type GalleryFilter } from '../components/FiltersBar'; import { Heart, Image as ImageIcon, Share2 } from 'lucide-react'; +import { motion } from 'framer-motion'; import { likePhoto } from '../services/photosApi'; import PhotoLightbox from './PhotoLightbox'; import { fetchEvent, type EventData } from '../services/eventApi'; @@ -15,6 +16,8 @@ import { createPhotoShareLink } from '../services/photosApi'; import { cn } from '@/lib/utils'; import { useEventBranding } from '../context/EventBrandingContext'; import ShareSheet from '../components/ShareSheet'; +import { FADE_SCALE, FADE_UP, STAGGER_FAST, getMotionContainerProps, getMotionItemProps, prefersReducedMotion } from '../lib/motion'; +import PullToRefresh from '../components/PullToRefresh'; const allGalleryFilters: GalleryFilter[] = ['latest', 'popular', 'mine', 'photobooth']; type GalleryPhoto = { @@ -54,7 +57,7 @@ export default function GalleryPage() { const { token } = useParams<{ token?: string }>(); const { t, locale } = useTranslation(); const { branding } = useEventBranding(); - const { photos, loading, newCount, acknowledgeNew } = usePollGalleryDelta(token ?? '', locale); + const { photos, loading, newCount, acknowledgeNew, refreshNow } = usePollGalleryDelta(token ?? '', locale); const [searchParams, setSearchParams] = useSearchParams(); const photoIdParam = searchParams.get('photoId'); const modeParam = searchParams.get('mode'); @@ -63,6 +66,11 @@ export default function GalleryPage() { const linkColor = branding.buttons?.linkColor ?? branding.secondaryColor; const bodyFont = branding.typography?.body ?? branding.fontFamily ?? undefined; const headingFont = branding.typography?.heading ?? branding.fontFamily ?? undefined; + const motionEnabled = !prefersReducedMotion(); + const containerMotion = getMotionContainerProps(motionEnabled, STAGGER_FAST); + const fadeUpMotion = getMotionItemProps(motionEnabled, FADE_UP); + const fadeScaleMotion = getMotionItemProps(motionEnabled, FADE_SCALE); + const gridMotion = getMotionContainerProps(motionEnabled, STAGGER_FAST); const [filter, setFilterState] = React.useState('latest'); const [currentPhotoIndex, setCurrentPhotoIndex] = React.useState(null); const [hasOpenedPhoto, setHasOpenedPhoto] = useState(false); @@ -122,24 +130,28 @@ export default function GalleryPage() { }, [typedPhotos, photos.length, photoIdParam, currentPhotoIndex, hasOpenedPhoto]); // Load event and package info - useEffect(() => { + const loadEventData = React.useCallback(async () => { if (!token) return; - - const loadEventData = async () => { - try { - setEventLoading(true); - const eventData = await fetchEvent(token); - setEvent(eventData); - } catch (err) { - console.error('Failed to load event data', err); - } finally { - setEventLoading(false); - } - }; - - loadEventData(); + try { + setEventLoading(true); + const eventData = await fetchEvent(token); + setEvent(eventData); + } catch (err) { + console.error('Failed to load event data', err); + } finally { + setEventLoading(false); + } }, [token]); + useEffect(() => { + void loadEventData(); + }, [loadEventData]); + + const handleRefresh = React.useCallback(async () => { + await Promise.all([refreshNow(), loadEventData()]); + acknowledgeNew(); + }, [acknowledgeNew, loadEventData, refreshNow]); + const myPhotoIds = React.useMemo(() => { try { const raw = localStorage.getItem('my-photo-ids'); @@ -287,152 +299,167 @@ export default function GalleryPage() { return ( -
-
-
- -
-
-

{t('galleryPage.title')}

-

{t('galleryPage.subtitle')}

-
+ + + +
+ +
+
+

{t('galleryPage.title')}

+

{t('galleryPage.subtitle')}

+
- {newCount > 0 ? ( - - ) : ( - - {newPhotosBadgeText} - - )} -
-
+ {newCount > 0 ? ( + + ) : ( + + {newPhotosBadgeText} + + )} + + - - {loading &&

{t('galleryPage.loading', 'Lade…')}

} -
- {list.map((p: GalleryPhoto) => { - const imageUrl = normalizeImageUrl(p.thumbnail_path || p.file_path); - const createdLabel = p.created_at - ? new Date(p.created_at).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }) - : t('galleryPage.photo.justNow', 'Gerade eben'); - const likeCount = counts[p.id] ?? (p.likes_count || 0); - const localizedTaskTitle = localizeTaskLabel(p.task_title ?? null, locale); - const altSuffix = localizedTaskTitle - ? t('galleryPage.photo.altTaskSuffix', { task: localizedTaskTitle }) - : ''; - const altText = t('galleryPage.photo.alt', { id: p.id, suffix: altSuffix }, `Foto ${p.id}${altSuffix}`); + + + + {loading && ( + + {t('galleryPage.loading', 'Lade…')} + + )} + + {list.map((p: GalleryPhoto) => { + const imageUrl = normalizeImageUrl(p.thumbnail_path || p.file_path); + const createdLabel = p.created_at + ? new Date(p.created_at).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }) + : t('galleryPage.photo.justNow', 'Gerade eben'); + const likeCount = counts[p.id] ?? (p.likes_count || 0); + const localizedTaskTitle = localizeTaskLabel(p.task_title ?? null, locale); + const altSuffix = localizedTaskTitle + ? t('galleryPage.photo.altTaskSuffix', { task: localizedTaskTitle }) + : ''; + const altText = t('galleryPage.photo.alt', { id: p.id, suffix: altSuffix }, `Foto ${p.id}${altSuffix}`); - const openPhoto = () => { - const index = list.findIndex((photo) => photo.id === p.id); - setCurrentPhotoIndex(index >= 0 ? index : null); - }; + const openPhoto = () => { + const index = list.findIndex((photo) => photo.id === p.id); + setCurrentPhotoIndex(index >= 0 ? index : null); + }; - return ( -
{ - if (e.key === 'Enter') { - openPhoto(); - } - }} - className="group relative overflow-hidden border border-white/20 bg-gray-950 text-white shadow-lg focus:outline-none focus-visible:ring-2 focus-visible:ring-pink-400" - style={{ borderRadius: radius }} - > - {altText} { - (e.target as HTMLImageElement).src = 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTAwIiBoZWlnaHQ9IjEwMCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjRjNGNEY2Ii8+PHRleHQgeD0iNTAlIiB5PSI1MCUiIGZvbnQtZmFtaWx5PSJBcmlhbCIgZm9udC1zaXplPSIxNCIgZmlsbD0iIzk5OSIgdGV4dC1hbmNob3I9Im1pZGRsZSIgZHk9Ii4zZW0iPk5vIEltYWdlPC90ZXh0Pjwvc3ZnPg=='; + return ( + { + if (e.key === 'Enter') { + openPhoto(); + } }} - loading="lazy" - /> -
-
- {localizedTaskTitle &&

{localizedTaskTitle}

} -
- {createdLabel} - {p.uploader_name || t('galleryPage.photo.anonymous', 'Gast')} + className="group relative overflow-hidden border border-white/20 bg-gray-950 text-white shadow-lg focus:outline-none focus-visible:ring-2 focus-visible:ring-pink-400" + style={{ borderRadius: radius }} + {...fadeScaleMotion} + > + {altText} { + (e.target as HTMLImageElement).src = 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTAwIiBoZWlnaHQ9IjEwMCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjRjNGNEY2Ii8+PHRleHQgeD0iNTAlIiB5PSI1MCUiIGZvbnQtZmFtaWx5PSJBcmlhbCIgZm9udC1zaXplPSIxNCIgZmlsbD0iIzk5OSIgdGV4dC1hbmNob3I9Im1pZGRsZSIgZHk9Ii4zZW0iPk5vIEltYWdlPC90ZXh0Pjwvc3ZnPg=='; + }} + loading="lazy" + /> +
+
+ {localizedTaskTitle &&

{localizedTaskTitle}

} +
+ {createdLabel} + {p.uploader_name || t('galleryPage.photo.anonymous', 'Gast')} +
+
+ + +
+ + ); + })} + {list.length === 0 && Array.from({ length: 6 }).map((_, idx) => ( + +
+
+ +
-
- - -
-
- ); - })} - {list.length === 0 && Array.from({ length: 6 }).map((_, idx) => ( -
-
-
- -
-
-
-
- ))} -
+
+ + ))} + + {currentPhotoIndex !== null && list.length > 0 && ( (); @@ -40,69 +41,76 @@ export default function HelpArticlePage() { return ( -
- -
- - {state === 'loading' && ( -
- - {t('common.actions.loading')} -
- )} - - {state === 'error' && ( -
-

{t('help.article.unavailable')}

-
- )} - {state === 'ready' && article && ( -
-
- {article.updated_at && ( -
{t('help.article.updated', { date: formatDate(article.updated_at, locale) })}
- )} -
-
-
-
- {article.related && article.related.length > 0 && ( -
-

{t('help.article.relatedTitle')}

-
- {article.related.map((rel) => ( - - ))} -
-
- )} -
- )} + )} + + {state === 'ready' && article && ( +
+
+ {article.updated_at && ( +
{t('help.article.updated', { date: formatDate(article.updated_at, locale) })}
+ )} + +
+
+
+
+ {article.related && article.related.length > 0 && ( +
+

{t('help.article.relatedTitle')}

+
+ {article.related.map((rel) => ( + + ))} +
+
+ )} +
+ )} +
); } diff --git a/resources/js/guest/pages/HelpCenterPage.tsx b/resources/js/guest/pages/HelpCenterPage.tsx index 1430676..d5daf00 100644 --- a/resources/js/guest/pages/HelpCenterPage.tsx +++ b/resources/js/guest/pages/HelpCenterPage.tsx @@ -8,6 +8,7 @@ import { Page } from './_util'; import { useLocale } from '../i18n/LocaleContext'; import { useTranslation } from '../i18n/useTranslation'; import { getHelpArticles, type HelpArticleSummary } from '../services/helpApi'; +import PullToRefresh from '../components/PullToRefresh'; export default function HelpCenterPage() { const params = useParams<{ token?: string }>(); @@ -48,94 +49,101 @@ export default function HelpCenterPage() { return ( -

{t('help.center.subtitle')}

+ loadArticles(true)} + pullLabel={t('common.pullToRefresh')} + releaseLabel={t('common.releaseToRefresh')} + refreshingLabel={t('common.refreshing')} + > +

{t('help.center.subtitle')}

-
-
- setQuery(event.target.value)} - className="flex-1" - aria-label={t('help.center.searchPlaceholder')} - /> - -
- {servedFromCache && ( -
- - {t('help.center.offlineBadge')} - - {t('help.center.offlineDescription')} -
- )} -
- -
-

{t('help.center.listTitle')}

- {state === 'loading' && ( -
- - {t('common.actions.loading')} -
- )} - {state === 'error' && ( -
-

{t('help.center.error')}

+
+
+ setQuery(event.target.value)} + className="flex-1" + aria-label={t('help.center.searchPlaceholder')} + />
- )} - {state === 'ready' && filteredArticles.length === 0 && ( -
- {t('help.center.empty')} -
- )} - {state === 'ready' && filteredArticles.length > 0 && ( -
- {filteredArticles.map((article) => ( - + + {t('help.center.offlineBadge')} + + {t('help.center.offlineDescription')} +
+ )} +
+ +
+

{t('help.center.listTitle')}

+ {state === 'loading' && ( +
+ + {t('common.actions.loading')} +
+ )} + {state === 'error' && ( +
+

{t('help.center.error')}

+ +
+ )} + {state === 'ready' && filteredArticles.length === 0 && ( +
+ {t('help.center.empty')} +
+ )} + {state === 'ready' && filteredArticles.length > 0 && ( +
+ {filteredArticles.map((article) => ( + +
+
+

{article.title}

+

{article.summary}

+
+ + {article.updated_at ? formatDate(article.updated_at, locale) : ''} +
- - {article.updated_at ? formatDate(article.updated_at, locale) : ''} - -
- - ))} -
- )} -
+ + ))} +
+ )} + + ); } diff --git a/resources/js/guest/pages/HomePage.tsx b/resources/js/guest/pages/HomePage.tsx index bf1d11d..8beca27 100644 --- a/resources/js/guest/pages/HomePage.tsx +++ b/resources/js/guest/pages/HomePage.tsx @@ -7,12 +7,13 @@ import { Avatar, AvatarFallback } from '@/components/ui/avatar'; import { Progress } from '@/components/ui/progress'; import { Skeleton } from '@/components/ui/skeleton'; import { Separator } from '@/components/ui/separator'; +import { AnimatePresence, motion } from 'framer-motion'; import EmotionPicker from '../components/EmotionPicker'; import GalleryPreview from '../components/GalleryPreview'; import { useGuestIdentity } from '../context/GuestIdentityContext'; import { useEventData } from '../hooks/useEventData'; import { useGuestTaskProgress } from '../hooks/useGuestTaskProgress'; -import { Camera, ChevronDown, Sparkles, UploadCloud, X, RefreshCw, Timer } from 'lucide-react'; +import { ArrowLeft, ArrowRight, Camera, ChevronDown, Sparkles, UploadCloud, X, RefreshCw, Timer } from 'lucide-react'; import { useTranslation, type TranslateFn } from '../i18n/useTranslation'; import { useEventBranding } from '../context/EventBrandingContext'; import type { EventBranding } from '../types/event-branding'; @@ -25,6 +26,7 @@ import { getDeviceId } from '../lib/device'; import { useDirectUpload } from '../hooks/useDirectUpload'; import { useNavigate } from 'react-router-dom'; import { isTaskModeEnabled } from '../lib/engagement'; +import { FADE_SCALE, FADE_UP, STAGGER_FAST, getMotionContainerProps, getMotionItemProps, prefersReducedMotion } from '../lib/motion'; export default function HomePage() { const { token } = useParams<{ token: string }>(); @@ -74,6 +76,10 @@ export default function HomePage() { const uploadsRequireApproval = (event?.guest_upload_visibility as 'immediate' | 'review' | undefined) !== 'immediate'; const tasksEnabled = isTaskModeEnabled(event); + const motionEnabled = !prefersReducedMotion(); + const containerMotion = getMotionContainerProps(motionEnabled, STAGGER_FAST); + const fadeUpMotion = getMotionItemProps(motionEnabled, FADE_UP); + const fadeScaleMotion = getMotionItemProps(motionEnabled, FADE_SCALE); const [missionDeck, setMissionDeck] = React.useState([]); const [missionPool, setMissionPool] = React.useState([]); @@ -288,15 +294,19 @@ export default function HomePage() { if (!tasksEnabled) { return ( -
-
+ +

{t('home.welcomeLine').replace('{name}', displayName)}

{introMessage &&

{introMessage}

} -
+ -
+ -
+ - + + + - -
+ + + + ); } return ( -
-
+ +

{t('home.welcomeLine').replace('{name}', displayName)}

@@ -325,22 +343,24 @@ export default function HomePage() { {introMessage}

)} -
+ {heroVisible && ( - + + + )} -
+
{ poolIndexRef.current = idx % Math.max(1, total); if (sliderStateKey && typeof window !== 'undefined') { @@ -371,12 +392,16 @@ export default function HomePage() { />
-
+ - + + + - -
+ + + + ); } @@ -462,6 +487,7 @@ export function MissionActionCard({ initialIndex, onIndexChange, swiperRef, + swipeHintLabel, }: { token: string; mission: MissionPreview | null; @@ -471,6 +497,7 @@ export function MissionActionCard({ initialIndex: number; onIndexChange: (index: number, total: number) => void; swiperRef: React.MutableRefObject; + swipeHintLabel?: string; }) { const { branding } = useEventBranding(); const radius = branding.buttons?.radius ?? 12; @@ -486,6 +513,64 @@ export function MissionActionCard({ const lastSlideIndexRef = React.useRef(initialIndex); const titleRefs = React.useRef(new Map()); const [expandableTitles, setExpandableTitles] = React.useState>({}); + const [showSwipeHint, setShowSwipeHint] = React.useState(false); + const hintTimeoutRef = React.useRef(null); + const hintStorageKey = token ? `guestMissionSwipeHintSeen_${token}` : 'guestMissionSwipeHintSeen'; + const motionEnabled = !prefersReducedMotion(); + const shouldShowHint = motionEnabled && cards.length > 1 && Boolean(swipeHintLabel); + + const dismissSwipeHint = React.useCallback((persist = true) => { + if (!showSwipeHint) { + return; + } + setShowSwipeHint(false); + if (hintTimeoutRef.current) { + window.clearTimeout(hintTimeoutRef.current); + hintTimeoutRef.current = null; + } + if (persist && typeof window !== 'undefined') { + try { + window.sessionStorage.setItem(hintStorageKey, '1'); + } catch { + // ignore storage exceptions + } + } + }, [hintStorageKey, showSwipeHint]); + + React.useEffect(() => { + if (!shouldShowHint) { + setShowSwipeHint(false); + return; + } + if (typeof window === 'undefined') { + return; + } + try { + if (window.sessionStorage.getItem(hintStorageKey)) { + setShowSwipeHint(false); + return; + } + } catch { + // ignore storage exceptions + } + + setShowSwipeHint(true); + hintTimeoutRef.current = window.setTimeout(() => { + setShowSwipeHint(false); + try { + window.sessionStorage.setItem(hintStorageKey, '1'); + } catch { + // ignore storage exceptions + } + }, 3200); + + return () => { + if (hintTimeoutRef.current) { + window.clearTimeout(hintTimeoutRef.current); + hintTimeoutRef.current = null; + } + }; + }, [hintStorageKey, shouldShowHint]); const measureTitleOverflow = React.useCallback(() => { setExpandableTitles((prev) => { @@ -686,7 +771,10 @@ export function MissionActionCard({ type="button" variant="secondary" className="w-full border border-slate-200 bg-white/80 text-slate-800 shadow-sm backdrop-blur" - onClick={onAdvance} + onClick={() => { + dismissSwipeHint(); + onAdvance(); + }} disabled={loading} style={{ borderRadius: `${radius}px`, @@ -708,6 +796,33 @@ export function MissionActionCard({
+ + {showSwipeHint ? ( + +
+ + + + {swipeHintLabel} + + + +
+
+ ) : null} +
(); @@ -16,6 +18,10 @@ export default function ProfileSetupPage() { const [name, setName] = useState(storedName); const [submitting, setSubmitting] = useState(false); const { t } = useTranslation(); + const motionEnabled = !prefersReducedMotion(); + const containerMotion = getMotionContainerProps(motionEnabled, STAGGER_FAST); + const fadeUpMotion = getMotionItemProps(motionEnabled, FADE_UP); + const fadeScaleMotion = getMotionItemProps(motionEnabled, FADE_SCALE); useEffect(() => { if (!token) { @@ -67,9 +73,10 @@ export default function ProfileSetupPage() { } return ( -
-
- + + + + {event.name} @@ -97,8 +104,9 @@ export default function ProfileSetupPage() { {submitting ? t('profileSetup.form.submitting') : t('profileSetup.form.submit')} - -
-
+ + + + ); } diff --git a/resources/js/guest/pages/TaskPickerPage.tsx b/resources/js/guest/pages/TaskPickerPage.tsx index 2e973c1..9455ba8 100644 --- a/resources/js/guest/pages/TaskPickerPage.tsx +++ b/resources/js/guest/pages/TaskPickerPage.tsx @@ -10,6 +10,7 @@ import { cn } from '@/lib/utils'; import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@/components/ui/dialog'; import { useEventBranding } from '../context/EventBrandingContext'; import { useTranslation, type TranslateFn } from '../i18n/useTranslation'; +import { motion } from 'framer-motion'; import { getEmotionIcon, getEmotionTheme, @@ -17,6 +18,8 @@ import { type EmotionTheme, } from '../lib/emotionTheme'; import { getDeviceId } from '../lib/device'; +import { FADE_SCALE, FADE_UP, STAGGER_FAST, getMotionContainerProps, getMotionItemProps, prefersReducedMotion } from '../lib/motion'; +import PullToRefresh from '../components/PullToRefresh'; interface Task { id: number; @@ -243,6 +246,13 @@ export default function TaskPickerPage() { fetchTasks(); }; + const handleRefresh = React.useCallback(async () => { + tasksCacheRef.current.clear(); + await fetchTasks(); + setPhotoPool([]); + setPhotoPoolError(null); + }, [fetchTasks]); + const handlePhotoPreview = React.useCallback( (photoId: number) => { if (!eventKey) return; @@ -354,6 +364,10 @@ export default function TaskPickerPage() { [emotionOptions, recentEmotionSlug] ); const toggleValue = selectedEmotion === 'all' ? 'none' : 'recent'; + const motionEnabled = !prefersReducedMotion(); + const containerMotion = getMotionContainerProps(motionEnabled, STAGGER_FAST); + const fadeUpMotion = getMotionItemProps(motionEnabled, FADE_UP); + const fadeScaleMotion = getMotionItemProps(motionEnabled, FADE_SCALE); const handleToggleChange = React.useCallback( (value: string) => { @@ -379,211 +393,225 @@ export default function TaskPickerPage() { return ( <> -
-
-
-

{t('tasks.page.eyebrow')}

-

{t('tasks.page.title')}

-

{t('tasks.page.subtitle')}

-
- {emotionOptions.length > 0 && ( -
- - - 🎲 - {t('tasks.page.filters.none')} - - - {getEmotionIcon(recentEmotionOption)} - {recentEmotionOption?.name ?? t('tasks.page.filters.recentFallback')} - - - 🗂️ - {t('tasks.page.filters.showAll')} - - + + + +
+

{t('tasks.page.eyebrow')}

+

{t('tasks.page.title')}

+

{t('tasks.page.subtitle')}

- )} -
- - {loading && ( -
- - -
- )} - - {error && !loading && ( - - - {error} - - - - )} - - {emptyState && ( - - )} - - {!emptyState && currentTask && ( -
-
-
-
- - - {heroEmotionIcon} {currentTask.emotion?.name ?? 'Neue Mission'} - - - - {currentTask.duration} Min - -
- -
-

{currentTask.title}

-

{currentTask.description}

-
- - {!hasSwiped && ( -

- {t('tasks.page.swipeHint')} -

- )} - - {currentTask.instructions && ( -
- {currentTask.instructions} -
- )} - -
- {isCompleted(currentTask.id) && ( - - - {t('tasks.page.completedLabel')} - - )} -
- -
- - -
+ 🎲 + {t('tasks.page.filters.none')} + + + {getEmotionIcon(recentEmotionOption)} + {recentEmotionOption?.name ?? t('tasks.page.filters.recentFallback')} + + + 🗂️ + {t('tasks.page.filters.showAll')} + + + + )} + - {(photoPoolLoading || photoPoolError || similarPhotos.length > 0) && ( -
-
- {t('tasks.page.inspirationTitle')} - {photoPoolLoading && {t('tasks.page.inspirationLoading')}} + {loading && ( + + + + + )} + + {error && !loading && ( + + + + {error} + + + + + )} + + {emptyState && ( + + + + )} + + {!emptyState && currentTask && ( + + +
+
+ + + {heroEmotionIcon} {currentTask.emotion?.name ?? 'Neue Mission'} + + + + {currentTask.duration} Min + +
+ +
+

{currentTask.title}

+

{currentTask.description}

+
+ + {!hasSwiped && ( +

+ {t('tasks.page.swipeHint')} +

+ )} + + {currentTask.instructions && ( +
+ {currentTask.instructions}
- {photoPoolError && similarPhotos.length === 0 ? ( -

{photoPoolError}

- ) : similarPhotos.length > 0 ? ( -
- {similarPhotos.map((photo) => ( - - ))} - -
- ) : ( - + )} + +
+ {isCompleted(currentTask.id) && ( + + + {t('tasks.page.completedLabel')} + )}
- )} -
-
- {alternativeTasks.length > 0 && ( -
-
-
-

{t('tasks.page.suggestionsEyebrow')}

-

{t('tasks.page.suggestionsTitle')}

+
+ +
- -
-
- {alternativeTasks.map((task) => ( - - ))} -
-
- )} -
- )} - {!loading && !tasks.length && !error && ( - - {t('tasks.page.noTasksAlert')} - - )} -
+ {(photoPoolLoading || photoPoolError || similarPhotos.length > 0) && ( +
+
+ {t('tasks.page.inspirationTitle')} + {photoPoolLoading && {t('tasks.page.inspirationLoading')}} +
+ {photoPoolError && similarPhotos.length === 0 ? ( +

{photoPoolError}

+ ) : similarPhotos.length > 0 ? ( +
+ {similarPhotos.map((photo) => ( + + ))} + +
+ ) : ( + + )} +
+ )} +
+ + + {alternativeTasks.length > 0 && ( + +
+
+

{t('tasks.page.suggestionsEyebrow')}

+

{t('tasks.page.suggestionsTitle')}

+
+ +
+
+ {alternativeTasks.map((task) => ( + + ))} +
+
+ )} + + )} + + {!loading && !tasks.length && !error && ( + + + {t('tasks.page.noTasksAlert')} + + + )} + + diff --git a/resources/js/guest/pages/UploadPage.tsx b/resources/js/guest/pages/UploadPage.tsx index 47ba4cd..a00d61d 100644 --- a/resources/js/guest/pages/UploadPage.tsx +++ b/resources/js/guest/pages/UploadPage.tsx @@ -4,6 +4,7 @@ import { useNavigate, useParams, useSearchParams } from 'react-router-dom'; import { Button } from '@/components/ui/button'; import { Badge } from '@/components/ui/badge'; import { Alert, AlertDescription } from '@/components/ui/alert'; +import { motion } from 'framer-motion'; import { Dialog, DialogContent, @@ -38,6 +39,7 @@ import { resolveUploadErrorDialog, type UploadErrorDialog } from '../lib/uploadE import { useEventStats } from '../context/EventStatsContext'; import { useEventBranding } from '../context/EventBrandingContext'; import { compressPhoto, formatBytes } from '../lib/image'; +import { FADE_SCALE, FADE_UP, prefersReducedMotion } from '../lib/motion'; import { useGuestIdentity } from '../context/GuestIdentityContext'; import { useEventData } from '../hooks/useEventData'; import { isTaskModeEnabled } from '../lib/engagement'; @@ -147,6 +149,9 @@ export default function UploadPage() { const uploadsRequireApproval = (event?.guest_upload_visibility as 'immediate' | 'review' | undefined) !== 'immediate'; const demoReadOnly = Boolean(event?.demo_read_only); + const motionEnabled = !prefersReducedMotion(); + const overlayMotion = motionEnabled ? { initial: 'hidden', animate: 'show', variants: FADE_SCALE } : {}; + const fadeUpMotion = motionEnabled ? { initial: 'hidden', animate: 'show', variants: FADE_UP } : {}; const taskIdParam = searchParams.get('task'); const emotionSlug = searchParams.get('emotion') || ''; @@ -958,10 +963,11 @@ const [canUpload, setCanUpload] = useState(true); }, [beginCapture, isCameraActive, startCamera]); const taskFloatingCard = showTaskOverlay && task ? ( -
) : null} - + ) : null; const heroOverlay = !task && showHeroOverlay && mode !== 'uploading' ? ( -
+

Bereit für dein Foto?

@@ -1015,7 +1021,7 @@ const [canUpload, setCanUpload] = useState(true); Live
-
+
) : null; const dialogToneIconClass: Record, string> = { @@ -1079,7 +1085,10 @@ const renderWithDialog = (content: ReactNode, wrapperClassName = 'space-y-6 pb-[ const renderPrimer = () => ( showPrimer && ( -
+
@@ -1093,7 +1102,7 @@ const renderWithDialog = (content: ReactNode, wrapperClassName = 'space-y-6 pb-[ {t('upload.primer.dismiss')}
-
+
) ); @@ -1133,9 +1142,10 @@ const renderWithDialog = (content: ReactNode, wrapperClassName = 'space-y-6 pb-[ : null; return ( -
@@ -1179,7 +1189,7 @@ const renderWithDialog = (content: ReactNode, wrapperClassName = 'space-y-6 pb-[ {t('upload.galleryButton')}
-
+ ); }; diff --git a/resources/js/guest/pages/_util.tsx b/resources/js/guest/pages/_util.tsx index ca3bcd1..e205890 100644 --- a/resources/js/guest/pages/_util.tsx +++ b/resources/js/guest/pages/_util.tsx @@ -1,11 +1,18 @@ import React from 'react'; +import { motion } from 'framer-motion'; +import { FADE_UP, STAGGER_FAST, getMotionContainerProps, getMotionItemProps, prefersReducedMotion } from '../lib/motion'; export function Page({ title, children }: { title: string; children?: React.ReactNode }) { + const motionEnabled = !prefersReducedMotion(); + const containerProps = getMotionContainerProps(motionEnabled, STAGGER_FAST); + const itemProps = getMotionItemProps(motionEnabled, FADE_UP); + return ( -
-

{title}

-
{children}
-
+ + + {title} + + {children} + ); } - diff --git a/resources/js/guest/polling/usePollGalleryDelta.ts b/resources/js/guest/polling/usePollGalleryDelta.ts index 4fb6f37..3d40783 100644 --- a/resources/js/guest/polling/usePollGalleryDelta.ts +++ b/resources/js/guest/polling/usePollGalleryDelta.ts @@ -150,6 +150,18 @@ export function usePollGalleryDelta(token: string, locale: LocaleCode) { }; }, [token, visible, locale, fetchDelta]); + const refreshNow = useCallback(async () => { + if (!token) { + return; + } + setLoading(true); + latestAt.current = null; + etagRef.current = null; + setNewCount(0); + setPhotos([]); + await fetchDelta(); + }, [fetchDelta, token]); + function acknowledgeNew() { setNewCount(0); } - return { loading, photos, newCount, acknowledgeNew }; + return { loading, photos, newCount, acknowledgeNew, refreshNow }; } diff --git a/resources/js/guest/router.tsx b/resources/js/guest/router.tsx index 1cb36eb..5f7255a 100644 --- a/resources/js/guest/router.tsx +++ b/resources/js/guest/router.tsx @@ -1,8 +1,9 @@ import React from 'react'; import { Button } from '@/components/ui/button'; -import { createBrowserRouter, Outlet, useParams, Link, Navigate } from 'react-router-dom'; +import { createBrowserRouter, useParams, Link, Navigate } from 'react-router-dom'; import Header from './components/Header'; import BottomNav from './components/BottomNav'; +import RouteTransition from './components/RouteTransition'; import { useEventData } from './hooks/useEventData'; import { AlertTriangle, Loader2 } from 'lucide-react'; import { EventStatsProvider } from './context/EventStatsContext'; @@ -44,7 +45,7 @@ function HomeLayout() {
- +
@@ -123,7 +124,7 @@ function EventBoundary({ token }: { token: string }) {
- +
@@ -164,7 +165,7 @@ function SetupLayout() {
- +
@@ -325,7 +326,7 @@ function SimpleLayout({ title, children }: { title: string; children: React.Reac
- {children} + {children}
diff --git a/resources/js/types/vite-env.d.ts b/resources/js/types/vite-env.d.ts index 3368cf7..0c13b04 100644 --- a/resources/js/types/vite-env.d.ts +++ b/resources/js/types/vite-env.d.ts @@ -1,4 +1,5 @@ /// +/// interface ImportMetaEnv { readonly VITE_ENABLE_TENANT_SWITCHER?: string; diff --git a/resources/views/guest.blade.php b/resources/views/guest.blade.php index abb21b4..f48581e 100644 --- a/resources/views/guest.blade.php +++ b/resources/views/guest.blade.php @@ -6,6 +6,9 @@ {{ config('app.name', 'Fotospiel') }} + + + @viteReactRefresh @vite(['resources/css/app.css', 'resources/js/guest/main.tsx']) @php diff --git a/vite.config.ts b/vite.config.ts index 1b8cf89..17c9521 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -6,6 +6,7 @@ import { defineConfig, type PluginOption } from 'vite'; import path from 'path'; import { tamaguiPlugin } from '@tamagui/vite-plugin'; import { sentryVitePlugin } from '@sentry/vite-plugin'; +import { VitePWA } from 'vite-plugin-pwa'; const devServerHost = process.env.VITE_DEV_SERVER_HOST ?? 'fotospiel-app.test'; const devServerPort = Number.parseInt(process.env.VITE_DEV_SERVER_PORT ?? '5173', 10); @@ -36,6 +37,63 @@ const plugins: PluginOption[] = [ wayfinder({ formVariants: true, }), + VitePWA({ + strategies: 'injectManifest', + srcDir: 'resources/js/guest', + filename: 'guest-sw.ts', + manifestFilename: 'guest.webmanifest', + outDir: 'public', + injectRegister: null, + registerType: 'prompt', + includeAssets: [ + 'favicon.ico', + 'favicon.svg', + 'apple-touch-icon.png', + 'logo-transparent-md.png', + 'logo-transparent-lg.png', + ], + injectManifest: { + globDirectory: 'public/build', + globPatterns: ['**/*.{js,css,woff,woff2,svg,png,webp,ico,txt}'], + }, + manifest: { + name: 'Fotospiel', + short_name: 'Fotospiel', + id: '/event', + start_url: '/event', + scope: '/', + display: 'standalone', + lang: 'de-DE', + description: 'Offline-fähige Event-Galerie für Gäste – Fotos aufnehmen, Aufgaben lösen und teilen.', + background_color: '#0f172a', + theme_color: '#ec4899', + orientation: 'portrait', + categories: ['photo-video', 'social'], + icons: [ + { + src: '/favicon.svg', + sizes: 'any', + type: 'image/svg+xml', + purpose: 'any', + }, + { + src: '/apple-touch-icon.png', + sizes: '166x169', + type: 'image/png', + purpose: 'any', + }, + { + src: '/logo-transparent-lg.png', + sizes: '698x684', + type: 'image/png', + purpose: 'any', + }, + ], + }, + devOptions: { + enabled: false, + }, + }), tamaguiPlugin({ config: './tamagui.config.ts', components: ['@tamagui/core', '@tamagui/stacks', '@tamagui/text', '@tamagui/button'],