diff --git a/.forgejo/workflows/test.yml b/.forgejo/workflows/test.yml
index b31a43d..6ad541e 100644
--- a/.forgejo/workflows/test.yml
+++ b/.forgejo/workflows/test.yml
@@ -1,4 +1,4 @@
-name: Testing
+name: Run Tests
 
 on:
   push:
@@ -8,21 +8,121 @@ on:
     branches:
       - '*'
 
+env:
+  NODE_VERSION: 23
+  APT_PACKAGES: "xvfb libnss3 libatk-bridge2.0-0 libxkbcommon-x11-0 libgtk-3-0 libgbm1 libasound2 libxss1"
+
 jobs:
-  test:
-    runs-on: docker
-
+  install:
+    name: Prepare environment
+    runs-on: ubuntu-latest
     steps:
-    - name: Checkout code
-      uses: actions/checkout@v2
+      - name: Checkout repository
+        uses: actions/checkout@v4
 
-    - name: Set up Node.js
-      uses: actions/setup-node@v2
-      with:
-        node-version: '16'
+      - name: Set up Node.js
+        uses: actions/setup-node@v4
+        with:
+          node-version: ${{ env.NODE_VERSION }}
+          cache: 'npm'
 
-    - name: Install dependencies
-      run: npm ci
+      - name: Cache APT dependencies
+        id: cache-apt
+        uses: actions/cache@v4
+        with:
+          path: /var/cache/apt/archives
+          key: apt-${{ runner.os }}-${{ env.APT_PACKAGES }}
 
-    - name: Run Unit tests
-      run: npm run test:unit
\ No newline at end of file
+      - name: Install APT dependencies (if cache miss)
+        if: steps.cache-apt.outputs.cache-hit != 'true'
+        run: sudo apt-get update && sudo apt-get install -y ${{ env.APT_PACKAGES }}
+
+      - name: Install NPM dependencies
+        run: npm ci
+
+      - name: Cache Cypress binary
+        uses: actions/cache@v4
+        with:
+          path: ~/.cache/Cypress
+          key: cypress-${{ runner.os }}-${{ env.NODE_VERSION }}-${{ hashFiles('package-lock.json') }}
+
+      - name: Cache Node modules
+        uses: actions/cache@v4
+        with:
+          path: node_modules
+          key: node-modules-${{ runner.os }}-${{ env.NODE_VERSION }}-${{ hashFiles('package-lock.json') }}
+
+      - name: Install Cypress
+        run: npx cypress install
+
+      - name: Build project
+        run: npm run build
+
+      - name: Save build artifact
+        uses: actions/upload-artifact@v3
+        with:
+          name: build
+          path: dist
+
+  unit-tests:
+    name: Run unit tests
+    runs-on: ubuntu-latest
+    needs: install
+    steps:
+      - name: Checkout repository
+        uses: actions/checkout@v4
+      
+      - name: Set up Node.js
+        uses: actions/setup-node@v4
+        with:
+          node-version: ${{ env.NODE_VERSION }}
+          cache: 'npm'
+
+      - name: Download build artifact
+        uses: actions/download-artifact@v3
+        with:
+          name: build
+          path: dist
+
+      - name: Restore Node modules cache
+        uses: actions/cache@v4
+        with:
+          name: node-modules
+          key: node-modules-${{ runner.os }}-${{ env.NODE_VERSION }}-${{ hashFiles('package-lock.json') }}
+          path: node_modules
+          
+      - name: Run unit tests
+        run: npm run test:unit
+
+  cypress-tests:
+    name: Run E2E tests (${{ matrix.browser }})
+    runs-on: ubuntu-latest
+    needs: install
+    strategy:
+      fail-fast: false
+      matrix:
+        browser: [chrome, firefox, edge]
+    steps:
+      - name: Checkout repository
+        uses: actions/checkout@v4
+
+      - name: Set up Node.js
+        uses: actions/setup-node@v4
+        with:
+          node-version: ${{ env.NODE_VERSION }}
+          cache: 'npm'
+
+      - name: Download build artifact
+        uses: actions/download-artifact@v3
+        with:
+          name: build
+          path: dist
+
+      - name: Install APT dependencies
+        run: sudo apt-get update && sudo apt-get install -y ${{ env.APT_PACKAGES }}
+
+      - name: Cypress run (${{ matrix.browser }})
+        uses: https://github.com/cypress-io/github-action@v6
+        with:
+          start: npm start
+          browser: ${{ matrix.browser }}
diff --git a/Dockerfile b/Dockerfile
index 77347f6..33da06a 100755
--- a/Dockerfile
+++ b/Dockerfile
@@ -16,6 +16,6 @@ COPY --from=build-stage /app/dist /usr/share/nginx/html
 EXPOSE 80
 
 # Use a non-root user for security
-USER nginx
+USER 1000
 
 CMD ["nginx", "-g", "daemon off;"]
\ No newline at end of file
diff --git a/cypress/e2e/example.cy.ts b/cypress/e2e/example.cy.ts
index 7554c35..ccb2def 100755
--- a/cypress/e2e/example.cy.ts
+++ b/cypress/e2e/example.cy.ts
@@ -1,8 +1,8 @@
 // https://on.cypress.io/api
 
-describe('My First Test', () => {
+describe('Site Sanity Check', () => {
   it('visits the app root url', () => {
     cy.visit('/')
-    cy.contains('h1', 'You did it!')
+    cy.contains('h2', 'Development Projects')
   })
 })
diff --git a/package-lock.json b/package-lock.json
index ee68625..0a86265 100755
--- a/package-lock.json
+++ b/package-lock.json
@@ -101,9 +101,9 @@
       }
     },
     "node_modules/@babel/compat-data": {
-      "version": "7.26.2",
-      "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.26.2.tgz",
-      "integrity": "sha512-Z0WgzSEa+aUcdiJuCIqgujCshpMWgUpgOxXotrYPSA53hA3qopNaqcJpyr0hVb1FeWdnqFA35/fUtXgBK8srQg==",
+      "version": "7.26.8",
+      "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.26.8.tgz",
+      "integrity": "sha512-oH5UPLMWR3L2wEFLnFJ1TZXqHufiTKAiLfqw5zkhS4dKXLJ10yVztfil/twG8EDTA4F/tvVNw9nOl4ZMslB8rQ==",
       "dev": true,
       "license": "MIT",
       "engines": {
@@ -111,22 +111,22 @@
       }
     },
     "node_modules/@babel/core": {
-      "version": "7.26.0",
-      "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.26.0.tgz",
-      "integrity": "sha512-i1SLeK+DzNnQ3LL/CswPCa/E5u4lh1k6IAEphON8F+cXt0t9euTshDru0q7/IqMa1PMPz5RnHuHscF8/ZJsStg==",
+      "version": "7.26.9",
+      "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.26.9.tgz",
+      "integrity": "sha512-lWBYIrF7qK5+GjY5Uy+/hEgp8OJWOD/rpy74GplYRhEauvbHDeFB8t5hPOZxCZ0Oxf4Cc36tK51/l3ymJysrKw==",
       "dev": true,
       "license": "MIT",
       "dependencies": {
         "@ampproject/remapping": "^2.2.0",
-        "@babel/code-frame": "^7.26.0",
-        "@babel/generator": "^7.26.0",
-        "@babel/helper-compilation-targets": "^7.25.9",
+        "@babel/code-frame": "^7.26.2",
+        "@babel/generator": "^7.26.9",
+        "@babel/helper-compilation-targets": "^7.26.5",
         "@babel/helper-module-transforms": "^7.26.0",
-        "@babel/helpers": "^7.26.0",
-        "@babel/parser": "^7.26.0",
-        "@babel/template": "^7.25.9",
-        "@babel/traverse": "^7.25.9",
-        "@babel/types": "^7.26.0",
+        "@babel/helpers": "^7.26.9",
+        "@babel/parser": "^7.26.9",
+        "@babel/template": "^7.26.9",
+        "@babel/traverse": "^7.26.9",
+        "@babel/types": "^7.26.9",
         "convert-source-map": "^2.0.0",
         "debug": "^4.1.0",
         "gensync": "^1.0.0-beta.2",
@@ -152,14 +152,14 @@
       }
     },
     "node_modules/@babel/generator": {
-      "version": "7.26.2",
-      "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.26.2.tgz",
-      "integrity": "sha512-zevQbhbau95nkoxSq3f/DC/SC+EEOUZd3DYqfSkMhY2/wfSeaHV1Ew4vk8e+x8lja31IbyuUa2uQ3JONqKbysw==",
+      "version": "7.26.9",
+      "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.26.9.tgz",
+      "integrity": "sha512-kEWdzjOAUMW4hAyrzJ0ZaTOu9OmpyDIQicIh0zg0EEcEkYXZb2TjtBhnHi2ViX7PKwZqF4xwqfAm299/QMP3lg==",
       "dev": true,
       "license": "MIT",
       "dependencies": {
-        "@babel/parser": "^7.26.2",
-        "@babel/types": "^7.26.0",
+        "@babel/parser": "^7.26.9",
+        "@babel/types": "^7.26.9",
         "@jridgewell/gen-mapping": "^0.3.5",
         "@jridgewell/trace-mapping": "^0.3.25",
         "jsesc": "^3.0.2"
@@ -182,13 +182,13 @@
       }
     },
     "node_modules/@babel/helper-compilation-targets": {
-      "version": "7.25.9",
-      "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.25.9.tgz",
-      "integrity": "sha512-j9Db8Suy6yV/VHa4qzrj9yZfZxhLWQdVnRlXxmKLYlhWUVB1sB2G5sxuWYXk/whHD9iW76PmNzxZ4UCnTQTVEQ==",
+      "version": "7.26.5",
+      "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.26.5.tgz",
+      "integrity": "sha512-IXuyn5EkouFJscIDuFF5EsiSolseme1s0CZB+QxVugqJLYmKdxI1VfIBOst0SUu4rnk2Z7kqTwmoO1lp3HIfnA==",
       "dev": true,
       "license": "MIT",
       "dependencies": {
-        "@babel/compat-data": "^7.25.9",
+        "@babel/compat-data": "^7.26.5",
         "@babel/helper-validator-option": "^7.25.9",
         "browserslist": "^4.24.0",
         "lru-cache": "^5.1.1",
@@ -219,18 +219,18 @@
       }
     },
     "node_modules/@babel/helper-create-class-features-plugin": {
-      "version": "7.25.9",
-      "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.25.9.tgz",
-      "integrity": "sha512-UTZQMvt0d/rSz6KI+qdu7GQze5TIajwTS++GUozlw8VBJDEOAqSXwm1WvmYEZwqdqSGQshRocPDqrt4HBZB3fQ==",
+      "version": "7.26.9",
+      "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.26.9.tgz",
+      "integrity": "sha512-ubbUqCofvxPRurw5L8WTsCLSkQiVpov4Qx0WMA+jUN+nXBK8ADPlJO1grkFw5CWKC5+sZSOfuGMdX1aI1iT9Sg==",
       "dev": true,
       "license": "MIT",
       "dependencies": {
         "@babel/helper-annotate-as-pure": "^7.25.9",
         "@babel/helper-member-expression-to-functions": "^7.25.9",
         "@babel/helper-optimise-call-expression": "^7.25.9",
-        "@babel/helper-replace-supers": "^7.25.9",
+        "@babel/helper-replace-supers": "^7.26.5",
         "@babel/helper-skip-transparent-expression-wrappers": "^7.25.9",
-        "@babel/traverse": "^7.25.9",
+        "@babel/traverse": "^7.26.9",
         "semver": "^6.3.1"
       },
       "engines": {
@@ -310,9 +310,9 @@
       }
     },
     "node_modules/@babel/helper-plugin-utils": {
-      "version": "7.25.9",
-      "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.25.9.tgz",
-      "integrity": "sha512-kSMlyUVdWe25rEsRGviIgOWnoT/nfABVWlqt9N19/dIPWViAOW2s9wznP5tURbs/IDuNk4gPy3YdYRgH3uxhBw==",
+      "version": "7.26.5",
+      "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.26.5.tgz",
+      "integrity": "sha512-RS+jZcRdZdRFzMyr+wcsaqOmld1/EqTghfaBGQQd/WnRdzdlvSZ//kF7U8VQTxf1ynZ4cjUcYgjVGx13ewNPMg==",
       "dev": true,
       "license": "MIT",
       "engines": {
@@ -320,15 +320,15 @@
       }
     },
     "node_modules/@babel/helper-replace-supers": {
-      "version": "7.25.9",
-      "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.25.9.tgz",
-      "integrity": "sha512-IiDqTOTBQy0sWyeXyGSC5TBJpGFXBkRynjBeXsvbhQFKj2viwJC76Epz35YLU1fpe/Am6Vppb7W7zM4fPQzLsQ==",
+      "version": "7.26.5",
+      "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.26.5.tgz",
+      "integrity": "sha512-bJ6iIVdYX1YooY2X7w1q6VITt+LnUILtNk7zT78ykuwStx8BauCzxvFqFaHjOpW1bVnSUM1PN1f0p5P21wHxvg==",
       "dev": true,
       "license": "MIT",
       "dependencies": {
         "@babel/helper-member-expression-to-functions": "^7.25.9",
         "@babel/helper-optimise-call-expression": "^7.25.9",
-        "@babel/traverse": "^7.25.9"
+        "@babel/traverse": "^7.26.5"
       },
       "engines": {
         "node": ">=6.9.0"
@@ -380,26 +380,26 @@
       }
     },
     "node_modules/@babel/helpers": {
-      "version": "7.26.0",
-      "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.26.0.tgz",
-      "integrity": "sha512-tbhNuIxNcVb21pInl3ZSjksLCvgdZy9KwJ8brv993QtIVKJBBkYXz4q4ZbAv31GdnC+R90np23L5FbEBlthAEw==",
+      "version": "7.26.9",
+      "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.26.9.tgz",
+      "integrity": "sha512-Mz/4+y8udxBKdmzt/UjPACs4G3j5SshJJEFFKxlCGPydG4JAHXxjWjAwjd09tf6oINvl1VfMJo+nB7H2YKQ0dA==",
       "dev": true,
       "license": "MIT",
       "dependencies": {
-        "@babel/template": "^7.25.9",
-        "@babel/types": "^7.26.0"
+        "@babel/template": "^7.26.9",
+        "@babel/types": "^7.26.9"
       },
       "engines": {
         "node": ">=6.9.0"
       }
     },
     "node_modules/@babel/parser": {
-      "version": "7.26.2",
-      "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.26.2.tgz",
-      "integrity": "sha512-DWMCZH9WA4Maitz2q21SRKHo9QXZxkDsbNZoVD62gusNtNBBqDg9i7uOhASfTfIGNzW+O+r7+jAlM8dwphcJKQ==",
+      "version": "7.26.9",
+      "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.26.9.tgz",
+      "integrity": "sha512-81NWa1njQblgZbQHxWHpxxCzNsa3ZwvFqpUg7P+NNUU6f3UU2jBEg4OlF/J6rl8+PQGh1q6/zWScd001YwcA5A==",
       "license": "MIT",
       "dependencies": {
-        "@babel/types": "^7.26.0"
+        "@babel/types": "^7.26.9"
       },
       "bin": {
         "parser": "bin/babel-parser.js"
@@ -504,15 +504,15 @@
       }
     },
     "node_modules/@babel/plugin-transform-typescript": {
-      "version": "7.25.9",
-      "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.25.9.tgz",
-      "integrity": "sha512-7PbZQZP50tzv2KGGnhh82GSyMB01yKY9scIjf1a+GfZCtInOWqUH5+1EBU4t9fyR5Oykkkc9vFTs4OHrhHXljQ==",
+      "version": "7.26.8",
+      "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.26.8.tgz",
+      "integrity": "sha512-bME5J9AC8ChwA7aEPJ6zym3w7aObZULHhbNLU0bKUhKsAkylkzUdq+0kdymh9rzi8nlNFl2bmldFBCKNJBUpuw==",
       "dev": true,
       "license": "MIT",
       "dependencies": {
         "@babel/helper-annotate-as-pure": "^7.25.9",
         "@babel/helper-create-class-features-plugin": "^7.25.9",
-        "@babel/helper-plugin-utils": "^7.25.9",
+        "@babel/helper-plugin-utils": "^7.26.5",
         "@babel/helper-skip-transparent-expression-wrappers": "^7.25.9",
         "@babel/plugin-syntax-typescript": "^7.25.9"
       },
@@ -524,32 +524,32 @@
       }
     },
     "node_modules/@babel/template": {
-      "version": "7.25.9",
-      "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.25.9.tgz",
-      "integrity": "sha512-9DGttpmPvIxBb/2uwpVo3dqJ+O6RooAFOS+lB+xDqoE2PVCE8nfoHMdZLpfCQRLwvohzXISPZcgxt80xLfsuwg==",
+      "version": "7.26.9",
+      "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.26.9.tgz",
+      "integrity": "sha512-qyRplbeIpNZhmzOysF/wFMuP9sctmh2cFzRAZOn1YapxBsE1i9bJIY586R/WBLfLcmcBlM8ROBiQURnnNy+zfA==",
       "dev": true,
       "license": "MIT",
       "dependencies": {
-        "@babel/code-frame": "^7.25.9",
-        "@babel/parser": "^7.25.9",
-        "@babel/types": "^7.25.9"
+        "@babel/code-frame": "^7.26.2",
+        "@babel/parser": "^7.26.9",
+        "@babel/types": "^7.26.9"
       },
       "engines": {
         "node": ">=6.9.0"
       }
     },
     "node_modules/@babel/traverse": {
-      "version": "7.25.9",
-      "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.25.9.tgz",
-      "integrity": "sha512-ZCuvfwOwlz/bawvAuvcj8rrithP2/N55Tzz342AkTvq4qaWbGfmCk/tKhNaV2cthijKrPAA8SRJV5WWe7IBMJw==",
+      "version": "7.26.9",
+      "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.26.9.tgz",
+      "integrity": "sha512-ZYW7L+pL8ahU5fXmNbPF+iZFHCv5scFak7MZ9bwaRPLUhHh7QQEMjZUg0HevihoqCM5iSYHN61EyCoZvqC+bxg==",
       "dev": true,
       "license": "MIT",
       "dependencies": {
-        "@babel/code-frame": "^7.25.9",
-        "@babel/generator": "^7.25.9",
-        "@babel/parser": "^7.25.9",
-        "@babel/template": "^7.25.9",
-        "@babel/types": "^7.25.9",
+        "@babel/code-frame": "^7.26.2",
+        "@babel/generator": "^7.26.9",
+        "@babel/parser": "^7.26.9",
+        "@babel/template": "^7.26.9",
+        "@babel/types": "^7.26.9",
         "debug": "^4.3.1",
         "globals": "^11.1.0"
       },
@@ -568,9 +568,9 @@
       }
     },
     "node_modules/@babel/types": {
-      "version": "7.26.0",
-      "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.26.0.tgz",
-      "integrity": "sha512-Z/yiTPj+lDVnF7lWeKCIJzaIkI0vYO87dMpZ4bg4TDrFe4XXLFWL1TbXU27gBP3QccxV9mZICCrnjnYlJjXHOA==",
+      "version": "7.26.9",
+      "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.26.9.tgz",
+      "integrity": "sha512-Y3IR1cRnOxOCDvMmNiym7XpXQ93iGDDPHx+Zj+NM+rg0fBaShfQLkg+hKPaZCEvg5N/LeCo4+Rj/i3FuJsIQaw==",
       "license": "MIT",
       "dependencies": {
         "@babel/helper-string-parser": "^7.25.9",
@@ -1537,9 +1537,9 @@
       "license": "MIT"
     },
     "node_modules/@rollup/pluginutils": {
-      "version": "5.1.3",
-      "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.1.3.tgz",
-      "integrity": "sha512-Pnsb6f32CD2W3uCaLZIzDmeFyQ2b8UWMFI7xtwUezpcGBDVDW6y9XgAWIlARiGAo6eNF5FK5aQTr0LFyNyqq5A==",
+      "version": "5.1.4",
+      "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.1.4.tgz",
+      "integrity": "sha512-USm05zrsFxYLPdWWq+K3STlWiT/3ELn3RcV5hJMghpeAIhxfsUIg6mt12CBJBInWMV4VneoV7SfGv8xIwo2qNQ==",
       "dev": true,
       "license": "MIT",
       "dependencies": {
@@ -1824,6 +1824,13 @@
         "win32"
       ]
     },
+    "node_modules/@sec-ant/readable-stream": {
+      "version": "0.4.1",
+      "resolved": "https://registry.npmjs.org/@sec-ant/readable-stream/-/readable-stream-0.4.1.tgz",
+      "integrity": "sha512-831qok9r2t8AlxLko40y2ebgSDhenenCatLVeW/uBtnHPyhHOvG0C7TvfgecV+wHzIm5KUICgzmVpWS+IMEAeg==",
+      "dev": true,
+      "license": "MIT"
+    },
     "node_modules/@sideway/address": {
       "version": "4.1.5",
       "resolved": "https://registry.npmjs.org/@sideway/address/-/address-4.1.5.tgz",
@@ -1848,6 +1855,19 @@
       "dev": true,
       "license": "BSD-3-Clause"
     },
+    "node_modules/@sindresorhus/merge-streams": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmjs.org/@sindresorhus/merge-streams/-/merge-streams-4.0.0.tgz",
+      "integrity": "sha512-tlqY9xq5ukxTUZBmoOp+m61cqwQD5pHJtFY3Mn8CA8ps6yghLH/Hw8UPdqg4OLmFW3IFlcXnQNmo/dh8HzXYIQ==",
+      "dev": true,
+      "license": "MIT",
+      "engines": {
+        "node": ">=18"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
     "node_modules/@tsconfig/node22": {
       "version": "22.0.0",
       "resolved": "https://registry.npmjs.org/@tsconfig/node22/-/node22-22.0.0.tgz",
@@ -2520,16 +2540,16 @@
       }
     },
     "node_modules/@vitejs/plugin-vue": {
-      "version": "5.2.0",
-      "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-5.2.0.tgz",
-      "integrity": "sha512-7n7KdUEtx/7Yl7I/WVAMZ1bEb0eVvXF3ummWTeLcs/9gvo9pJhuLdouSXGjdZ/MKD1acf1I272+X0RMua4/R3g==",
+      "version": "5.2.1",
+      "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-5.2.1.tgz",
+      "integrity": "sha512-cxh314tzaWwOLqVes2gnnCtvBDcM1UMdn+iFR+UjAn411dPT3tOmqrJjbMd7koZpMAmBM/GqeV4n9ge7JSiJJQ==",
       "dev": true,
       "license": "MIT",
       "engines": {
         "node": "^18.0.0 || >=20.0.0"
       },
       "peerDependencies": {
-        "vite": "^5.0.0",
+        "vite": "^5.0.0 || ^6.0.0",
         "vue": "^3.2.25"
       }
     },
@@ -2555,14 +2575,14 @@
       }
     },
     "node_modules/@vitest/expect": {
-      "version": "2.1.5",
-      "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-2.1.5.tgz",
-      "integrity": "sha512-nZSBTW1XIdpZvEJyoP/Sy8fUg0b8od7ZpGDkTUcfJ7wz/VoZAFzFfLyxVxGFhUjJzhYqSbIpfMtl/+k/dpWa3Q==",
+      "version": "2.1.9",
+      "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-2.1.9.tgz",
+      "integrity": "sha512-UJCIkTBenHeKT1TTlKMJWy1laZewsRIzYighyYiJKZreqtdxSos/S1t+ktRMQWu2CKqaarrkeszJx1cgC5tGZw==",
       "dev": true,
       "license": "MIT",
       "dependencies": {
-        "@vitest/spy": "2.1.5",
-        "@vitest/utils": "2.1.5",
+        "@vitest/spy": "2.1.9",
+        "@vitest/utils": "2.1.9",
         "chai": "^5.1.2",
         "tinyrainbow": "^1.2.0"
       },
@@ -2571,13 +2591,13 @@
       }
     },
     "node_modules/@vitest/mocker": {
-      "version": "2.1.5",
-      "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-2.1.5.tgz",
-      "integrity": "sha512-XYW6l3UuBmitWqSUXTNXcVBUCRytDogBsWuNXQijc00dtnU/9OqpXWp4OJroVrad/gLIomAq9aW8yWDBtMthhQ==",
+      "version": "2.1.9",
+      "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-2.1.9.tgz",
+      "integrity": "sha512-tVL6uJgoUdi6icpxmdrn5YNo3g3Dxv+IHJBr0GXHaEdTcw3F+cPKnsXFhli6nO+f/6SDKPHEK1UN+k+TQv0Ehg==",
       "dev": true,
       "license": "MIT",
       "dependencies": {
-        "@vitest/spy": "2.1.5",
+        "@vitest/spy": "2.1.9",
         "estree-walker": "^3.0.3",
         "magic-string": "^0.30.12"
       },
@@ -2608,9 +2628,9 @@
       }
     },
     "node_modules/@vitest/pretty-format": {
-      "version": "2.1.5",
-      "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-2.1.5.tgz",
-      "integrity": "sha512-4ZOwtk2bqG5Y6xRGHcveZVr+6txkH7M2e+nPFd6guSoN638v/1XQ0K06eOpi0ptVU/2tW/pIU4IoPotY/GZ9fw==",
+      "version": "2.1.9",
+      "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-2.1.9.tgz",
+      "integrity": "sha512-KhRIdGV2U9HOUzxfiHmY8IFHTdqtOhIzCpd8WRdJiE7D/HUcZVD0EgQCVjm+Q9gkUXWgBvMmTtZgIG48wq7sOQ==",
       "dev": true,
       "license": "MIT",
       "dependencies": {
@@ -2621,13 +2641,13 @@
       }
     },
     "node_modules/@vitest/runner": {
-      "version": "2.1.5",
-      "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-2.1.5.tgz",
-      "integrity": "sha512-pKHKy3uaUdh7X6p1pxOkgkVAFW7r2I818vHDthYLvUyjRfkKOU6P45PztOch4DZarWQne+VOaIMwA/erSSpB9g==",
+      "version": "2.1.9",
+      "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-2.1.9.tgz",
+      "integrity": "sha512-ZXSSqTFIrzduD63btIfEyOmNcBmQvgOVsPNPe0jYtESiXkhd8u2erDLnMxmGrDCwHCCHE7hxwRDCT3pt0esT4g==",
       "dev": true,
       "license": "MIT",
       "dependencies": {
-        "@vitest/utils": "2.1.5",
+        "@vitest/utils": "2.1.9",
         "pathe": "^1.1.2"
       },
       "funding": {
@@ -2635,13 +2655,13 @@
       }
     },
     "node_modules/@vitest/snapshot": {
-      "version": "2.1.5",
-      "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-2.1.5.tgz",
-      "integrity": "sha512-zmYw47mhfdfnYbuhkQvkkzYroXUumrwWDGlMjpdUr4jBd3HZiV2w7CQHj+z7AAS4VOtWxI4Zt4bWt4/sKcoIjg==",
+      "version": "2.1.9",
+      "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-2.1.9.tgz",
+      "integrity": "sha512-oBO82rEjsxLNJincVhLhaxxZdEtV0EFHMK5Kmx5sJ6H9L183dHECjiefOAdnqpIgT5eZwT04PoggUnW88vOBNQ==",
       "dev": true,
       "license": "MIT",
       "dependencies": {
-        "@vitest/pretty-format": "2.1.5",
+        "@vitest/pretty-format": "2.1.9",
         "magic-string": "^0.30.12",
         "pathe": "^1.1.2"
       },
@@ -2650,9 +2670,9 @@
       }
     },
     "node_modules/@vitest/spy": {
-      "version": "2.1.5",
-      "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-2.1.5.tgz",
-      "integrity": "sha512-aWZF3P0r3w6DiYTVskOYuhBc7EMc3jvn1TkBg8ttylFFRqNN2XGD7V5a4aQdk6QiUzZQ4klNBSpCLJgWNdIiNw==",
+      "version": "2.1.9",
+      "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-2.1.9.tgz",
+      "integrity": "sha512-E1B35FwzXXTs9FHNK6bDszs7mtydNi5MIfUWpceJ8Xbfb1gBMscAnwLbEu+B44ed6W3XjL9/ehLPHR1fkf1KLQ==",
       "dev": true,
       "license": "MIT",
       "dependencies": {
@@ -2663,13 +2683,13 @@
       }
     },
     "node_modules/@vitest/utils": {
-      "version": "2.1.5",
-      "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-2.1.5.tgz",
-      "integrity": "sha512-yfj6Yrp0Vesw2cwJbP+cl04OC+IHFsuQsrsJBL9pyGeQXE56v1UAOQco+SR55Vf1nQzfV0QJg1Qum7AaWUwwYg==",
+      "version": "2.1.9",
+      "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-2.1.9.tgz",
+      "integrity": "sha512-v0psaMSkNJ3A2NMrUEHFRzJtDPFn+/VWZ5WxImB21T9fjucJRmS7xCS3ppEnARb9y11OAzaD+P2Ps+b+BGX5iQ==",
       "dev": true,
       "license": "MIT",
       "dependencies": {
-        "@vitest/pretty-format": "2.1.5",
+        "@vitest/pretty-format": "2.1.9",
         "loupe": "^3.1.2",
         "tinyrainbow": "^1.2.0"
       },
@@ -2707,29 +2727,28 @@
       }
     },
     "node_modules/@vue/babel-helper-vue-transform-on": {
-      "version": "1.2.5",
-      "resolved": "https://registry.npmjs.org/@vue/babel-helper-vue-transform-on/-/babel-helper-vue-transform-on-1.2.5.tgz",
-      "integrity": "sha512-lOz4t39ZdmU4DJAa2hwPYmKc8EsuGa2U0L9KaZaOJUt0UwQNjNA3AZTq6uEivhOKhhG1Wvy96SvYBoFmCg3uuw==",
+      "version": "1.3.0",
+      "resolved": "https://registry.npmjs.org/@vue/babel-helper-vue-transform-on/-/babel-helper-vue-transform-on-1.3.0.tgz",
+      "integrity": "sha512-vrNyYNQcz1gfc87uuN+Z+On9fFOBQTYRlTUEDovpeCmjuwH83lAm6YM0VBvTx6eRTHg3SU5jP2CD+kSXY30PGg==",
       "dev": true,
       "license": "MIT"
     },
     "node_modules/@vue/babel-plugin-jsx": {
-      "version": "1.2.5",
-      "resolved": "https://registry.npmjs.org/@vue/babel-plugin-jsx/-/babel-plugin-jsx-1.2.5.tgz",
-      "integrity": "sha512-zTrNmOd4939H9KsRIGmmzn3q2zvv1mjxkYZHgqHZgDrXz5B1Q3WyGEjO2f+JrmKghvl1JIRcvo63LgM1kH5zFg==",
+      "version": "1.3.0",
+      "resolved": "https://registry.npmjs.org/@vue/babel-plugin-jsx/-/babel-plugin-jsx-1.3.0.tgz",
+      "integrity": "sha512-ODZSs93FCxLMOiMFAGJXe7QMJp1tk8hkMbk84OcHOTVwYU2cFwFu1z7jjrRv44wCCfPNkflqn6hnexVprb+G7A==",
       "dev": true,
       "license": "MIT",
       "dependencies": {
-        "@babel/helper-module-imports": "^7.24.7",
-        "@babel/helper-plugin-utils": "^7.24.8",
-        "@babel/plugin-syntax-jsx": "^7.24.7",
-        "@babel/template": "^7.25.0",
-        "@babel/traverse": "^7.25.6",
-        "@babel/types": "^7.25.6",
-        "@vue/babel-helper-vue-transform-on": "1.2.5",
-        "@vue/babel-plugin-resolve-type": "1.2.5",
-        "html-tags": "^3.3.1",
-        "svg-tags": "^1.0.0"
+        "@babel/helper-module-imports": "^7.25.9",
+        "@babel/helper-plugin-utils": "^7.26.5",
+        "@babel/plugin-syntax-jsx": "^7.25.9",
+        "@babel/template": "^7.26.9",
+        "@babel/traverse": "^7.26.9",
+        "@babel/types": "^7.26.9",
+        "@vue/babel-helper-vue-transform-on": "1.3.0",
+        "@vue/babel-plugin-resolve-type": "1.3.0",
+        "@vue/shared": "^3.5.13"
       },
       "peerDependencies": {
         "@babel/core": "^7.0.0-0"
@@ -2740,23 +2759,91 @@
         }
       }
     },
+    "node_modules/@vue/babel-plugin-jsx/node_modules/@vue/shared": {
+      "version": "3.5.13",
+      "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.13.tgz",
+      "integrity": "sha512-/hnE/qP5ZoGpol0a5mDi45bOd7t3tjYJBjsgCsivow7D48cJeV5l05RD82lPqi7gRiphZM37rnhW1l6ZoCNNnQ==",
+      "dev": true,
+      "license": "MIT"
+    },
     "node_modules/@vue/babel-plugin-resolve-type": {
-      "version": "1.2.5",
-      "resolved": "https://registry.npmjs.org/@vue/babel-plugin-resolve-type/-/babel-plugin-resolve-type-1.2.5.tgz",
-      "integrity": "sha512-U/ibkQrf5sx0XXRnUZD1mo5F7PkpKyTbfXM3a3rC4YnUz6crHEz9Jg09jzzL6QYlXNto/9CePdOg/c87O4Nlfg==",
+      "version": "1.3.0",
+      "resolved": "https://registry.npmjs.org/@vue/babel-plugin-resolve-type/-/babel-plugin-resolve-type-1.3.0.tgz",
+      "integrity": "sha512-3SmusE11QKNKtnVfbsKegUEArpf1fXE85Dzi/Q6lvaz3MA3tmL8BXyq/vA7GJeZ183XeNpLIZHrHDdUh9V348A==",
       "dev": true,
       "license": "MIT",
       "dependencies": {
-        "@babel/code-frame": "^7.24.7",
-        "@babel/helper-module-imports": "^7.24.7",
-        "@babel/helper-plugin-utils": "^7.24.8",
-        "@babel/parser": "^7.25.6",
-        "@vue/compiler-sfc": "^3.5.3"
+        "@babel/code-frame": "^7.26.2",
+        "@babel/helper-module-imports": "^7.25.9",
+        "@babel/helper-plugin-utils": "^7.26.5",
+        "@babel/parser": "^7.26.9",
+        "@vue/compiler-sfc": "^3.5.13"
       },
       "peerDependencies": {
         "@babel/core": "^7.0.0-0"
       }
     },
+    "node_modules/@vue/babel-plugin-resolve-type/node_modules/@vue/compiler-core": {
+      "version": "3.5.13",
+      "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.13.tgz",
+      "integrity": "sha512-oOdAkwqUfW1WqpwSYJce06wvt6HljgY3fGeM9NcVA1HaYOij3mZG9Rkysn0OHuyUAGMbEbARIpsG+LPVlBJ5/Q==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@babel/parser": "^7.25.3",
+        "@vue/shared": "3.5.13",
+        "entities": "^4.5.0",
+        "estree-walker": "^2.0.2",
+        "source-map-js": "^1.2.0"
+      }
+    },
+    "node_modules/@vue/babel-plugin-resolve-type/node_modules/@vue/compiler-dom": {
+      "version": "3.5.13",
+      "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.13.tgz",
+      "integrity": "sha512-ZOJ46sMOKUjO3e94wPdCzQ6P1Lx/vhp2RSvfaab88Ajexs0AHeV0uasYhi99WPaogmBlRHNRuly8xV75cNTMDA==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@vue/compiler-core": "3.5.13",
+        "@vue/shared": "3.5.13"
+      }
+    },
+    "node_modules/@vue/babel-plugin-resolve-type/node_modules/@vue/compiler-sfc": {
+      "version": "3.5.13",
+      "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.13.tgz",
+      "integrity": "sha512-6VdaljMpD82w6c2749Zhf5T9u5uLBWKnVue6XWxprDobftnletJ8+oel7sexFfM3qIxNmVE7LSFGTpv6obNyaQ==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@babel/parser": "^7.25.3",
+        "@vue/compiler-core": "3.5.13",
+        "@vue/compiler-dom": "3.5.13",
+        "@vue/compiler-ssr": "3.5.13",
+        "@vue/shared": "3.5.13",
+        "estree-walker": "^2.0.2",
+        "magic-string": "^0.30.11",
+        "postcss": "^8.4.48",
+        "source-map-js": "^1.2.0"
+      }
+    },
+    "node_modules/@vue/babel-plugin-resolve-type/node_modules/@vue/compiler-ssr": {
+      "version": "3.5.13",
+      "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.13.tgz",
+      "integrity": "sha512-wMH6vrYHxQl/IybKJagqbquvxpWCuVYpoUJfCqFZwa/JY1GdATAQ+TgVtgrwwMZ0D07QhA99rs/EAAWfvG6KpA==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@vue/compiler-dom": "3.5.13",
+        "@vue/shared": "3.5.13"
+      }
+    },
+    "node_modules/@vue/babel-plugin-resolve-type/node_modules/@vue/shared": {
+      "version": "3.5.13",
+      "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.13.tgz",
+      "integrity": "sha512-/hnE/qP5ZoGpol0a5mDi45bOd7t3tjYJBjsgCsivow7D48cJeV5l05RD82lPqi7gRiphZM37rnhW1l6ZoCNNnQ==",
+      "dev": true,
+      "license": "MIT"
+    },
     "node_modules/@vue/compiler-core": {
       "version": "3.5.12",
       "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.12.tgz",
@@ -2825,31 +2912,57 @@
       "license": "MIT"
     },
     "node_modules/@vue/devtools-core": {
-      "version": "7.6.4",
-      "resolved": "https://registry.npmjs.org/@vue/devtools-core/-/devtools-core-7.6.4.tgz",
-      "integrity": "sha512-blSwGVYpb7b5TALMjjoBiAl5imuBF7WEOAtaJaBMNikR8SQkm6mkUt4YlIKh9874/qoimwmpDOm+GHBZ4Y5m+g==",
+      "version": "7.7.2",
+      "resolved": "https://registry.npmjs.org/@vue/devtools-core/-/devtools-core-7.7.2.tgz",
+      "integrity": "sha512-lexREWj1lKi91Tblr38ntSsy6CvI8ba7u+jmwh2yruib/ltLUcsIzEjCnrkh1yYGGIKXbAuYV2tOG10fGDB9OQ==",
       "dev": true,
       "license": "MIT",
       "dependencies": {
-        "@vue/devtools-kit": "^7.6.4",
-        "@vue/devtools-shared": "^7.6.4",
+        "@vue/devtools-kit": "^7.7.2",
+        "@vue/devtools-shared": "^7.7.2",
         "mitt": "^3.0.1",
-        "nanoid": "^3.3.4",
-        "pathe": "^1.1.2",
-        "vite-hot-client": "^0.2.3"
+        "nanoid": "^5.0.9",
+        "pathe": "^2.0.2",
+        "vite-hot-client": "^0.2.4"
       },
       "peerDependencies": {
         "vue": "^3.0.0"
       }
     },
+    "node_modules/@vue/devtools-core/node_modules/nanoid": {
+      "version": "5.1.2",
+      "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-5.1.2.tgz",
+      "integrity": "sha512-b+CiXQCNMUGe0Ri64S9SXFcP9hogjAJ2Rd6GdVxhPLRm7mhGaM7VgOvCAJ1ZshfHbqVDI3uqTI5C8/GaKuLI7g==",
+      "dev": true,
+      "funding": [
+        {
+          "type": "github",
+          "url": "https://github.com/sponsors/ai"
+        }
+      ],
+      "license": "MIT",
+      "bin": {
+        "nanoid": "bin/nanoid.js"
+      },
+      "engines": {
+        "node": "^18 || >=20"
+      }
+    },
+    "node_modules/@vue/devtools-core/node_modules/pathe": {
+      "version": "2.0.3",
+      "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz",
+      "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==",
+      "dev": true,
+      "license": "MIT"
+    },
     "node_modules/@vue/devtools-kit": {
-      "version": "7.6.4",
-      "resolved": "https://registry.npmjs.org/@vue/devtools-kit/-/devtools-kit-7.6.4.tgz",
-      "integrity": "sha512-Zs86qIXXM9icU0PiGY09PQCle4TI750IPLmAJzW5Kf9n9t5HzSYf6Rz6fyzSwmfMPiR51SUKJh9sXVZu78h2QA==",
+      "version": "7.7.2",
+      "resolved": "https://registry.npmjs.org/@vue/devtools-kit/-/devtools-kit-7.7.2.tgz",
+      "integrity": "sha512-CY0I1JH3Z8PECbn6k3TqM1Bk9ASWxeMtTCvZr7vb+CHi+X/QwQm5F1/fPagraamKMAHVfuuCbdcnNg1A4CYVWQ==",
       "dev": true,
       "license": "MIT",
       "dependencies": {
-        "@vue/devtools-shared": "^7.6.4",
+        "@vue/devtools-shared": "^7.7.2",
         "birpc": "^0.2.19",
         "hookable": "^5.5.3",
         "mitt": "^3.0.1",
@@ -2859,9 +2972,9 @@
       }
     },
     "node_modules/@vue/devtools-shared": {
-      "version": "7.6.4",
-      "resolved": "https://registry.npmjs.org/@vue/devtools-shared/-/devtools-shared-7.6.4.tgz",
-      "integrity": "sha512-nD6CUvBEel+y7zpyorjiUocy0nh77DThZJ0k1GRnJeOmY3ATq2fWijEp7wk37gb023Cb0R396uYh5qMSBQ5WFg==",
+      "version": "7.7.2",
+      "resolved": "https://registry.npmjs.org/@vue/devtools-shared/-/devtools-shared-7.7.2.tgz",
+      "integrity": "sha512-uBFxnp8gwW2vD6FrJB8JZLUzVb6PNRG0B0jBnHsOH8uKyva2qINY8PTF5Te4QlTbMDqU5K6qtJDr6cNsKWhbOA==",
       "dev": true,
       "license": "MIT",
       "dependencies": {
@@ -3711,9 +3824,9 @@
       "license": "Apache-2.0"
     },
     "node_modules/chai": {
-      "version": "5.1.2",
-      "resolved": "https://registry.npmjs.org/chai/-/chai-5.1.2.tgz",
-      "integrity": "sha512-aGtmf24DW6MLHHG5gCx4zaI3uBq3KRtxeVs0DjFH6Z0rDNbsvTxFASFvdj79pxjxZ8/5u3PIiN3IwEIQkiiuPw==",
+      "version": "5.2.0",
+      "resolved": "https://registry.npmjs.org/chai/-/chai-5.2.0.tgz",
+      "integrity": "sha512-mCuXncKXk5iCLhfhwTc0izo0gtEmpz5CtG2y8GiOINBlMVS6v8TMRc5TaLWKS6692m9+dVVfzgeVxR5UxWHTYw==",
       "dev": true,
       "license": "MIT",
       "dependencies": {
@@ -4582,9 +4695,9 @@
       }
     },
     "node_modules/es-module-lexer": {
-      "version": "1.5.4",
-      "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.5.4.tgz",
-      "integrity": "sha512-MVNK56NiMrOwitFB7cqDwq0CQutbw+0BvLshJSse0MUNU+y1FC3bUS/AQg7oUng+/wKrrki7JfmwtVHkVfPLlw==",
+      "version": "1.6.0",
+      "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.6.0.tgz",
+      "integrity": "sha512-qqnD1yMU6tk/jnaMosogGySTZP8YtUgAffA9nMN+E/rjxcfRQ6IEk7IiozUjgxKoFHBGjTLnrHB/YC45r/59EQ==",
       "dev": true,
       "license": "MIT"
     },
@@ -5067,9 +5180,10 @@
       }
     },
     "node_modules/express": {
-      "version": "4.21.1",
-      "resolved": "https://registry.npmjs.org/express/-/express-4.21.1.tgz",
-      "integrity": "sha512-YSFlK1Ee0/GC8QaO91tHcDxJiE/X4FbpAyQWkxAvG6AXCuR65YzK8ua6D9hvi/TzUfZMpc+BwuM1IPw8fmQBiQ==",
+      "version": "4.21.2",
+      "resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz",
+      "integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==",
+      "license": "MIT",
       "dependencies": {
         "accepts": "~1.3.8",
         "array-flatten": "1.1.1",
@@ -5090,7 +5204,7 @@
         "methods": "~1.1.2",
         "on-finished": "2.4.1",
         "parseurl": "~1.3.3",
-        "path-to-regexp": "0.1.10",
+        "path-to-regexp": "0.1.12",
         "proxy-addr": "~2.0.7",
         "qs": "6.13.0",
         "range-parser": "~1.2.1",
@@ -5105,6 +5219,10 @@
       },
       "engines": {
         "node": ">= 0.10.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/express"
       }
     },
     "node_modules/express/node_modules/debug": {
@@ -5815,19 +5933,6 @@
         "node": ">=18"
       }
     },
-    "node_modules/html-tags": {
-      "version": "3.3.1",
-      "resolved": "https://registry.npmjs.org/html-tags/-/html-tags-3.3.1.tgz",
-      "integrity": "sha512-ztqyC3kLto0e9WbNp0aeP+M3kTt+nbaIveGmUxAtZa+8iFgKLUOD4YKM5j+f3QD89bra7UeumolZHKuOXnTmeQ==",
-      "dev": true,
-      "license": "MIT",
-      "engines": {
-        "node": ">=8"
-      },
-      "funding": {
-        "url": "https://github.com/sponsors/sindresorhus"
-      }
-    },
     "node_modules/http-errors": {
       "version": "2.0.0",
       "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz",
@@ -6143,6 +6248,19 @@
         "node": ">=8"
       }
     },
+    "node_modules/is-plain-obj": {
+      "version": "4.1.0",
+      "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz",
+      "integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==",
+      "dev": true,
+      "license": "MIT",
+      "engines": {
+        "node": ">=12"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
     "node_modules/is-potential-custom-element-name": {
       "version": "1.0.1",
       "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz",
@@ -6377,9 +6495,9 @@
       }
     },
     "node_modules/jsesc": {
-      "version": "3.0.2",
-      "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.0.2.tgz",
-      "integrity": "sha512-xKqzzWXDttJuOcawBt4KnKHHIf5oQ/Cxax+0PWFG+DFDgHNAdi+TXECADI+RYiFUMmx8792xsMbbgXj4CwnP4g==",
+      "version": "3.1.0",
+      "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz",
+      "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==",
       "dev": true,
       "license": "MIT",
       "bin": {
@@ -6756,9 +6874,9 @@
       }
     },
     "node_modules/loupe": {
-      "version": "3.1.2",
-      "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.1.2.tgz",
-      "integrity": "sha512-23I4pFZHmAemUnz8WZXbYRSKYj801VDaNv9ETuMh7IrMc7VuVVSo+Z9iLE3ni30+U48iDWfi30d3twAXBYmnCg==",
+      "version": "3.1.3",
+      "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.1.3.tgz",
+      "integrity": "sha512-kkIp7XSkP78ZxJEsSxW3712C6teJVoeHHwgo9zJ380de7IYyJ2ISlxojcH2pC5OFLewESmnRi/+XCDIEEVyoug==",
       "dev": true,
       "license": "MIT"
     },
@@ -7030,9 +7148,9 @@
       }
     },
     "node_modules/nanoid": {
-      "version": "3.3.7",
-      "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz",
-      "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==",
+      "version": "3.3.8",
+      "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.8.tgz",
+      "integrity": "sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==",
       "funding": [
         {
           "type": "github",
@@ -7448,6 +7566,19 @@
         "node": ">=6"
       }
     },
+    "node_modules/parse-ms": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmjs.org/parse-ms/-/parse-ms-4.0.0.tgz",
+      "integrity": "sha512-TXfryirbmq34y8QBwgqCVLi+8oA3oWx2eAnSn62ITyEhEYaWRlVZ2DvMM9eZbMs/RfxPu/PK/aBLyGj4IrqMHw==",
+      "dev": true,
+      "license": "MIT",
+      "engines": {
+        "node": ">=18"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
     "node_modules/parse5": {
       "version": "7.2.1",
       "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.2.1.tgz",
@@ -7529,9 +7660,10 @@
       }
     },
     "node_modules/path-to-regexp": {
-      "version": "0.1.10",
-      "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.10.tgz",
-      "integrity": "sha512-7lf7qcQidTku0Gu3YDPc8DJ1q7OOucfa/BSsIwjuh56VU7katFvuM8hULfkwB3Fns/rsVF7PwPKVw1sl5KQS9w=="
+      "version": "0.1.12",
+      "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz",
+      "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==",
+      "license": "MIT"
     },
     "node_modules/pathe": {
       "version": "1.1.2",
@@ -8018,6 +8150,22 @@
         "url": "https://github.com/sponsors/sindresorhus"
       }
     },
+    "node_modules/pretty-ms": {
+      "version": "9.2.0",
+      "resolved": "https://registry.npmjs.org/pretty-ms/-/pretty-ms-9.2.0.tgz",
+      "integrity": "sha512-4yf0QO/sllf/1zbZWYnvWw3NxCQwLXKzIj0G849LSufP15BXKM0rbD2Z3wVnkMfjdn/CB0Dpp444gYAACdsplg==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "parse-ms": "^4.0.0"
+      },
+      "engines": {
+        "node": ">=18"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
     "node_modules/process": {
       "version": "0.11.10",
       "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz",
@@ -8957,9 +9105,9 @@
       }
     },
     "node_modules/superjson": {
-      "version": "2.2.1",
-      "resolved": "https://registry.npmjs.org/superjson/-/superjson-2.2.1.tgz",
-      "integrity": "sha512-8iGv75BYOa0xRJHK5vRLEjE2H/i4lulTjzpUXic3Eg8akftYjkmQDa8JARQ42rlczXyFR3IeRoeFCc7RxHsYZA==",
+      "version": "2.2.2",
+      "resolved": "https://registry.npmjs.org/superjson/-/superjson-2.2.2.tgz",
+      "integrity": "sha512-5JRxVqC8I8NuOUjzBbvVJAKNM8qoVuH0O77h4WInc/qC2q5IreqKxYwgkga3PfA22OayK2ikceb/B26dztPl+Q==",
       "dev": true,
       "license": "MIT",
       "dependencies": {
@@ -8998,12 +9146,6 @@
         "url": "https://github.com/sponsors/ljharb"
       }
     },
-    "node_modules/svg-tags": {
-      "version": "1.0.0",
-      "resolved": "https://registry.npmjs.org/svg-tags/-/svg-tags-1.0.0.tgz",
-      "integrity": "sha512-ovssysQTa+luh7A5Weu3Rta6FJlFBBbInjOh722LIt6klpU2/HtdUbszju/G4devcvk8PGt7FCLv5wftu3THUA==",
-      "dev": true
-    },
     "node_modules/symbol-tree": {
       "version": "3.2.4",
       "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz",
@@ -9413,6 +9555,19 @@
       "dev": true,
       "license": "MIT"
     },
+    "node_modules/unicorn-magic": {
+      "version": "0.3.0",
+      "resolved": "https://registry.npmjs.org/unicorn-magic/-/unicorn-magic-0.3.0.tgz",
+      "integrity": "sha512-+QBBXBCvifc56fsbuxZQ6Sic3wqqc3WWaqxs58gvJrcOuN83HGTCwz3oS5phzU9LthRNE9VrJCFCLUgHeeFnfA==",
+      "dev": true,
+      "license": "MIT",
+      "engines": {
+        "node": ">=18"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
     "node_modules/universalify": {
       "version": "2.0.1",
       "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz",
@@ -9530,9 +9685,9 @@
       }
     },
     "node_modules/vite": {
-      "version": "5.4.11",
-      "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.11.tgz",
-      "integrity": "sha512-c7jFQRklXua0mTzneGW9QVyxFjUgwcihC4bXEtujIo2ouWCe1Ajt/amn2PCxYnhYfd5k09JX3SB7OYWFKYqj8Q==",
+      "version": "5.4.14",
+      "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.14.tgz",
+      "integrity": "sha512-EK5cY7Q1D8JNhSaPKVK4pwBFvaTmZxEnoKXLG/U9gmdDcihQGNzFlgIvaxezFR4glP1LsuiedwMBqCXH3wZccA==",
       "dev": true,
       "license": "MIT",
       "dependencies": {
@@ -9590,22 +9745,22 @@
       }
     },
     "node_modules/vite-hot-client": {
-      "version": "0.2.3",
-      "resolved": "https://registry.npmjs.org/vite-hot-client/-/vite-hot-client-0.2.3.tgz",
-      "integrity": "sha512-rOGAV7rUlUHX89fP2p2v0A2WWvV3QMX2UYq0fRqsWSvFvev4atHWqjwGoKaZT1VTKyLGk533ecu3eyd0o59CAg==",
+      "version": "0.2.4",
+      "resolved": "https://registry.npmjs.org/vite-hot-client/-/vite-hot-client-0.2.4.tgz",
+      "integrity": "sha512-a1nzURqO7DDmnXqabFOliz908FRmIppkBKsJthS8rbe8hBEXwEwe4C3Pp33Z1JoFCYfVL4kTOMLKk0ZZxREIeA==",
       "dev": true,
       "license": "MIT",
       "funding": {
         "url": "https://github.com/sponsors/antfu"
       },
       "peerDependencies": {
-        "vite": "^2.6.0 || ^3.0.0 || ^4.0.0 || ^5.0.0-0"
+        "vite": "^2.6.0 || ^3.0.0 || ^4.0.0 || ^5.0.0-0 || ^6.0.0-0"
       }
     },
     "node_modules/vite-node": {
-      "version": "2.1.5",
-      "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-2.1.5.tgz",
-      "integrity": "sha512-rd0QIgx74q4S1Rd56XIiL2cYEdyWn13cunYBIuqh9mpmQr7gGS0IxXoP8R6OaZtNQQLyXSWbd4rXKYUbhFpK5w==",
+      "version": "2.1.9",
+      "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-2.1.9.tgz",
+      "integrity": "sha512-AM9aQ/IPrW/6ENLQg3AGY4K1N2TGZdR5e4gu/MmmR2xR3Ll1+dib+nook92g4TV3PXVyeyxdWwtaCAiUL0hMxA==",
       "dev": true,
       "license": "MIT",
       "dependencies": {
@@ -9626,21 +9781,21 @@
       }
     },
     "node_modules/vite-plugin-inspect": {
-      "version": "0.8.7",
-      "resolved": "https://registry.npmjs.org/vite-plugin-inspect/-/vite-plugin-inspect-0.8.7.tgz",
-      "integrity": "sha512-/XXou3MVc13A5O9/2Nd6xczjrUwt7ZyI9h8pTnUMkr5SshLcb0PJUOVq2V+XVkdeU4njsqAtmK87THZuO2coGA==",
+      "version": "0.8.9",
+      "resolved": "https://registry.npmjs.org/vite-plugin-inspect/-/vite-plugin-inspect-0.8.9.tgz",
+      "integrity": "sha512-22/8qn+LYonzibb1VeFZmISdVao5kC22jmEKm24vfFE8siEn47EpVcCLYMv6iKOYMJfjSvSJfueOwcFCkUnV3A==",
       "dev": true,
       "license": "MIT",
       "dependencies": {
         "@antfu/utils": "^0.7.10",
-        "@rollup/pluginutils": "^5.1.0",
-        "debug": "^4.3.6",
+        "@rollup/pluginutils": "^5.1.3",
+        "debug": "^4.3.7",
         "error-stack-parser-es": "^0.1.5",
         "fs-extra": "^11.2.0",
         "open": "^10.1.0",
         "perfect-debounce": "^1.0.0",
-        "picocolors": "^1.0.1",
-        "sirv": "^2.0.4"
+        "picocolors": "^1.1.1",
+        "sirv": "^3.0.0"
       },
       "engines": {
         "node": ">=14"
@@ -9649,7 +9804,7 @@
         "url": "https://github.com/sponsors/antfu"
       },
       "peerDependencies": {
-        "vite": "^3.1.0 || ^4.0.0 || ^5.0.0-0"
+        "vite": "^3.1.0 || ^4.0.0 || ^5.0.0-0 || ^6.0.1"
       },
       "peerDependenciesMeta": {
         "@nuxt/kit": {
@@ -9658,9 +9813,9 @@
       }
     },
     "node_modules/vite-plugin-inspect/node_modules/fs-extra": {
-      "version": "11.2.0",
-      "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.2.0.tgz",
-      "integrity": "sha512-PmDi3uwK5nFuXh7XDTlVnS17xJS7vW36is2+w3xcv8SVxiB4NyATf4ctkVY5bkSjX0Y4nbvZCq1/EjtEyr9ktw==",
+      "version": "11.3.0",
+      "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.0.tgz",
+      "integrity": "sha512-Z4XaCL6dUDHfP/jT25jJKMmtxvuwbkrD1vNSMFlo9lNLY2c5FHYSQgHPRZUjAB26TpDEoW9HCOgplrdbaPV/ew==",
       "dev": true,
       "license": "MIT",
       "dependencies": {
@@ -9672,143 +9827,136 @@
         "node": ">=14.14"
       }
     },
-    "node_modules/vite-plugin-inspect/node_modules/sirv": {
-      "version": "2.0.4",
-      "resolved": "https://registry.npmjs.org/sirv/-/sirv-2.0.4.tgz",
-      "integrity": "sha512-94Bdh3cC2PKrbgSOUqTiGPWVZeSiXfKOVZNJniWoqrWrRkB1CJzBU3NEbiTsPcYy1lDsANA/THzS+9WBiy5nfQ==",
-      "dev": true,
-      "license": "MIT",
-      "dependencies": {
-        "@polka/url": "^1.0.0-next.24",
-        "mrmime": "^2.0.0",
-        "totalist": "^3.0.0"
-      },
-      "engines": {
-        "node": ">= 10"
-      }
-    },
     "node_modules/vite-plugin-vue-devtools": {
-      "version": "7.6.4",
-      "resolved": "https://registry.npmjs.org/vite-plugin-vue-devtools/-/vite-plugin-vue-devtools-7.6.4.tgz",
-      "integrity": "sha512-jxSsLyuETfmZ1OSrmnDp28BG6rmURrP7lkeyHW2gBFDyo+4dUcqVeQNMhbV7uKZn80mDdv06Mysw/5AdGxDvJQ==",
+      "version": "7.7.2",
+      "resolved": "https://registry.npmjs.org/vite-plugin-vue-devtools/-/vite-plugin-vue-devtools-7.7.2.tgz",
+      "integrity": "sha512-5V0UijQWiSBj32blkyPEqIbzc6HO9c1bwnBhx+ay2dzU0FakH+qMdNUT8nF9BvDE+i6I1U8CqCuJiO20vKEdQw==",
       "dev": true,
       "license": "MIT",
       "dependencies": {
-        "@vue/devtools-core": "^7.6.4",
-        "@vue/devtools-kit": "^7.6.4",
-        "@vue/devtools-shared": "^7.6.4",
-        "execa": "^8.0.1",
+        "@vue/devtools-core": "^7.7.2",
+        "@vue/devtools-kit": "^7.7.2",
+        "@vue/devtools-shared": "^7.7.2",
+        "execa": "^9.5.1",
         "sirv": "^3.0.0",
-        "vite-plugin-inspect": "^0.8.7",
-        "vite-plugin-vue-inspector": "^5.2.0"
+        "vite-plugin-inspect": "0.8.9",
+        "vite-plugin-vue-inspector": "^5.3.1"
       },
       "engines": {
         "node": ">=v14.21.3"
       },
       "peerDependencies": {
-        "vite": "^3.1.0 || ^4.0.0-0 || ^5.0.0-0"
+        "vite": "^3.1.0 || ^4.0.0-0 || ^5.0.0-0 || ^6.0.0-0"
       }
     },
     "node_modules/vite-plugin-vue-devtools/node_modules/execa": {
-      "version": "8.0.1",
-      "resolved": "https://registry.npmjs.org/execa/-/execa-8.0.1.tgz",
-      "integrity": "sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==",
+      "version": "9.5.2",
+      "resolved": "https://registry.npmjs.org/execa/-/execa-9.5.2.tgz",
+      "integrity": "sha512-EHlpxMCpHWSAh1dgS6bVeoLAXGnJNdR93aabr4QCGbzOM73o5XmRfM/e5FUqsw3aagP8S8XEWUWFAxnRBnAF0Q==",
       "dev": true,
       "license": "MIT",
       "dependencies": {
+        "@sindresorhus/merge-streams": "^4.0.0",
         "cross-spawn": "^7.0.3",
-        "get-stream": "^8.0.1",
-        "human-signals": "^5.0.0",
-        "is-stream": "^3.0.0",
-        "merge-stream": "^2.0.0",
-        "npm-run-path": "^5.1.0",
-        "onetime": "^6.0.0",
+        "figures": "^6.1.0",
+        "get-stream": "^9.0.0",
+        "human-signals": "^8.0.0",
+        "is-plain-obj": "^4.1.0",
+        "is-stream": "^4.0.1",
+        "npm-run-path": "^6.0.0",
+        "pretty-ms": "^9.0.0",
         "signal-exit": "^4.1.0",
-        "strip-final-newline": "^3.0.0"
+        "strip-final-newline": "^4.0.0",
+        "yoctocolors": "^2.0.0"
       },
       "engines": {
-        "node": ">=16.17"
+        "node": "^18.19.0 || >=20.5.0"
       },
       "funding": {
         "url": "https://github.com/sindresorhus/execa?sponsor=1"
       }
     },
-    "node_modules/vite-plugin-vue-devtools/node_modules/get-stream": {
-      "version": "8.0.1",
-      "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-8.0.1.tgz",
-      "integrity": "sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==",
+    "node_modules/vite-plugin-vue-devtools/node_modules/figures": {
+      "version": "6.1.0",
+      "resolved": "https://registry.npmjs.org/figures/-/figures-6.1.0.tgz",
+      "integrity": "sha512-d+l3qxjSesT4V7v2fh+QnmFnUWv9lSpjarhShNTgBOfA0ttejbQUAlHLitbjkoRiDulW0OPoQPYIGhIC8ohejg==",
       "dev": true,
       "license": "MIT",
+      "dependencies": {
+        "is-unicode-supported": "^2.0.0"
+      },
       "engines": {
-        "node": ">=16"
+        "node": ">=18"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
+    "node_modules/vite-plugin-vue-devtools/node_modules/get-stream": {
+      "version": "9.0.1",
+      "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-9.0.1.tgz",
+      "integrity": "sha512-kVCxPF3vQM/N0B1PmoqVUqgHP+EeVjmZSQn+1oCRPxd2P21P2F19lIgbR3HBosbB1PUhOAoctJnfEn2GbN2eZA==",
+      "dev": true,
+      "license": "MIT",
+      "dependencies": {
+        "@sec-ant/readable-stream": "^0.4.1",
+        "is-stream": "^4.0.1"
+      },
+      "engines": {
+        "node": ">=18"
       },
       "funding": {
         "url": "https://github.com/sponsors/sindresorhus"
       }
     },
     "node_modules/vite-plugin-vue-devtools/node_modules/human-signals": {
-      "version": "5.0.0",
-      "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-5.0.0.tgz",
-      "integrity": "sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==",
+      "version": "8.0.0",
+      "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-8.0.0.tgz",
+      "integrity": "sha512-/1/GPCpDUCCYwlERiYjxoczfP0zfvZMU/OWgQPMya9AbAE24vseigFdhAMObpc8Q4lc/kjutPfUddDYyAmejnA==",
       "dev": true,
       "license": "Apache-2.0",
       "engines": {
-        "node": ">=16.17.0"
+        "node": ">=18.18.0"
       }
     },
     "node_modules/vite-plugin-vue-devtools/node_modules/is-stream": {
-      "version": "3.0.0",
-      "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-3.0.0.tgz",
-      "integrity": "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==",
+      "version": "4.0.1",
+      "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-4.0.1.tgz",
+      "integrity": "sha512-Dnz92NInDqYckGEUJv689RbRiTSEHCQ7wOVeALbkOz999YpqT46yMRIGtSNl2iCL1waAZSx40+h59NV/EwzV/A==",
       "dev": true,
       "license": "MIT",
       "engines": {
-        "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
+        "node": ">=18"
       },
       "funding": {
         "url": "https://github.com/sponsors/sindresorhus"
       }
     },
-    "node_modules/vite-plugin-vue-devtools/node_modules/mimic-fn": {
-      "version": "4.0.0",
-      "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-4.0.0.tgz",
-      "integrity": "sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==",
+    "node_modules/vite-plugin-vue-devtools/node_modules/is-unicode-supported": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-2.1.0.tgz",
+      "integrity": "sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ==",
       "dev": true,
       "license": "MIT",
       "engines": {
-        "node": ">=12"
+        "node": ">=18"
       },
       "funding": {
         "url": "https://github.com/sponsors/sindresorhus"
       }
     },
     "node_modules/vite-plugin-vue-devtools/node_modules/npm-run-path": {
-      "version": "5.3.0",
-      "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.3.0.tgz",
-      "integrity": "sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==",
-      "dev": true,
-      "license": "MIT",
-      "dependencies": {
-        "path-key": "^4.0.0"
-      },
-      "engines": {
-        "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
-      },
-      "funding": {
-        "url": "https://github.com/sponsors/sindresorhus"
-      }
-    },
-    "node_modules/vite-plugin-vue-devtools/node_modules/onetime": {
       "version": "6.0.0",
-      "resolved": "https://registry.npmjs.org/onetime/-/onetime-6.0.0.tgz",
-      "integrity": "sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==",
+      "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-6.0.0.tgz",
+      "integrity": "sha512-9qny7Z9DsQU8Ou39ERsPU4OZQlSTP47ShQzuKZ6PRXpYLtIFgl/DEBYEXKlvcEa+9tHVcK8CF81Y2V72qaZhWA==",
       "dev": true,
       "license": "MIT",
       "dependencies": {
-        "mimic-fn": "^4.0.0"
+        "path-key": "^4.0.0",
+        "unicorn-magic": "^0.3.0"
       },
       "engines": {
-        "node": ">=12"
+        "node": ">=18"
       },
       "funding": {
         "url": "https://github.com/sponsors/sindresorhus"
@@ -9841,22 +9989,22 @@
       }
     },
     "node_modules/vite-plugin-vue-devtools/node_modules/strip-final-newline": {
-      "version": "3.0.0",
-      "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-3.0.0.tgz",
-      "integrity": "sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==",
+      "version": "4.0.0",
+      "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-4.0.0.tgz",
+      "integrity": "sha512-aulFJcD6YK8V1G7iRB5tigAP4TsHBZZrOV8pjV++zdUwmeV8uzbY7yn6h9MswN62adStNZFuCIx4haBnRuMDaw==",
       "dev": true,
       "license": "MIT",
       "engines": {
-        "node": ">=12"
+        "node": ">=18"
       },
       "funding": {
         "url": "https://github.com/sponsors/sindresorhus"
       }
     },
     "node_modules/vite-plugin-vue-inspector": {
-      "version": "5.2.0",
-      "resolved": "https://registry.npmjs.org/vite-plugin-vue-inspector/-/vite-plugin-vue-inspector-5.2.0.tgz",
-      "integrity": "sha512-wWxyb9XAtaIvV/Lr7cqB1HIzmHZFVUJsTNm3yAxkS87dgh/Ky4qr2wDEWNxF23fdhVa3jQ8MZREpr4XyiuaRqA==",
+      "version": "5.3.1",
+      "resolved": "https://registry.npmjs.org/vite-plugin-vue-inspector/-/vite-plugin-vue-inspector-5.3.1.tgz",
+      "integrity": "sha512-cBk172kZKTdvGpJuzCCLg8lJ909wopwsu3Ve9FsL1XsnLBiRT9U3MePcqrgGHgCX2ZgkqZmAGR8taxw+TV6s7A==",
       "dev": true,
       "license": "MIT",
       "dependencies": {
@@ -9871,23 +10019,23 @@
         "magic-string": "^0.30.4"
       },
       "peerDependencies": {
-        "vite": "^3.0.0-0 || ^4.0.0-0 || ^5.0.0-0"
+        "vite": "^3.0.0-0 || ^4.0.0-0 || ^5.0.0-0 || ^6.0.0-0"
       }
     },
     "node_modules/vitest": {
-      "version": "2.1.5",
-      "resolved": "https://registry.npmjs.org/vitest/-/vitest-2.1.5.tgz",
-      "integrity": "sha512-P4ljsdpuzRTPI/kbND2sDZ4VmieerR2c9szEZpjc+98Z9ebvnXmM5+0tHEKqYZumXqlvnmfWsjeFOjXVriDG7A==",
+      "version": "2.1.9",
+      "resolved": "https://registry.npmjs.org/vitest/-/vitest-2.1.9.tgz",
+      "integrity": "sha512-MSmPM9REYqDGBI8439mA4mWhV5sKmDlBKWIYbA3lRb2PTHACE0mgKwA8yQ2xq9vxDTuk4iPrECBAEW2aoFXY0Q==",
       "dev": true,
       "license": "MIT",
       "dependencies": {
-        "@vitest/expect": "2.1.5",
-        "@vitest/mocker": "2.1.5",
-        "@vitest/pretty-format": "^2.1.5",
-        "@vitest/runner": "2.1.5",
-        "@vitest/snapshot": "2.1.5",
-        "@vitest/spy": "2.1.5",
-        "@vitest/utils": "2.1.5",
+        "@vitest/expect": "2.1.9",
+        "@vitest/mocker": "2.1.9",
+        "@vitest/pretty-format": "^2.1.9",
+        "@vitest/runner": "2.1.9",
+        "@vitest/snapshot": "2.1.9",
+        "@vitest/spy": "2.1.9",
+        "@vitest/utils": "2.1.9",
         "chai": "^5.1.2",
         "debug": "^4.3.7",
         "expect-type": "^1.1.0",
@@ -9899,7 +10047,7 @@
         "tinypool": "^1.0.1",
         "tinyrainbow": "^1.2.0",
         "vite": "^5.0.0",
-        "vite-node": "2.1.5",
+        "vite-node": "2.1.9",
         "why-is-node-running": "^2.3.0"
       },
       "bin": {
@@ -9914,8 +10062,8 @@
       "peerDependencies": {
         "@edge-runtime/vm": "*",
         "@types/node": "^18.0.0 || >=20.0.0",
-        "@vitest/browser": "2.1.5",
-        "@vitest/ui": "2.1.5",
+        "@vitest/browser": "2.1.9",
+        "@vitest/ui": "2.1.9",
         "happy-dom": "*",
         "jsdom": "*"
       },
@@ -10445,6 +10593,19 @@
       "funding": {
         "url": "https://github.com/sponsors/sindresorhus"
       }
+    },
+    "node_modules/yoctocolors": {
+      "version": "2.1.1",
+      "resolved": "https://registry.npmjs.org/yoctocolors/-/yoctocolors-2.1.1.tgz",
+      "integrity": "sha512-GQHQqAopRhwU8Kt1DDM8NjibDXHC8eoh1erhGAJPEyveY9qqVeXvVikNKrDz69sHowPMorbPUrH/mx8c50eiBQ==",
+      "dev": true,
+      "license": "MIT",
+      "engines": {
+        "node": ">=18"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
     }
   }
 }
diff --git a/package.json b/package.json
index e828e33..404f977 100755
--- a/package.json
+++ b/package.json
@@ -3,19 +3,19 @@
   "version": "0.0.0",
   "private": true,
   "type": "module",
-"scripts": {
-  "dev": "vite",
-  "build": "run-p type-check \"build-only {@}\" --",
-  "preview": "vite preview",
-  "test:unit": "vitest --passWithNoTests",
-  "test:e2e": "start-server-and-test preview http://localhost:4173 'cypress run --e2e'",
-  "test:e2e:dev": "start-server-and-test 'vite dev --port 4173' http://localhost:4173 'cypress open --e2e'",
-  "build-only": "vite build",
-  "type-check": "vue-tsc --build --force",
-  "lint": "eslint . --fix",
-  "format": "prettier --write src/",
-  "start": "npm run dev --host"
-},
+  "scripts": {
+    "dev": "vite",
+    "build": "run-p type-check \"build-only {@}\" --",
+    "preview": "vite preview",
+    "test:unit": "vitest --passWithNoTests",
+    "test:e2e": "start-server-and-test 'vite preview' http://localhost:4173 'cypress run --e2e'",
+    "test:e2e:dev": "start-server-and-test 'vite dev --port 4173' http://localhost:4173 'cypress open --e2e'",
+    "build-only": "vite build",
+    "type-check": "vue-tsc --build --force",
+    "lint": "eslint . --fix",
+    "format": "prettier --write src/",
+    "start": "npm run dev --host"
+  },
   "dependencies": {
     "@tsparticles/slim": "^3.5.0",
     "@tsparticles/vue3": "^3.0.1",
diff --git a/renovate.json b/renovate.json
new file mode 100644
index 0000000..7190a60
--- /dev/null
+++ b/renovate.json
@@ -0,0 +1,3 @@
+{
+  "$schema": "https://docs.renovatebot.com/renovate-schema.json"
+}
diff --git a/src/App.vue b/src/App.vue
index 77523bd..668f95f 100755
--- a/src/App.vue
+++ b/src/App.vue
@@ -1,35 +1,35 @@
 <script setup lang="ts">
-import { RouterLink, RouterView } from 'vue-router';
-import { ref, onMounted } from 'vue';
+import { RouterLink, RouterView } from 'vue-router'
+import { ref, onMounted } from 'vue'
 
 // Theme state and available themes
-const theme = ref(localStorage.getItem("theme") || "mocha");
+const theme = ref(localStorage.getItem('theme') || 'mocha')
 const availableThemes = [
-  "mocha",
-  "latte",
-  "yule-night",
-  "yule-day",
-  "midsummer-twilight",
-  "midsummer-daylight",
-  "fireworks-night",
-  "parade-day",
-  "harvest-twilight",
-  "golden-hour",
-  "stargazer",
-  "daydreamer",
-];
+  'mocha',
+  'latte',
+  'yule-night',
+  'yule-day',
+  'midsummer-twilight',
+  'midsummer-daylight',
+  'fireworks-night',
+  'parade-day',
+  'harvest-twilight',
+  'golden-hour',
+  'stargazer',
+  'daydreamer',
+]
 
 // Apply theme
 onMounted(() => {
-  document.documentElement.setAttribute("data-theme", theme.value);
-});
+  document.documentElement.setAttribute('data-theme', theme.value)
+})
 
 // Change theme function
 const changeTheme = (newTheme: string) => {
-  theme.value = newTheme;
-  document.documentElement.setAttribute("data-theme", newTheme);
-  localStorage.setItem("theme", newTheme);
-};
+  theme.value = newTheme
+  document.documentElement.setAttribute('data-theme', newTheme)
+  localStorage.setItem('theme', newTheme)
+}
 </script>
 
 <template>
@@ -48,11 +48,11 @@ const changeTheme = (newTheme: string) => {
         </div>
         <!-- Theme Selector -->
         <div class="theme-selector">
-        <label for="theme-switcher">Theme:</label>
-        <select id="theme-switcher" v-model="theme" @change="changeTheme(theme)">
-          <option v-for="t in availableThemes" :key="t" :value="t">{{ t }}</option>
-        </select>
-      </div>
+          <label for="theme-switcher">Theme:</label>
+          <select id="theme-switcher" v-model="theme" @change="changeTheme(theme)">
+            <option v-for="t in availableThemes" :key="t" :value="t">{{ t }}</option>
+          </select>
+        </div>
       </nav>
     </header>
 
diff --git a/src/assets/base.css b/src/assets/base.css
index ea83d05..8227582 100755
--- a/src/assets/base.css
+++ b/src/assets/base.css
@@ -1,5 +1,5 @@
 /* Catppuccin Mocha */
-:root[data-theme="mocha"] {
+:root[data-theme='mocha'] {
   --color-surface0: #1e1e2e; /* Base */
   --color-surface1: #313244; /* Mantle */
   --color-surface2: #45475a; /* Surface */
@@ -26,7 +26,7 @@
 }
 
 /* Catppuccin Latte */
-:root[data-theme="latte"] {
+:root[data-theme='latte'] {
   --color-surface0: #eff1f5; /* Base */
   --color-surface1: #e6e9ef; /* Mantle */
   --color-surface2: #ccd0da; /* Surface */
@@ -52,7 +52,7 @@
   --color-overlay2: #9ca0b0;
 }
 
-:root[data-theme="yule-night"] {
+:root[data-theme='yule-night'] {
   --color-surface0: #1b1d28; /* Deep midnight */
   --color-surface1: #252936; /* Frosty steel */
   --color-surface2: #343a48; /* Snow shadow */
@@ -62,7 +62,7 @@
   --color-border: #475266; /* Frosty edges */
 }
 
-:root[data-theme="yule-day"] {
+:root[data-theme='yule-day'] {
   --color-surface0: #f5f3ed; /* Fresh snow */
   --color-surface1: #ece7df; /* Frosty beige */
   --color-surface2: #dcd3c3; /* Hearth ash */
@@ -72,7 +72,7 @@
   --color-border: #9d9684; /* Frosted wood */
 }
 
-:root[data-theme="midsummer-twilight"] {
+:root[data-theme='midsummer-twilight'] {
   --color-surface0: #241f36; /* Starry violet */
   --color-surface1: #2e2746; /* Dusky purple */
   --color-surface2: #403659; /* Twilight shadow */
@@ -82,7 +82,7 @@
   --color-border: #6b5a89; /* Lavender dusk */
 }
 
-:root[data-theme="midsummer-daylight"] {
+:root[data-theme='midsummer-daylight'] {
   --color-surface0: #faf8eb; /* Bright sunlight */
   --color-surface1: #f2e7c4; /* Sunlit field */
   --color-surface2: #e6d399; /* Wheat gold */
@@ -92,7 +92,7 @@
   --color-border: #a38a5b; /* Golden shadows */
 }
 
-:root[data-theme="fireworks-night"] {
+:root[data-theme='fireworks-night'] {
   --color-surface0: #0a0e1a; /* Starry sky */
   --color-surface1: #121b32; /* Midnight blue */
   --color-surface2: #1f2945; /* Smoke cloud */
@@ -102,7 +102,7 @@
   --color-border: #3b4e7e; /* Steel blue */
 }
 
-:root[data-theme="parade-day"] {
+:root[data-theme='parade-day'] {
   --color-surface0: #fafafa; /* White fabric */
   --color-surface1: #eaeaea; /* Pale silver */
   --color-surface2: #c9d3e3; /* Cerulean mist */
@@ -112,7 +112,7 @@
   --color-border: #8795b4; /* Cloud blue */
 }
 
-:root[data-theme="harvest-twilight"] {
+:root[data-theme='harvest-twilight'] {
   --color-surface0: #1d1b13; /* Shadowed wheat field */
   --color-surface1: #29231a; /* Earthen soil */
   --color-surface2: #4b3b27; /* Golden dusk */
@@ -122,7 +122,7 @@
   --color-border: #5d4633; /* Bark brown */
 }
 
-:root[data-theme="golden-hour"] {
+:root[data-theme='golden-hour'] {
   --color-surface0: #fef6e6; /* Golden wheat */
   --color-surface1: #fdecc8; /* Honey glow */
   --color-surface2: #fcd399; /* Pumpkin yellow */
@@ -132,7 +132,7 @@
   --color-border: #a88a5f; /* Field shadows */
 }
 
-:root[data-theme="stargazer"] {
+:root[data-theme='stargazer'] {
   --color-surface0: #0d1321; /* Midnight sky */
   --color-surface1: #1c2533; /* Cloudy night */
   --color-surface2: #283142; /* Subtle twilight */
@@ -142,7 +142,7 @@
   --color-border: #3e506a; /* Lunar blue */
 }
 
-:root[data-theme="daydreamer"] {
+:root[data-theme='daydreamer'] {
   --color-surface0: #f9f9fc; /* Light paper */
   --color-surface1: #eceef3; /* Morning mist */
   --color-surface2: #d7dcea; /* Overcast sky */
@@ -151,4 +151,3 @@
   --color-accent-hover: #81a1c1; /* Brighter sky blue */
   --color-border: #b2c4d4; /* Subtle frost */
 }
-
diff --git a/src/assets/main.css b/src/assets/main.css
index dc48fa0..f366ce3 100755
--- a/src/assets/main.css
+++ b/src/assets/main.css
@@ -20,7 +20,9 @@ body {
 a {
   text-decoration: none;
   color: var(--color-accent);
-  transition: color 0.3s, background-color 0.3s;
+  transition:
+    color 0.3s,
+    background-color 0.3s;
 }
 
 a:hover {
@@ -48,7 +50,9 @@ header nav a {
   padding: 0.5rem 1rem;
   font-size: 1rem;
   color: var(--color-accent);
-  transition: color 0.3s, background-color 0.3s;
+  transition:
+    color 0.3s,
+    background-color 0.3s;
 }
 
 header nav a:hover {
@@ -104,7 +108,9 @@ header nav a.router-link-exact-active {
   padding: 0.5rem 1rem;
   border-radius: 4px;
   cursor: pointer;
-  transition: background-color 0.3s, color 0.3s;
+  transition:
+    background-color 0.3s,
+    color 0.3s;
 }
 
 .logout-button:hover {
@@ -300,7 +306,9 @@ main {
   padding: 0.5rem 1rem;
   border-radius: 4px;
   cursor: pointer;
-  transition: background 0.3s ease, transform 0.2s ease;
+  transition:
+    background 0.3s ease,
+    transform 0.2s ease;
 }
 
 .close-button:hover {
@@ -330,7 +338,9 @@ main {
   box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
   text-align: center;
   cursor: pointer;
-  transition: transform 0.2s, box-shadow 0.2s;
+  transition:
+    transform 0.2s,
+    box-shadow 0.2s;
 }
 
 .server-card:hover {
@@ -429,7 +439,9 @@ main {
   padding: 0.5rem 1rem;
   border-radius: 4px;
   cursor: pointer;
-  transition: background-color 0.3s ease, transform 0.2s ease;
+  transition:
+    background-color 0.3s ease,
+    transform 0.2s ease;
 }
 
 .close-button:hover {
@@ -459,7 +471,9 @@ main {
   padding: 0.5rem 1rem;
   border-radius: 4px;
   cursor: pointer;
-  transition: background 0.3s, transform 0.2s ease;
+  transition:
+    background 0.3s,
+    transform 0.2s ease;
 }
 
 .faq-button:hover {
@@ -601,7 +615,9 @@ main {
   padding: 0.5rem 1rem;
   border-radius: 4px;
   cursor: pointer;
-  transition: background 0.3s ease, transform 0.2s ease;
+  transition:
+    background 0.3s ease,
+    transform 0.2s ease;
   margin-top: 1rem;
 }
 
@@ -679,7 +695,9 @@ main {
   border-radius: 4px;
   cursor: pointer;
   font-size: 1rem;
-  transition: background 0.3s, transform 0.2s;
+  transition:
+    background 0.3s,
+    transform 0.2s;
 }
 
 .login .btn-login:hover {
diff --git a/src/assets/tailwind.css b/src/assets/tailwind.css
index 04b35af..b5c61c9 100755
--- a/src/assets/tailwind.css
+++ b/src/assets/tailwind.css
@@ -1,3 +1,3 @@
-@tailwind base;
-@tailwind components;
-@tailwind utilities;
+@tailwind base;
+@tailwind components;
+@tailwind utilities;
diff --git a/src/authService.ts b/src/authService.ts
index b514b76..222598c 100644
--- a/src/authService.ts
+++ b/src/authService.ts
@@ -1,21 +1,21 @@
-import axios from "axios";
+import axios from 'axios'
 
-const API_BASE = "http://localhost:3000"; // Update with your backend address
+const API_BASE = 'http://localhost:3000' // Update with your backend address
 
 export const register = async (username: string, password: string) => {
-  return axios.post(`${API_BASE}/register`, { username, password });
-};
+  return axios.post(`${API_BASE}/register`, { username, password })
+}
 
 export const login = async (username: string, password: string) => {
-  const response = await axios.post(`${API_BASE}/login`, { username, password });
-  const token = response.data.token;
-  localStorage.setItem("token", token); // Save token for authenticated requests
-  return token;
-};
+  const response = await axios.post(`${API_BASE}/login`, { username, password })
+  const token = response.data.token
+  localStorage.setItem('token', token) // Save token for authenticated requests
+  return token
+}
 
 export const getProtectedData = async () => {
-  const token = localStorage.getItem("token");
+  const token = localStorage.getItem('token')
   return axios.get(`${API_BASE}/protected`, {
     headers: { Authorization: `Bearer ${token}` },
-  });
-};
+  })
+}
diff --git a/src/views/HomeView.vue b/src/views/HomeView.vue
index 965d929..91c4ab6 100755
--- a/src/views/HomeView.vue
+++ b/src/views/HomeView.vue
@@ -1,6 +1,5 @@
 <template>
   <div class="landing-page">
-
     <section class="sections">
       <!-- Family Content -->
       <div class="section-box">
@@ -11,7 +10,9 @@
             <h3>Sharkey</h3>
             <p>
               Twitter alternative without a flood of Nazis.<br /><br />
-              We manually approve all members + verify them outside of the net, too. Fam & friends only to make an account in our space, but you can use any Sharkey/Mastodon/etc instance to register elsewhere and talk to us.
+              We manually approve all members + verify them outside of the net, too. Fam & friends
+              only to make an account in our space, but you can use any Sharkey/Mastodon/etc
+              instance to register elsewhere and talk to us.
             </p>
             <a href="https://social.smgames.club/" class="link">Go to Platform</a>
           </div>
@@ -19,8 +20,8 @@
           <div class="card">
             <h3>Game Servers</h3>
             <p>
-              Privately hosted servers for Minecraft, Garry's Mod, TF2, Terraria, and more.
-              Clicking each game shows instructions to get on.
+              Privately hosted servers for Minecraft, Garry's Mod, TF2, Terraria, and more. Clicking
+              each game shows instructions to get on.
             </p>
             <a href="/servers" class="link">See Our Game Servers</a>
           </div>
diff --git a/src/views/ProjectsView.vue b/src/views/ProjectsView.vue
index 22edf41..ac8fb41 100644
--- a/src/views/ProjectsView.vue
+++ b/src/views/ProjectsView.vue
@@ -5,37 +5,51 @@
     <!-- Overview Section -->
     <div class="overview">
       <p>
-        Welcome to our projects page! We create <strong>websites</strong>, <strong>video games</strong>,
-        <strong>addons for games</strong>, <strong>avatars and worlds for VRChat</strong>, and
-        <strong>game assets</strong>. Explore our ongoing projects below.
+        Welcome to our projects page! We create <strong>websites</strong>,
+        <strong>video games</strong>, <strong>addons for games</strong>,
+        <strong>avatars and worlds for VRChat</strong>, and <strong>game assets</strong>. Explore
+        our ongoing projects below.
       </p>
       <button @click="showFaqModal = true" class="faq-button">FAQ</button>
     </div>
 
     <!-- Filter Buttons -->
     <div class="filter-bar">
-      <button v-for="tag in tags" :key="tag" @click="filterByTag(tag)" :class="{ active: selectedTag === tag }">
+      <button
+        v-for="tag in tags"
+        :key="tag"
+        @click="filterByTag(tag)"
+        :class="{ active: selectedTag === tag }"
+      >
         {{ tag }}
       </button>
     </div>
 
     <!-- Projects Grid -->
     <div class="project-grid">
-      <div
-        v-for="project in filteredProjects"
-        :key="project.name"
-        class="project-card"
-      >
+      <div v-for="project in filteredProjects" :key="project.name" class="project-card">
         <h3>{{ project.name }}</h3>
         <p>{{ project.description }}</p>
         <ul class="tags">
           <li v-for="tag in project.tags" :key="tag" class="tag">{{ tag }}</li>
         </ul>
         <div class="links">
-          <p v-if="project.links.public"><strong>Public:</strong> <a :href="project.links.public" target="_blank">{{ project.links.public }}</a></p>
-          <p v-if="project.links.local"><strong>Local:</strong> <a :href="project.links.local" target="_blank">{{ project.links.local }}</a></p>
-          <p v-if="project.links.testing"><strong>Testing:</strong> <a :href="project.links.testing" target="_blank">{{ project.links.testing }}</a></p>
-          <p v-if="project.links.wiki"><strong>Wiki:</strong> <a :href="project.links.wiki" target="_blank">{{ project.links.wiki }}</a></p>
+          <p v-if="project.links.public">
+            <strong>Public:</strong>
+            <a :href="project.links.public" target="_blank">{{ project.links.public }}</a>
+          </p>
+          <p v-if="project.links.local">
+            <strong>Local:</strong>
+            <a :href="project.links.local" target="_blank">{{ project.links.local }}</a>
+          </p>
+          <p v-if="project.links.testing">
+            <strong>Testing:</strong>
+            <a :href="project.links.testing" target="_blank">{{ project.links.testing }}</a>
+          </p>
+          <p v-if="project.links.wiki">
+            <strong>Wiki:</strong>
+            <a :href="project.links.wiki" target="_blank">{{ project.links.wiki }}</a>
+          </p>
         </div>
       </div>
     </div>
@@ -44,58 +58,70 @@
     <div v-if="showFaqModal" class="modal-overlay" @click="closeFaqModal">
       <div class="modal-content" @click.stop>
         <h2>Frequently Asked Questions</h2>
-        <hr>
+        <hr />
         <ul class="faq-list">
-    <li>
-      <strong>"Do you need an idea person?"</strong>
+          <li>
+            <strong>"Do you need an idea person?"</strong>
             <p>No. Not now, and likely not ever.</p>
           </li>
-          <hr>
+          <hr />
           <li>
             <strong>"Do you need an asset creator? What would you pay me?"</strong>
             <p>
-              This applies to graphics, models, sounds, music, and similar work. Suggesting random ideas and demanding payment isn’t how this works.
-              Provide a portfolio and clear price sheets. We offer fair indie rates based on quality. If your work is lower quality, understand that others
-              may carry more of the creative load.
+              This applies to graphics, models, sounds, music, and similar work. Suggesting random
+              ideas and demanding payment isn’t how this works. Provide a portfolio and clear price
+              sheets. We offer fair indie rates based on quality. If your work is lower quality,
+              understand that others may carry more of the creative load.
             </p>
           </li>
-          <hr>
+          <hr />
           <li>
-            <strong>"If I'm hired to work on sound effects, that means I'm the sound director, right?"</strong>
+            <strong
+              >"If I'm hired to work on sound effects, that means I'm the sound director,
+              right?"</strong
+            >
             <p>
-              No, it doesn’t. Roles like "director" require experience, leadership skills, and a proven track record. Freelance roles are just that—freelance.
-              Don’t expect a salary or profit share from such a position. Additionally, if we hire you to create assets, <b>we</b> decide what’s needed,
-              unless creative freedom is explicitly granted. Thank you for understanding.
+              No, it doesn’t. Roles like "director" require experience, leadership skills, and a
+              proven track record. Freelance roles are just that—freelance. Don’t expect a salary or
+              profit share from such a position. Additionally, if we hire you to create assets,
+              <b>we</b> decide what’s needed, unless creative freedom is explicitly granted. Thank
+              you for understanding.
             </p>
           </li>
-          <hr>
+          <hr />
           <li>
             <strong>"Well, how much WOULD you pay me?"</strong>
             <p>
-              Provide your portfolio and rates. If you don’t have one, we’ll evaluate your work against indie standards to make a fair offer. We're not presently hiring.
+              Provide your portfolio and rates. If you don’t have one, we’ll evaluate your work
+              against indie standards to make a fair offer. We're not presently hiring.
             </p>
           </li>
-          <hr>
+          <hr />
           <li>
             <strong>On suggesting ideas and claiming ownership</strong>
             <p>
-              Ideas, concepts, and words alone are not copyrightable—only tangible creations such as art, sound effects, or completed works can be protected by copyright.
-              Claiming ownership of an idea without contributing to its execution is not recognized in the professional world. That said, we value constructive suggestions and
-              may acknowledge meaningful contributions with in-game benefits or recognition. However, we maintain a zero-tolerance policy for unfounded claims or disruptive behavior,
-              to ensure a fair and respectful environment for everyone.
+              Ideas, concepts, and words alone are not copyrightable—only tangible creations such as
+              art, sound effects, or completed works can be protected by copyright. Claiming
+              ownership of an idea without contributing to its execution is not recognized in the
+              professional world. That said, we value constructive suggestions and may acknowledge
+              meaningful contributions with in-game benefits or recognition. However, we maintain a
+              zero-tolerance policy for unfounded claims or disruptive behavior, to ensure a fair
+              and respectful environment for everyone.
             </p>
           </li>
-          <hr>
+          <hr />
           <li>
             <strong>"You guys make games, so I want you to make this concept of mine!"</strong>
             <p>
-              Not for free. We have our own projects to focus on, and <u>we are not for hire at this time</u>. Additionally, being an "idea person" is not a paid position in the game
-              development industry. Successful game creation requires collaboration, execution, and tangible contributions.
+              Not for free. We have our own projects to focus on, and
+              <u>we are not for hire at this time</u>. Additionally, being an "idea person" is not a
+              paid position in the game development industry. Successful game creation requires
+              collaboration, execution, and tangible contributions.
             </p>
           </li>
         </ul>
 
-        <hr>
+        <hr />
         <button @click="closeFaqModal" class="close-button">Close</button>
       </div>
     </div>
@@ -107,71 +133,80 @@ export default {
   data() {
     return {
       showFaqModal: false,
-      selectedTag: "All",
-      tags: ["All", "Website", "PC", "Singleplayer", "Multiplayer", "ALPHA", "Horror", "Relaxing", "Sim", "RPG"],
+      selectedTag: 'All',
+      tags: [
+        'All',
+        'Website',
+        'PC',
+        'Singleplayer',
+        'Multiplayer',
+        'ALPHA',
+        'Horror',
+        'Relaxing',
+        'Sim',
+        'RPG',
+      ],
       projects: [
         {
-          name: "Wildspace",
-          description: "A browser pet game.",
-          tags: ["Website", "Multiplayer", "ALPHA"],
+          name: 'Wildspace',
+          description: 'A browser pet game.',
+          tags: ['Website', 'Multiplayer', 'ALPHA'],
           links: {
-            public: "https://thewild.space",
+            public: 'https://thewild.space',
             local: null,
             testing: null,
-            wiki: "wiki.smgames.club/wildspace",
+            wiki: 'wiki.smgames.club/wildspace',
           },
         },
         {
-          name: "Ghostbound",
-          description: "A ghost hunting game.",
-          tags: ["PC", "Singleplayer", "Multiplayer", "ALPHA", "Horror", "Team"],
+          name: 'Ghostbound',
+          description: 'A ghost hunting game.',
+          tags: ['PC', 'Singleplayer', 'Multiplayer', 'ALPHA', 'Horror', 'Team'],
           links: {
             public: null,
             local: null,
             testing: null,
-            wiki: "wiki.smgames.club/ghostbound",
+            wiki: 'wiki.smgames.club/ghostbound',
           },
         },
         {
           name: '"Island"',
-          description: "A cozy play-at-your-pace game.",
-          tags: ["PC", "Singleplayer", "Multiplayer", "ALPHA", "Relaxing", "Sim"],
+          description: 'A cozy play-at-your-pace game.',
+          tags: ['PC', 'Singleplayer', 'Multiplayer', 'ALPHA', 'Relaxing', 'Sim'],
           links: {
             public: null,
             local: null,
             testing: null,
-            wiki: "wiki.smgames.club/island",
+            wiki: 'wiki.smgames.club/island',
           },
         },
         {
           name: '"Random RPG"',
-          description: "An RPG game with actions and consequences.",
-          tags: ["PC", "Singleplayer", "ALPHA", "RPG"],
+          description: 'An RPG game with actions and consequences.',
+          tags: ['PC', 'Singleplayer', 'ALPHA', 'RPG'],
           links: {
             public: null,
             local: null,
             testing: null,
-            wiki: "wiki.smgames.club/randomrpg",
+            wiki: 'wiki.smgames.club/randomrpg',
           },
         },
       ],
-    };
+    }
   },
   computed: {
     filteredProjects() {
-      if (this.selectedTag === "All") return this.projects;
-      return this.projects.filter((project) =>
-        project.tags.includes(this.selectedTag)
-      );
+      if (this.selectedTag === 'All') return this.projects
+      return this.projects.filter((project) => project.tags.includes(this.selectedTag))
     },
   },
   methods: {
     filterByTag(tag: string) {
-      this.selectedTag = tag;
+      this.selectedTag = tag
     },
     closeFaqModal() {
-      this.showFaqModal = false;
+      this.showFaqModal = false
     },
   },
-};
+}
 </script>
diff --git a/src/views/ServersView.vue b/src/views/ServersView.vue
index 8a87780..fc5033f 100644
--- a/src/views/ServersView.vue
+++ b/src/views/ServersView.vue
@@ -13,7 +13,9 @@
           @click="openModal(name)"
         >
           <h3>{{ name }}</h3>
-          <p>Status: <span :class="server.status">{{ server.status }}</span></p>
+          <p>
+            Status: <span :class="server.status">{{ server.status }}</span>
+          </p>
         </div>
       </div>
     </section>
@@ -29,7 +31,9 @@
           @click="openModal(name)"
         >
           <h3>{{ name }}</h3>
-          <p>Status: <span :class="server.status">{{ server.status }}</span></p>
+          <p>
+            Status: <span :class="server.status">{{ server.status }}</span>
+          </p>
         </div>
       </div>
     </section>
@@ -39,10 +43,14 @@
       <div class="modal-content" @click.stop>
         <img v-if="modalData.banner" :src="modalData.banner" alt="Server Banner" class="banner" />
         <h2>{{ modalData.name || 'No Server Selected' }}</h2>
-        <p>Status:
+        <p>
+          Status:
           <span :class="modalData.status">
             <i v-if="modalData.status === 'online'" class="fas fa-check-circle online-icon"></i>
-            <i v-else-if="modalData.status === 'offline'" class="fas fa-times-circle offline-icon"></i>
+            <i
+              v-else-if="modalData.status === 'offline'"
+              class="fas fa-times-circle offline-icon"
+            ></i>
             <i v-else class="fas fa-question-circle unknown-icon"></i>
             {{ modalData.status || 'unknown' }}
           </span>
@@ -82,24 +90,24 @@
 </template>
 
 <script lang="ts">
-import { defineComponent } from "vue";
+import { defineComponent } from 'vue'
 
 interface ServerInstructions {
-  public: string;
-  local: string;
+  public: string
+  local: string
 }
 
 interface Server {
-  name?: string;
-  banner: string;
-  status: string;
-  playersOnline?: number;
-  maxPlayers?: number;
-  about: string;
-  instructions: ServerInstructions;
-  installInstructions?: string;
-  queryIP: string;
-  link: string | null;
+  name?: string
+  banner: string
+  status: string
+  playersOnline?: number
+  maxPlayers?: number
+  about: string
+  instructions: ServerInstructions
+  installInstructions?: string
+  queryIP: string
+  link: string | null
 }
 
 export default defineComponent({
@@ -109,207 +117,202 @@ export default defineComponent({
       modalData: {} as Server,
 
       alwaysOnServers: {
-        "Minecraft Java Modern": {
-          banner: "minecraft-modern-banner.jpg",
-          status: "unknown",
+        'Minecraft Java Modern': {
+          banner: 'minecraft-modern-banner.jpg',
+          status: 'unknown',
           playersOnline: 0,
           maxPlayers: 20,
-          about: "This is the modern Minecraft Java server.",
+          about: 'This is the modern Minecraft Java server.',
           instructions: {
-            public: "",
-            local: "192.168.1.201",
+            public: '',
+            local: '192.168.1.201',
           },
           installInstructions: "Download Minecraft Java Edition from Mojang's website.",
-          queryIP: "",
-          link: "",
+          queryIP: '',
+          link: '',
         },
-        "Minecraft Java 1.12.2": {
-          banner: "minecraft-1-12-banner.jpg",
-          status: "unknown",
+        'Minecraft Java 1.12.2': {
+          banner: 'minecraft-1-12-banner.jpg',
+          status: 'unknown',
           playersOnline: 0,
           maxPlayers: 10,
-          about: "This is the Minecraft Java 1.12.2 server with mods.",
+          about: 'This is the Minecraft Java 1.12.2 server with mods.',
           instructions: {
-            public: "",
-            local: "",
+            public: '',
+            local: '',
           },
-          installInstructions: "Install Forge 1.12.2 and download the modpack provided.",
-          queryIP: "",
-          link: "",
+          installInstructions: 'Install Forge 1.12.2 and download the modpack provided.',
+          queryIP: '',
+          link: '',
         },
         Terraria: {
-          banner: "terraria-banner.jpg",
-          status: "unknown",
+          banner: 'terraria-banner.jpg',
+          status: 'unknown',
           playersOnline: 0,
           maxPlayers: 8,
-          about: "Our modded Terraria instance for friends and family.",
+          about: 'Our modded Terraria instance for friends and family.',
           instructions: {
-            public: "",
-            local: "192.168.1.121",
+            public: '',
+            local: '192.168.1.121',
           },
           installInstructions:
-            "Use TModLoader from Steam (not base Terraria) and download our pack.",
-          queryIP: "",
-          link: "https://steamcommunity.com/sharedfiles/filedetails/?id=2943030068",
+            'Use TModLoader from Steam (not base Terraria) and download our pack.',
+          queryIP: '',
+          link: 'https://steamcommunity.com/sharedfiles/filedetails/?id=2943030068',
         },
-        "Team Fortress 2": {
-          banner: "tf2-banner.jpg",
-          status: "unknown",
+        'Team Fortress 2': {
+          banner: 'tf2-banner.jpg',
+          status: 'unknown',
           playersOnline: 0,
           maxPlayers: 16,
-          about: "Classic Team Fortress 2 fun with friends.",
+          about: 'Classic Team Fortress 2 fun with friends.',
           instructions: {
-            public: "",
-            local: "192.168.1.203",
+            public: '',
+            local: '192.168.1.203',
           },
-          installInstructions: "Download Team Fortress 2 for free on Steam.",
-          queryIP: "",
-          link: "",
+          installInstructions: 'Download Team Fortress 2 for free on Steam.',
+          queryIP: '',
+          link: '',
         },
       } as Record<string, Server>, // Add type Record<string, Server> for alwaysOnServers
 
       toggledServers: {
-        "Core Keeper": {
-          banner: "core-keeper-banner.jpg",
-          status: "unknown",
+        'Core Keeper': {
+          banner: 'core-keeper-banner.jpg',
+          status: 'unknown',
           playersOnline: 0,
           maxPlayers: 16,
-          about: "Explore, mine, and survive in this pixelated adventure.",
+          about: 'Explore, mine, and survive in this pixelated adventure.',
           instructions: {
-            public: "",
-            local: "192.168.1.204",
+            public: '',
+            local: '192.168.1.204',
           },
-          installInstructions: "Available on Steam. Ensure your game is up to date.",
-          queryIP: "",
+          installInstructions: 'Available on Steam. Ensure your game is up to date.',
+          queryIP: '',
           link: null,
         },
         ECO: {
-          banner: "eco-banner.jpg",
-          status: "unknown",
+          banner: 'eco-banner.jpg',
+          status: 'unknown',
           playersOnline: 0,
           maxPlayers: 50,
-          about: "A global survival game where players build an ecosystem.",
+          about: 'A global survival game where players build an ecosystem.',
           instructions: {
-            public: "eco.example.com",
-            local: "",
+            public: 'eco.example.com',
+            local: '',
           },
           installInstructions:
-            "Download ECO from the official website or Steam. Use the provided IP.",
-          queryIP: "http://eco.example.com/status",
+            'Download ECO from the official website or Steam. Use the provided IP.',
+          queryIP: 'http://eco.example.com/status',
           link: null,
         },
         Enshrouded: {
-          banner: "enshrouded-banner.jpg",
-          status: "unknown",
+          banner: 'enshrouded-banner.jpg',
+          status: 'unknown',
           playersOnline: 0,
           maxPlayers: 16,
-          about: "A survival crafting game in a mysterious fantasy setting.",
+          about: 'A survival crafting game in a mysterious fantasy setting.',
           instructions: {
-            public: "",
-            local: "",
+            public: '',
+            local: '',
           },
           installInstructions: undefined,
-          queryIP: "http://enshrouded.example.com/status",
+          queryIP: 'http://enshrouded.example.com/status',
           link: null,
         },
         Empyrion: {
-          banner: "empyrion-banner.jpg",
-          status: "unknown",
+          banner: 'empyrion-banner.jpg',
+          status: 'unknown',
           playersOnline: 0,
           maxPlayers: 16,
-          about: "Explore space and build your intergalactic empire.",
+          about: 'Explore space and build your intergalactic empire.',
           instructions: {
-            public: "",
-            local: "",
+            public: '',
+            local: '',
           },
           installInstructions:
-            "Download Empyrion from Steam and ensure mods match server settings.",
-          queryIP: "http://empyrion.example.com/status",
+            'Download Empyrion from Steam and ensure mods match server settings.',
+          queryIP: 'http://empyrion.example.com/status',
           link: null,
         },
         Palworld: {
-          banner: "palworld-banner.jpg",
-          status: "unknown",
+          banner: 'palworld-banner.jpg',
+          status: 'unknown',
           playersOnline: 0,
           maxPlayers: 32,
-          about: "A multiplayer game where you befriend and fight alongside creatures.",
+          about: 'A multiplayer game where you befriend and fight alongside creatures.',
           instructions: {
-            public: "",
-            local: "",
+            public: '',
+            local: '',
           },
-          installInstructions: "Available on Steam. Join the server via multiplayer menu.",
-          queryIP: "http://palworld.example.com/status",
+          installInstructions: 'Available on Steam. Join the server via multiplayer menu.',
+          queryIP: 'http://palworld.example.com/status',
           link: null,
         },
-        "Survive The Nights": {
-          banner: "survive-the-nights-banner.jpg",
-          status: "unknown",
+        'Survive The Nights': {
+          banner: 'survive-the-nights-banner.jpg',
+          status: 'unknown',
           playersOnline: 0,
           maxPlayers: 10,
-          about: "A survival horror game set in a post-apocalyptic world.",
+          about: 'A survival horror game set in a post-apocalyptic world.',
           instructions: {
-            public: "",
-            local: "",
+            public: '',
+            local: '',
           },
-          installInstructions: "Purchase on Steam and ensure to update your client.",
-          queryIP: "http://survivethenights.example.com/status",
+          installInstructions: 'Purchase on Steam and ensure to update your client.',
+          queryIP: 'http://survivethenights.example.com/status',
           link: null,
         },
         Valheim: {
-          banner: "valheim-banner.jpg",
-          status: "unknown",
+          banner: 'valheim-banner.jpg',
+          status: 'unknown',
           playersOnline: 0,
           maxPlayers: 10,
-          about: "A Viking-themed survival game set in a procedurally generated world.",
+          about: 'A Viking-themed survival game set in a procedurally generated world.',
           instructions: {
-            public: "",
-            local: "",
+            public: '',
+            local: '',
           },
-          installInstructions: "Install via Steam and ensure mods match server settings.",
-          queryIP: "http://valheim.example.com/status",
+          installInstructions: 'Install via Steam and ensure mods match server settings.',
+          queryIP: 'http://valheim.example.com/status',
           link: null,
         },
-        "V Rising": {
-          banner: "v-rising-banner.jpg",
-          status: "unknown",
+        'V Rising': {
+          banner: 'v-rising-banner.jpg',
+          status: 'unknown',
           playersOnline: 0,
           maxPlayers: 20,
-          about: "Rise as a vampire lord in this survival RPG.",
+          about: 'Rise as a vampire lord in this survival RPG.',
           instructions: {
-            public: "",
-            local: "",
+            public: '',
+            local: '',
           },
-          installInstructions:
-            "Available on Steam. Ensure your game and server mods are synced.",
-          queryIP: "http://vrising.example.com/status",
+          installInstructions: 'Available on Steam. Ensure your game and server mods are synced.',
+          queryIP: 'http://vrising.example.com/status',
           link: null,
         },
       } as Record<string, Server>, // Add type Record<string, Server> for toggledServers
-    };
+    }
   },
   computed: {
     showPlayerCount(): boolean {
-      return (
-        this.modalData.playersOnline !== undefined &&
-        this.modalData.maxPlayers !== undefined
-      );
+      return this.modalData.playersOnline !== undefined && this.modalData.maxPlayers !== undefined
     },
     displayPlayerCount(): string {
-      if (this.modalData.maxPlayers === undefined) return "? / ?";
-      if (this.modalData.playersOnline === undefined) return "? / ?";
-      return `${this.modalData.playersOnline} / ${this.modalData.maxPlayers}`;
+      if (this.modalData.maxPlayers === undefined) return '? / ?'
+      if (this.modalData.playersOnline === undefined) return '? / ?'
+      return `${this.modalData.playersOnline} / ${this.modalData.maxPlayers}`
     },
   },
   methods: {
     openModal(serverName: string) {
-      this.modalData =
-        this.alwaysOnServers[serverName] || this.toggledServers[serverName] || {};
-      this.modalData.name = serverName;
-      this.showModal = true;
+      this.modalData = this.alwaysOnServers[serverName] || this.toggledServers[serverName] || {}
+      this.modalData.name = serverName
+      this.showModal = true
     },
     closeModal() {
-      this.showModal = false;
+      this.showModal = false
     },
   },
-});
+})
 </script>