diff --git a/package-lock.json b/package-lock.json
index 34179a6..d0efd8e 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1656,9 +1656,9 @@
       }
     },
     "node_modules/@rollup/rollup-android-arm-eabi": {
-      "version": "4.27.3",
-      "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.27.3.tgz",
-      "integrity": "sha512-EzxVSkIvCFxUd4Mgm4xR9YXrcp976qVaHnqom/Tgm+vU79k4vV4eYTjmRvGfeoW8m9LVcsAy/lGjcgVegKEhLQ==",
+      "version": "4.27.4",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.27.4.tgz",
+      "integrity": "sha512-2Y3JT6f5MrQkICUyRVCw4oa0sutfAsgaSsb0Lmmy1Wi2y7X5vT9Euqw4gOsCyy0YfKURBg35nhUKZS4mDcfULw==",
       "cpu": [
         "arm"
       ],
@@ -1670,9 +1670,9 @@
       ]
     },
     "node_modules/@rollup/rollup-android-arm64": {
-      "version": "4.27.3",
-      "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.27.3.tgz",
-      "integrity": "sha512-LJc5pDf1wjlt9o/Giaw9Ofl+k/vLUaYsE2zeQGH85giX2F+wn/Cg8b3c5CDP3qmVmeO5NzwVUzQQxwZvC2eQKw==",
+      "version": "4.27.4",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.27.4.tgz",
+      "integrity": "sha512-wzKRQXISyi9UdCVRqEd0H4cMpzvHYt1f/C3CoIjES6cG++RHKhrBj2+29nPF0IB5kpy9MS71vs07fvrNGAl/iA==",
       "cpu": [
         "arm64"
       ],
@@ -1684,9 +1684,9 @@
       ]
     },
     "node_modules/@rollup/rollup-darwin-arm64": {
-      "version": "4.27.3",
-      "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.27.3.tgz",
-      "integrity": "sha512-OuRysZ1Mt7wpWJ+aYKblVbJWtVn3Cy52h8nLuNSzTqSesYw1EuN6wKp5NW/4eSre3mp12gqFRXOKTcN3AI3LqA==",
+      "version": "4.27.4",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.27.4.tgz",
+      "integrity": "sha512-PlNiRQapift4LNS8DPUHuDX/IdXiLjf8mc5vdEmUR0fF/pyy2qWwzdLjB+iZquGr8LuN4LnUoSEvKRwjSVYz3Q==",
       "cpu": [
         "arm64"
       ],
@@ -1698,9 +1698,9 @@
       ]
     },
     "node_modules/@rollup/rollup-darwin-x64": {
-      "version": "4.27.3",
-      "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.27.3.tgz",
-      "integrity": "sha512-xW//zjJMlJs2sOrCmXdB4d0uiilZsOdlGQIC/jjmMWT47lkLLoB1nsNhPUcnoqyi5YR6I4h+FjBpILxbEy8JRg==",
+      "version": "4.27.4",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.27.4.tgz",
+      "integrity": "sha512-o9bH2dbdgBDJaXWJCDTNDYa171ACUdzpxSZt+u/AAeQ20Nk5x+IhA+zsGmrQtpkLiumRJEYef68gcpn2ooXhSQ==",
       "cpu": [
         "x64"
       ],
@@ -1712,9 +1712,9 @@
       ]
     },
     "node_modules/@rollup/rollup-freebsd-arm64": {
-      "version": "4.27.3",
-      "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.27.3.tgz",
-      "integrity": "sha512-58E0tIcwZ+12nK1WiLzHOD8I0d0kdrY/+o7yFVPRHuVGY3twBwzwDdTIBGRxLmyjciMYl1B/U515GJy+yn46qw==",
+      "version": "4.27.4",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.27.4.tgz",
+      "integrity": "sha512-NBI2/i2hT9Q+HySSHTBh52da7isru4aAAo6qC3I7QFVsuhxi2gM8t/EI9EVcILiHLj1vfi+VGGPaLOUENn7pmw==",
       "cpu": [
         "arm64"
       ],
@@ -1726,9 +1726,9 @@
       ]
     },
     "node_modules/@rollup/rollup-freebsd-x64": {
-      "version": "4.27.3",
-      "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.27.3.tgz",
-      "integrity": "sha512-78fohrpcVwTLxg1ZzBMlwEimoAJmY6B+5TsyAZ3Vok7YabRBUvjYTsRXPTjGEvv/mfgVBepbW28OlMEz4w8wGA==",
+      "version": "4.27.4",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.27.4.tgz",
+      "integrity": "sha512-wYcC5ycW2zvqtDYrE7deary2P2UFmSh85PUpAx+dwTCO9uw3sgzD6Gv9n5X4vLaQKsrfTSZZ7Z7uynQozPVvWA==",
       "cpu": [
         "x64"
       ],
@@ -1740,9 +1740,9 @@
       ]
     },
     "node_modules/@rollup/rollup-linux-arm-gnueabihf": {
-      "version": "4.27.3",
-      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.27.3.tgz",
-      "integrity": "sha512-h2Ay79YFXyQi+QZKo3ISZDyKaVD7uUvukEHTOft7kh00WF9mxAaxZsNs3o/eukbeKuH35jBvQqrT61fzKfAB/Q==",
+      "version": "4.27.4",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.27.4.tgz",
+      "integrity": "sha512-9OwUnK/xKw6DyRlgx8UizeqRFOfi9mf5TYCw1uolDaJSbUmBxP85DE6T4ouCMoN6pXw8ZoTeZCSEfSaYo+/s1w==",
       "cpu": [
         "arm"
       ],
@@ -1754,9 +1754,9 @@
       ]
     },
     "node_modules/@rollup/rollup-linux-arm-musleabihf": {
-      "version": "4.27.3",
-      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.27.3.tgz",
-      "integrity": "sha512-Sv2GWmrJfRY57urktVLQ0VKZjNZGogVtASAgosDZ1aUB+ykPxSi3X1nWORL5Jk0sTIIwQiPH7iE3BMi9zGWfkg==",
+      "version": "4.27.4",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.27.4.tgz",
+      "integrity": "sha512-Vgdo4fpuphS9V24WOV+KwkCVJ72u7idTgQaBoLRD0UxBAWTF9GWurJO9YD9yh00BzbkhpeXtm6na+MvJU7Z73A==",
       "cpu": [
         "arm"
       ],
@@ -1768,9 +1768,9 @@
       ]
     },
     "node_modules/@rollup/rollup-linux-arm64-gnu": {
-      "version": "4.27.3",
-      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.27.3.tgz",
-      "integrity": "sha512-FPoJBLsPW2bDNWjSrwNuTPUt30VnfM8GPGRoLCYKZpPx0xiIEdFip3dH6CqgoT0RnoGXptaNziM0WlKgBc+OWQ==",
+      "version": "4.27.4",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.27.4.tgz",
+      "integrity": "sha512-pleyNgyd1kkBkw2kOqlBx+0atfIIkkExOTiifoODo6qKDSpnc6WzUY5RhHdmTdIJXBdSnh6JknnYTtmQyobrVg==",
       "cpu": [
         "arm64"
       ],
@@ -1782,9 +1782,9 @@
       ]
     },
     "node_modules/@rollup/rollup-linux-arm64-musl": {
-      "version": "4.27.3",
-      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.27.3.tgz",
-      "integrity": "sha512-TKxiOvBorYq4sUpA0JT+Fkh+l+G9DScnG5Dqx7wiiqVMiRSkzTclP35pE6eQQYjP4Gc8yEkJGea6rz4qyWhp3g==",
+      "version": "4.27.4",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.27.4.tgz",
+      "integrity": "sha512-caluiUXvUuVyCHr5DxL8ohaaFFzPGmgmMvwmqAITMpV/Q+tPoaHZ/PWa3t8B2WyoRcIIuu1hkaW5KkeTDNSnMA==",
       "cpu": [
         "arm64"
       ],
@@ -1796,9 +1796,9 @@
       ]
     },
     "node_modules/@rollup/rollup-linux-powerpc64le-gnu": {
-      "version": "4.27.3",
-      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.27.3.tgz",
-      "integrity": "sha512-v2M/mPvVUKVOKITa0oCFksnQQ/TqGrT+yD0184/cWHIu0LoIuYHwox0Pm3ccXEz8cEQDLk6FPKd1CCm+PlsISw==",
+      "version": "4.27.4",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.27.4.tgz",
+      "integrity": "sha512-FScrpHrO60hARyHh7s1zHE97u0KlT/RECzCKAdmI+LEoC1eDh/RDji9JgFqyO+wPDb86Oa/sXkily1+oi4FzJQ==",
       "cpu": [
         "ppc64"
       ],
@@ -1810,9 +1810,9 @@
       ]
     },
     "node_modules/@rollup/rollup-linux-riscv64-gnu": {
-      "version": "4.27.3",
-      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.27.3.tgz",
-      "integrity": "sha512-LdrI4Yocb1a/tFVkzmOE5WyYRgEBOyEhWYJe4gsDWDiwnjYKjNs7PS6SGlTDB7maOHF4kxevsuNBl2iOcj3b4A==",
+      "version": "4.27.4",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.27.4.tgz",
+      "integrity": "sha512-qyyprhyGb7+RBfMPeww9FlHwKkCXdKHeGgSqmIXw9VSUtvyFZ6WZRtnxgbuz76FK7LyoN8t/eINRbPUcvXB5fw==",
       "cpu": [
         "riscv64"
       ],
@@ -1824,9 +1824,9 @@
       ]
     },
     "node_modules/@rollup/rollup-linux-s390x-gnu": {
-      "version": "4.27.3",
-      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.27.3.tgz",
-      "integrity": "sha512-d4wVu6SXij/jyiwPvI6C4KxdGzuZOvJ6y9VfrcleHTwo68fl8vZC5ZYHsCVPUi4tndCfMlFniWgwonQ5CUpQcA==",
+      "version": "4.27.4",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.27.4.tgz",
+      "integrity": "sha512-PFz+y2kb6tbh7m3A7nA9++eInGcDVZUACulf/KzDtovvdTizHpZaJty7Gp0lFwSQcrnebHOqxF1MaKZd7psVRg==",
       "cpu": [
         "s390x"
       ],
@@ -1838,9 +1838,9 @@
       ]
     },
     "node_modules/@rollup/rollup-linux-x64-gnu": {
-      "version": "4.27.3",
-      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.27.3.tgz",
-      "integrity": "sha512-/6bn6pp1fsCGEY5n3yajmzZQAh+mW4QPItbiWxs69zskBzJuheb3tNynEjL+mKOsUSFK11X4LYF2BwwXnzWleA==",
+      "version": "4.27.4",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.27.4.tgz",
+      "integrity": "sha512-Ni8mMtfo+o/G7DVtweXXV/Ol2TFf63KYjTtoZ5f078AUgJTmaIJnj4JFU7TK/9SVWTaSJGxPi5zMDgK4w+Ez7Q==",
       "cpu": [
         "x64"
       ],
@@ -1852,9 +1852,9 @@
       ]
     },
     "node_modules/@rollup/rollup-linux-x64-musl": {
-      "version": "4.27.3",
-      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.27.3.tgz",
-      "integrity": "sha512-nBXOfJds8OzUT1qUreT/en3eyOXd2EH5b0wr2bVB5999qHdGKkzGzIyKYaKj02lXk6wpN71ltLIaQpu58YFBoQ==",
+      "version": "4.27.4",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.27.4.tgz",
+      "integrity": "sha512-5AeeAF1PB9TUzD+3cROzFTnAJAcVUGLuR8ng0E0WXGkYhp6RD6L+6szYVX+64Rs0r72019KHZS1ka1q+zU/wUw==",
       "cpu": [
         "x64"
       ],
@@ -1866,9 +1866,9 @@
       ]
     },
     "node_modules/@rollup/rollup-win32-arm64-msvc": {
-      "version": "4.27.3",
-      "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.27.3.tgz",
-      "integrity": "sha512-ogfbEVQgIZOz5WPWXF2HVb6En+kWzScuxJo/WdQTqEgeyGkaa2ui5sQav9Zkr7bnNCLK48uxmmK0TySm22eiuw==",
+      "version": "4.27.4",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.27.4.tgz",
+      "integrity": "sha512-yOpVsA4K5qVwu2CaS3hHxluWIK5HQTjNV4tWjQXluMiiiu4pJj4BN98CvxohNCpcjMeTXk/ZMJBRbgRg8HBB6A==",
       "cpu": [
         "arm64"
       ],
@@ -1880,9 +1880,9 @@
       ]
     },
     "node_modules/@rollup/rollup-win32-ia32-msvc": {
-      "version": "4.27.3",
-      "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.27.3.tgz",
-      "integrity": "sha512-ecE36ZBMLINqiTtSNQ1vzWc5pXLQHlf/oqGp/bSbi7iedcjcNb6QbCBNG73Euyy2C+l/fn8qKWEwxr+0SSfs3w==",
+      "version": "4.27.4",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.27.4.tgz",
+      "integrity": "sha512-KtwEJOaHAVJlxV92rNYiG9JQwQAdhBlrjNRp7P9L8Cb4Rer3in+0A+IPhJC9y68WAi9H0sX4AiG2NTsVlmqJeQ==",
       "cpu": [
         "ia32"
       ],
@@ -1894,9 +1894,9 @@
       ]
     },
     "node_modules/@rollup/rollup-win32-x64-msvc": {
-      "version": "4.27.3",
-      "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.27.3.tgz",
-      "integrity": "sha512-vliZLrDmYKyaUoMzEbMTg2JkerfBjn03KmAw9CykO0Zzkzoyd7o3iZNam/TpyWNjNT+Cz2iO3P9Smv2wgrR+Eg==",
+      "version": "4.27.4",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.27.4.tgz",
+      "integrity": "sha512-3j4jx1TppORdTAoBJRd+/wJRGCPC0ETWkXOecJ6PPZLj6SptXkrXcNqdj0oclbKML6FkQltdz7bBA3rUSirZug==",
       "cpu": [
         "x64"
       ],
@@ -1953,9 +1953,9 @@
       }
     },
     "node_modules/@types/node": {
-      "version": "20.17.6",
-      "resolved": "https://registry.npmjs.org/@types/node/-/node-20.17.6.tgz",
-      "integrity": "sha512-VEI7OdvK2wP7XHnsuXbAJnEpEkF6NjSN45QJlL4VGqZSXsnicpesdTWsg9RISeSdYd3yeRj/y3k5KGjUXYnFwQ==",
+      "version": "20.17.7",
+      "resolved": "https://registry.npmjs.org/@types/node/-/node-20.17.7.tgz",
+      "integrity": "sha512-sZXXnpBFMKbao30dUAvzKbdwA2JM1fwUtVEq/kxKuPI5mMwZiRElCpTXb0Biq/LMEVpXDZL5G5V0RPnxKeyaYg==",
       "dev": true,
       "license": "MIT",
       "dependencies": {
@@ -3147,9 +3147,9 @@
       }
     },
     "node_modules/caniuse-lite": {
-      "version": "1.0.30001680",
-      "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001680.tgz",
-      "integrity": "sha512-rPQy70G6AGUMnbwS1z6Xg+RkHYPAi18ihs47GH0jcxIG7wArmPgY3XbS2sRdBbxJljp3thdT8BIqv9ccCypiPA==",
+      "version": "1.0.30001683",
+      "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001683.tgz",
+      "integrity": "sha512-iqmNnThZ0n70mNwvxpEC2nBJ037ZHZUoBI5Gorh1Mw6IlEAZujEoU1tXA628iZfzm7R9FvFzxbfdgml82a3k8Q==",
       "dev": true,
       "funding": [
         {
@@ -3621,9 +3621,9 @@
       }
     },
     "node_modules/electron-to-chromium": {
-      "version": "1.5.63",
-      "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.63.tgz",
-      "integrity": "sha512-ddeXKuY9BHo/mw145axlyWjlJ1UBt4WK3AlvkT7W2AbqfRQoacVoRUCF6wL3uIx/8wT9oLKXzI+rFqHHscByaA==",
+      "version": "1.5.64",
+      "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.64.tgz",
+      "integrity": "sha512-IXEuxU+5ClW2IGEYFC2T7szbyVgehupCWQe5GNh+H065CD6U6IFN0s4KeAMFGNmQolRU4IV7zGBWSYMmZ8uuqQ==",
       "dev": true,
       "license": "ISC"
     },
@@ -4454,9 +4454,9 @@
       }
     },
     "node_modules/graphology-types": {
-      "version": "0.24.7",
-      "resolved": "https://registry.npmjs.org/graphology-types/-/graphology-types-0.24.7.tgz",
-      "integrity": "sha512-tdcqOOpwArNjEr0gNQKCXwaNCWnQJrog14nJNQPeemcLnXQUUGrsCWpWkVKt46zLjcS6/KGoayeJfHHyPDlvwA==",
+      "version": "0.24.8",
+      "resolved": "https://registry.npmjs.org/graphology-types/-/graphology-types-0.24.8.tgz",
+      "integrity": "sha512-hDRKYXa8TsoZHjgEaysSRyPdT6uB78Ci8WnjgbStlQysz7xR52PInxNsmnB7IBOM1BhikxkNyCVEFgmPKnpx3Q==",
       "dev": true,
       "license": "MIT",
       "peer": true
@@ -4596,9 +4596,9 @@
       }
     },
     "node_modules/i18next-http-backend": {
-      "version": "2.7.0",
-      "resolved": "https://registry.npmjs.org/i18next-http-backend/-/i18next-http-backend-2.7.0.tgz",
-      "integrity": "sha512-ruusEX6azT8pXy+C2etRQZ0ylbs1ix/z6D+zNv/tUcy9Sw8TWRO989Z7X1LLV31JVO/cAoSk3gH3rapSTL+j9w==",
+      "version": "2.7.1",
+      "resolved": "https://registry.npmjs.org/i18next-http-backend/-/i18next-http-backend-2.7.1.tgz",
+      "integrity": "sha512-vPksHIckysGgykCD8JwCr2YsJEml9Cyw+Yu2wtb4fQ7xIn9RH/hkUDh5UkwnIzb0kSL4SJ30Ab/sCInhQxbCgg==",
       "dev": true,
       "license": "MIT",
       "dependencies": {
@@ -6181,9 +6181,9 @@
       "license": "MIT"
     },
     "node_modules/psl": {
-      "version": "1.10.0",
-      "resolved": "https://registry.npmjs.org/psl/-/psl-1.10.0.tgz",
-      "integrity": "sha512-KSKHEbjAnpUuAUserOq0FxGXCUrzC3WniuSJhvdbs102rL55266ZcHBqLWOsG30spQMlPdpy7icATiAQehg/iA==",
+      "version": "1.13.0",
+      "resolved": "https://registry.npmjs.org/psl/-/psl-1.13.0.tgz",
+      "integrity": "sha512-BFwmFXiJoFqlUpZ5Qssolv15DMyc84gTBds1BjsV1BfXEo1UyyD7GsmN67n7J77uRhoSNW1AXtXKPLcBFQn9Aw==",
       "dev": true,
       "license": "MIT",
       "dependencies": {
@@ -6390,9 +6390,9 @@
       }
     },
     "node_modules/rollup": {
-      "version": "4.27.3",
-      "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.27.3.tgz",
-      "integrity": "sha512-SLsCOnlmGt9VoZ9Ek8yBK8tAdmPHeppkw+Xa7yDlCEhDTvwYei03JlWo1fdc7YTfLZ4tD8riJCUyAgTbszk1fQ==",
+      "version": "4.27.4",
+      "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.27.4.tgz",
+      "integrity": "sha512-RLKxqHEMjh/RGLsDxAEsaLO3mWgyoU6x9w6n1ikAzet4B3gI2/3yP6PWY2p9QzRTh6MfEIXB3MwsOY0Iv3vNrw==",
       "dev": true,
       "license": "MIT",
       "dependencies": {
@@ -6406,24 +6406,24 @@
         "npm": ">=8.0.0"
       },
       "optionalDependencies": {
-        "@rollup/rollup-android-arm-eabi": "4.27.3",
-        "@rollup/rollup-android-arm64": "4.27.3",
-        "@rollup/rollup-darwin-arm64": "4.27.3",
-        "@rollup/rollup-darwin-x64": "4.27.3",
-        "@rollup/rollup-freebsd-arm64": "4.27.3",
-        "@rollup/rollup-freebsd-x64": "4.27.3",
-        "@rollup/rollup-linux-arm-gnueabihf": "4.27.3",
-        "@rollup/rollup-linux-arm-musleabihf": "4.27.3",
-        "@rollup/rollup-linux-arm64-gnu": "4.27.3",
-        "@rollup/rollup-linux-arm64-musl": "4.27.3",
-        "@rollup/rollup-linux-powerpc64le-gnu": "4.27.3",
-        "@rollup/rollup-linux-riscv64-gnu": "4.27.3",
-        "@rollup/rollup-linux-s390x-gnu": "4.27.3",
-        "@rollup/rollup-linux-x64-gnu": "4.27.3",
-        "@rollup/rollup-linux-x64-musl": "4.27.3",
-        "@rollup/rollup-win32-arm64-msvc": "4.27.3",
-        "@rollup/rollup-win32-ia32-msvc": "4.27.3",
-        "@rollup/rollup-win32-x64-msvc": "4.27.3",
+        "@rollup/rollup-android-arm-eabi": "4.27.4",
+        "@rollup/rollup-android-arm64": "4.27.4",
+        "@rollup/rollup-darwin-arm64": "4.27.4",
+        "@rollup/rollup-darwin-x64": "4.27.4",
+        "@rollup/rollup-freebsd-arm64": "4.27.4",
+        "@rollup/rollup-freebsd-x64": "4.27.4",
+        "@rollup/rollup-linux-arm-gnueabihf": "4.27.4",
+        "@rollup/rollup-linux-arm-musleabihf": "4.27.4",
+        "@rollup/rollup-linux-arm64-gnu": "4.27.4",
+        "@rollup/rollup-linux-arm64-musl": "4.27.4",
+        "@rollup/rollup-linux-powerpc64le-gnu": "4.27.4",
+        "@rollup/rollup-linux-riscv64-gnu": "4.27.4",
+        "@rollup/rollup-linux-s390x-gnu": "4.27.4",
+        "@rollup/rollup-linux-x64-gnu": "4.27.4",
+        "@rollup/rollup-linux-x64-musl": "4.27.4",
+        "@rollup/rollup-win32-arm64-msvc": "4.27.4",
+        "@rollup/rollup-win32-ia32-msvc": "4.27.4",
+        "@rollup/rollup-win32-x64-msvc": "4.27.4",
         "fsevents": "~2.3.2"
       }
     },
diff --git a/src/components/gameMaster/assetManager/AssetManager.vue b/src/components/gameMaster/assetManager/AssetManager.vue
index 959d543..ee9a05e 100644
--- a/src/components/gameMaster/assetManager/AssetManager.vue
+++ b/src/components/gameMaster/assetManager/AssetManager.vue
@@ -55,6 +55,7 @@
       <ObjectList v-if="selectedCategory === 'objects'" />
       <SpriteList v-if="selectedCategory === 'sprites'" />
       <CharacterTypeList v-if="selectedCategory === 'characterTypes'" />
+      <CharacterHairList v-if="selectedCategory === 'characterHair'" />
     </div>
 
     <div class="absolute w-px bg-gray-500 h-full top-0 left-1/2"></div>
@@ -65,6 +66,7 @@
       <ObjectDetails v-if="selectedCategory === 'objects' && assetManagerStore.selectedObject" />
       <SpriteDetails v-if="selectedCategory === 'sprites' && assetManagerStore.selectedSprite" />
       <CharacterTypeDetails v-if="selectedCategory === 'characterTypes' && assetManagerStore.selectedCharacterType" />
+      <CharacterHairDetails v-if="selectedCategory === 'characterHair' && assetManagerStore.selectedCharacterHair" />
     </div>
   </div>
 </template>
@@ -80,6 +82,8 @@ import SpriteList from '@/components/gameMaster/assetManager/partials/sprite/Spr
 import SpriteDetails from '@/components/gameMaster/assetManager/partials/sprite/SpriteDetails.vue'
 import CharacterTypeList from '@/components/gameMaster/assetManager/partials/characterType/CharacterTypeList.vue'
 import CharacterTypeDetails from '@/components/gameMaster/assetManager/partials/characterType/CharacterTypeDetails.vue'
+import CharacterHairList from '@/components/gameMaster/assetManager/partials/characterHair/CharacterHairList.vue'
+import CharacterHairDetails from '@/components/gameMaster/assetManager/partials/characterHair/CharacterHairDetails.vue'
 
 const assetManagerStore = useAssetManagerStore()
 const selectedCategory = ref('tiles')
diff --git a/src/components/gameMaster/assetManager/partials/characterHair/CharacterHairDetails.vue b/src/components/gameMaster/assetManager/partials/characterHair/CharacterHairDetails.vue
new file mode 100644
index 0000000..952c9ef
--- /dev/null
+++ b/src/components/gameMaster/assetManager/partials/characterHair/CharacterHairDetails.vue
@@ -0,0 +1,124 @@
+<template>
+  <div class="h-full overflow-auto">
+    <div class="m-2.5 p-2.5 block">
+      <form class="flex gap-2.5 flex-wrap" @submit.prevent="saveCharacterHair">
+        <div class="form-field-full">
+          <label for="name">Name</label>
+          <input v-model="characterName" class="input-field" type="text" name="name" placeholder="Character Type Name" />
+        </div>
+        <div class="form-field-full">
+          <label for="gender">Gender</label>
+          <select v-model="characterGender" class="input-field" name="gender">
+            <option v-for="gender in genderOptions" :key="gender" :value="gender">{{ gender }}</option>
+          </select>
+        </div>
+        <div class="form-field-full">
+          <label for="isEnabledForCharCreation">Is enabled for character creation</label>
+          <select v-model="characterIsEnabledForCharCreation" class="input-field" name="isEnabledForCharCreation">
+            <option :value="false">No</option>
+            <option :value="true">Yes</option>
+          </select>
+        </div>
+        <div class="form-field-full">
+          <label for="spriteId">Sprite</label>
+          <select v-model="characterSpriteId" class="input-field" name="spriteId">
+            <option disabled selected value="">Select sprite</option>
+            <option v-for="sprite in assetManagerStore.spriteList" :key="sprite.id" :value="sprite.id">{{ sprite.name }}</option>
+          </select>
+        </div>
+        <button class="btn-cyan px-4 py-1.5 min-w-24" type="submit">Save</button>
+        <button class="btn-red px-4 py-1.5 min-w-24" type="button" @click.prevent="removeCharacterHair">Remove</button>
+      </form>
+    </div>
+  </div>
+</template>
+
+<script setup lang="ts">
+import type { CharacterHair, CharacterGender, Sprite } from '@/types'
+import { computed, onBeforeUnmount, onMounted, ref, watch } from 'vue'
+import { useAssetManagerStore } from '@/stores/assetManagerStore'
+import { useGameStore } from '@/stores/gameStore'
+
+const gameStore = useGameStore()
+const assetManagerStore = useAssetManagerStore()
+
+const selectedCharacterHair = computed(() => assetManagerStore.selectedCharacterHair)
+
+const characterName = ref('')
+const characterGender = ref<CharacterGender>('MALE' as CharacterGender.MALE)
+const characterIsEnabledForCharCreation = ref<boolean>(false)
+const characterSpriteId = ref<string | null | undefined>(null)
+
+const genderOptions: CharacterGender[] = ['MALE' as CharacterGender.MALE, 'FEMALE' as CharacterGender.FEMALE]
+
+if (!selectedCharacterHair.value) {
+  console.error('No character hair selected')
+}
+
+if (selectedCharacterHair.value) {
+  characterName.value = selectedCharacterHair.value.name
+  characterGender.value = selectedCharacterHair.value.gender
+  characterIsEnabledForCharCreation.value = selectedCharacterHair.value.isEnabledForCharCreation
+  characterSpriteId.value = selectedCharacterHair.value.spriteId
+}
+
+function removeCharacterHair() {
+  if (!selectedCharacterHair.value) return
+
+  gameStore.connection?.emit('gm:characterHair:remove', { id: selectedCharacterHair.value.id }, (response: boolean) => {
+    if (!response) {
+      console.error('Failed to remove character hair')
+      return
+    }
+    refreshCharacterHairList()
+  })
+}
+
+function refreshCharacterHairList(unsetSelectedCharacterHair = true) {
+  gameStore.connection?.emit('gm:characterHair:list', {}, (response: CharacterHair[]) => {
+    assetManagerStore.setCharacterHairList(response)
+
+    if (unsetSelectedCharacterHair) {
+      assetManagerStore.setSelectedCharacterHair(null)
+    }
+  })
+}
+
+function saveCharacterHair() {
+  const characterHairData = {
+    id: selectedCharacterHair.value!.id,
+    name: characterName.value,
+    gender: characterGender.value,
+    isEnabledForCharCreation: characterIsEnabledForCharCreation.value,
+    spriteId: characterSpriteId.value
+  }
+
+  gameStore.connection?.emit('gm:characterHair:update', characterHairData, (response: boolean) => {
+    if (!response) {
+      console.error('Failed to save character type')
+      return
+    }
+    refreshCharacterHairList(false)
+  })
+}
+
+watch(selectedCharacterHair, (characterHair: CharacterHair | null) => {
+  if (!characterHair) return
+  characterName.value = characterHair.name
+  characterGender.value = characterHair.gender
+  characterIsEnabledForCharCreation.value = characterHair.isEnabledForCharCreation
+  characterSpriteId.value = characterHair.spriteId
+})
+
+onMounted(() => {
+  if (!selectedCharacterHair.value) return
+
+  gameStore.connection?.emit('gm:sprite:list', {}, (response: Sprite[]) => {
+    assetManagerStore.setSpriteList(response)
+  })
+})
+
+onBeforeUnmount(() => {
+  assetManagerStore.setSelectedCharacterHair(null)
+})
+</script>
diff --git a/src/components/gameMaster/assetManager/partials/characterHair/CharacterHairList.vue b/src/components/gameMaster/assetManager/partials/characterHair/CharacterHairList.vue
new file mode 100644
index 0000000..49747c8
--- /dev/null
+++ b/src/components/gameMaster/assetManager/partials/characterHair/CharacterHairList.vue
@@ -0,0 +1,93 @@
+<template>
+  <div class="relative p-2.5 flex items-center gap-x-2.5">
+    <input v-model="searchQuery" class="input-field flex-grow" placeholder="Search..." @input="handleSearch" />
+    <label for="create-character" class="bg-cyan text-white border border-solid border-white/25 rounded drop-shadow-20 p-2 inline-flex items-center justify-center hover:bg-cyan-800 hover:cursor-pointer">
+      <button class="p-0 h-5" id="create-character" @click="createNewCharacterHair">
+        <svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="white">
+          <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4v16m8-8H4" />
+        </svg>
+      </button>
+    </label>
+    <div class="absolute left-0 bottom-0 w-full h-px bg-gray-500"></div>
+  </div>
+  <div v-bind="containerProps" class="overflow-y-auto relative" @scroll="onScroll">
+    <div v-bind="wrapperProps" ref="elementToScroll">
+      <a v-for="{ data: characterHair } in list" :key="characterHair.id" class="relative p-2.5 cursor-pointer block" :class="{ 'bg-cyan/80': assetManagerStore.selectedCharacterHair?.id === characterHair.id }" @click="assetManagerStore.setSelectedCharacterHair(characterHair as CharacterHair)">
+        <div class="flex items-center gap-2.5">
+          <span :class="{ 'text-white': assetManagerStore.selectedCharacterHair?.id === characterHair.id }">{{ characterHair.name }}</span>
+        </div>
+        <div class="absolute left-0 bottom-0 w-full h-px bg-gray-500"></div>
+      </a>
+    </div>
+    <button class="left-[calc(50%_-_60px)] fixed bottom-2.5 min-w-[unset] w-12 h-12 rounded-md bg-cyan p-0 hover:bg-cyan-800" v-show="hasScrolled" @click="toTop">
+      <img class="absolute invert w-8 h-8 left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2 rotate-180" src="/assets/icons/zoneEditor/chevron.svg" alt="" />
+    </button>
+  </div>
+</template>
+
+<script setup lang="ts">
+import { useGameStore } from '@/stores/gameStore'
+import { onMounted, ref, computed } from 'vue'
+import { useAssetManagerStore } from '@/stores/assetManagerStore'
+import type { CharacterHair } from '@/types'
+import { useVirtualList } from '@vueuse/core'
+
+const gameStore = useGameStore()
+const assetManagerStore = useAssetManagerStore()
+
+const searchQuery = ref('')
+
+const hasScrolled = ref(false)
+const elementToScroll = ref()
+
+const handleSearch = () => {
+  // Trigger a re-render of the virtual list
+  virtualList.value?.scrollTo(0)
+}
+
+const createNewCharacterHair = () => {
+  gameStore.connection?.emit('gm:characterHair:create', {}, (response: boolean) => {
+    if (!response) {
+      console.error('Failed to create new character type')
+      return
+    }
+
+    gameStore.connection?.emit('gm:characterHair:list', {}, (response: CharacterHair[]) => {
+      assetManagerStore.setCharacterHairList(response)
+    })
+  })
+}
+
+const filteredCharacterHairs = computed(() => {
+  if (!searchQuery.value) {
+    return assetManagerStore.characterHairList
+  }
+  return assetManagerStore.characterHairList.filter((character) => character.name.toLowerCase().includes(searchQuery.value.toLowerCase()))
+})
+
+const { list, containerProps, wrapperProps, scrollTo } = useVirtualList(filteredCharacterHairs, {
+  itemHeight: 48
+})
+
+const virtualList = ref({ scrollTo })
+
+const onScroll = () => {
+  let scrollTop = elementToScroll.value.style.marginTop.replace('px', '')
+
+  if (scrollTop > 80) {
+    hasScrolled.value = true
+  } else if (scrollTop <= 80) {
+    hasScrolled.value = false
+  }
+}
+
+function toTop() {
+  virtualList.value?.scrollTo(0)
+}
+
+onMounted(() => {
+  gameStore.connection?.emit('gm:characterHair:list', {}, (response: CharacterHair[]) => {
+    assetManagerStore.setCharacterHairList(response)
+  })
+})
+</script>
diff --git a/src/stores/assetManagerStore.ts b/src/stores/assetManagerStore.ts
index 72c9c69..4a5de59 100644
--- a/src/stores/assetManagerStore.ts
+++ b/src/stores/assetManagerStore.ts
@@ -1,6 +1,6 @@
 import { ref } from 'vue'
 import { defineStore } from 'pinia'
-import type { Tile, Object, Sprite, CharacterType } from '@/types'
+import type { Tile, Object, Sprite, CharacterType, CharacterHair } from '@/types'
 
 export const useAssetManagerStore = defineStore('assetManager', () => {
   const tileList = ref<Tile[]>([])
@@ -15,6 +15,9 @@ export const useAssetManagerStore = defineStore('assetManager', () => {
   const characterTypeList = ref<CharacterType[]>([])
   const selectedCharacterType = ref<CharacterType | null>(null)
 
+  const characterHairList = ref<CharacterHair[]>([])
+  const selectedCharacterHair = ref<CharacterHair | null>(null)
+
   function setTileList(tiles: Tile[]) {
     tileList.value = tiles
   }
@@ -47,6 +50,14 @@ export const useAssetManagerStore = defineStore('assetManager', () => {
     selectedCharacterType.value = characterType
   }
 
+  function setCharacterHairList(characterHair: CharacterHair[]) {
+    characterHairList.value = characterHair
+  }
+
+  function setSelectedCharacterHair(characterHair: CharacterHair | null) {
+    selectedCharacterHair.value = characterHair
+  }
+
   return {
     tileList,
     selectedTile,
@@ -56,6 +67,8 @@ export const useAssetManagerStore = defineStore('assetManager', () => {
     selectedSprite,
     characterTypeList,
     selectedCharacterType,
+    characterHairList,
+    selectedCharacterHair,
     setTileList,
     setSelectedTile,
     setObjectList,
@@ -63,6 +76,8 @@ export const useAssetManagerStore = defineStore('assetManager', () => {
     setSelectedObject,
     setSpriteList,
     setSelectedSprite,
-    setSelectedCharacterType
+    setSelectedCharacterType,
+    setCharacterHairList,
+    setSelectedCharacterHair
   }
 })
diff --git a/src/types.ts b/src/types.ts
index f249531..dce086c 100644
--- a/src/types.ts
+++ b/src/types.ts
@@ -145,6 +145,16 @@ export type CharacterType = {
   updatedAt: Date
 }
 
+export type CharacterHair = {
+  id: number
+  name: string
+  spriteId: string
+  sprite: Sprite
+  gender: CharacterGender
+  isEnabledForCharCreation: boolean
+  // @TODO: Do we need addedAt and updatedAt?
+}
+
 export type Character = {
   id: number
   userId: number
@@ -163,6 +173,8 @@ export type Character = {
   zone: Zone
   characterTypeId: number | null
   characterType: CharacterType | null
+  hairId: number | null
+  hair: CharacterHair | null
   chats: Chat[]
   items: CharacterItem[]
 }
@@ -172,10 +184,6 @@ export type ZoneCharacter = {
   isMoving?: boolean
 }
 
-export type ExtendedCharacter = Character & {
-  isMoving?: boolean
-}
-
 export type CharacterItem = {
   id: number
   characterId: number