diff --git a/package-lock.json b/package-lock.json
index 60f82ca..a4918d3 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -85,6 +85,7 @@
"integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==",
"dev": true,
"license": "MIT",
+ "peer": true,
"dependencies": {
"@babel/code-frame": "^7.29.0",
"@babel/generator": "^7.29.0",
@@ -847,9 +848,9 @@
}
},
"node_modules/@eslint/config-array/node_modules/minimatch": {
- "version": "3.1.2",
- "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
- "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
+ "version": "3.1.5",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz",
+ "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==",
"dev": true,
"license": "ISC",
"dependencies": {
@@ -934,9 +935,9 @@
}
},
"node_modules/@eslint/eslintrc/node_modules/minimatch": {
- "version": "3.1.2",
- "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
- "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
+ "version": "3.1.5",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz",
+ "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==",
"dev": true,
"license": "ISC",
"dependencies": {
@@ -1093,9 +1094,9 @@
"license": "MIT"
},
"node_modules/@rollup/rollup-android-arm-eabi": {
- "version": "4.57.1",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.57.1.tgz",
- "integrity": "sha512-A6ehUVSiSaaliTxai040ZpZ2zTevHYbvu/lDoeAteHI8QnaosIzm4qwtezfRg1jOYaUmnzLX1AOD6Z+UJjtifg==",
+ "version": "4.59.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.59.0.tgz",
+ "integrity": "sha512-upnNBkA6ZH2VKGcBj9Fyl9IGNPULcjXRlg0LLeaioQWueH30p6IXtJEbKAgvyv+mJaMxSm1l6xwDXYjpEMiLMg==",
"cpu": [
"arm"
],
@@ -1107,9 +1108,9 @@
]
},
"node_modules/@rollup/rollup-android-arm64": {
- "version": "4.57.1",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.57.1.tgz",
- "integrity": "sha512-dQaAddCY9YgkFHZcFNS/606Exo8vcLHwArFZ7vxXq4rigo2bb494/xKMMwRRQW6ug7Js6yXmBZhSBRuBvCCQ3w==",
+ "version": "4.59.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.59.0.tgz",
+ "integrity": "sha512-hZ+Zxj3SySm4A/DylsDKZAeVg0mvi++0PYVceVyX7hemkw7OreKdCvW2oQ3T1FMZvCaQXqOTHb8qmBShoqk69Q==",
"cpu": [
"arm64"
],
@@ -1121,9 +1122,9 @@
]
},
"node_modules/@rollup/rollup-darwin-arm64": {
- "version": "4.57.1",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.57.1.tgz",
- "integrity": "sha512-crNPrwJOrRxagUYeMn/DZwqN88SDmwaJ8Cvi/TN1HnWBU7GwknckyosC2gd0IqYRsHDEnXf328o9/HC6OkPgOg==",
+ "version": "4.59.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.59.0.tgz",
+ "integrity": "sha512-W2Psnbh1J8ZJw0xKAd8zdNgF9HRLkdWwwdWqubSVk0pUuQkoHnv7rx4GiF9rT4t5DIZGAsConRE3AxCdJ4m8rg==",
"cpu": [
"arm64"
],
@@ -1135,9 +1136,9 @@
]
},
"node_modules/@rollup/rollup-darwin-x64": {
- "version": "4.57.1",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.57.1.tgz",
- "integrity": "sha512-Ji8g8ChVbKrhFtig5QBV7iMaJrGtpHelkB3lsaKzadFBe58gmjfGXAOfI5FV0lYMH8wiqsxKQ1C9B0YTRXVy4w==",
+ "version": "4.59.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.59.0.tgz",
+ "integrity": "sha512-ZW2KkwlS4lwTv7ZVsYDiARfFCnSGhzYPdiOU4IM2fDbL+QGlyAbjgSFuqNRbSthybLbIJ915UtZBtmuLrQAT/w==",
"cpu": [
"x64"
],
@@ -1149,9 +1150,9 @@
]
},
"node_modules/@rollup/rollup-freebsd-arm64": {
- "version": "4.57.1",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.57.1.tgz",
- "integrity": "sha512-R+/WwhsjmwodAcz65guCGFRkMb4gKWTcIeLy60JJQbXrJ97BOXHxnkPFrP+YwFlaS0m+uWJTstrUA9o+UchFug==",
+ "version": "4.59.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.59.0.tgz",
+ "integrity": "sha512-EsKaJ5ytAu9jI3lonzn3BgG8iRBjV4LxZexygcQbpiU0wU0ATxhNVEpXKfUa0pS05gTcSDMKpn3Sx+QB9RlTTA==",
"cpu": [
"arm64"
],
@@ -1163,9 +1164,9 @@
]
},
"node_modules/@rollup/rollup-freebsd-x64": {
- "version": "4.57.1",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.57.1.tgz",
- "integrity": "sha512-IEQTCHeiTOnAUC3IDQdzRAGj3jOAYNr9kBguI7MQAAZK3caezRrg0GxAb6Hchg4lxdZEI5Oq3iov/w/hnFWY9Q==",
+ "version": "4.59.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.59.0.tgz",
+ "integrity": "sha512-d3DuZi2KzTMjImrxoHIAODUZYoUUMsuUiY4SRRcJy6NJoZ6iIqWnJu9IScV9jXysyGMVuW+KNzZvBLOcpdl3Vg==",
"cpu": [
"x64"
],
@@ -1177,9 +1178,9 @@
]
},
"node_modules/@rollup/rollup-linux-arm-gnueabihf": {
- "version": "4.57.1",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.57.1.tgz",
- "integrity": "sha512-F8sWbhZ7tyuEfsmOxwc2giKDQzN3+kuBLPwwZGyVkLlKGdV1nvnNwYD0fKQ8+XS6hp9nY7B+ZeK01EBUE7aHaw==",
+ "version": "4.59.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.59.0.tgz",
+ "integrity": "sha512-t4ONHboXi/3E0rT6OZl1pKbl2Vgxf9vJfWgmUoCEVQVxhW6Cw/c8I6hbbu7DAvgp82RKiH7TpLwxnJeKv2pbsw==",
"cpu": [
"arm"
],
@@ -1191,9 +1192,9 @@
]
},
"node_modules/@rollup/rollup-linux-arm-musleabihf": {
- "version": "4.57.1",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.57.1.tgz",
- "integrity": "sha512-rGfNUfn0GIeXtBP1wL5MnzSj98+PZe/AXaGBCRmT0ts80lU5CATYGxXukeTX39XBKsxzFpEeK+Mrp9faXOlmrw==",
+ "version": "4.59.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.59.0.tgz",
+ "integrity": "sha512-CikFT7aYPA2ufMD086cVORBYGHffBo4K8MQ4uPS/ZnY54GKj36i196u8U+aDVT2LX4eSMbyHtyOh7D7Zvk2VvA==",
"cpu": [
"arm"
],
@@ -1205,9 +1206,9 @@
]
},
"node_modules/@rollup/rollup-linux-arm64-gnu": {
- "version": "4.57.1",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.57.1.tgz",
- "integrity": "sha512-MMtej3YHWeg/0klK2Qodf3yrNzz6CGjo2UntLvk2RSPlhzgLvYEB3frRvbEF2wRKh1Z2fDIg9KRPe1fawv7C+g==",
+ "version": "4.59.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.59.0.tgz",
+ "integrity": "sha512-jYgUGk5aLd1nUb1CtQ8E+t5JhLc9x5WdBKew9ZgAXg7DBk0ZHErLHdXM24rfX+bKrFe+Xp5YuJo54I5HFjGDAA==",
"cpu": [
"arm64"
],
@@ -1219,9 +1220,9 @@
]
},
"node_modules/@rollup/rollup-linux-arm64-musl": {
- "version": "4.57.1",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.57.1.tgz",
- "integrity": "sha512-1a/qhaaOXhqXGpMFMET9VqwZakkljWHLmZOX48R0I/YLbhdxr1m4gtG1Hq7++VhVUmf+L3sTAf9op4JlhQ5u1Q==",
+ "version": "4.59.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.59.0.tgz",
+ "integrity": "sha512-peZRVEdnFWZ5Bh2KeumKG9ty7aCXzzEsHShOZEFiCQlDEepP1dpUl/SrUNXNg13UmZl+gzVDPsiCwnV1uI0RUA==",
"cpu": [
"arm64"
],
@@ -1233,9 +1234,9 @@
]
},
"node_modules/@rollup/rollup-linux-loong64-gnu": {
- "version": "4.57.1",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.57.1.tgz",
- "integrity": "sha512-QWO6RQTZ/cqYtJMtxhkRkidoNGXc7ERPbZN7dVW5SdURuLeVU7lwKMpo18XdcmpWYd0qsP1bwKPf7DNSUinhvA==",
+ "version": "4.59.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.59.0.tgz",
+ "integrity": "sha512-gbUSW/97f7+r4gHy3Jlup8zDG190AuodsWnNiXErp9mT90iCy9NKKU0Xwx5k8VlRAIV2uU9CsMnEFg/xXaOfXg==",
"cpu": [
"loong64"
],
@@ -1247,9 +1248,9 @@
]
},
"node_modules/@rollup/rollup-linux-loong64-musl": {
- "version": "4.57.1",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.57.1.tgz",
- "integrity": "sha512-xpObYIf+8gprgWaPP32xiN5RVTi/s5FCR+XMXSKmhfoJjrpRAjCuuqQXyxUa/eJTdAE6eJ+KDKaoEqjZQxh3Gw==",
+ "version": "4.59.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.59.0.tgz",
+ "integrity": "sha512-yTRONe79E+o0FWFijasoTjtzG9EBedFXJMl888NBEDCDV9I2wGbFFfJQQe63OijbFCUZqxpHz1GzpbtSFikJ4Q==",
"cpu": [
"loong64"
],
@@ -1261,9 +1262,9 @@
]
},
"node_modules/@rollup/rollup-linux-ppc64-gnu": {
- "version": "4.57.1",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.57.1.tgz",
- "integrity": "sha512-4BrCgrpZo4hvzMDKRqEaW1zeecScDCR+2nZ86ATLhAoJ5FQ+lbHVD3ttKe74/c7tNT9c6F2viwB3ufwp01Oh2w==",
+ "version": "4.59.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.59.0.tgz",
+ "integrity": "sha512-sw1o3tfyk12k3OEpRddF68a1unZ5VCN7zoTNtSn2KndUE+ea3m3ROOKRCZxEpmT9nsGnogpFP9x6mnLTCaoLkA==",
"cpu": [
"ppc64"
],
@@ -1275,9 +1276,9 @@
]
},
"node_modules/@rollup/rollup-linux-ppc64-musl": {
- "version": "4.57.1",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.57.1.tgz",
- "integrity": "sha512-NOlUuzesGauESAyEYFSe3QTUguL+lvrN1HtwEEsU2rOwdUDeTMJdO5dUYl/2hKf9jWydJrO9OL/XSSf65R5+Xw==",
+ "version": "4.59.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.59.0.tgz",
+ "integrity": "sha512-+2kLtQ4xT3AiIxkzFVFXfsmlZiG5FXYW7ZyIIvGA7Bdeuh9Z0aN4hVyXS/G1E9bTP/vqszNIN/pUKCk/BTHsKA==",
"cpu": [
"ppc64"
],
@@ -1289,9 +1290,9 @@
]
},
"node_modules/@rollup/rollup-linux-riscv64-gnu": {
- "version": "4.57.1",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.57.1.tgz",
- "integrity": "sha512-ptA88htVp0AwUUqhVghwDIKlvJMD/fmL/wrQj99PRHFRAG6Z5nbWoWG4o81Nt9FT+IuqUQi+L31ZKAFeJ5Is+A==",
+ "version": "4.59.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.59.0.tgz",
+ "integrity": "sha512-NDYMpsXYJJaj+I7UdwIuHHNxXZ/b/N2hR15NyH3m2qAtb/hHPA4g4SuuvrdxetTdndfj9b1WOmy73kcPRoERUg==",
"cpu": [
"riscv64"
],
@@ -1303,9 +1304,9 @@
]
},
"node_modules/@rollup/rollup-linux-riscv64-musl": {
- "version": "4.57.1",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.57.1.tgz",
- "integrity": "sha512-S51t7aMMTNdmAMPpBg7OOsTdn4tySRQvklmL3RpDRyknk87+Sp3xaumlatU+ppQ+5raY7sSTcC2beGgvhENfuw==",
+ "version": "4.59.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.59.0.tgz",
+ "integrity": "sha512-nLckB8WOqHIf1bhymk+oHxvM9D3tyPndZH8i8+35p/1YiVoVswPid2yLzgX7ZJP0KQvnkhM4H6QZ5m0LzbyIAg==",
"cpu": [
"riscv64"
],
@@ -1317,9 +1318,9 @@
]
},
"node_modules/@rollup/rollup-linux-s390x-gnu": {
- "version": "4.57.1",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.57.1.tgz",
- "integrity": "sha512-Bl00OFnVFkL82FHbEqy3k5CUCKH6OEJL54KCyx2oqsmZnFTR8IoNqBF+mjQVcRCT5sB6yOvK8A37LNm/kPJiZg==",
+ "version": "4.59.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.59.0.tgz",
+ "integrity": "sha512-oF87Ie3uAIvORFBpwnCvUzdeYUqi2wY6jRFWJAy1qus/udHFYIkplYRW+wo+GRUP4sKzYdmE1Y3+rY5Gc4ZO+w==",
"cpu": [
"s390x"
],
@@ -1331,9 +1332,9 @@
]
},
"node_modules/@rollup/rollup-linux-x64-gnu": {
- "version": "4.57.1",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.57.1.tgz",
- "integrity": "sha512-ABca4ceT4N+Tv/GtotnWAeXZUZuM/9AQyCyKYyKnpk4yoA7QIAuBt6Hkgpw8kActYlew2mvckXkvx0FfoInnLg==",
+ "version": "4.59.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.59.0.tgz",
+ "integrity": "sha512-3AHmtQq/ppNuUspKAlvA8HtLybkDflkMuLK4DPo77DfthRb71V84/c4MlWJXixZz4uruIH4uaa07IqoAkG64fg==",
"cpu": [
"x64"
],
@@ -1345,9 +1346,9 @@
]
},
"node_modules/@rollup/rollup-linux-x64-musl": {
- "version": "4.57.1",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.57.1.tgz",
- "integrity": "sha512-HFps0JeGtuOR2convgRRkHCekD7j+gdAuXM+/i6kGzQtFhlCtQkpwtNzkNj6QhCDp7DRJ7+qC/1Vg2jt5iSOFw==",
+ "version": "4.59.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.59.0.tgz",
+ "integrity": "sha512-2UdiwS/9cTAx7qIUZB/fWtToJwvt0Vbo0zmnYt7ED35KPg13Q0ym1g442THLC7VyI6JfYTP4PiSOWyoMdV2/xg==",
"cpu": [
"x64"
],
@@ -1359,9 +1360,9 @@
]
},
"node_modules/@rollup/rollup-openbsd-x64": {
- "version": "4.57.1",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.57.1.tgz",
- "integrity": "sha512-H+hXEv9gdVQuDTgnqD+SQffoWoc0Of59AStSzTEj/feWTBAnSfSD3+Dql1ZruJQxmykT/JVY0dE8Ka7z0DH1hw==",
+ "version": "4.59.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.59.0.tgz",
+ "integrity": "sha512-M3bLRAVk6GOwFlPTIxVBSYKUaqfLrn8l0psKinkCFxl4lQvOSz8ZrKDz2gxcBwHFpci0B6rttydI4IpS4IS/jQ==",
"cpu": [
"x64"
],
@@ -1373,9 +1374,9 @@
]
},
"node_modules/@rollup/rollup-openharmony-arm64": {
- "version": "4.57.1",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.57.1.tgz",
- "integrity": "sha512-4wYoDpNg6o/oPximyc/NG+mYUejZrCU2q+2w6YZqrAs2UcNUChIZXjtafAiiZSUc7On8v5NyNj34Kzj/Ltk6dQ==",
+ "version": "4.59.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.59.0.tgz",
+ "integrity": "sha512-tt9KBJqaqp5i5HUZzoafHZX8b5Q2Fe7UjYERADll83O4fGqJ49O1FsL6LpdzVFQcpwvnyd0i+K/VSwu/o/nWlA==",
"cpu": [
"arm64"
],
@@ -1387,9 +1388,9 @@
]
},
"node_modules/@rollup/rollup-win32-arm64-msvc": {
- "version": "4.57.1",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.57.1.tgz",
- "integrity": "sha512-O54mtsV/6LW3P8qdTcamQmuC990HDfR71lo44oZMZlXU4tzLrbvTii87Ni9opq60ds0YzuAlEr/GNwuNluZyMQ==",
+ "version": "4.59.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.59.0.tgz",
+ "integrity": "sha512-V5B6mG7OrGTwnxaNUzZTDTjDS7F75PO1ae6MJYdiMu60sq0CqN5CVeVsbhPxalupvTX8gXVSU9gq+Rx1/hvu6A==",
"cpu": [
"arm64"
],
@@ -1401,9 +1402,9 @@
]
},
"node_modules/@rollup/rollup-win32-ia32-msvc": {
- "version": "4.57.1",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.57.1.tgz",
- "integrity": "sha512-P3dLS+IerxCT/7D2q2FYcRdWRl22dNbrbBEtxdWhXrfIMPP9lQhb5h4Du04mdl5Woq05jVCDPCMF7Ub0NAjIew==",
+ "version": "4.59.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.59.0.tgz",
+ "integrity": "sha512-UKFMHPuM9R0iBegwzKF4y0C4J9u8C6MEJgFuXTBerMk7EJ92GFVFYBfOZaSGLu6COf7FxpQNqhNS4c4icUPqxA==",
"cpu": [
"ia32"
],
@@ -1415,9 +1416,9 @@
]
},
"node_modules/@rollup/rollup-win32-x64-gnu": {
- "version": "4.57.1",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.57.1.tgz",
- "integrity": "sha512-VMBH2eOOaKGtIJYleXsi2B8CPVADrh+TyNxJ4mWPnKfLB/DBUmzW+5m1xUrcwWoMfSLagIRpjUFeW5CO5hyciQ==",
+ "version": "4.59.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.59.0.tgz",
+ "integrity": "sha512-laBkYlSS1n2L8fSo1thDNGrCTQMmxjYY5G0WFWjFFYZkKPjsMBsgJfGf4TLxXrF6RyhI60L8TMOjBMvXiTcxeA==",
"cpu": [
"x64"
],
@@ -1429,9 +1430,9 @@
]
},
"node_modules/@rollup/rollup-win32-x64-msvc": {
- "version": "4.57.1",
- "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.57.1.tgz",
- "integrity": "sha512-mxRFDdHIWRxg3UfIIAwCm6NzvxG0jDX/wBN6KsQFTvKFqqg9vTrWUE68qEjHt19A5wwx5X5aUi2zuZT7YR0jrA==",
+ "version": "4.59.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.59.0.tgz",
+ "integrity": "sha512-2HRCml6OztYXyJXAvdDXPKcawukWY2GpR5/nxKp4iBgiO3wcoEGkAaqctIbZcNB6KlUQBIqt8VYkNSj2397EfA==",
"cpu": [
"x64"
],
@@ -1528,6 +1529,7 @@
"integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
"dev": true,
"license": "MIT",
+ "peer": true,
"bin": {
"acorn": "bin/acorn"
},
@@ -1546,9 +1548,9 @@
}
},
"node_modules/ajv": {
- "version": "6.12.6",
- "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
- "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
+ "version": "6.14.0",
+ "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.14.0.tgz",
+ "integrity": "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -1672,6 +1674,7 @@
}
],
"license": "MIT",
+ "peer": true,
"dependencies": {
"baseline-browser-mapping": "^2.9.0",
"caniuse-lite": "^1.0.30001759",
@@ -1956,6 +1959,7 @@
"integrity": "sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw==",
"dev": true,
"license": "MIT",
+ "peer": true,
"dependencies": {
"@eslint-community/eslint-utils": "^4.8.0",
"@eslint-community/regexpp": "^4.12.1",
@@ -2091,9 +2095,9 @@
}
},
"node_modules/eslint/node_modules/minimatch": {
- "version": "3.1.2",
- "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
- "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
+ "version": "3.1.5",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz",
+ "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==",
"dev": true,
"license": "ISC",
"dependencies": {
@@ -2742,9 +2746,9 @@
}
},
"node_modules/minimatch": {
- "version": "5.1.6",
- "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz",
- "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==",
+ "version": "5.1.9",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.9.tgz",
+ "integrity": "sha512-7o1wEA2RyMP7Iu7GNba9vc0RWWGACJOCZBJX2GJWip0ikV+wcOsgVuY9uE8CPiyQhkGFSlhuSkZPavN7u1c2Fw==",
"license": "ISC",
"dependencies": {
"brace-expansion": "^2.0.1"
@@ -2927,6 +2931,7 @@
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
"dev": true,
"license": "MIT",
+ "peer": true,
"engines": {
"node": ">=12"
},
@@ -3017,6 +3022,7 @@
"resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz",
"integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==",
"license": "MIT",
+ "peer": true,
"dependencies": {
"loose-envify": "^1.1.0"
},
@@ -3082,9 +3088,9 @@
"license": "MIT"
},
"node_modules/rollup": {
- "version": "4.57.1",
- "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.57.1.tgz",
- "integrity": "sha512-oQL6lgK3e2QZeQ7gcgIkS2YZPg5slw37hYufJ3edKlfQSGGm8ICoxswK15ntSzF/a8+h7ekRy7k7oWc3BQ7y8A==",
+ "version": "4.59.0",
+ "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.59.0.tgz",
+ "integrity": "sha512-2oMpl67a3zCH9H79LeMcbDhXW/UmWG/y2zuqnF2jQq5uq9TbM9TVyXvA4+t+ne2IIkBdrLpAaRQAvo7YI/Yyeg==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -3098,31 +3104,31 @@
"npm": ">=8.0.0"
},
"optionalDependencies": {
- "@rollup/rollup-android-arm-eabi": "4.57.1",
- "@rollup/rollup-android-arm64": "4.57.1",
- "@rollup/rollup-darwin-arm64": "4.57.1",
- "@rollup/rollup-darwin-x64": "4.57.1",
- "@rollup/rollup-freebsd-arm64": "4.57.1",
- "@rollup/rollup-freebsd-x64": "4.57.1",
- "@rollup/rollup-linux-arm-gnueabihf": "4.57.1",
- "@rollup/rollup-linux-arm-musleabihf": "4.57.1",
- "@rollup/rollup-linux-arm64-gnu": "4.57.1",
- "@rollup/rollup-linux-arm64-musl": "4.57.1",
- "@rollup/rollup-linux-loong64-gnu": "4.57.1",
- "@rollup/rollup-linux-loong64-musl": "4.57.1",
- "@rollup/rollup-linux-ppc64-gnu": "4.57.1",
- "@rollup/rollup-linux-ppc64-musl": "4.57.1",
- "@rollup/rollup-linux-riscv64-gnu": "4.57.1",
- "@rollup/rollup-linux-riscv64-musl": "4.57.1",
- "@rollup/rollup-linux-s390x-gnu": "4.57.1",
- "@rollup/rollup-linux-x64-gnu": "4.57.1",
- "@rollup/rollup-linux-x64-musl": "4.57.1",
- "@rollup/rollup-openbsd-x64": "4.57.1",
- "@rollup/rollup-openharmony-arm64": "4.57.1",
- "@rollup/rollup-win32-arm64-msvc": "4.57.1",
- "@rollup/rollup-win32-ia32-msvc": "4.57.1",
- "@rollup/rollup-win32-x64-gnu": "4.57.1",
- "@rollup/rollup-win32-x64-msvc": "4.57.1",
+ "@rollup/rollup-android-arm-eabi": "4.59.0",
+ "@rollup/rollup-android-arm64": "4.59.0",
+ "@rollup/rollup-darwin-arm64": "4.59.0",
+ "@rollup/rollup-darwin-x64": "4.59.0",
+ "@rollup/rollup-freebsd-arm64": "4.59.0",
+ "@rollup/rollup-freebsd-x64": "4.59.0",
+ "@rollup/rollup-linux-arm-gnueabihf": "4.59.0",
+ "@rollup/rollup-linux-arm-musleabihf": "4.59.0",
+ "@rollup/rollup-linux-arm64-gnu": "4.59.0",
+ "@rollup/rollup-linux-arm64-musl": "4.59.0",
+ "@rollup/rollup-linux-loong64-gnu": "4.59.0",
+ "@rollup/rollup-linux-loong64-musl": "4.59.0",
+ "@rollup/rollup-linux-ppc64-gnu": "4.59.0",
+ "@rollup/rollup-linux-ppc64-musl": "4.59.0",
+ "@rollup/rollup-linux-riscv64-gnu": "4.59.0",
+ "@rollup/rollup-linux-riscv64-musl": "4.59.0",
+ "@rollup/rollup-linux-s390x-gnu": "4.59.0",
+ "@rollup/rollup-linux-x64-gnu": "4.59.0",
+ "@rollup/rollup-linux-x64-musl": "4.59.0",
+ "@rollup/rollup-openbsd-x64": "4.59.0",
+ "@rollup/rollup-openharmony-arm64": "4.59.0",
+ "@rollup/rollup-win32-arm64-msvc": "4.59.0",
+ "@rollup/rollup-win32-ia32-msvc": "4.59.0",
+ "@rollup/rollup-win32-x64-gnu": "4.59.0",
+ "@rollup/rollup-win32-x64-msvc": "4.59.0",
"fsevents": "~2.3.2"
}
},
@@ -3389,6 +3395,7 @@
"integrity": "sha512-+Oxm7q9hDoLMyJOYfUYBuHQo+dkAloi33apOPP56pzj+vsdJDzr+j1NISE5pyaAuKL4A3UD34qd0lx5+kfKp2g==",
"dev": true,
"license": "MIT",
+ "peer": true,
"dependencies": {
"esbuild": "^0.25.0",
"fdir": "^6.4.4",
diff --git a/src/components/MitigationCard.jsx b/src/components/MitigationCard.jsx
index 435d0b9..09827ae 100644
--- a/src/components/MitigationCard.jsx
+++ b/src/components/MitigationCard.jsx
@@ -4,12 +4,13 @@ import styles from "./MitigationCard.module.css";
export default function MitigationCard({ group, active, accent, t }) {
const [open, setOpen] = useState(false);
+
return (
-
{group.icon}
+
+ {active ? group.icon : "🔒"}
+
-
+
{group.title}
-
- {group.measures.length} {group.measures.length !== 1 ? t.measures : t.measure}
+
+ {active
+ ? `${group.measures.length} ${group.measures.length !== 1 ? t.measures : t.measure}`
+ : `Unlocks at Tier ${group.tier}`}
{active &&
▾}
- {active && open && (
-
- {group.measures.map((m, i) => {
- const tc = TYPE_COLORS[m.type];
- return (
-
-
-
{m.name}
-
- {t.typeBadges[m.type]}
-
+
+ {/* Always rendered — grid-template-rows animates open/close smoothly */}
+ {active && (
+
+
+ {group.measures.map((m, i) => {
+ const tc = TYPE_COLORS[m.type];
+ return (
+
+
+ {m.name}
+
+ {t.typeBadges[m.type]}
+
+
+
{m.desc}
-
{m.desc}
-
- );
- })}
+ );
+ })}
+
)}
diff --git a/src/components/MitigationCard.module.css b/src/components/MitigationCard.module.css
index 1aaf1a2..31ea0b2 100644
--- a/src/components/MitigationCard.module.css
+++ b/src/components/MitigationCard.module.css
@@ -1,11 +1,23 @@
.card {
- border-radius: 12px;
- padding: 12px 14px;
- transition: all 0.3s;
+ border-radius: 10px;
+ padding: 10px 14px;
+ transition:
+ border-color 0.25s ease,
+ background 0.25s ease,
+ box-shadow 0.25s ease,
+ transform 0.25s cubic-bezier(0.34, 1.2, 0.64, 1);
+}
+
+.card:not(.cardInactive):hover {
+ transform: translateY(-2px) scale(1.01);
+ box-shadow: 0 6px 24px -4px rgba(0, 0, 0, 0.25);
+ border-width: 2px !important;
+ filter: brightness(1.08);
}
.cardInactive {
- opacity: 0.5;
+ padding: 7px 14px;
+ border-radius: 8px;
}
.header {
@@ -21,40 +33,75 @@
}
.icon {
- font-size: 24px;
+ font-size: 20px;
+ transition: opacity 0.3s ease;
}
.title {
- font-weight: 700;
- font-size: 18px;
+ font-weight: 600;
+ font-size: 16px;
+ transition:
+ color 0.3s ease,
+ opacity 0.3s ease;
}
.subtitle {
- font-size: 13px;
+ font-size: 12px;
color: var(--text-secondary);
+ transition: opacity 0.3s ease;
}
.chevron {
- font-size: 22px;
+ font-size: 20px;
color: var(--text-secondary);
- transition: transform 0.2s;
+ transition: transform 0.3s cubic-bezier(0.34, 1.56, 0.64, 1);
}
.chevronOpen {
transform: rotate(180deg);
}
+/* ── Smooth expand/collapse ──────────────────────────────────────────────── */
+/*
+ grid-template-rows trick: animating from 0fr → 1fr smoothly collapses
+ and expands content of any height without needing to know it in advance.
+ The inner div needs overflow:hidden to clip during animation.
+*/
+
+.measuresWrapper {
+ max-height: 0;
+ overflow: hidden;
+ transition: max-height 0.6s cubic-bezier(0.4, 0, 0.2, 1);
+}
+
+.measuresWrapperOpen {
+ max-height: 600px;
+ transition: max-height 0.8s cubic-bezier(0.4, 0, 0.2, 1);
+}
+
.measures {
margin-top: 10px;
display: flex;
flex-direction: column;
gap: 6px;
+ padding-bottom: 10px;
}
.measure {
background: var(--bg-card);
border-radius: 8px;
padding: 8px 10px;
+ transition:
+ box-shadow 0.2s ease,
+ transform 0.2s cubic-bezier(0.34, 1.2, 0.64, 1),
+ background 0.2s ease;
+}
+
+.measure:hover {
+ transform: translateX(3px);
+ box-shadow: 0 2px 12px -2px rgba(0, 0, 0, 0.2);
+ background: var(--bg-sidebar);
+ filter: brightness(1.1);
}
.measureHeader {
@@ -67,7 +114,7 @@
.measureName {
font-weight: 600;
- font-size: 16px;
+ font-size: 15px;
color: var(--text-primary);
}
@@ -81,7 +128,7 @@
}
.measureDesc {
- font-size: 15px;
+ font-size: 14px;
color: var(--text-secondary);
margin-top: 3px;
line-height: 1.4;
diff --git a/src/components/RadarChart.jsx b/src/components/RadarChart.jsx
index f0eea72..6a20703 100644
--- a/src/components/RadarChart.jsx
+++ b/src/components/RadarChart.jsx
@@ -1,70 +1,441 @@
+import { useRef, useEffect, useState, useCallback } from "react";
import { TIER_BG } from "../constants.js";
import { getTierIndex, polarToCartesian } from "../utils.js";
import styles from "./RadarChart.module.css";
-export default function RadarChart({ values, dimensions, size = 320 }) {
- const cx = size / 2,
- cy = size / 2,
- maxR = size / 2 - 48,
- levels = 5,
- n = dimensions.length,
- step = 360 / n;
- const ti = getTierIndex(values);
- const tc = TIER_BG[ti];
+// ── Animation helpers ────────────────────────────────────────────────────────
+function animatePoints(el, toStr, duration = 800) {
+ if (!el) return;
+ const from = el.getAttribute("points");
+ if (!from || from === toStr) {
+ el.setAttribute("points", toStr);
+ return;
+ }
+ const fromPts = from
+ .trim()
+ .split(/\s+/)
+ .map((p) => p.split(",").map(Number));
+ const toPts = toStr
+ .trim()
+ .split(/\s+/)
+ .map((p) => p.split(",").map(Number));
+ if (fromPts.length !== toPts.length) {
+ el.setAttribute("points", toStr);
+ return;
+ }
+ if (el._rafId) cancelAnimationFrame(el._rafId);
+ let start = null;
+ function step(ts) {
+ if (!start) start = ts;
+ const t = Math.min((ts - start) / duration, 1);
+ const ease = t < 0.5 ? 2 * t * t : -1 + (4 - 2 * t) * t;
+ el.setAttribute(
+ "points",
+ fromPts
+ .map((fp, i) => {
+ const tp = toPts[i];
+ return `${fp[0] + (tp[0] - fp[0]) * ease},${fp[1] + (tp[1] - fp[1]) * ease}`;
+ })
+ .join(" "),
+ );
+ if (t < 1) {
+ el._rafId = requestAnimationFrame(step);
+ } else {
+ el.setAttribute("points", toStr);
+ el._rafId = null;
+ }
+ }
+ el._rafId = requestAnimationFrame(step);
+}
+
+function animateAttr(el, attr, from, to, duration = 800) {
+ if (!el) return;
+ const key = `_raf_${attr}`;
+ if (el[key]) cancelAnimationFrame(el[key]);
+ let start = null;
+ function step(ts) {
+ if (!start) start = ts;
+ const t = Math.min((ts - start) / duration, 1);
+ const ease = t < 0.5 ? 2 * t * t : -1 + (4 - 2 * t) * t;
+ el.setAttribute(attr, from + (to - from) * ease);
+ if (t < 1) {
+ el[key] = requestAnimationFrame(step);
+ } else {
+ el.setAttribute(attr, to);
+ el[key] = null;
+ }
+ }
+ el[key] = requestAnimationFrame(step);
+}
+
+function animateColor(el, fromHex, toHex, duration = 800, attrs = ["fill", "stroke"]) {
+ if (!el || !fromHex || !toHex || fromHex === toHex) return;
+ const parse = (h) => {
+ const s = h.replace("#", "");
+ return [parseInt(s.slice(0, 2), 16), parseInt(s.slice(2, 4), 16), parseInt(s.slice(4, 6), 16)];
+ };
+ const toHexStr = (r, g, b) => "#" + [r, g, b].map((v) => Math.round(v).toString(16).padStart(2, "0")).join("");
+ const f = parse(fromHex),
+ t = parse(toHex);
+ if (el._colorRafId) cancelAnimationFrame(el._colorRafId);
+ let start = null;
+ function step(ts) {
+ if (!start) start = ts;
+ const p = Math.min((ts - start) / duration, 1);
+ const ease = p < 0.5 ? 2 * p * p : -1 + (4 - 2 * p) * p;
+ const color = toHexStr(f[0] + (t[0] - f[0]) * ease, f[1] + (t[1] - f[1]) * ease, f[2] + (t[2] - f[2]) * ease);
+ attrs.forEach((a) => el.setAttribute(a, color));
+ if (p < 1) {
+ el._colorRafId = requestAnimationFrame(step);
+ } else {
+ attrs.forEach((a) => el.setAttribute(a, toHex));
+ el._colorRafId = null;
+ }
+ }
+ el._colorRafId = requestAnimationFrame(step);
+}
+
+function getValueLabelPos(dotX, dotY, cx, cy, offset = 18) {
+ const dx = dotX - cx;
+ const dy = dotY - cy;
+ const len = Math.sqrt(dx * dx + dy * dy) || 1;
+ const px = -dy / len;
+ const py = dx / len;
+ const rx = dx / len;
+ const ry = dy / len;
+ return {
+ x: dotX + px * offset * 0.9 + rx * 4,
+ y: dotY + py * offset * 0.9 + ry * 4,
+ };
+}
+
+// ── Component ────────────────────────────────────────────────────────────────
+
+export default function RadarChart({
+ values = {},
+ dimensions = [],
+ size = 460,
+ determiningKey = null,
+ registerUpdater = null,
+}) {
+ // Safe fallbacks to prevent math from crashing if data is missing
+ const safeDims = dimensions || [];
+ const safeVals = values || {};
+
+ const cx = size / 2;
+ const cy = size / 2;
+ const maxR = 130;
+ const levels = 5;
+ const n = safeDims.length || 1; // prevents division by zero
+ const angStep = 360 / n;
+
+ const ti = getTierIndex(safeVals);
+ const tc = TIER_BG[ti] || "#6b7280";
+
+ const [tooltip, setTooltip] = useState(null);
+
+ const svgRef = useRef(null);
+ const riskPolygonRef = useRef(null);
+ const gridRingRefs = useRef([]);
+ const dotRefs = useRef([]);
+ const dotGroupRefs = useRef([]);
+ const labelRefs = useRef([]);
+ const prevTcRef = useRef(tc);
+ const mountedRef = useRef(false);
+ const dimensionsRef = useRef(safeDims);
+
+ useEffect(() => {
+ if (!registerUpdater) return;
+ registerUpdater((newValues) => {
+ if (!mountedRef.current) return;
+ const dims = dimensionsRef.current;
+ const newTi = getTierIndex(newValues);
+ const newTc = TIER_BG[newTi] || "#6b7280";
+ const newRiskPts = dims.map((d, i) =>
+ polarToCartesian(cx, cy, (maxR / levels) * (newValues[d.key] + 1), i * angStep),
+ );
+ const newRiskPointsStr = newRiskPts.map((p) => `${p.x},${p.y}`).join(" ");
+
+ if (riskPolygonRef.current) {
+ riskPolygonRef.current.setAttribute("points", newRiskPointsStr);
+ riskPolygonRef.current.setAttribute("fill", newTc);
+ riskPolygonRef.current.setAttribute("stroke", newTc);
+ }
+ dotRefs.current.forEach((el, i) => {
+ if (!el) return;
+ el.setAttribute("cx", newRiskPts[i].x);
+ el.setAttribute("cy", newRiskPts[i].y);
+ el.setAttribute("fill", newTc);
+ el.style.color = newTc;
+ });
+ labelRefs.current.forEach((el, i) => {
+ if (!el) return;
+ const lp = getValueLabelPos(newRiskPts[i].x, newRiskPts[i].y, cx, cy, 18);
+ el.setAttribute("x", lp.x);
+ el.setAttribute("y", lp.y);
+ el.setAttribute("fill", newTc);
+ el.textContent = Math.round(newValues[dimensionsRef.current[i].key]);
+ });
+ prevTcRef.current = newTc;
+ });
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, [registerUpdater]);
+
+ const riskPts = safeDims.map((d, i) =>
+ polarToCartesian(cx, cy, (maxR / levels) * ((safeVals[d.key] || 0) + 1), i * angStep),
+ );
+ const riskPointsStr = riskPts.map((p) => `${p.x},${p.y}`).join(" ");
+
+ const gridPointStrs = [1, 2, 3, 4, 5].map((l) => {
+ const r = (maxR / levels) * l;
+ return Array.from({ length: safeDims.length }, (_, i) => polarToCartesian(cx, cy, r, i * angStep))
+ .map((p) => `${p.x},${p.y}`)
+ .join(" ");
+ });
+
+ useEffect(() => {
+ mountedRef.current = false;
+
+ riskPolygonRef.current?.setAttribute("points", riskPointsStr);
+ riskPolygonRef.current?.setAttribute("fill", tc);
+ riskPolygonRef.current?.setAttribute("stroke", tc);
+
+ gridRingRefs.current.forEach((el, i) => el?.setAttribute("points", gridPointStrs[i]));
+
+ dotRefs.current.forEach((el, i) => {
+ if (!el) return;
+ el.setAttribute("cx", riskPts[i].x);
+ el.setAttribute("cy", riskPts[i].y);
+ el.setAttribute("fill", tc);
+ el.style.color = tc;
+ });
+
+ labelRefs.current.forEach((el, i) => {
+ if (!el) return;
+ const lp = getValueLabelPos(riskPts[i].x, riskPts[i].y, cx, cy, 18);
+ el.setAttribute("x", lp.x);
+ el.setAttribute("y", lp.y);
+ el.setAttribute("fill", tc);
+ });
+
+ dotGroupRefs.current.forEach((g, i) => {
+ if (!g) return;
+ const ox = riskPts[i].x;
+ const oy = riskPts[i].y;
+ g.style.transformOrigin = `${ox}px ${oy}px`;
+ g.style.opacity = "0";
+ g.style.transform = "scale(0.2)";
+ g.style.transition = "none";
+ requestAnimationFrame(() =>
+ requestAnimationFrame(() => {
+ const delay = 300 + i * 90;
+ g.style.transition = `opacity 0.35s ease ${delay}ms, transform 0.5s cubic-bezier(0.34, 1.56, 0.64, 1) ${delay}ms`;
+ g.style.opacity = "1";
+ g.style.transform = "scale(1)";
+ }),
+ );
+ });
+
+ mountedRef.current = true;
+
+ return () => {
+ mountedRef.current = false;
+ };
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, []);
+
+ useEffect(() => {
+ if (!mountedRef.current) return;
+
+ const prevTc = prevTcRef.current;
+ const colorChanged = prevTc !== tc;
+
+ animatePoints(riskPolygonRef.current, riskPointsStr);
+
+ if (colorChanged) {
+ animateColor(riskPolygonRef.current, prevTc, tc, 800, ["fill", "stroke"]);
+ }
+
+ dotRefs.current.forEach((el, i) => {
+ if (!el) return;
+ const fromCx = parseFloat(el.getAttribute("cx") ?? riskPts[i].x);
+ const fromCy = parseFloat(el.getAttribute("cy") ?? riskPts[i].y);
+ animateAttr(el, "cx", fromCx, riskPts[i].x);
+ animateAttr(el, "cy", fromCy, riskPts[i].y);
+ if (colorChanged) animateColor(el, prevTc, tc, 800, ["fill"]);
+ });
+
+ labelRefs.current.forEach((el, i) => {
+ if (!el) return;
+ const lp = getValueLabelPos(riskPts[i].x, riskPts[i].y, cx, cy, 18);
+ const fromX = parseFloat(el.getAttribute("x") ?? lp.x);
+ const fromY = parseFloat(el.getAttribute("y") ?? lp.y);
+ animateAttr(el, "x", fromX, lp.x);
+ animateAttr(el, "y", fromY, lp.y);
+ if (colorChanged) animateColor(el, prevTc, tc, 800, ["fill"]);
+ });
+
+ if (colorChanged) prevTcRef.current = tc;
+
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, [riskPointsStr, tc]);
+
+ const handleDotEnter = useCallback(
+ (e, i) => {
+ if (!safeDims[i]) return;
+ const rect = svgRef.current.getBoundingClientRect();
+ setTooltip({
+ x: (e.clientX - rect.left) * (size / rect.width),
+ y: (e.clientY - rect.top) * (size / rect.height),
+ label: safeDims[i].label || safeDims[i].shortLabel,
+ value: safeVals[safeDims[i].key],
+ });
+ },
+ [safeDims, safeVals, size],
+ );
+
+ const handleDotLeave = useCallback(() => setTooltip(null), []);
+
+ const tooltipW = 120,
+ tooltipH = 56;
+ const tipX = tooltip ? Math.min(Math.max(tooltip.x - tooltipW / 2, 8), size - tooltipW - 8) : 0;
+ const tipY = tooltip ? tooltip.y - tooltipH - 18 : 0;
+
+ // ── THE FIX: Early return is now safely AFTER all hooks! ───────────────
+ if (!dimensions?.length || !values) return null;
+
+ // ── Render ───────────────────────────────────────────────────────────────
return (
-