diff --git a/package-lock.json b/package-lock.json index 4f43a91..ba40beb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4994,13 +4994,14 @@ } }, "node_modules/@vitest/browser-playwright/node_modules/@esbuild/aix-ppc64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz", - "integrity": "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.7.tgz", + "integrity": "sha512-EKX3Qwmhz1eMdEJokhALr0YiD0lhQNwDqkPYyPhiSwKrh7/4KRjQc04sZ8db+5DVVnZ1LmbNDI1uAMPEUBnQPg==", "cpu": [ "ppc64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "aix" @@ -5011,13 +5012,14 @@ } }, "node_modules/@vitest/browser-playwright/node_modules/@esbuild/android-arm": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.12.tgz", - "integrity": "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.7.tgz", + "integrity": "sha512-jbPXvB4Yj2yBV7HUfE2KHe4GJX51QplCN1pGbYjvsyCZbQmies29EoJbkEc+vYuU5o45AfQn37vZlyXy4YJ8RQ==", "cpu": [ "arm" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "android" @@ -5028,13 +5030,14 @@ } }, "node_modules/@vitest/browser-playwright/node_modules/@esbuild/android-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.12.tgz", - "integrity": "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.7.tgz", + "integrity": "sha512-62dPZHpIXzvChfvfLJow3q5dDtiNMkwiRzPylSCfriLvZeq0a1bWChrGx/BbUbPwOrsWKMn8idSllklzBy+dgQ==", "cpu": [ "arm64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "android" @@ -5045,13 +5048,14 @@ } }, "node_modules/@vitest/browser-playwright/node_modules/@esbuild/android-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.12.tgz", - "integrity": "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.7.tgz", + "integrity": "sha512-x5VpMODneVDb70PYV2VQOmIUUiBtY3D3mPBG8NxVk5CogneYhkR7MmM3yR/uMdITLrC1ml/NV1rj4bMJuy9MCg==", "cpu": [ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "android" @@ -5062,13 +5066,14 @@ } }, "node_modules/@vitest/browser-playwright/node_modules/@esbuild/darwin-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.12.tgz", - "integrity": "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.7.tgz", + "integrity": "sha512-5lckdqeuBPlKUwvoCXIgI2D9/ABmPq3Rdp7IfL70393YgaASt7tbju3Ac+ePVi3KDH6N2RqePfHnXkaDtY9fkw==", "cpu": [ "arm64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "darwin" @@ -5079,13 +5084,14 @@ } }, "node_modules/@vitest/browser-playwright/node_modules/@esbuild/darwin-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.12.tgz", - "integrity": "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.7.tgz", + "integrity": "sha512-rYnXrKcXuT7Z+WL5K980jVFdvVKhCHhUwid+dDYQpH+qu+TefcomiMAJpIiC2EM3Rjtq0sO3StMV/+3w3MyyqQ==", "cpu": [ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "darwin" @@ -5096,13 +5102,14 @@ } }, "node_modules/@vitest/browser-playwright/node_modules/@esbuild/freebsd-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.12.tgz", - "integrity": "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.7.tgz", + "integrity": "sha512-B48PqeCsEgOtzME2GbNM2roU29AMTuOIN91dsMO30t+Ydis3z/3Ngoj5hhnsOSSwNzS+6JppqWsuhTp6E82l2w==", "cpu": [ "arm64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "freebsd" @@ -5113,13 +5120,14 @@ } }, "node_modules/@vitest/browser-playwright/node_modules/@esbuild/freebsd-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.12.tgz", - "integrity": "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.7.tgz", + "integrity": "sha512-jOBDK5XEjA4m5IJK3bpAQF9/Lelu/Z9ZcdhTRLf4cajlB+8VEhFFRjWgfy3M1O4rO2GQ/b2dLwCUGpiF/eATNQ==", "cpu": [ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "freebsd" @@ -5130,13 +5138,14 @@ } }, "node_modules/@vitest/browser-playwright/node_modules/@esbuild/linux-arm": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.12.tgz", - "integrity": "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.7.tgz", + "integrity": "sha512-RkT/YXYBTSULo3+af8Ib0ykH8u2MBh57o7q/DAs3lTJlyVQkgQvlrPTnjIzzRPQyavxtPtfg0EopvDyIt0j1rA==", "cpu": [ "arm" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" @@ -5147,13 +5156,14 @@ } }, "node_modules/@vitest/browser-playwright/node_modules/@esbuild/linux-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.12.tgz", - "integrity": "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.7.tgz", + "integrity": "sha512-RZPHBoxXuNnPQO9rvjh5jdkRmVizktkT7TCDkDmQ0W2SwHInKCAV95GRuvdSvA7w4VMwfCjUiPwDi0ZO6Nfe9A==", "cpu": [ "arm64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" @@ -5164,13 +5174,14 @@ } }, "node_modules/@vitest/browser-playwright/node_modules/@esbuild/linux-ia32": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.12.tgz", - "integrity": "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.7.tgz", + "integrity": "sha512-GA48aKNkyQDbd3KtkplYWT102C5sn/EZTY4XROkxONgruHPU72l+gW+FfF8tf2cFjeHaRbWpOYa/uRBz/Xq1Pg==", "cpu": [ "ia32" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" @@ -5181,13 +5192,14 @@ } }, "node_modules/@vitest/browser-playwright/node_modules/@esbuild/linux-loong64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.12.tgz", - "integrity": "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.7.tgz", + "integrity": "sha512-a4POruNM2oWsD4WKvBSEKGIiWQF8fZOAsycHOt6JBpZ+JN2n2JH9WAv56SOyu9X5IqAjqSIPTaJkqN8F7XOQ5Q==", "cpu": [ "loong64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" @@ -5198,13 +5210,14 @@ } }, "node_modules/@vitest/browser-playwright/node_modules/@esbuild/linux-mips64el": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.12.tgz", - "integrity": "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.7.tgz", + "integrity": "sha512-KabT5I6StirGfIz0FMgl1I+R1H73Gp0ofL9A3nG3i/cYFJzKHhouBV5VWK1CSgKvVaG4q1RNpCTR2LuTVB3fIw==", "cpu": [ "mips64el" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" @@ -5215,13 +5228,14 @@ } }, "node_modules/@vitest/browser-playwright/node_modules/@esbuild/linux-ppc64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.12.tgz", - "integrity": "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.7.tgz", + "integrity": "sha512-gRsL4x6wsGHGRqhtI+ifpN/vpOFTQtnbsupUF5R5YTAg+y/lKelYR1hXbnBdzDjGbMYjVJLJTd2OFmMewAgwlQ==", "cpu": [ "ppc64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" @@ -5232,13 +5246,14 @@ } }, "node_modules/@vitest/browser-playwright/node_modules/@esbuild/linux-riscv64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.12.tgz", - "integrity": "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.7.tgz", + "integrity": "sha512-hL25LbxO1QOngGzu2U5xeXtxXcW+/GvMN3ejANqXkxZ/opySAZMrc+9LY/WyjAan41unrR3YrmtTsUpwT66InQ==", "cpu": [ "riscv64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" @@ -5249,13 +5264,14 @@ } }, "node_modules/@vitest/browser-playwright/node_modules/@esbuild/linux-s390x": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.12.tgz", - "integrity": "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.7.tgz", + "integrity": "sha512-2k8go8Ycu1Kb46vEelhu1vqEP+UeRVj2zY1pSuPdgvbd5ykAw82Lrro28vXUrRmzEsUV0NzCf54yARIK8r0fdw==", "cpu": [ "s390x" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" @@ -5266,13 +5282,14 @@ } }, "node_modules/@vitest/browser-playwright/node_modules/@esbuild/linux-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.12.tgz", - "integrity": "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.7.tgz", + "integrity": "sha512-hzznmADPt+OmsYzw1EE33ccA+HPdIqiCRq7cQeL1Jlq2gb1+OyWBkMCrYGBJ+sxVzve2ZJEVeePbLM2iEIZSxA==", "cpu": [ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" @@ -5282,14 +5299,15 @@ "node": ">=18" } }, - "node_modules/@vitest/browser-playwright/node_modules/@esbuild/netbsd-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.12.tgz", - "integrity": "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==", + "node_modules/@vitest/browser-playwright/node_modules/@esbuild/netbsd-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.7.tgz", + "integrity": "sha512-b6pqtrQdigZBwZxAn1UpazEisvwaIDvdbMbmrly7cDTMFnw/+3lVxxCTGOrkPVnsYIosJJXAsILG9XcQS+Yu6w==", "cpu": [ - "x64" + "arm64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "netbsd" @@ -5299,14 +5317,33 @@ "node": ">=18" } }, - "node_modules/@vitest/browser-playwright/node_modules/@esbuild/openbsd-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.12.tgz", - "integrity": "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==", + "node_modules/@vitest/browser-playwright/node_modules/@esbuild/netbsd-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.7.tgz", + "integrity": "sha512-OfatkLojr6U+WN5EDYuoQhtM+1xco+/6FSzJJnuWiUw5eVcicbyK3dq5EeV/QHT1uy6GoDhGbFpprUiHUYggrw==", "cpu": [ "x64" ], "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@vitest/browser-playwright/node_modules/@esbuild/openbsd-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.7.tgz", + "integrity": "sha512-AFuojMQTxAz75Fo8idVcqoQWEHIXFRbOc1TrVcFSgCZtQfSdc1RXgB3tjOn/krRHENUB4j00bfGjyl2mJrU37A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", "optional": true, "os": [ "openbsd" @@ -5316,14 +5353,51 @@ "node": ">=18" } }, - "node_modules/@vitest/browser-playwright/node_modules/@esbuild/sunos-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.12.tgz", - "integrity": "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==", + "node_modules/@vitest/browser-playwright/node_modules/@esbuild/openbsd-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.7.tgz", + "integrity": "sha512-+A1NJmfM8WNDv5CLVQYJ5PshuRm/4cI6WMZRg1by1GwPIQPCTs1GLEUHwiiQGT5zDdyLiRM/l1G0Pv54gvtKIg==", "cpu": [ "x64" ], "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@vitest/browser-playwright/node_modules/@esbuild/openharmony-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.7.tgz", + "integrity": "sha512-+KrvYb/C8zA9CU/g0sR6w2RBw7IGc5J2BPnc3dYc5VJxHCSF1yNMxTV5LQ7GuKteQXZtspjFbiuW5/dOj7H4Yw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@vitest/browser-playwright/node_modules/@esbuild/sunos-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.7.tgz", + "integrity": "sha512-ikktIhFBzQNt/QDyOL580ti9+5mL/YZeUPKU2ivGtGjdTYoqz6jObj6nOMfhASpS4GU4Q/Clh1QtxWAvcYKamA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", "optional": true, "os": [ "sunos" @@ -5334,13 +5408,14 @@ } }, "node_modules/@vitest/browser-playwright/node_modules/@esbuild/win32-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.12.tgz", - "integrity": "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.7.tgz", + "integrity": "sha512-7yRhbHvPqSpRUV7Q20VuDwbjW5kIMwTHpptuUzV+AA46kiPze5Z7qgt6CLCK3pWFrHeNfDd1VKgyP4O+ng17CA==", "cpu": [ "arm64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "win32" @@ -5351,13 +5426,14 @@ } }, "node_modules/@vitest/browser-playwright/node_modules/@esbuild/win32-ia32": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.12.tgz", - "integrity": "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.7.tgz", + "integrity": "sha512-SmwKXe6VHIyZYbBLJrhOoCJRB/Z1tckzmgTLfFYOfpMAx63BJEaL9ExI8x7v0oAO3Zh6D/Oi1gVxEYr5oUCFhw==", "cpu": [ "ia32" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "win32" @@ -5368,13 +5444,14 @@ } }, "node_modules/@vitest/browser-playwright/node_modules/@esbuild/win32-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.12.tgz", - "integrity": "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.7.tgz", + "integrity": "sha512-56hiAJPhwQ1R4i+21FVF7V8kSD5zZTdHcVuRFMW0hn753vVfQN8xlx4uOPT4xoGH0Z/oVATuR82AiqSTDIpaHg==", "cpu": [ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "win32" @@ -5411,11 +5488,12 @@ } }, "node_modules/@vitest/browser-playwright/node_modules/esbuild": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz", - "integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.7.tgz", + "integrity": "sha512-IxpibTjyVnmrIQo5aqNpCgoACA/dTKLTlhMHihVHhdkxKyPO1uBBthumT0rdHmcsk9uMonIWS0m4FljWzILh3w==", "dev": true, "hasInstallScript": true, + "license": "MIT", "optional": true, "peer": true, "bin": { @@ -5425,32 +5503,32 @@ "node": ">=18" }, "optionalDependencies": { - "@esbuild/aix-ppc64": "0.25.12", - "@esbuild/android-arm": "0.25.12", - "@esbuild/android-arm64": "0.25.12", - "@esbuild/android-x64": "0.25.12", - "@esbuild/darwin-arm64": "0.25.12", - "@esbuild/darwin-x64": "0.25.12", - "@esbuild/freebsd-arm64": "0.25.12", - "@esbuild/freebsd-x64": "0.25.12", - "@esbuild/linux-arm": "0.25.12", - "@esbuild/linux-arm64": "0.25.12", - "@esbuild/linux-ia32": "0.25.12", - "@esbuild/linux-loong64": "0.25.12", - "@esbuild/linux-mips64el": "0.25.12", - "@esbuild/linux-ppc64": "0.25.12", - "@esbuild/linux-riscv64": "0.25.12", - "@esbuild/linux-s390x": "0.25.12", - "@esbuild/linux-x64": "0.25.12", - "@esbuild/netbsd-arm64": "0.25.12", - "@esbuild/netbsd-x64": "0.25.12", - "@esbuild/openbsd-arm64": "0.25.12", - "@esbuild/openbsd-x64": "0.25.12", - "@esbuild/openharmony-arm64": "0.25.12", - "@esbuild/sunos-x64": "0.25.12", - "@esbuild/win32-arm64": "0.25.12", - "@esbuild/win32-ia32": "0.25.12", - "@esbuild/win32-x64": "0.25.12" + "@esbuild/aix-ppc64": "0.27.7", + "@esbuild/android-arm": "0.27.7", + "@esbuild/android-arm64": "0.27.7", + "@esbuild/android-x64": "0.27.7", + "@esbuild/darwin-arm64": "0.27.7", + "@esbuild/darwin-x64": "0.27.7", + "@esbuild/freebsd-arm64": "0.27.7", + "@esbuild/freebsd-x64": "0.27.7", + "@esbuild/linux-arm": "0.27.7", + "@esbuild/linux-arm64": "0.27.7", + "@esbuild/linux-ia32": "0.27.7", + "@esbuild/linux-loong64": "0.27.7", + "@esbuild/linux-mips64el": "0.27.7", + "@esbuild/linux-ppc64": "0.27.7", + "@esbuild/linux-riscv64": "0.27.7", + "@esbuild/linux-s390x": "0.27.7", + "@esbuild/linux-x64": "0.27.7", + "@esbuild/netbsd-arm64": "0.27.7", + "@esbuild/netbsd-x64": "0.27.7", + "@esbuild/openbsd-arm64": "0.27.7", + "@esbuild/openbsd-x64": "0.27.7", + "@esbuild/openharmony-arm64": "0.27.7", + "@esbuild/sunos-x64": "0.27.7", + "@esbuild/win32-arm64": "0.27.7", + "@esbuild/win32-ia32": "0.27.7", + "@esbuild/win32-x64": "0.27.7" } }, "node_modules/@vitest/browser-playwright/node_modules/fdir": { @@ -5458,6 +5536,7 @@ "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", "dev": true, + "license": "MIT", "optional": true, "peer": true, "engines": { @@ -5477,6 +5556,7 @@ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", "dev": true, + "license": "MIT", "optional": true, "peer": true, "engines": { @@ -5487,25 +5567,26 @@ } }, "node_modules/@vitest/browser-playwright/node_modules/vite": { - "version": "6.4.2", - "resolved": "https://registry.npmjs.org/vite/-/vite-6.4.2.tgz", - "integrity": "sha512-2N/55r4JDJ4gdrCvGgINMy+HH3iRpNIz8K6SFwVsA+JbQScLiC+clmAxBgwiSPgcG9U15QmvqCGWzMbqda5zGQ==", + "version": "7.3.2", + "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.2.tgz", + "integrity": "sha512-Bby3NOsna2jsjfLVOHKes8sGwgl4TT0E6vvpYgnAYDIF/tie7MRaFthmKuHx1NSXjiTueXH3do80FMQgvEktRg==", "dev": true, + "license": "MIT", "optional": true, "peer": true, "dependencies": { - "esbuild": "^0.25.0", - "fdir": "^6.4.4", - "picomatch": "^4.0.2", - "postcss": "^8.5.3", - "rollup": "^4.34.9", - "tinyglobby": "^0.2.13" + "esbuild": "^0.27.0", + "fdir": "^6.5.0", + "picomatch": "^4.0.3", + "postcss": "^8.5.6", + "rollup": "^4.43.0", + "tinyglobby": "^0.2.15" }, "bin": { "vite": "bin/vite.js" }, "engines": { - "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + "node": "^20.19.0 || >=22.12.0" }, "funding": { "url": "https://github.com/vitejs/vite?sponsor=1" @@ -5514,14 +5595,14 @@ "fsevents": "~2.3.3" }, "peerDependencies": { - "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", + "@types/node": "^20.19.0 || >=22.12.0", "jiti": ">=1.21.0", - "less": "*", + "less": "^4.0.0", "lightningcss": "^1.21.0", - "sass": "*", - "sass-embedded": "*", - "stylus": "*", - "sugarss": "*", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" @@ -5563,13 +5644,14 @@ } }, "node_modules/@vitest/browser/node_modules/@esbuild/aix-ppc64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz", - "integrity": "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.7.tgz", + "integrity": "sha512-EKX3Qwmhz1eMdEJokhALr0YiD0lhQNwDqkPYyPhiSwKrh7/4KRjQc04sZ8db+5DVVnZ1LmbNDI1uAMPEUBnQPg==", "cpu": [ "ppc64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "aix" @@ -5580,13 +5662,14 @@ } }, "node_modules/@vitest/browser/node_modules/@esbuild/android-arm": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.12.tgz", - "integrity": "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.7.tgz", + "integrity": "sha512-jbPXvB4Yj2yBV7HUfE2KHe4GJX51QplCN1pGbYjvsyCZbQmies29EoJbkEc+vYuU5o45AfQn37vZlyXy4YJ8RQ==", "cpu": [ "arm" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "android" @@ -5597,13 +5680,14 @@ } }, "node_modules/@vitest/browser/node_modules/@esbuild/android-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.12.tgz", - "integrity": "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.7.tgz", + "integrity": "sha512-62dPZHpIXzvChfvfLJow3q5dDtiNMkwiRzPylSCfriLvZeq0a1bWChrGx/BbUbPwOrsWKMn8idSllklzBy+dgQ==", "cpu": [ "arm64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "android" @@ -5614,13 +5698,14 @@ } }, "node_modules/@vitest/browser/node_modules/@esbuild/android-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.12.tgz", - "integrity": "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.7.tgz", + "integrity": "sha512-x5VpMODneVDb70PYV2VQOmIUUiBtY3D3mPBG8NxVk5CogneYhkR7MmM3yR/uMdITLrC1ml/NV1rj4bMJuy9MCg==", "cpu": [ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "android" @@ -5631,13 +5716,14 @@ } }, "node_modules/@vitest/browser/node_modules/@esbuild/darwin-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.12.tgz", - "integrity": "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.7.tgz", + "integrity": "sha512-5lckdqeuBPlKUwvoCXIgI2D9/ABmPq3Rdp7IfL70393YgaASt7tbju3Ac+ePVi3KDH6N2RqePfHnXkaDtY9fkw==", "cpu": [ "arm64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "darwin" @@ -5648,13 +5734,14 @@ } }, "node_modules/@vitest/browser/node_modules/@esbuild/darwin-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.12.tgz", - "integrity": "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.7.tgz", + "integrity": "sha512-rYnXrKcXuT7Z+WL5K980jVFdvVKhCHhUwid+dDYQpH+qu+TefcomiMAJpIiC2EM3Rjtq0sO3StMV/+3w3MyyqQ==", "cpu": [ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "darwin" @@ -5665,13 +5752,14 @@ } }, "node_modules/@vitest/browser/node_modules/@esbuild/freebsd-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.12.tgz", - "integrity": "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.7.tgz", + "integrity": "sha512-B48PqeCsEgOtzME2GbNM2roU29AMTuOIN91dsMO30t+Ydis3z/3Ngoj5hhnsOSSwNzS+6JppqWsuhTp6E82l2w==", "cpu": [ "arm64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "freebsd" @@ -5682,13 +5770,14 @@ } }, "node_modules/@vitest/browser/node_modules/@esbuild/freebsd-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.12.tgz", - "integrity": "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.7.tgz", + "integrity": "sha512-jOBDK5XEjA4m5IJK3bpAQF9/Lelu/Z9ZcdhTRLf4cajlB+8VEhFFRjWgfy3M1O4rO2GQ/b2dLwCUGpiF/eATNQ==", "cpu": [ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "freebsd" @@ -5699,13 +5788,14 @@ } }, "node_modules/@vitest/browser/node_modules/@esbuild/linux-arm": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.12.tgz", - "integrity": "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.7.tgz", + "integrity": "sha512-RkT/YXYBTSULo3+af8Ib0ykH8u2MBh57o7q/DAs3lTJlyVQkgQvlrPTnjIzzRPQyavxtPtfg0EopvDyIt0j1rA==", "cpu": [ "arm" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" @@ -5716,13 +5806,14 @@ } }, "node_modules/@vitest/browser/node_modules/@esbuild/linux-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.12.tgz", - "integrity": "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.7.tgz", + "integrity": "sha512-RZPHBoxXuNnPQO9rvjh5jdkRmVizktkT7TCDkDmQ0W2SwHInKCAV95GRuvdSvA7w4VMwfCjUiPwDi0ZO6Nfe9A==", "cpu": [ "arm64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" @@ -5733,13 +5824,14 @@ } }, "node_modules/@vitest/browser/node_modules/@esbuild/linux-ia32": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.12.tgz", - "integrity": "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.7.tgz", + "integrity": "sha512-GA48aKNkyQDbd3KtkplYWT102C5sn/EZTY4XROkxONgruHPU72l+gW+FfF8tf2cFjeHaRbWpOYa/uRBz/Xq1Pg==", "cpu": [ "ia32" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" @@ -5750,13 +5842,14 @@ } }, "node_modules/@vitest/browser/node_modules/@esbuild/linux-loong64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.12.tgz", - "integrity": "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.7.tgz", + "integrity": "sha512-a4POruNM2oWsD4WKvBSEKGIiWQF8fZOAsycHOt6JBpZ+JN2n2JH9WAv56SOyu9X5IqAjqSIPTaJkqN8F7XOQ5Q==", "cpu": [ "loong64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" @@ -5767,13 +5860,14 @@ } }, "node_modules/@vitest/browser/node_modules/@esbuild/linux-mips64el": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.12.tgz", - "integrity": "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.7.tgz", + "integrity": "sha512-KabT5I6StirGfIz0FMgl1I+R1H73Gp0ofL9A3nG3i/cYFJzKHhouBV5VWK1CSgKvVaG4q1RNpCTR2LuTVB3fIw==", "cpu": [ "mips64el" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" @@ -5784,13 +5878,14 @@ } }, "node_modules/@vitest/browser/node_modules/@esbuild/linux-ppc64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.12.tgz", - "integrity": "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.7.tgz", + "integrity": "sha512-gRsL4x6wsGHGRqhtI+ifpN/vpOFTQtnbsupUF5R5YTAg+y/lKelYR1hXbnBdzDjGbMYjVJLJTd2OFmMewAgwlQ==", "cpu": [ "ppc64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" @@ -5801,13 +5896,14 @@ } }, "node_modules/@vitest/browser/node_modules/@esbuild/linux-riscv64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.12.tgz", - "integrity": "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.7.tgz", + "integrity": "sha512-hL25LbxO1QOngGzu2U5xeXtxXcW+/GvMN3ejANqXkxZ/opySAZMrc+9LY/WyjAan41unrR3YrmtTsUpwT66InQ==", "cpu": [ "riscv64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" @@ -5818,13 +5914,14 @@ } }, "node_modules/@vitest/browser/node_modules/@esbuild/linux-s390x": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.12.tgz", - "integrity": "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.7.tgz", + "integrity": "sha512-2k8go8Ycu1Kb46vEelhu1vqEP+UeRVj2zY1pSuPdgvbd5ykAw82Lrro28vXUrRmzEsUV0NzCf54yARIK8r0fdw==", "cpu": [ "s390x" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" @@ -5835,13 +5932,14 @@ } }, "node_modules/@vitest/browser/node_modules/@esbuild/linux-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.12.tgz", - "integrity": "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.7.tgz", + "integrity": "sha512-hzznmADPt+OmsYzw1EE33ccA+HPdIqiCRq7cQeL1Jlq2gb1+OyWBkMCrYGBJ+sxVzve2ZJEVeePbLM2iEIZSxA==", "cpu": [ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" @@ -5851,14 +5949,15 @@ "node": ">=18" } }, - "node_modules/@vitest/browser/node_modules/@esbuild/netbsd-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.12.tgz", - "integrity": "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==", + "node_modules/@vitest/browser/node_modules/@esbuild/netbsd-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.7.tgz", + "integrity": "sha512-b6pqtrQdigZBwZxAn1UpazEisvwaIDvdbMbmrly7cDTMFnw/+3lVxxCTGOrkPVnsYIosJJXAsILG9XcQS+Yu6w==", "cpu": [ - "x64" + "arm64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "netbsd" @@ -5868,14 +5967,33 @@ "node": ">=18" } }, - "node_modules/@vitest/browser/node_modules/@esbuild/openbsd-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.12.tgz", - "integrity": "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==", + "node_modules/@vitest/browser/node_modules/@esbuild/netbsd-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.7.tgz", + "integrity": "sha512-OfatkLojr6U+WN5EDYuoQhtM+1xco+/6FSzJJnuWiUw5eVcicbyK3dq5EeV/QHT1uy6GoDhGbFpprUiHUYggrw==", "cpu": [ "x64" ], "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@vitest/browser/node_modules/@esbuild/openbsd-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.7.tgz", + "integrity": "sha512-AFuojMQTxAz75Fo8idVcqoQWEHIXFRbOc1TrVcFSgCZtQfSdc1RXgB3tjOn/krRHENUB4j00bfGjyl2mJrU37A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", "optional": true, "os": [ "openbsd" @@ -5885,14 +6003,51 @@ "node": ">=18" } }, - "node_modules/@vitest/browser/node_modules/@esbuild/sunos-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.12.tgz", - "integrity": "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==", + "node_modules/@vitest/browser/node_modules/@esbuild/openbsd-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.7.tgz", + "integrity": "sha512-+A1NJmfM8WNDv5CLVQYJ5PshuRm/4cI6WMZRg1by1GwPIQPCTs1GLEUHwiiQGT5zDdyLiRM/l1G0Pv54gvtKIg==", "cpu": [ "x64" ], "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@vitest/browser/node_modules/@esbuild/openharmony-arm64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.7.tgz", + "integrity": "sha512-+KrvYb/C8zA9CU/g0sR6w2RBw7IGc5J2BPnc3dYc5VJxHCSF1yNMxTV5LQ7GuKteQXZtspjFbiuW5/dOj7H4Yw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "peer": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@vitest/browser/node_modules/@esbuild/sunos-x64": { + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.7.tgz", + "integrity": "sha512-ikktIhFBzQNt/QDyOL580ti9+5mL/YZeUPKU2ivGtGjdTYoqz6jObj6nOMfhASpS4GU4Q/Clh1QtxWAvcYKamA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", "optional": true, "os": [ "sunos" @@ -5903,13 +6058,14 @@ } }, "node_modules/@vitest/browser/node_modules/@esbuild/win32-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.12.tgz", - "integrity": "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.7.tgz", + "integrity": "sha512-7yRhbHvPqSpRUV7Q20VuDwbjW5kIMwTHpptuUzV+AA46kiPze5Z7qgt6CLCK3pWFrHeNfDd1VKgyP4O+ng17CA==", "cpu": [ "arm64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "win32" @@ -5920,13 +6076,14 @@ } }, "node_modules/@vitest/browser/node_modules/@esbuild/win32-ia32": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.12.tgz", - "integrity": "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.7.tgz", + "integrity": "sha512-SmwKXe6VHIyZYbBLJrhOoCJRB/Z1tckzmgTLfFYOfpMAx63BJEaL9ExI8x7v0oAO3Zh6D/Oi1gVxEYr5oUCFhw==", "cpu": [ "ia32" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "win32" @@ -5937,13 +6094,14 @@ } }, "node_modules/@vitest/browser/node_modules/@esbuild/win32-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.12.tgz", - "integrity": "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.7.tgz", + "integrity": "sha512-56hiAJPhwQ1R4i+21FVF7V8kSD5zZTdHcVuRFMW0hn753vVfQN8xlx4uOPT4xoGH0Z/oVATuR82AiqSTDIpaHg==", "cpu": [ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "win32" @@ -5980,11 +6138,12 @@ } }, "node_modules/@vitest/browser/node_modules/esbuild": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz", - "integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==", + "version": "0.27.7", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.7.tgz", + "integrity": "sha512-IxpibTjyVnmrIQo5aqNpCgoACA/dTKLTlhMHihVHhdkxKyPO1uBBthumT0rdHmcsk9uMonIWS0m4FljWzILh3w==", "dev": true, "hasInstallScript": true, + "license": "MIT", "optional": true, "peer": true, "bin": { @@ -5994,32 +6153,32 @@ "node": ">=18" }, "optionalDependencies": { - "@esbuild/aix-ppc64": "0.25.12", - "@esbuild/android-arm": "0.25.12", - "@esbuild/android-arm64": "0.25.12", - "@esbuild/android-x64": "0.25.12", - "@esbuild/darwin-arm64": "0.25.12", - "@esbuild/darwin-x64": "0.25.12", - "@esbuild/freebsd-arm64": "0.25.12", - "@esbuild/freebsd-x64": "0.25.12", - "@esbuild/linux-arm": "0.25.12", - "@esbuild/linux-arm64": "0.25.12", - "@esbuild/linux-ia32": "0.25.12", - "@esbuild/linux-loong64": "0.25.12", - "@esbuild/linux-mips64el": "0.25.12", - "@esbuild/linux-ppc64": "0.25.12", - "@esbuild/linux-riscv64": "0.25.12", - "@esbuild/linux-s390x": "0.25.12", - "@esbuild/linux-x64": "0.25.12", - "@esbuild/netbsd-arm64": "0.25.12", - "@esbuild/netbsd-x64": "0.25.12", - "@esbuild/openbsd-arm64": "0.25.12", - "@esbuild/openbsd-x64": "0.25.12", - "@esbuild/openharmony-arm64": "0.25.12", - "@esbuild/sunos-x64": "0.25.12", - "@esbuild/win32-arm64": "0.25.12", - "@esbuild/win32-ia32": "0.25.12", - "@esbuild/win32-x64": "0.25.12" + "@esbuild/aix-ppc64": "0.27.7", + "@esbuild/android-arm": "0.27.7", + "@esbuild/android-arm64": "0.27.7", + "@esbuild/android-x64": "0.27.7", + "@esbuild/darwin-arm64": "0.27.7", + "@esbuild/darwin-x64": "0.27.7", + "@esbuild/freebsd-arm64": "0.27.7", + "@esbuild/freebsd-x64": "0.27.7", + "@esbuild/linux-arm": "0.27.7", + "@esbuild/linux-arm64": "0.27.7", + "@esbuild/linux-ia32": "0.27.7", + "@esbuild/linux-loong64": "0.27.7", + "@esbuild/linux-mips64el": "0.27.7", + "@esbuild/linux-ppc64": "0.27.7", + "@esbuild/linux-riscv64": "0.27.7", + "@esbuild/linux-s390x": "0.27.7", + "@esbuild/linux-x64": "0.27.7", + "@esbuild/netbsd-arm64": "0.27.7", + "@esbuild/netbsd-x64": "0.27.7", + "@esbuild/openbsd-arm64": "0.27.7", + "@esbuild/openbsd-x64": "0.27.7", + "@esbuild/openharmony-arm64": "0.27.7", + "@esbuild/sunos-x64": "0.27.7", + "@esbuild/win32-arm64": "0.27.7", + "@esbuild/win32-ia32": "0.27.7", + "@esbuild/win32-x64": "0.27.7" } }, "node_modules/@vitest/browser/node_modules/fdir": { @@ -6027,6 +6186,7 @@ "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", "dev": true, + "license": "MIT", "optional": true, "peer": true, "engines": { @@ -6046,6 +6206,7 @@ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", "dev": true, + "license": "MIT", "optional": true, "peer": true, "engines": { @@ -6077,25 +6238,26 @@ } }, "node_modules/@vitest/browser/node_modules/vite": { - "version": "6.4.2", - "resolved": "https://registry.npmjs.org/vite/-/vite-6.4.2.tgz", - "integrity": "sha512-2N/55r4JDJ4gdrCvGgINMy+HH3iRpNIz8K6SFwVsA+JbQScLiC+clmAxBgwiSPgcG9U15QmvqCGWzMbqda5zGQ==", + "version": "7.3.2", + "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.2.tgz", + "integrity": "sha512-Bby3NOsna2jsjfLVOHKes8sGwgl4TT0E6vvpYgnAYDIF/tie7MRaFthmKuHx1NSXjiTueXH3do80FMQgvEktRg==", "dev": true, + "license": "MIT", "optional": true, "peer": true, "dependencies": { - "esbuild": "^0.25.0", - "fdir": "^6.4.4", - "picomatch": "^4.0.2", - "postcss": "^8.5.3", - "rollup": "^4.34.9", - "tinyglobby": "^0.2.13" + "esbuild": "^0.27.0", + "fdir": "^6.5.0", + "picomatch": "^4.0.3", + "postcss": "^8.5.6", + "rollup": "^4.43.0", + "tinyglobby": "^0.2.15" }, "bin": { "vite": "bin/vite.js" }, "engines": { - "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + "node": "^20.19.0 || >=22.12.0" }, "funding": { "url": "https://github.com/vitejs/vite?sponsor=1" @@ -6104,14 +6266,14 @@ "fsevents": "~2.3.3" }, "peerDependencies": { - "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", + "@types/node": "^20.19.0 || >=22.12.0", "jiti": ">=1.21.0", - "less": "*", + "less": "^4.0.0", "lightningcss": "^1.21.0", - "sass": "*", - "sass-embedded": "*", - "stylus": "*", - "sugarss": "*", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" @@ -10718,268 +10880,6 @@ "node": ">=0.10.0" } }, - "node_modules/lightningcss": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz", - "integrity": "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==", - "dev": true, - "optional": true, - "peer": true, - "dependencies": { - "detect-libc": "^2.0.3" - }, - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - }, - "optionalDependencies": { - "lightningcss-android-arm64": "1.32.0", - "lightningcss-darwin-arm64": "1.32.0", - "lightningcss-darwin-x64": "1.32.0", - "lightningcss-freebsd-x64": "1.32.0", - "lightningcss-linux-arm-gnueabihf": "1.32.0", - "lightningcss-linux-arm64-gnu": "1.32.0", - "lightningcss-linux-arm64-musl": "1.32.0", - "lightningcss-linux-x64-gnu": "1.32.0", - "lightningcss-linux-x64-musl": "1.32.0", - "lightningcss-win32-arm64-msvc": "1.32.0", - "lightningcss-win32-x64-msvc": "1.32.0" - } - }, - "node_modules/lightningcss-android-arm64": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.32.0.tgz", - "integrity": "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "android" - ], - "peer": true, - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-darwin-arm64": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.32.0.tgz", - "integrity": "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "darwin" - ], - "peer": true, - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-darwin-x64": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.32.0.tgz", - "integrity": "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "darwin" - ], - "peer": true, - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-freebsd-x64": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.32.0.tgz", - "integrity": "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "freebsd" - ], - "peer": true, - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-arm-gnueabihf": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.32.0.tgz", - "integrity": "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==", - "cpu": [ - "arm" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-arm64-gnu": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.32.0.tgz", - "integrity": "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-arm64-musl": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.32.0.tgz", - "integrity": "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-x64-gnu": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.32.0.tgz", - "integrity": "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-x64-musl": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.32.0.tgz", - "integrity": "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "peer": true, - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-win32-arm64-msvc": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.32.0.tgz", - "integrity": "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==", - "cpu": [ - "arm64" - ], - "dev": true, - "optional": true, - "os": [ - "win32" - ], - "peer": true, - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-win32-x64-msvc": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.32.0.tgz", - "integrity": "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==", - "cpu": [ - "x64" - ], - "dev": true, - "optional": true, - "os": [ - "win32" - ], - "peer": true, - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, "node_modules/lilconfig": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz", diff --git a/src/components/video-editor/AnnotationOverlay.tsx b/src/components/video-editor/AnnotationOverlay.tsx index 11548c7..3120f0b 100644 --- a/src/components/video-editor/AnnotationOverlay.tsx +++ b/src/components/video-editor/AnnotationOverlay.tsx @@ -1,8 +1,28 @@ -import { useRef } from "react"; +import { type CSSProperties, type PointerEvent, useRef, useState } from "react"; import { Rnd } from "react-rnd"; import { cn } from "@/lib/utils"; import { getArrowComponent } from "./ArrowSvgs"; -import type { AnnotationRegion } from "./types"; +import { + type AnnotationRegion, + type BlurData, + DEFAULT_BLUR_DATA, + DEFAULT_BLUR_INTENSITY, +} from "./types"; + +const FREEHAND_POINT_THRESHOLD = 1; + +function buildBlurPolygonClipPath(points: Array<{ x: number; y: number }>) { + if (points.length < 3) return undefined; + const polygon = points.map((point) => `${point.x}% ${point.y}%`).join(", "); + return `polygon(${polygon})`; +} + +function buildBlurFreehandPath(points: Array<{ x: number; y: number }>, closed = true) { + if (closed ? points.length < 3 : points.length < 2) return null; + const [firstPoint, ...rest] = points; + const path = `M ${firstPoint.x} ${firstPoint.y} ${rest.map((point) => `L ${point.x} ${point.y}`).join(" ")}`; + return closed ? `${path} Z` : path; +} interface AnnotationOverlayProps { annotation: AnnotationRegion; @@ -11,6 +31,8 @@ interface AnnotationOverlayProps { containerHeight: number; onPositionChange: (id: string, position: { x: number; y: number }) => void; onSizeChange: (id: string, size: { width: number; height: number }) => void; + onBlurDataChange?: (id: string, blurData: BlurData) => void; + onBlurDataCommit?: () => void; onClick: (id: string) => void; zIndex: number; isSelectedBoost: boolean; // Boost z-index when selected for easy editing @@ -23,6 +45,8 @@ export function AnnotationOverlay({ containerHeight, onPositionChange, onSizeChange, + onBlurDataChange, + onBlurDataCommit, onClick, zIndex, isSelectedBoost, @@ -31,8 +55,16 @@ export function AnnotationOverlay({ const y = (annotation.position.y / 100) * containerHeight; const width = (annotation.size.width / 100) * containerWidth; const height = (annotation.size.height / 100) * containerHeight; - + const blurShape = annotation.type === "blur" ? (annotation.blurData?.shape ?? "rectangle") : null; + const isSelectedFreehandBlur = isSelected && blurShape === "freehand"; const isDraggingRef = useRef(false); + const isDrawingFreehandRef = useRef(false); + const freehandPointsRef = useRef>([]); + const [isFreehandDrawing, setIsFreehandDrawing] = useState(false); + const [draftFreehandPoints, setDraftFreehandPoints] = useState>( + [], + ); + const [livePointerPoint, setLivePointerPoint] = useState<{ x: number; y: number } | null>(null); const renderArrow = () => { const direction = annotation.figureData?.arrowDirection || "right"; @@ -43,6 +75,95 @@ export function AnnotationOverlay({ return ; }; + const normalizePoint = (event: PointerEvent) => { + const rect = event.currentTarget.getBoundingClientRect(); + const x = ((event.clientX - rect.left) / rect.width) * 100; + const y = ((event.clientY - rect.top) / rect.height) * 100; + return { + x: Math.max(0, Math.min(100, x)), + y: Math.max(0, Math.min(100, y)), + }; + }; + + const appendFreehandPoint = (point: { x: number; y: number }) => { + const points = freehandPointsRef.current; + const lastPoint = points[points.length - 1]; + if (!lastPoint) { + points.push(point); + return; + } + const dx = point.x - lastPoint.x; + const dy = point.y - lastPoint.y; + // Sample freehand points in annotation-space percent units to avoid overly dense paths. + if (Math.hypot(dx, dy) >= FREEHAND_POINT_THRESHOLD) { + points.push(point); + } + }; + + const handleFreehandPointerDown = (event: PointerEvent) => { + if ( + !isSelected || + annotation.type !== "blur" || + annotation.blurData?.shape !== "freehand" || + !onBlurDataChange + ) { + return; + } + event.preventDefault(); + event.stopPropagation(); + event.currentTarget.setPointerCapture(event.pointerId); + isDrawingFreehandRef.current = true; + setIsFreehandDrawing(true); + const point = normalizePoint(event); + freehandPointsRef.current = [point]; + setDraftFreehandPoints([point]); + setLivePointerPoint(point); + }; + + const handleFreehandPointerMove = (event: PointerEvent) => { + if (!isDrawingFreehandRef.current) return; + event.preventDefault(); + event.stopPropagation(); + const point = normalizePoint(event); + setLivePointerPoint(point); + appendFreehandPoint(point); + setDraftFreehandPoints([...freehandPointsRef.current]); + }; + + const finishFreehandPointer = (event: PointerEvent) => { + if (!isDrawingFreehandRef.current || !onBlurDataChange) return; + isDrawingFreehandRef.current = false; + setIsFreehandDrawing(false); + try { + event.currentTarget.releasePointerCapture(event.pointerId); + } catch { + // no-op if already released + } + const points = [...freehandPointsRef.current]; + if (livePointerPoint) { + const last = points[points.length - 1]; + if (!last || Math.hypot(last.x - livePointerPoint.x, last.y - livePointerPoint.y) > 0.001) { + points.push(livePointerPoint); + } + } + if (points.length >= 3) { + const closedPoints = [...points]; + const first = closedPoints[0]; + const last = closedPoints[closedPoints.length - 1]; + if (Math.hypot(last.x - first.x, last.y - first.y) > 0.001) { + closedPoints.push({ ...first }); + } + onBlurDataChange(annotation.id, { + ...(annotation.blurData || { ...DEFAULT_BLUR_DATA, shape: "freehand" }), + shape: "freehand", + freehandPoints: closedPoints, + }); + setDraftFreehandPoints(closedPoints); + onBlurDataCommit?.(); + } + setLivePointerPoint(null); + }; + const renderContent = () => { switch (annotation.type) { case "text": @@ -113,6 +234,114 @@ export function AnnotationOverlay({
{renderArrow()}
); + case "blur": { + const shape = annotation.blurData?.shape ?? "rectangle"; + const blurIntensity = Math.max( + 1, + Math.round(annotation.blurData?.intensity ?? DEFAULT_BLUR_INTENSITY), + ); + const activeFreehandPoints = + shape === "freehand" + ? isFreehandDrawing + ? draftFreehandPoints + : (annotation.blurData?.freehandPoints ?? []) + : []; + const drawingPoints = + isFreehandDrawing && livePointerPoint + ? (() => { + const last = activeFreehandPoints[activeFreehandPoints.length - 1]; + if (!last) return [livePointerPoint]; + const dx = livePointerPoint.x - last.x; + const dy = livePointerPoint.y - last.y; + return Math.hypot(dx, dy) > 0.01 + ? [...activeFreehandPoints, livePointerPoint] + : activeFreehandPoints; + })() + : activeFreehandPoints; + const clipPath = + shape === "freehand" ? buildBlurPolygonClipPath(activeFreehandPoints) : undefined; + const freehandPath = + shape === "freehand" + ? buildBlurFreehandPath( + isFreehandDrawing ? drawingPoints : activeFreehandPoints, + !isFreehandDrawing, + ) + : null; + const currentPointerPoint = isFreehandDrawing + ? livePointerPoint || drawingPoints[drawingPoints.length - 1] || null + : null; + const shapeBorderRadius = shape === "oval" ? "50%" : shape === "rectangle" ? "8px" : "0"; + const shouldShowFreehandBlurFill = + shape !== "freehand" || (!!clipPath && !isFreehandDrawing); + const shapeMaskStyle: CSSProperties = { + borderRadius: shapeBorderRadius, + clipPath: isFreehandDrawing ? undefined : clipPath, + WebkitClipPath: isFreehandDrawing ? undefined : clipPath, + }; + const isFreehandSelected = isSelectedFreehandBlur; + return ( +
+
+
+ {isSelected && shape !== "freehand" && ( +
+ )} +
+ {isSelected && shape === "freehand" && freehandPath && ( + + + {currentPointerPoint && ( + + )} + + )} + {isFreehandSelected && ( +
+ )} +
+ ); + } + default: return null; } @@ -149,18 +378,23 @@ export function AnnotationOverlay({ }} bounds="parent" className={cn( - "cursor-move transition-all", - isSelected && "ring-2 ring-[#34B27B] ring-offset-2 ring-offset-transparent", + "cursor-move", + isSelected && + annotation.type !== "blur" && + "ring-2 ring-[#34B27B] ring-offset-2 ring-offset-transparent", )} style={{ zIndex: isSelectedBoost ? zIndex + 1000 : zIndex, // Boost selected annotation to ensure it's on top pointerEvents: isSelected ? "auto" : "none", - border: isSelected ? "2px solid rgba(52, 178, 123, 0.8)" : "none", - backgroundColor: isSelected ? "rgba(52, 178, 123, 0.1)" : "transparent", - boxShadow: isSelected ? "0 0 0 1px rgba(52, 178, 123, 0.35)" : "none", + border: + isSelected && annotation.type !== "blur" ? "2px solid rgba(52, 178, 123, 0.8)" : "none", + backgroundColor: + isSelected && annotation.type !== "blur" ? "rgba(52, 178, 123, 0.1)" : "transparent", + boxShadow: + isSelected && annotation.type !== "blur" ? "0 0 0 1px rgba(52, 178, 123, 0.35)" : "none", }} - enableResizing={isSelected} - disableDragging={!isSelected} + enableResizing={isSelected && !isSelectedFreehandBlur} + disableDragging={!isSelected || isSelectedFreehandBlur} resizeHandleStyles={{ topLeft: { width: "12px", @@ -206,11 +440,13 @@ export function AnnotationOverlay({ >
{renderContent()} diff --git a/src/components/video-editor/AnnotationSettingsPanel.tsx b/src/components/video-editor/AnnotationSettingsPanel.tsx index b289392..f5c2a0b 100644 --- a/src/components/video-editor/AnnotationSettingsPanel.tsx +++ b/src/components/video-editor/AnnotationSettingsPanel.tsx @@ -32,7 +32,12 @@ import { type CustomFont, getCustomFonts } from "@/lib/customFonts"; import { cn } from "@/lib/utils"; import { AddCustomFontDialog } from "./AddCustomFontDialog"; import { getArrowComponent } from "./ArrowSvgs"; -import type { AnnotationRegion, AnnotationType, ArrowDirection, FigureData } from "./types"; +import { + type AnnotationRegion, + type AnnotationType, + type ArrowDirection, + type FigureData, +} from "./types"; interface AnnotationSettingsPanelProps { annotation: AnnotationRegion; diff --git a/src/components/video-editor/BlurSettingsPanel.tsx b/src/components/video-editor/BlurSettingsPanel.tsx new file mode 100644 index 0000000..382cd80 --- /dev/null +++ b/src/components/video-editor/BlurSettingsPanel.tsx @@ -0,0 +1,142 @@ +import { Info, Trash2 } from "lucide-react"; +import { Button } from "@/components/ui/button"; +import { Slider } from "@/components/ui/slider"; +import { useScopedT } from "@/contexts/I18nContext"; +import { cn } from "@/lib/utils"; +import { + type AnnotationRegion, + type BlurData, + type BlurShape, + DEFAULT_BLUR_DATA, + MAX_BLUR_INTENSITY, + MIN_BLUR_INTENSITY, +} from "./types"; + +interface BlurSettingsPanelProps { + blurRegion: AnnotationRegion; + onBlurDataChange: (blurData: BlurData) => void; + onBlurDataCommit?: () => void; + onDelete: () => void; +} + +export function BlurSettingsPanel({ + blurRegion, + onBlurDataChange, + onBlurDataCommit, + onDelete, +}: BlurSettingsPanelProps) { + const t = useScopedT("settings"); + + const blurShapeOptions: Array<{ value: BlurShape; labelKey: string }> = [ + { value: "rectangle", labelKey: "blurShapeRectangle" }, + { value: "oval", labelKey: "blurShapeOval" }, + ]; + + return ( +
+
+
+ {t("annotation.blurShape")} + + {t("annotation.active")} + +
+ +
+ {blurShapeOptions.map((shape) => { + const activeShape = blurRegion.blurData?.shape || DEFAULT_BLUR_DATA.shape; + const isActive = activeShape === shape.value; + return ( + + ); + })} +
+ +
+
+ + {t("annotation.blurIntensity")} + + + {Math.round(blurRegion.blurData?.intensity ?? DEFAULT_BLUR_DATA.intensity)}px + +
+ { + onBlurDataChange({ + ...DEFAULT_BLUR_DATA, + ...blurRegion.blurData, + intensity: values[0], + }); + }} + onValueCommit={() => onBlurDataCommit?.()} + min={MIN_BLUR_INTENSITY} + max={MAX_BLUR_INTENSITY} + step={1} + className="w-full [&_[role=slider]]:bg-[#34B27B] [&_[role=slider]]:border-[#34B27B] [&_[role=slider]]:h-3 [&_[role=slider]]:w-3" + /> +
+ + + +
+
+ + {t("annotation.shortcutsAndTips")} +
+
    +
  • {t("annotation.tipMovePlayhead")}
  • +
+
+
+
+ ); +} diff --git a/src/components/video-editor/SettingsPanel.tsx b/src/components/video-editor/SettingsPanel.tsx index daf5f42..b1cd78d 100644 --- a/src/components/video-editor/SettingsPanel.tsx +++ b/src/components/video-editor/SettingsPanel.tsx @@ -42,11 +42,13 @@ import { cn } from "@/lib/utils"; import { type AspectRatio, isPortraitAspectRatio } from "@/utils/aspectRatioUtils"; import { getTestId } from "@/utils/getTestId"; import { AnnotationSettingsPanel } from "./AnnotationSettingsPanel"; +import { BlurSettingsPanel } from "./BlurSettingsPanel"; import { CropControl } from "./CropControl"; import { KeyboardShortcutsHelp } from "./KeyboardShortcutsHelp"; import type { AnnotationRegion, AnnotationType, + BlurData, CropRegion, FigureData, PlaybackSpeed, @@ -209,6 +211,11 @@ interface SettingsPanelProps { onAnnotationStyleChange?: (id: string, style: Partial) => void; onAnnotationFigureDataChange?: (id: string, figureData: FigureData) => void; onAnnotationDelete?: (id: string) => void; + selectedBlurId?: string | null; + blurRegions?: AnnotationRegion[]; + onBlurDataChange?: (id: string, blurData: BlurData) => void; + onBlurDataCommit?: () => void; + onBlurDelete?: (id: string) => void; selectedSpeedId?: string | null; selectedSpeedValue?: PlaybackSpeed | null; onSpeedChange?: (speed: PlaybackSpeed) => void; @@ -285,6 +292,11 @@ export function SettingsPanel({ onAnnotationStyleChange, onAnnotationFigureDataChange, onAnnotationDelete, + selectedBlurId, + blurRegions = [], + onBlurDataChange, + onBlurDataCommit, + onBlurDelete, selectedSpeedId, selectedSpeedValue, onSpeedChange, @@ -520,6 +532,9 @@ export function SettingsPanel({ const selectedAnnotation = selectedAnnotationId ? annotationRegions.find((a) => a.id === selectedAnnotationId) : null; + const selectedBlur = selectedBlurId + ? blurRegions.find((region) => region.id === selectedBlurId) + : null; // If an annotation is selected, show annotation settings instead if ( @@ -545,6 +560,17 @@ export function SettingsPanel({ ); } + if (selectedBlur && onBlurDataChange && onBlurDelete) { + return ( + onBlurDataChange(selectedBlur.id, blurData)} + onBlurDataCommit={onBlurDataCommit} + onDelete={() => onBlurDelete(selectedBlur.id)} + /> + ); + } + return (
diff --git a/src/components/video-editor/VideoEditor.tsx b/src/components/video-editor/VideoEditor.tsx index 88c3aae..ffe3add 100644 --- a/src/components/video-editor/VideoEditor.tsx +++ b/src/components/video-editor/VideoEditor.tsx @@ -54,11 +54,13 @@ import { SettingsPanel } from "./SettingsPanel"; import TimelineEditor from "./timeline/TimelineEditor"; import { type AnnotationRegion, + type BlurData, type CursorTelemetryPoint, clampFocusToDepth, DEFAULT_ANNOTATION_POSITION, DEFAULT_ANNOTATION_SIZE, DEFAULT_ANNOTATION_STYLE, + DEFAULT_BLUR_DATA, DEFAULT_FIGURE_DATA, DEFAULT_PLAYBACK_SPEED, DEFAULT_ZOOM_DEPTH, @@ -122,6 +124,7 @@ export default function VideoEditor() { const [selectedTrimId, setSelectedTrimId] = useState(null); const [selectedSpeedId, setSelectedSpeedId] = useState(null); const [selectedAnnotationId, setSelectedAnnotationId] = useState(null); + const [selectedBlurId, setSelectedBlurId] = useState(null); const [isExporting, setIsExporting] = useState(false); const [exportProgress, setExportProgress] = useState(null); const [exportError, setExportError] = useState(null); @@ -157,6 +160,15 @@ export default function VideoEditor() { const nextAnnotationZIndexRef = useRef(1); const exporterRef = useRef(null); + const annotationOnlyRegions = useMemo( + () => annotationRegions.filter((region) => region.type !== "blur"), + [annotationRegions], + ); + const blurRegions = useMemo( + () => annotationRegions.filter((region) => region.type === "blur"), + [annotationRegions], + ); + const currentProjectMedia = useMemo(() => { const screenVideoPath = videoSourcePath ?? (videoPath ? fromFileUrl(videoPath) : null); if (!screenVideoPath) { @@ -229,6 +241,7 @@ export default function VideoEditor() { setSelectedTrimId(null); setSelectedSpeedId(null); setSelectedAnnotationId(null); + setSelectedBlurId(null); nextZoomIdRef.current = deriveNextId( "zoom", @@ -626,7 +639,11 @@ export default function VideoEditor() { const handleSelectZoom = useCallback((id: string | null) => { setSelectedZoomId(id); - if (id) setSelectedTrimId(null); + if (id) { + setSelectedTrimId(null); + setSelectedAnnotationId(null); + setSelectedBlurId(null); + } }, []); const handleSelectTrim = useCallback((id: string | null) => { @@ -634,6 +651,7 @@ export default function VideoEditor() { if (id) { setSelectedZoomId(null); setSelectedAnnotationId(null); + setSelectedBlurId(null); } }, []); @@ -642,6 +660,17 @@ export default function VideoEditor() { if (id) { setSelectedZoomId(null); setSelectedTrimId(null); + setSelectedBlurId(null); + } + }, []); + + const handleSelectBlur = useCallback((id: string | null) => { + setSelectedBlurId(id); + if (id) { + setSelectedZoomId(null); + setSelectedTrimId(null); + setSelectedAnnotationId(null); + setSelectedSpeedId(null); } }, []); @@ -659,6 +688,7 @@ export default function VideoEditor() { setSelectedZoomId(id); setSelectedTrimId(null); setSelectedAnnotationId(null); + setSelectedBlurId(null); }, [pushState], ); @@ -677,6 +707,7 @@ export default function VideoEditor() { setSelectedZoomId(id); setSelectedTrimId(null); setSelectedAnnotationId(null); + setSelectedBlurId(null); }, [pushState], ); @@ -693,6 +724,7 @@ export default function VideoEditor() { setSelectedTrimId(id); setSelectedZoomId(null); setSelectedAnnotationId(null); + setSelectedBlurId(null); }, [pushState], ); @@ -803,6 +835,7 @@ export default function VideoEditor() { setSelectedZoomId(null); setSelectedTrimId(null); setSelectedAnnotationId(null); + setSelectedBlurId(null); } }, []); @@ -822,6 +855,7 @@ export default function VideoEditor() { setSelectedZoomId(null); setSelectedTrimId(null); setSelectedAnnotationId(null); + setSelectedBlurId(null); }, [pushState], ); @@ -888,6 +922,35 @@ export default function VideoEditor() { setSelectedAnnotationId(id); setSelectedZoomId(null); setSelectedTrimId(null); + setSelectedBlurId(null); + }, + [pushState], + ); + + const handleBlurAdded = useCallback( + (span: Span) => { + const id = `annotation-${nextAnnotationIdRef.current++}`; + const zIndex = nextAnnotationZIndexRef.current++; + const newRegion: AnnotationRegion = { + id, + startMs: Math.round(span.start), + endMs: Math.round(span.end), + type: "blur", + content: "", + position: { ...DEFAULT_ANNOTATION_POSITION }, + size: { ...DEFAULT_ANNOTATION_SIZE }, + style: { ...DEFAULT_ANNOTATION_STYLE }, + zIndex, + blurData: { ...DEFAULT_BLUR_DATA }, + }; + pushState((prev) => ({ + annotationRegions: [...prev.annotationRegions, newRegion], + })); + setSelectedBlurId(id); + setSelectedAnnotationId(null); + setSelectedZoomId(null); + setSelectedTrimId(null); + setSelectedSpeedId(null); }, [pushState], ); @@ -917,8 +980,11 @@ export default function VideoEditor() { if (selectedAnnotationId === id) { setSelectedAnnotationId(null); } + if (selectedBlurId === id) { + setSelectedBlurId(null); + } }, - [selectedAnnotationId, pushState], + [selectedAnnotationId, selectedBlurId, pushState], ); const handleAnnotationContentChange = useCallback( @@ -953,12 +1019,26 @@ export default function VideoEditor() { if (!region.figureData) { updatedRegion.figureData = { ...DEFAULT_FIGURE_DATA }; } + } else if (type === "blur") { + updatedRegion.content = ""; + if (!region.blurData) { + updatedRegion.blurData = { ...DEFAULT_BLUR_DATA }; + } } return updatedRegion; }), })); + + if (type === "blur" && selectedAnnotationId === id) { + setSelectedAnnotationId(null); + setSelectedBlurId(id); + setSelectedSpeedId(null); + } else if (type !== "blur" && selectedBlurId === id) { + setSelectedBlurId(null); + setSelectedAnnotationId(id); + } }, - [pushState], + [pushState, selectedAnnotationId, selectedBlurId], ); const handleAnnotationStyleChange = useCallback( @@ -983,6 +1063,51 @@ export default function VideoEditor() { [pushState], ); + const handleBlurDataPreviewChange = useCallback( + (id: string, blurData: BlurData) => { + updateState((prev) => ({ + annotationRegions: prev.annotationRegions.map((region) => + region.id === id + ? { + ...region, + blurData, + // Freehand drawing area is the full video surface. + ...(blurData.shape === "freehand" + ? { + position: { x: 0, y: 0 }, + size: { width: 100, height: 100 }, + } + : {}), + } + : region, + ), + })); + }, + [updateState], + ); + + const handleBlurDataPanelChange = useCallback( + (id: string, blurData: BlurData) => { + pushState((prev) => ({ + annotationRegions: prev.annotationRegions.map((region) => + region.id === id + ? { + ...region, + blurData, + ...(blurData.shape === "freehand" + ? { + position: { x: 0, y: 0 }, + size: { width: 100, height: 100 }, + } + : {}), + } + : region, + ), + })); + }, + [pushState], + ); + const handleAnnotationPositionChange = useCallback( (id: string, position: { x: number; y: number }) => { pushState((prev) => ({ @@ -1096,11 +1221,14 @@ export default function VideoEditor() { useEffect(() => { if ( selectedAnnotationId && - !annotationRegions.some((region) => region.id === selectedAnnotationId) + !annotationOnlyRegions.some((region) => region.id === selectedAnnotationId) ) { setSelectedAnnotationId(null); } - }, [selectedAnnotationId, annotationRegions]); + if (selectedBlurId && !blurRegions.some((region) => region.id === selectedBlurId)) { + setSelectedBlurId(null); + } + }, [selectedAnnotationId, selectedBlurId, annotationOnlyRegions, blurRegions]); useEffect(() => { if (selectedSpeedId && !speedRegions.some((region) => region.id === selectedSpeedId)) { @@ -1675,11 +1803,18 @@ export default function VideoEditor() { cropRegion={cropRegion} trimRegions={trimRegions} speedRegions={speedRegions} - annotationRegions={annotationRegions} + annotationRegions={annotationOnlyRegions} selectedAnnotationId={selectedAnnotationId} onSelectAnnotation={handleSelectAnnotation} onAnnotationPositionChange={handleAnnotationPositionChange} onAnnotationSizeChange={handleAnnotationSizeChange} + blurRegions={blurRegions} + selectedBlurId={selectedBlurId} + onSelectBlur={handleSelectBlur} + onBlurPositionChange={handleAnnotationPositionChange} + onBlurSizeChange={handleAnnotationSizeChange} + onBlurDataChange={handleBlurDataPreviewChange} + onBlurDataCommit={commitState} cursorTelemetry={cursorTelemetry} />
@@ -1732,12 +1867,18 @@ export default function VideoEditor() { onSpeedDelete={handleSpeedDelete} selectedSpeedId={selectedSpeedId} onSelectSpeed={handleSelectSpeed} - annotationRegions={annotationRegions} + annotationRegions={annotationOnlyRegions} onAnnotationAdded={handleAnnotationAdded} onAnnotationSpanChange={handleAnnotationSpanChange} onAnnotationDelete={handleAnnotationDelete} selectedAnnotationId={selectedAnnotationId} onSelectAnnotation={handleSelectAnnotation} + blurRegions={blurRegions} + onBlurAdded={handleBlurAdded} + onBlurSpanChange={handleAnnotationSpanChange} + onBlurDelete={handleAnnotationDelete} + selectedBlurId={selectedBlurId} + onSelectBlur={handleSelectBlur} aspectRatio={aspectRatio} onAspectRatioChange={(ar) => pushState({ @@ -1830,12 +1971,17 @@ export default function VideoEditor() { )} onExport={handleOpenExportDialog} selectedAnnotationId={selectedAnnotationId} - annotationRegions={annotationRegions} + annotationRegions={annotationOnlyRegions} onAnnotationContentChange={handleAnnotationContentChange} onAnnotationTypeChange={handleAnnotationTypeChange} onAnnotationStyleChange={handleAnnotationStyleChange} onAnnotationFigureDataChange={handleAnnotationFigureDataChange} onAnnotationDelete={handleAnnotationDelete} + selectedBlurId={selectedBlurId} + blurRegions={blurRegions} + onBlurDataChange={handleBlurDataPanelChange} + onBlurDataCommit={commitState} + onBlurDelete={handleAnnotationDelete} selectedSpeedId={selectedSpeedId} selectedSpeedValue={ selectedSpeedId diff --git a/src/components/video-editor/VideoPlayback.tsx b/src/components/video-editor/VideoPlayback.tsx index 08c1c25..acc3f2e 100644 --- a/src/components/video-editor/VideoPlayback.tsx +++ b/src/components/video-editor/VideoPlayback.tsx @@ -35,6 +35,7 @@ import { import { AnnotationOverlay } from "./AnnotationOverlay"; import { type AnnotationRegion, + type BlurData, type SpeedRegion, type TrimRegion, ZOOM_DEPTH_SCALES, @@ -101,6 +102,13 @@ interface VideoPlaybackProps { onSelectAnnotation?: (id: string | null) => void; onAnnotationPositionChange?: (id: string, position: { x: number; y: number }) => void; onAnnotationSizeChange?: (id: string, size: { width: number; height: number }) => void; + blurRegions?: AnnotationRegion[]; + selectedBlurId?: string | null; + onSelectBlur?: (id: string | null) => void; + onBlurPositionChange?: (id: string, position: { x: number; y: number }) => void; + onBlurSizeChange?: (id: string, size: { width: number; height: number }) => void; + onBlurDataChange?: (id: string, blurData: BlurData) => void; + onBlurDataCommit?: () => void; cursorTelemetry?: import("./types").CursorTelemetryPoint[]; } @@ -152,6 +160,13 @@ const VideoPlayback = forwardRef( onSelectAnnotation, onAnnotationPositionChange, onAnnotationSizeChange, + blurRegions = [], + selectedBlurId, + onSelectBlur, + onBlurPositionChange, + onBlurSizeChange, + onBlurDataChange, + onBlurDataCommit, cursorTelemetry = [], }, ref, @@ -166,6 +181,8 @@ const VideoPlayback = forwardRef( const timeUpdateAnimationRef = useRef(null); const [pixiReady, setPixiReady] = useState(false); const [videoReady, setVideoReady] = useState(false); + const [overlaySize, setOverlaySize] = useState({ width: 800, height: 600 }); + const [overlayElement, setOverlayElement] = useState(null); const overlayRef = useRef(null); const focusIndicatorRef = useRef(null); const [webcamLayout, setWebcamLayout] = useState(null); @@ -330,6 +347,11 @@ const VideoPlayback = forwardRef( layoutVideoContentRef.current = layoutVideoContent; }, [layoutVideoContent]); + const setOverlayRefs = useCallback((node: HTMLDivElement | null) => { + overlayRef.current = node; + setOverlayElement(node); + }, []); + const selectedZoom = useMemo(() => { if (!selectedZoomId) return null; return zoomRegions.find((region) => region.id === selectedZoomId) ?? null; @@ -623,7 +645,8 @@ const VideoPlayback = forwardRef( }, [selectedZoom, pixiReady, videoReady, updateOverlayForRegion]); useEffect(() => { - const overlayEl = overlayRef.current; + if (!pixiReady || !videoReady) return; + const overlayEl = overlayElement; if (!overlayEl) return; if (!selectedZoom) { overlayEl.style.cursor = "default"; @@ -632,7 +655,34 @@ const VideoPlayback = forwardRef( } overlayEl.style.cursor = isPlaying ? "not-allowed" : "grab"; overlayEl.style.pointerEvents = isPlaying ? "none" : "auto"; - }, [selectedZoom, isPlaying]); + }, [selectedZoom, isPlaying, pixiReady, videoReady, overlayElement]); + + useEffect(() => { + const overlayEl = overlayElement; + if (!overlayEl) return; + + const updateOverlaySize = () => { + const width = overlayEl.clientWidth || 800; + const height = overlayEl.clientHeight || 600; + setOverlaySize((prev) => { + if (prev.width === width && prev.height === height) return prev; + return { width, height }; + }); + }; + + updateOverlaySize(); + + if (typeof ResizeObserver !== "undefined") { + const observer = new ResizeObserver(() => { + updateOverlaySize(); + }); + observer.observe(overlayEl); + return () => observer.disconnect(); + } + + window.addEventListener("resize", updateOverlaySize); + return () => window.removeEventListener("resize", updateOverlaySize); + }, [overlayElement]); useEffect(() => { const container = containerRef.current; @@ -1287,7 +1337,7 @@ const VideoPlayback = forwardRef( {/* Only render overlay after PIXI and video are fully initialized */} {pixiReady && videoReady && (
( style={{ display: "none", pointerEvents: "none" }} /> {(() => { - const filtered = (annotationRegions || []).filter((annotation) => { + const filteredAnnotations = (annotationRegions || []).filter((annotation) => { if (typeof annotation.startMs !== "number" || typeof annotation.endMs !== "number") return false; @@ -1311,37 +1361,93 @@ const VideoPlayback = forwardRef( return timeMs >= annotation.startMs && timeMs <= annotation.endMs; }); - // Sort by z-index (lowest to highest) so higher z-index renders on top - const sorted = [...filtered].sort((a, b) => a.zIndex - b.zIndex); + const filteredBlurRegions = (blurRegions || []).filter((blurRegion) => { + if (typeof blurRegion.startMs !== "number" || typeof blurRegion.endMs !== "number") + return false; + + if (blurRegion.id === selectedBlurId) return true; + + const timeMs = Math.round(currentTime * 1000); + return timeMs >= blurRegion.startMs && timeMs <= blurRegion.endMs; + }); + + const sorted = [ + ...filteredAnnotations.map((annotation) => ({ + kind: "annotation" as const, + region: annotation, + })), + ...filteredBlurRegions.map((blurRegion) => ({ + kind: "blur" as const, + region: blurRegion, + })), + ].sort((a, b) => a.region.zIndex - b.region.zIndex); // Handle click-through cycling: when clicking same annotation, cycle to next const handleAnnotationClick = (clickedId: string) => { if (!onSelectAnnotation) return; // If clicking on already selected annotation and there are multiple overlapping - if (clickedId === selectedAnnotationId && sorted.length > 1) { + if (clickedId === selectedAnnotationId && filteredAnnotations.length > 1) { // Find current index and cycle to next - const currentIndex = sorted.findIndex((a) => a.id === clickedId); - const nextIndex = (currentIndex + 1) % sorted.length; - onSelectAnnotation(sorted[nextIndex].id); + const currentIndex = filteredAnnotations.findIndex((a) => a.id === clickedId); + const nextIndex = (currentIndex + 1) % filteredAnnotations.length; + onSelectAnnotation(filteredAnnotations[nextIndex].id); } else { // First click or clicking different annotation onSelectAnnotation(clickedId); } }; - return sorted.map((annotation) => ( + const handleBlurClick = (clickedId: string) => { + if (!onSelectBlur) return; + + if (clickedId === selectedBlurId && filteredBlurRegions.length > 1) { + const currentIndex = filteredBlurRegions.findIndex((a) => a.id === clickedId); + const nextIndex = (currentIndex + 1) % filteredBlurRegions.length; + onSelectBlur(filteredBlurRegions[nextIndex].id); + } else { + onSelectBlur(clickedId); + } + }; + + return sorted.map((item) => ( onAnnotationPositionChange?.(id, position)} - onSizeChange={(id, size) => onAnnotationSizeChange?.(id, size)} - onClick={handleAnnotationClick} - zIndex={annotation.zIndex} - isSelectedBoost={annotation.id === selectedAnnotationId} + key={ + item.kind === "blur" + ? `${item.region.id}-${overlaySize.width}-${overlaySize.height}-${item.region.blurData?.shape ?? "rectangle"}-${(item.region.blurData?.freehandPoints ?? []).map((p) => `${Math.round(p.x)}_${Math.round(p.y)}`).join("-")}` + : `${item.region.id}-${overlaySize.width}-${overlaySize.height}` + } + annotation={item.region} + isSelected={ + item.kind === "blur" + ? item.region.id === selectedBlurId + : item.region.id === selectedAnnotationId + } + containerWidth={overlaySize.width} + containerHeight={overlaySize.height} + onPositionChange={(id, position) => + item.kind === "blur" + ? onBlurPositionChange?.(id, position) + : onAnnotationPositionChange?.(id, position) + } + onSizeChange={(id, size) => + item.kind === "blur" + ? onBlurSizeChange?.(id, size) + : onAnnotationSizeChange?.(id, size) + } + onBlurDataChange={ + item.kind === "blur" + ? (id, blurData) => onBlurDataChange?.(id, blurData) + : undefined + } + onBlurDataCommit={item.kind === "blur" ? onBlurDataCommit : undefined} + onClick={item.kind === "blur" ? handleBlurClick : handleAnnotationClick} + zIndex={item.region.zIndex} + isSelectedBoost={ + item.kind === "blur" + ? item.region.id === selectedBlurId + : item.region.id === selectedAnnotationId + } /> )); })()} diff --git a/src/components/video-editor/projectPersistence.ts b/src/components/video-editor/projectPersistence.ts index 45513d4..ce0c751 100644 --- a/src/components/video-editor/projectPersistence.ts +++ b/src/components/video-editor/projectPersistence.ts @@ -9,6 +9,9 @@ import { DEFAULT_ANNOTATION_POSITION, DEFAULT_ANNOTATION_SIZE, DEFAULT_ANNOTATION_STYLE, + DEFAULT_BLUR_DATA, + DEFAULT_BLUR_FREEHAND_POINTS, + DEFAULT_BLUR_INTENSITY, DEFAULT_CROP_REGION, DEFAULT_FIGURE_DATA, DEFAULT_PLAYBACK_SPEED, @@ -17,7 +20,9 @@ import { DEFAULT_WEBCAM_POSITION, DEFAULT_WEBCAM_SIZE_PRESET, DEFAULT_ZOOM_DEPTH, + MAX_BLUR_INTENSITY, MAX_PLAYBACK_SPEED, + MIN_BLUR_INTENSITY, MIN_PLAYBACK_SPEED, type SpeedRegion, type TrimRegion, @@ -29,6 +34,7 @@ import { } from "./types"; const WALLPAPER_COUNT = 18; +const VALID_BLUR_SHAPES = new Set(["rectangle", "oval", "freehand"] as const); export const WALLPAPER_PATHS = Array.from( { length: WALLPAPER_COUNT }, @@ -254,12 +260,20 @@ export function normalizeProjectEditor(editor: Partial): Pro const rawEnd = isFiniteNumber(region.endMs) ? Math.round(region.endMs) : rawStart + 1000; const startMs = Math.max(0, Math.min(rawStart, rawEnd)); const endMs = Math.max(startMs + 1, rawEnd); + const blurShape = + typeof region.blurData?.shape === "string" && + VALID_BLUR_SHAPES.has(region.blurData.shape) + ? region.blurData.shape + : DEFAULT_BLUR_DATA.shape; return { id: region.id, startMs, endMs, - type: region.type === "image" || region.type === "figure" ? region.type : "text", + type: + region.type === "image" || region.type === "figure" || region.type === "blur" + ? region.type + : "text", content: typeof region.content === "string" ? region.content : "", textContent: typeof region.textContent === "string" ? region.textContent : undefined, imageContent: typeof region.imageContent === "string" ? region.imageContent : undefined, @@ -306,6 +320,37 @@ export function normalizeProjectEditor(editor: Partial): Pro ...region.figureData, } : undefined, + blurData: + region.blurData && typeof region.blurData === "object" + ? { + ...DEFAULT_BLUR_DATA, + ...region.blurData, + shape: blurShape, + intensity: isFiniteNumber(region.blurData.intensity) + ? clamp(region.blurData.intensity, MIN_BLUR_INTENSITY, MAX_BLUR_INTENSITY) + : DEFAULT_BLUR_INTENSITY, + freehandPoints: Array.isArray(region.blurData.freehandPoints) + ? region.blurData.freehandPoints + .filter( + ( + point, + ): point is { + x: number; + y: number; + } => + Boolean( + point && + isFiniteNumber((point as { x?: unknown }).x) && + isFiniteNumber((point as { y?: unknown }).y), + ), + ) + .map((point) => ({ + x: clamp(point.x, 0, 100), + y: clamp(point.y, 0, 100), + })) + : DEFAULT_BLUR_FREEHAND_POINTS, + } + : undefined, }; }) : []; diff --git a/src/components/video-editor/timeline/Item.tsx b/src/components/video-editor/timeline/Item.tsx index f265fe4..d2d0298 100644 --- a/src/components/video-editor/timeline/Item.tsx +++ b/src/components/video-editor/timeline/Item.tsx @@ -14,7 +14,7 @@ interface ItemProps { onSelect?: () => void; zoomDepth?: number; speedValue?: number; - variant?: "zoom" | "trim" | "annotation" | "speed"; + variant?: "zoom" | "trim" | "annotation" | "speed" | "blur"; } // Map zoom depth to multiplier labels diff --git a/src/components/video-editor/timeline/TimelineEditor.tsx b/src/components/video-editor/timeline/TimelineEditor.tsx index b64aad0..5ef8c7b 100644 --- a/src/components/video-editor/timeline/TimelineEditor.tsx +++ b/src/components/video-editor/timeline/TimelineEditor.tsx @@ -44,6 +44,7 @@ import { detectZoomDwellCandidates, normalizeCursorTelemetry } from "./zoomSugge const ZOOM_ROW_ID = "row-zoom"; const TRIM_ROW_ID = "row-trim"; const ANNOTATION_ROW_ID = "row-annotation"; +const BLUR_ROW_ID = "row-blur"; const SPEED_ROW_ID = "row-speed"; const FALLBACK_RANGE_MS = 1000; const TARGET_MARKER_COUNT = 12; @@ -73,6 +74,12 @@ interface TimelineEditorProps { onAnnotationDelete?: (id: string) => void; selectedAnnotationId?: string | null; onSelectAnnotation?: (id: string | null) => void; + blurRegions?: AnnotationRegion[]; + onBlurAdded?: (span: Span) => void; + onBlurSpanChange?: (id: string, span: Span) => void; + onBlurDelete?: (id: string) => void; + selectedBlurId?: string | null; + onSelectBlur?: (id: string | null) => void; speedRegions?: SpeedRegion[]; onSpeedAdded?: (span: Span) => void; onSpeedSpanChange?: (id: string, span: Span) => void; @@ -96,7 +103,7 @@ interface TimelineRenderItem { label: string; zoomDepth?: number; speedValue?: number; - variant: "zoom" | "trim" | "annotation" | "speed"; + variant: "zoom" | "trim" | "annotation" | "speed" | "blur"; } const SCALE_CANDIDATES = [ @@ -525,10 +532,12 @@ function Timeline({ onSelectZoom, onSelectTrim, onSelectAnnotation, + onSelectBlur, onSelectSpeed, selectedZoomId, selectedTrimId, selectedAnnotationId, + selectedBlurId, selectedSpeedId, keyframes = [], }: { @@ -540,10 +549,12 @@ function Timeline({ onSelectZoom?: (id: string | null) => void; onSelectTrim?: (id: string | null) => void; onSelectAnnotation?: (id: string | null) => void; + onSelectBlur?: (id: string | null) => void; onSelectSpeed?: (id: string | null) => void; selectedZoomId: string | null; selectedTrimId?: string | null; selectedAnnotationId?: string | null; + selectedBlurId?: string | null; selectedSpeedId?: string | null; keyframes?: { id: string; time: number }[]; }) { @@ -568,6 +579,7 @@ function Timeline({ onSelectZoom?.(null); onSelectTrim?.(null); onSelectAnnotation?.(null); + onSelectBlur?.(null); onSelectSpeed?.(null); const rect = e.currentTarget.getBoundingClientRect(); @@ -586,6 +598,7 @@ function Timeline({ onSelectZoom, onSelectTrim, onSelectAnnotation, + onSelectBlur, onSelectSpeed, videoDurationMs, sidebarWidth, @@ -637,6 +650,7 @@ function Timeline({ const zoomItems = items.filter((item) => item.rowId === ZOOM_ROW_ID); const trimItems = items.filter((item) => item.rowId === TRIM_ROW_ID); const annotationItems = items.filter((item) => item.rowId === ANNOTATION_ROW_ID); + const blurItems = items.filter((item) => item.rowId === BLUR_ROW_ID); const speedItems = items.filter((item) => item.rowId === SPEED_ROW_ID); return ( @@ -711,6 +725,22 @@ function Timeline({ ))} + + {blurItems.map((item) => ( + onSelectBlur?.(item.id)} + variant={item.variant} + > + {item.label} + + ))} + + {speedItems.map((item) => ( { + if (!selectedBlurId || !onBlurDelete || !onSelectBlur) return; + onBlurDelete(selectedBlurId); + onSelectBlur(null); + }, [selectedBlurId, onBlurDelete, onSelectBlur]); + const deleteSelectedSpeed = useCallback(() => { if (!selectedSpeedId || !onSpeedDelete || !onSelectSpeed) return; onSpeedDelete(selectedSpeedId); @@ -908,9 +950,10 @@ export default function TimelineEditor({ const isZoomItem = zoomRegions.some((r) => r.id === excludeId); const isTrimItem = trimRegions.some((r) => r.id === excludeId); const isAnnotationItem = annotationRegions.some((r) => r.id === excludeId); + const isBlurItem = blurRegions.some((r) => r.id === excludeId); const isSpeedItem = speedRegions.some((r) => r.id === excludeId); - if (isAnnotationItem) { + if (isAnnotationItem || isBlurItem) { return false; } @@ -937,7 +980,7 @@ export default function TimelineEditor({ return false; }, - [zoomRegions, trimRegions, annotationRegions, speedRegions], + [zoomRegions, trimRegions, annotationRegions, blurRegions, speedRegions], ); // At least 5% of the timeline or 1000ms, whichever is larger, so the region @@ -1165,6 +1208,21 @@ export default function TimelineEditor({ onAnnotationAdded({ start: startPos, end: endPos }); }, [videoDuration, totalMs, currentTimeMs, onAnnotationAdded, defaultRegionDurationMs]); + const handleAddBlur = useCallback(() => { + if (!videoDuration || videoDuration === 0 || totalMs === 0 || !onBlurAdded) { + return; + } + + const defaultDuration = Math.min(defaultRegionDurationMs, totalMs); + if (defaultDuration <= 0) { + return; + } + + const startPos = Math.max(0, Math.min(currentTimeMs, totalMs)); + const endPos = Math.min(startPos + defaultDuration, totalMs); + onBlurAdded({ start: startPos, end: endPos }); + }, [videoDuration, totalMs, currentTimeMs, onBlurAdded, defaultRegionDurationMs]); + useEffect(() => { const handleKeyDown = (e: KeyboardEvent) => { if (e.target instanceof HTMLInputElement || e.target instanceof HTMLTextAreaElement) { @@ -1183,6 +1241,9 @@ export default function TimelineEditor({ if (matchesShortcut(e, keyShortcuts.addAnnotation, isMac)) { handleAddAnnotation(); } + if (matchesShortcut(e, keyShortcuts.addBlur, isMac)) { + handleAddBlur(); + } if (matchesShortcut(e, keyShortcuts.addSpeed, isMac)) { handleAddSpeed(); } @@ -1223,6 +1284,8 @@ export default function TimelineEditor({ deleteSelectedTrim(); } else if (selectedAnnotationId) { deleteSelectedAnnotation(); + } else if (selectedBlurId) { + deleteSelectedBlur(); } else if (selectedSpeedId) { deleteSelectedSpeed(); } @@ -1235,18 +1298,22 @@ export default function TimelineEditor({ handleAddZoom, handleAddTrim, handleAddAnnotation, + handleAddBlur, handleAddSpeed, deleteSelectedKeyframe, deleteSelectedZoom, deleteSelectedTrim, deleteSelectedAnnotation, + deleteSelectedBlur, deleteSelectedSpeed, selectedKeyframeId, selectedZoomId, selectedTrimId, selectedAnnotationId, + selectedBlurId, selectedSpeedId, annotationRegions, + blurRegions, currentTime, onSelectAnnotation, keyShortcuts, @@ -1304,6 +1371,14 @@ export default function TimelineEditor({ }; }); + const blurs: TimelineRenderItem[] = blurRegions.map((region, index) => ({ + id: region.id, + rowId: BLUR_ROW_ID, + span: { start: region.startMs, end: region.endMs }, + label: t("labels.blurItem", { index: String(index + 1) }), + variant: "blur", + })); + const speeds: TimelineRenderItem[] = speedRegions.map((region, index) => ({ id: region.id, rowId: SPEED_ROW_ID, @@ -1313,8 +1388,8 @@ export default function TimelineEditor({ variant: "speed", })); - return [...zooms, ...trims, ...annotations, ...speeds]; - }, [zoomRegions, trimRegions, annotationRegions, speedRegions, t]); + return [...zooms, ...trims, ...annotations, ...blurs, ...speeds]; + }, [zoomRegions, trimRegions, annotationRegions, blurRegions, speedRegions, t]); // Flat list of all non-annotation region spans for neighbour-clamping during drag/resize const allRegionSpans = useMemo(() => { @@ -1335,6 +1410,8 @@ export default function TimelineEditor({ onSpeedSpanChange?.(id, span); } else if (annotationRegions.some((r) => r.id === id)) { onAnnotationSpanChange?.(id, span); + } else if (blurRegions.some((r) => r.id === id)) { + onBlurSpanChange?.(id, span); } }, [ @@ -1342,10 +1419,12 @@ export default function TimelineEditor({ trimRegions, speedRegions, annotationRegions, + blurRegions, onZoomSpanChange, onTrimSpanChange, onSpeedSpanChange, onAnnotationSpanChange, + onBlurSpanChange, ], ); @@ -1403,6 +1482,25 @@ export default function TimelineEditor({ > +