diff --git a/.DS_Store b/.DS_Store
new file mode 100644
index 0000000000000000000000000000000000000000..accf79e327469c30c9457a0b38cc1c9df1046c48
Binary files /dev/null and b/.DS_Store differ
diff --git a/movie-group-8/.env b/movie-group-8/.env
new file mode 100644
index 0000000000000000000000000000000000000000..4ec34fde83f0116c4ff586e6f695eb0a151ad951
--- /dev/null
+++ b/movie-group-8/.env
@@ -0,0 +1,3 @@
+VITE_TMDB_API_KEY=42259df77843511296d8096fa29e08a8
+
+VITE_TMDB_BASE=https://api.themoviedb.org/3
diff --git a/movie-group-8/.gitignore b/movie-group-8/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..a547bf36d8d11a4f89c59c144f24795749086dd1
--- /dev/null
+++ b/movie-group-8/.gitignore
@@ -0,0 +1,24 @@
+# Logs
+logs
+*.log
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+pnpm-debug.log*
+lerna-debug.log*
+
+node_modules
+dist
+dist-ssr
+*.local
+
+# Editor directories and files
+.vscode/*
+!.vscode/extensions.json
+.idea
+.DS_Store
+*.suo
+*.ntvs*
+*.njsproj
+*.sln
+*.sw?
diff --git a/movie-group-8/.vscode/extensions.json b/movie-group-8/.vscode/extensions.json
new file mode 100644
index 0000000000000000000000000000000000000000..a7cea0b0678120a1b590d1b6592c7318039b9179
--- /dev/null
+++ b/movie-group-8/.vscode/extensions.json
@@ -0,0 +1,3 @@
+{
+  "recommendations": ["Vue.volar"]
+}
diff --git a/movie-group-8/README.md b/movie-group-8/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..1511959c22b74118c06679739cf9abe2ceeea48c
--- /dev/null
+++ b/movie-group-8/README.md
@@ -0,0 +1,5 @@
+# Vue 3 + Vite
+
+This template should help get you started developing with Vue 3 in Vite. The template uses Vue 3 `<script setup>` SFCs, check out the [script setup docs](https://v3.vuejs.org/api/sfc-script-setup.html#sfc-script-setup) to learn more.
+
+Learn more about IDE Support for Vue in the [Vue Docs Scaling up Guide](https://vuejs.org/guide/scaling-up/tooling.html#ide-support).
diff --git a/movie-group-8/index.html b/movie-group-8/index.html
new file mode 100644
index 0000000000000000000000000000000000000000..e693fa6a9079d02a1f3a87b4e6cae1e788a11a21
--- /dev/null
+++ b/movie-group-8/index.html
@@ -0,0 +1,13 @@
+<!doctype html>
+<html lang="en">
+  <head>
+    <meta charset="UTF-8" />
+    <link rel="icon" type="image/png" href="./src/assets/Dark_Mode.png" />
+    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+    <title>SceneIt</title>
+  </head>
+  <body>
+    <div id="app"></div>
+    <script type="module" src="/src/main.js"></script>
+  </body>
+</html>
diff --git a/movie-group-8/package-lock.json b/movie-group-8/package-lock.json
new file mode 100644
index 0000000000000000000000000000000000000000..5502da0ec210ec42ef3ac199421a2d8977f55e32
--- /dev/null
+++ b/movie-group-8/package-lock.json
@@ -0,0 +1,2877 @@
+{
+  "name": "movie-group-8",
+  "version": "0.0.0",
+  "lockfileVersion": 3,
+  "requires": true,
+  "packages": {
+    "": {
+      "name": "movie-group-8",
+      "version": "0.0.0",
+      "dependencies": {
+        "@headlessui/vue": "^1.7.23",
+        "@heroicons/vue": "^2.2.0",
+        "@tailwindcss/vite": "^4.0.14",
+        "axios": "^1.9.0",
+        "firebase": "^11.4.0",
+        "movie-group-8": "file:",
+        "tailwindcss": "^4.0.14",
+        "vue": "^3.5.13",
+        "vue-router": "^4.5.0"
+      },
+      "devDependencies": {
+        "@vitejs/plugin-vue": "^5.2.1",
+        "vite": "^6.2.0"
+      }
+    },
+    "node_modules/@babel/helper-string-parser": {
+      "version": "7.25.9",
+      "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.25.9.tgz",
+      "integrity": "sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==",
+      "engines": {
+        "node": ">=6.9.0"
+      }
+    },
+    "node_modules/@babel/helper-validator-identifier": {
+      "version": "7.25.9",
+      "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz",
+      "integrity": "sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==",
+      "engines": {
+        "node": ">=6.9.0"
+      }
+    },
+    "node_modules/@babel/parser": {
+      "version": "7.26.10",
+      "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.26.10.tgz",
+      "integrity": "sha512-6aQR2zGE/QFi8JpDLjUZEPYOs7+mhKXm86VaKFiLP35JQwQb6bwUE+XbvkH0EptsYhbNBSUGaUBLKqxH1xSgsA==",
+      "dependencies": {
+        "@babel/types": "^7.26.10"
+      },
+      "bin": {
+        "parser": "bin/babel-parser.js"
+      },
+      "engines": {
+        "node": ">=6.0.0"
+      }
+    },
+    "node_modules/@babel/types": {
+      "version": "7.26.10",
+      "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.26.10.tgz",
+      "integrity": "sha512-emqcG3vHrpxUKTrxcblR36dcrcoRDvKmnL/dCL6ZsHaShW80qxCAcNhzQZrpeM765VzEos+xOi4s+r4IXzTwdQ==",
+      "dependencies": {
+        "@babel/helper-string-parser": "^7.25.9",
+        "@babel/helper-validator-identifier": "^7.25.9"
+      },
+      "engines": {
+        "node": ">=6.9.0"
+      }
+    },
+    "node_modules/@esbuild/aix-ppc64": {
+      "version": "0.25.1",
+      "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.1.tgz",
+      "integrity": "sha512-kfYGy8IdzTGy+z0vFGvExZtxkFlA4zAxgKEahG9KE1ScBjpQnFsNOX8KTU5ojNru5ed5CVoJYXFtoxaq5nFbjQ==",
+      "cpu": [
+        "ppc64"
+      ],
+      "optional": true,
+      "os": [
+        "aix"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/android-arm": {
+      "version": "0.25.1",
+      "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.1.tgz",
+      "integrity": "sha512-dp+MshLYux6j/JjdqVLnMglQlFu+MuVeNrmT5nk6q07wNhCdSnB7QZj+7G8VMUGh1q+vj2Bq8kRsuyA00I/k+Q==",
+      "cpu": [
+        "arm"
+      ],
+      "optional": true,
+      "os": [
+        "android"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/android-arm64": {
+      "version": "0.25.1",
+      "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.1.tgz",
+      "integrity": "sha512-50tM0zCJW5kGqgG7fQ7IHvQOcAn9TKiVRuQ/lN0xR+T2lzEFvAi1ZcS8DiksFcEpf1t/GYOeOfCAgDHFpkiSmA==",
+      "cpu": [
+        "arm64"
+      ],
+      "optional": true,
+      "os": [
+        "android"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/android-x64": {
+      "version": "0.25.1",
+      "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.1.tgz",
+      "integrity": "sha512-GCj6WfUtNldqUzYkN/ITtlhwQqGWu9S45vUXs7EIYf+7rCiiqH9bCloatO9VhxsL0Pji+PF4Lz2XXCES+Q8hDw==",
+      "cpu": [
+        "x64"
+      ],
+      "optional": true,
+      "os": [
+        "android"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/darwin-arm64": {
+      "version": "0.25.1",
+      "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.1.tgz",
+      "integrity": "sha512-5hEZKPf+nQjYoSr/elb62U19/l1mZDdqidGfmFutVUjjUZrOazAtwK+Kr+3y0C/oeJfLlxo9fXb1w7L+P7E4FQ==",
+      "cpu": [
+        "arm64"
+      ],
+      "optional": true,
+      "os": [
+        "darwin"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/darwin-x64": {
+      "version": "0.25.1",
+      "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.1.tgz",
+      "integrity": "sha512-hxVnwL2Dqs3fM1IWq8Iezh0cX7ZGdVhbTfnOy5uURtao5OIVCEyj9xIzemDi7sRvKsuSdtCAhMKarxqtlyVyfA==",
+      "cpu": [
+        "x64"
+      ],
+      "optional": true,
+      "os": [
+        "darwin"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/freebsd-arm64": {
+      "version": "0.25.1",
+      "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.1.tgz",
+      "integrity": "sha512-1MrCZs0fZa2g8E+FUo2ipw6jw5qqQiH+tERoS5fAfKnRx6NXH31tXBKI3VpmLijLH6yriMZsxJtaXUyFt/8Y4A==",
+      "cpu": [
+        "arm64"
+      ],
+      "optional": true,
+      "os": [
+        "freebsd"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/freebsd-x64": {
+      "version": "0.25.1",
+      "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.1.tgz",
+      "integrity": "sha512-0IZWLiTyz7nm0xuIs0q1Y3QWJC52R8aSXxe40VUxm6BB1RNmkODtW6LHvWRrGiICulcX7ZvyH6h5fqdLu4gkww==",
+      "cpu": [
+        "x64"
+      ],
+      "optional": true,
+      "os": [
+        "freebsd"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/linux-arm": {
+      "version": "0.25.1",
+      "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.1.tgz",
+      "integrity": "sha512-NdKOhS4u7JhDKw9G3cY6sWqFcnLITn6SqivVArbzIaf3cemShqfLGHYMx8Xlm/lBit3/5d7kXvriTUGa5YViuQ==",
+      "cpu": [
+        "arm"
+      ],
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/linux-arm64": {
+      "version": "0.25.1",
+      "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.1.tgz",
+      "integrity": "sha512-jaN3dHi0/DDPelk0nLcXRm1q7DNJpjXy7yWaWvbfkPvI+7XNSc/lDOnCLN7gzsyzgu6qSAmgSvP9oXAhP973uQ==",
+      "cpu": [
+        "arm64"
+      ],
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/linux-ia32": {
+      "version": "0.25.1",
+      "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.1.tgz",
+      "integrity": "sha512-OJykPaF4v8JidKNGz8c/q1lBO44sQNUQtq1KktJXdBLn1hPod5rE/Hko5ugKKZd+D2+o1a9MFGUEIUwO2YfgkQ==",
+      "cpu": [
+        "ia32"
+      ],
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/linux-loong64": {
+      "version": "0.25.1",
+      "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.1.tgz",
+      "integrity": "sha512-nGfornQj4dzcq5Vp835oM/o21UMlXzn79KobKlcs3Wz9smwiifknLy4xDCLUU0BWp7b/houtdrgUz7nOGnfIYg==",
+      "cpu": [
+        "loong64"
+      ],
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/linux-mips64el": {
+      "version": "0.25.1",
+      "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.1.tgz",
+      "integrity": "sha512-1osBbPEFYwIE5IVB/0g2X6i1qInZa1aIoj1TdL4AaAb55xIIgbg8Doq6a5BzYWgr+tEcDzYH67XVnTmUzL+nXg==",
+      "cpu": [
+        "mips64el"
+      ],
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/linux-ppc64": {
+      "version": "0.25.1",
+      "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.1.tgz",
+      "integrity": "sha512-/6VBJOwUf3TdTvJZ82qF3tbLuWsscd7/1w+D9LH0W/SqUgM5/JJD0lrJ1fVIfZsqB6RFmLCe0Xz3fmZc3WtyVg==",
+      "cpu": [
+        "ppc64"
+      ],
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/linux-riscv64": {
+      "version": "0.25.1",
+      "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.1.tgz",
+      "integrity": "sha512-nSut/Mx5gnilhcq2yIMLMe3Wl4FK5wx/o0QuuCLMtmJn+WeWYoEGDN1ipcN72g1WHsnIbxGXd4i/MF0gTcuAjQ==",
+      "cpu": [
+        "riscv64"
+      ],
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/linux-s390x": {
+      "version": "0.25.1",
+      "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.1.tgz",
+      "integrity": "sha512-cEECeLlJNfT8kZHqLarDBQso9a27o2Zd2AQ8USAEoGtejOrCYHNtKP8XQhMDJMtthdF4GBmjR2au3x1udADQQQ==",
+      "cpu": [
+        "s390x"
+      ],
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/linux-x64": {
+      "version": "0.25.1",
+      "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.1.tgz",
+      "integrity": "sha512-xbfUhu/gnvSEg+EGovRc+kjBAkrvtk38RlerAzQxvMzlB4fXpCFCeUAYzJvrnhFtdeyVCDANSjJvOvGYoeKzFA==",
+      "cpu": [
+        "x64"
+      ],
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/netbsd-arm64": {
+      "version": "0.25.1",
+      "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.1.tgz",
+      "integrity": "sha512-O96poM2XGhLtpTh+s4+nP7YCCAfb4tJNRVZHfIE7dgmax+yMP2WgMd2OecBuaATHKTHsLWHQeuaxMRnCsH8+5g==",
+      "cpu": [
+        "arm64"
+      ],
+      "optional": true,
+      "os": [
+        "netbsd"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/netbsd-x64": {
+      "version": "0.25.1",
+      "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.1.tgz",
+      "integrity": "sha512-X53z6uXip6KFXBQ+Krbx25XHV/NCbzryM6ehOAeAil7X7oa4XIq+394PWGnwaSQ2WRA0KI6PUO6hTO5zeF5ijA==",
+      "cpu": [
+        "x64"
+      ],
+      "optional": true,
+      "os": [
+        "netbsd"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/openbsd-arm64": {
+      "version": "0.25.1",
+      "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.1.tgz",
+      "integrity": "sha512-Na9T3szbXezdzM/Kfs3GcRQNjHzM6GzFBeU1/6IV/npKP5ORtp9zbQjvkDJ47s6BCgaAZnnnu/cY1x342+MvZg==",
+      "cpu": [
+        "arm64"
+      ],
+      "optional": true,
+      "os": [
+        "openbsd"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/openbsd-x64": {
+      "version": "0.25.1",
+      "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.1.tgz",
+      "integrity": "sha512-T3H78X2h1tszfRSf+txbt5aOp/e7TAz3ptVKu9Oyir3IAOFPGV6O9c2naym5TOriy1l0nNf6a4X5UXRZSGX/dw==",
+      "cpu": [
+        "x64"
+      ],
+      "optional": true,
+      "os": [
+        "openbsd"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/sunos-x64": {
+      "version": "0.25.1",
+      "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.1.tgz",
+      "integrity": "sha512-2H3RUvcmULO7dIE5EWJH8eubZAI4xw54H1ilJnRNZdeo8dTADEZ21w6J22XBkXqGJbe0+wnNJtw3UXRoLJnFEg==",
+      "cpu": [
+        "x64"
+      ],
+      "optional": true,
+      "os": [
+        "sunos"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/win32-arm64": {
+      "version": "0.25.1",
+      "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.1.tgz",
+      "integrity": "sha512-GE7XvrdOzrb+yVKB9KsRMq+7a2U/K5Cf/8grVFRAGJmfADr/e/ODQ134RK2/eeHqYV5eQRFxb1hY7Nr15fv1NQ==",
+      "cpu": [
+        "arm64"
+      ],
+      "optional": true,
+      "os": [
+        "win32"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/win32-ia32": {
+      "version": "0.25.1",
+      "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.1.tgz",
+      "integrity": "sha512-uOxSJCIcavSiT6UnBhBzE8wy3n0hOkJsBOzy7HDAuTDE++1DJMRRVCPGisULScHL+a/ZwdXPpXD3IyFKjA7K8A==",
+      "cpu": [
+        "ia32"
+      ],
+      "optional": true,
+      "os": [
+        "win32"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@esbuild/win32-x64": {
+      "version": "0.25.1",
+      "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.1.tgz",
+      "integrity": "sha512-Y1EQdcfwMSeQN/ujR5VayLOJ1BHaK+ssyk0AEzPjC+t1lITgsnccPqFjb6V+LsTp/9Iov4ysfjxLaGJ9RPtkVg==",
+      "cpu": [
+        "x64"
+      ],
+      "optional": true,
+      "os": [
+        "win32"
+      ],
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/@firebase/analytics": {
+      "version": "0.10.12",
+      "resolved": "https://registry.npmjs.org/@firebase/analytics/-/analytics-0.10.12.tgz",
+      "integrity": "sha512-iDCGnw6qdFqwI5ywkgece99WADJNoymu+nLIQI4fZM/vCZ3bEo4wlpEetW71s1HqGpI0hQStiPhqVjFxDb2yyw==",
+      "dependencies": {
+        "@firebase/component": "0.6.13",
+        "@firebase/installations": "0.6.13",
+        "@firebase/logger": "0.4.4",
+        "@firebase/util": "1.11.0",
+        "tslib": "^2.1.0"
+      },
+      "peerDependencies": {
+        "@firebase/app": "0.x"
+      }
+    },
+    "node_modules/@firebase/analytics-compat": {
+      "version": "0.2.18",
+      "resolved": "https://registry.npmjs.org/@firebase/analytics-compat/-/analytics-compat-0.2.18.tgz",
+      "integrity": "sha512-Hw9mzsSMZaQu6wrTbi3kYYwGw9nBqOHr47pVLxfr5v8CalsdrG5gfs9XUlPOZjHRVISp3oQrh1j7d3E+ulHPjQ==",
+      "dependencies": {
+        "@firebase/analytics": "0.10.12",
+        "@firebase/analytics-types": "0.8.3",
+        "@firebase/component": "0.6.13",
+        "@firebase/util": "1.11.0",
+        "tslib": "^2.1.0"
+      },
+      "peerDependencies": {
+        "@firebase/app-compat": "0.x"
+      }
+    },
+    "node_modules/@firebase/analytics-types": {
+      "version": "0.8.3",
+      "resolved": "https://registry.npmjs.org/@firebase/analytics-types/-/analytics-types-0.8.3.tgz",
+      "integrity": "sha512-VrIp/d8iq2g501qO46uGz3hjbDb8xzYMrbu8Tp0ovzIzrvJZ2fvmj649gTjge/b7cCCcjT0H37g1gVtlNhnkbg=="
+    },
+    "node_modules/@firebase/app": {
+      "version": "0.11.2",
+      "resolved": "https://registry.npmjs.org/@firebase/app/-/app-0.11.2.tgz",
+      "integrity": "sha512-bFee0hPJZBzNtiizRxdgsu8C9DW3mn1y0OJJ4zHQsccjDYzGOfvN0G3CMGyBIiwNctsFpQa8orbp2IKywoUeqA==",
+      "dependencies": {
+        "@firebase/component": "0.6.13",
+        "@firebase/logger": "0.4.4",
+        "@firebase/util": "1.11.0",
+        "idb": "7.1.1",
+        "tslib": "^2.1.0"
+      },
+      "engines": {
+        "node": ">=18.0.0"
+      }
+    },
+    "node_modules/@firebase/app-check": {
+      "version": "0.8.12",
+      "resolved": "https://registry.npmjs.org/@firebase/app-check/-/app-check-0.8.12.tgz",
+      "integrity": "sha512-LxjcoIFOU4sgK07ZWb8XDHxuVB+UKs41vPK+Sg9PeZMvEoz84fndFAx8Nz2nipiya2EmyxBgVhff8Hi6GBt+XA==",
+      "dependencies": {
+        "@firebase/component": "0.6.13",
+        "@firebase/logger": "0.4.4",
+        "@firebase/util": "1.11.0",
+        "tslib": "^2.1.0"
+      },
+      "engines": {
+        "node": ">=18.0.0"
+      },
+      "peerDependencies": {
+        "@firebase/app": "0.x"
+      }
+    },
+    "node_modules/@firebase/app-check-compat": {
+      "version": "0.3.19",
+      "resolved": "https://registry.npmjs.org/@firebase/app-check-compat/-/app-check-compat-0.3.19.tgz",
+      "integrity": "sha512-G8FMiqhrKc4gEEujrBDBBrbRav8MGqoLObWj1hy/riCSg4XlRYhpnq3ev8E9HTirqU1tAGH6oJl7vr+jfM7YNA==",
+      "dependencies": {
+        "@firebase/app-check": "0.8.12",
+        "@firebase/app-check-types": "0.5.3",
+        "@firebase/component": "0.6.13",
+        "@firebase/logger": "0.4.4",
+        "@firebase/util": "1.11.0",
+        "tslib": "^2.1.0"
+      },
+      "engines": {
+        "node": ">=18.0.0"
+      },
+      "peerDependencies": {
+        "@firebase/app-compat": "0.x"
+      }
+    },
+    "node_modules/@firebase/app-check-interop-types": {
+      "version": "0.3.3",
+      "resolved": "https://registry.npmjs.org/@firebase/app-check-interop-types/-/app-check-interop-types-0.3.3.tgz",
+      "integrity": "sha512-gAlxfPLT2j8bTI/qfe3ahl2I2YcBQ8cFIBdhAQA4I2f3TndcO+22YizyGYuttLHPQEpWkhmpFW60VCFEPg4g5A=="
+    },
+    "node_modules/@firebase/app-check-types": {
+      "version": "0.5.3",
+      "resolved": "https://registry.npmjs.org/@firebase/app-check-types/-/app-check-types-0.5.3.tgz",
+      "integrity": "sha512-hyl5rKSj0QmwPdsAxrI5x1otDlByQ7bvNvVt8G/XPO2CSwE++rmSVf3VEhaeOR4J8ZFaF0Z0NDSmLejPweZ3ng=="
+    },
+    "node_modules/@firebase/app-compat": {
+      "version": "0.2.51",
+      "resolved": "https://registry.npmjs.org/@firebase/app-compat/-/app-compat-0.2.51.tgz",
+      "integrity": "sha512-pxF1+coABt+ugqNI0YXDlmkKv4kh3pjI5BqIJJ1VXBo42OZbKMsQbFeos14YBrWwiqqSjUvQ70FBNsv5E2wuxg==",
+      "dependencies": {
+        "@firebase/app": "0.11.2",
+        "@firebase/component": "0.6.13",
+        "@firebase/logger": "0.4.4",
+        "@firebase/util": "1.11.0",
+        "tslib": "^2.1.0"
+      },
+      "engines": {
+        "node": ">=18.0.0"
+      }
+    },
+    "node_modules/@firebase/app-types": {
+      "version": "0.9.3",
+      "resolved": "https://registry.npmjs.org/@firebase/app-types/-/app-types-0.9.3.tgz",
+      "integrity": "sha512-kRVpIl4vVGJ4baogMDINbyrIOtOxqhkZQg4jTq3l8Lw6WSk0xfpEYzezFu+Kl4ve4fbPl79dvwRtaFqAC/ucCw=="
+    },
+    "node_modules/@firebase/auth": {
+      "version": "1.9.1",
+      "resolved": "https://registry.npmjs.org/@firebase/auth/-/auth-1.9.1.tgz",
+      "integrity": "sha512-9KKo5SNVkyJzftsW+daS+PGDbeJ+MFJWXQFHDqqPPH3acWHtiNnGHH5HGpIJErEELrsm9xMPie5zfZ0XpGU8+w==",
+      "dependencies": {
+        "@firebase/component": "0.6.13",
+        "@firebase/logger": "0.4.4",
+        "@firebase/util": "1.11.0",
+        "tslib": "^2.1.0"
+      },
+      "engines": {
+        "node": ">=18.0.0"
+      },
+      "peerDependencies": {
+        "@firebase/app": "0.x",
+        "@react-native-async-storage/async-storage": "^1.18.1"
+      },
+      "peerDependenciesMeta": {
+        "@react-native-async-storage/async-storage": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/@firebase/auth-compat": {
+      "version": "0.5.19",
+      "resolved": "https://registry.npmjs.org/@firebase/auth-compat/-/auth-compat-0.5.19.tgz",
+      "integrity": "sha512-v898POphOIBJliKF76SiGOXh4EdhO5fM6S9a2ZKf/8wHdBea/qwxwZoVVya4DW6Mi7vWyp1lIzHbFgwRz8G9TA==",
+      "dependencies": {
+        "@firebase/auth": "1.9.1",
+        "@firebase/auth-types": "0.13.0",
+        "@firebase/component": "0.6.13",
+        "@firebase/util": "1.11.0",
+        "tslib": "^2.1.0"
+      },
+      "engines": {
+        "node": ">=18.0.0"
+      },
+      "peerDependencies": {
+        "@firebase/app-compat": "0.x"
+      }
+    },
+    "node_modules/@firebase/auth-interop-types": {
+      "version": "0.2.4",
+      "resolved": "https://registry.npmjs.org/@firebase/auth-interop-types/-/auth-interop-types-0.2.4.tgz",
+      "integrity": "sha512-JPgcXKCuO+CWqGDnigBtvo09HeBs5u/Ktc2GaFj2m01hLarbxthLNm7Fk8iOP1aqAtXV+fnnGj7U28xmk7IwVA=="
+    },
+    "node_modules/@firebase/auth-types": {
+      "version": "0.13.0",
+      "resolved": "https://registry.npmjs.org/@firebase/auth-types/-/auth-types-0.13.0.tgz",
+      "integrity": "sha512-S/PuIjni0AQRLF+l9ck0YpsMOdE8GO2KU6ubmBB7P+7TJUCQDa3R1dlgYm9UzGbbePMZsp0xzB93f2b/CgxMOg==",
+      "peerDependencies": {
+        "@firebase/app-types": "0.x",
+        "@firebase/util": "1.x"
+      }
+    },
+    "node_modules/@firebase/component": {
+      "version": "0.6.13",
+      "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.6.13.tgz",
+      "integrity": "sha512-I/Eg1NpAtZ8AAfq8mpdfXnuUpcLxIDdCDtTzWSh+FXnp/9eCKJ3SNbOCKrUCyhLzNa2SiPJYruei0sxVjaOTeg==",
+      "dependencies": {
+        "@firebase/util": "1.11.0",
+        "tslib": "^2.1.0"
+      },
+      "engines": {
+        "node": ">=18.0.0"
+      }
+    },
+    "node_modules/@firebase/data-connect": {
+      "version": "0.3.1",
+      "resolved": "https://registry.npmjs.org/@firebase/data-connect/-/data-connect-0.3.1.tgz",
+      "integrity": "sha512-PNlfAJ2mcbyRlWfm41nfk8EksTuvMFTFIX+puNzeUa6OTIDtyp1IX1NJVc7n6WpfbErN7tNqcOEMe6BMtpcjVA==",
+      "dependencies": {
+        "@firebase/auth-interop-types": "0.2.4",
+        "@firebase/component": "0.6.13",
+        "@firebase/logger": "0.4.4",
+        "@firebase/util": "1.11.0",
+        "tslib": "^2.1.0"
+      },
+      "peerDependencies": {
+        "@firebase/app": "0.x"
+      }
+    },
+    "node_modules/@firebase/database": {
+      "version": "1.0.13",
+      "resolved": "https://registry.npmjs.org/@firebase/database/-/database-1.0.13.tgz",
+      "integrity": "sha512-cdc+LuseKdJXzlrCx8ePMXyctSWtYS9SsP3y7EeA85GzNh/IL0b7HOq0eShridL935iQ0KScZCj5qJtKkGE53g==",
+      "dependencies": {
+        "@firebase/app-check-interop-types": "0.3.3",
+        "@firebase/auth-interop-types": "0.2.4",
+        "@firebase/component": "0.6.13",
+        "@firebase/logger": "0.4.4",
+        "@firebase/util": "1.11.0",
+        "faye-websocket": "0.11.4",
+        "tslib": "^2.1.0"
+      },
+      "engines": {
+        "node": ">=18.0.0"
+      }
+    },
+    "node_modules/@firebase/database-compat": {
+      "version": "2.0.4",
+      "resolved": "https://registry.npmjs.org/@firebase/database-compat/-/database-compat-2.0.4.tgz",
+      "integrity": "sha512-4qsptwZ3DTGNBje56ETItZQyA/HMalOelnLmkC3eR0M6+zkzOHjNHyWUWodW2mqxRKAM0sGkn+aIwYHKZFJXug==",
+      "dependencies": {
+        "@firebase/component": "0.6.13",
+        "@firebase/database": "1.0.13",
+        "@firebase/database-types": "1.0.9",
+        "@firebase/logger": "0.4.4",
+        "@firebase/util": "1.11.0",
+        "tslib": "^2.1.0"
+      },
+      "engines": {
+        "node": ">=18.0.0"
+      }
+    },
+    "node_modules/@firebase/database-types": {
+      "version": "1.0.9",
+      "resolved": "https://registry.npmjs.org/@firebase/database-types/-/database-types-1.0.9.tgz",
+      "integrity": "sha512-uCntrxPbJHhZsNRpMhxNCm7GzhYWX+7J2e57wq1ZZ4NJrQw5DORgkAzJMByYZcVAjgADnCxxhK/GkoypH+XpvQ==",
+      "dependencies": {
+        "@firebase/app-types": "0.9.3",
+        "@firebase/util": "1.11.0"
+      }
+    },
+    "node_modules/@firebase/firestore": {
+      "version": "4.7.9",
+      "resolved": "https://registry.npmjs.org/@firebase/firestore/-/firestore-4.7.9.tgz",
+      "integrity": "sha512-uq/bUtHDqJ5ZqPHAJIlNzHpXUtcVYcASz2V6y7UmP1WLlRKEt1yf1OcQW5u8pY2yq7162OnCl5J5mkOdMTMLZw==",
+      "dependencies": {
+        "@firebase/component": "0.6.13",
+        "@firebase/logger": "0.4.4",
+        "@firebase/util": "1.11.0",
+        "@firebase/webchannel-wrapper": "1.0.3",
+        "@grpc/grpc-js": "~1.9.0",
+        "@grpc/proto-loader": "^0.7.8",
+        "tslib": "^2.1.0"
+      },
+      "engines": {
+        "node": ">=18.0.0"
+      },
+      "peerDependencies": {
+        "@firebase/app": "0.x"
+      }
+    },
+    "node_modules/@firebase/firestore-compat": {
+      "version": "0.3.44",
+      "resolved": "https://registry.npmjs.org/@firebase/firestore-compat/-/firestore-compat-0.3.44.tgz",
+      "integrity": "sha512-4Lv2TyHEW+FugXPgmQ0ZylSbh9uFuKDP0lCL1hX9cbxXaafhC/Nww+DWokUQ2zZcynjc8fxFunw6Xbd3QHAlgA==",
+      "dependencies": {
+        "@firebase/component": "0.6.13",
+        "@firebase/firestore": "4.7.9",
+        "@firebase/firestore-types": "3.0.3",
+        "@firebase/util": "1.11.0",
+        "tslib": "^2.1.0"
+      },
+      "engines": {
+        "node": ">=18.0.0"
+      },
+      "peerDependencies": {
+        "@firebase/app-compat": "0.x"
+      }
+    },
+    "node_modules/@firebase/firestore-types": {
+      "version": "3.0.3",
+      "resolved": "https://registry.npmjs.org/@firebase/firestore-types/-/firestore-types-3.0.3.tgz",
+      "integrity": "sha512-hD2jGdiWRxB/eZWF89xcK9gF8wvENDJkzpVFb4aGkzfEaKxVRD1kjz1t1Wj8VZEp2LCB53Yx1zD8mrhQu87R6Q==",
+      "peerDependencies": {
+        "@firebase/app-types": "0.x",
+        "@firebase/util": "1.x"
+      }
+    },
+    "node_modules/@firebase/functions": {
+      "version": "0.12.3",
+      "resolved": "https://registry.npmjs.org/@firebase/functions/-/functions-0.12.3.tgz",
+      "integrity": "sha512-Wv7JZMUkKLb1goOWRtsu3t7m97uK6XQvjQLPvn8rncY91+VgdU72crqnaYCDI/ophNuBEmuK8mn0/pAnjUeA6A==",
+      "dependencies": {
+        "@firebase/app-check-interop-types": "0.3.3",
+        "@firebase/auth-interop-types": "0.2.4",
+        "@firebase/component": "0.6.13",
+        "@firebase/messaging-interop-types": "0.2.3",
+        "@firebase/util": "1.11.0",
+        "tslib": "^2.1.0"
+      },
+      "engines": {
+        "node": ">=18.0.0"
+      },
+      "peerDependencies": {
+        "@firebase/app": "0.x"
+      }
+    },
+    "node_modules/@firebase/functions-compat": {
+      "version": "0.3.20",
+      "resolved": "https://registry.npmjs.org/@firebase/functions-compat/-/functions-compat-0.3.20.tgz",
+      "integrity": "sha512-iIudmYDAML6n3c7uXO2YTlzra2/J6lnMzmJTXNthvrKVMgNMaseNoQP1wKfchK84hMuSF8EkM4AvufwbJ+Juew==",
+      "dependencies": {
+        "@firebase/component": "0.6.13",
+        "@firebase/functions": "0.12.3",
+        "@firebase/functions-types": "0.6.3",
+        "@firebase/util": "1.11.0",
+        "tslib": "^2.1.0"
+      },
+      "engines": {
+        "node": ">=18.0.0"
+      },
+      "peerDependencies": {
+        "@firebase/app-compat": "0.x"
+      }
+    },
+    "node_modules/@firebase/functions-types": {
+      "version": "0.6.3",
+      "resolved": "https://registry.npmjs.org/@firebase/functions-types/-/functions-types-0.6.3.tgz",
+      "integrity": "sha512-EZoDKQLUHFKNx6VLipQwrSMh01A1SaL3Wg6Hpi//x6/fJ6Ee4hrAeswK99I5Ht8roiniKHw4iO0B1Oxj5I4plg=="
+    },
+    "node_modules/@firebase/installations": {
+      "version": "0.6.13",
+      "resolved": "https://registry.npmjs.org/@firebase/installations/-/installations-0.6.13.tgz",
+      "integrity": "sha512-6ZpkUiaygPFwgVneYxuuOuHnSPnTA4KefLEaw/sKk/rNYgC7X6twaGfYb0sYLpbi9xV4i5jXsqZ3WO+yaguNgg==",
+      "dependencies": {
+        "@firebase/component": "0.6.13",
+        "@firebase/util": "1.11.0",
+        "idb": "7.1.1",
+        "tslib": "^2.1.0"
+      },
+      "peerDependencies": {
+        "@firebase/app": "0.x"
+      }
+    },
+    "node_modules/@firebase/installations-compat": {
+      "version": "0.2.13",
+      "resolved": "https://registry.npmjs.org/@firebase/installations-compat/-/installations-compat-0.2.13.tgz",
+      "integrity": "sha512-f/o6MqCI7LD/ulY9gvgkv6w5k6diaReD8BFHd/y/fEdpsXmFWYS/g28GXCB72bRVBOgPpkOUNl+VsMvDwlRKmw==",
+      "dependencies": {
+        "@firebase/component": "0.6.13",
+        "@firebase/installations": "0.6.13",
+        "@firebase/installations-types": "0.5.3",
+        "@firebase/util": "1.11.0",
+        "tslib": "^2.1.0"
+      },
+      "peerDependencies": {
+        "@firebase/app-compat": "0.x"
+      }
+    },
+    "node_modules/@firebase/installations-types": {
+      "version": "0.5.3",
+      "resolved": "https://registry.npmjs.org/@firebase/installations-types/-/installations-types-0.5.3.tgz",
+      "integrity": "sha512-2FJI7gkLqIE0iYsNQ1P751lO3hER+Umykel+TkLwHj6plzWVxqvfclPUZhcKFVQObqloEBTmpi2Ozn7EkCABAA==",
+      "peerDependencies": {
+        "@firebase/app-types": "0.x"
+      }
+    },
+    "node_modules/@firebase/logger": {
+      "version": "0.4.4",
+      "resolved": "https://registry.npmjs.org/@firebase/logger/-/logger-0.4.4.tgz",
+      "integrity": "sha512-mH0PEh1zoXGnaR8gD1DeGeNZtWFKbnz9hDO91dIml3iou1gpOnLqXQ2dJfB71dj6dpmUjcQ6phY3ZZJbjErr9g==",
+      "dependencies": {
+        "tslib": "^2.1.0"
+      },
+      "engines": {
+        "node": ">=18.0.0"
+      }
+    },
+    "node_modules/@firebase/messaging": {
+      "version": "0.12.17",
+      "resolved": "https://registry.npmjs.org/@firebase/messaging/-/messaging-0.12.17.tgz",
+      "integrity": "sha512-W3CnGhTm6Nx8XGb6E5/+jZTuxX/EK8Vur4QXvO1DwZta/t0xqWMRgO9vNsZFMYBqFV4o3j4F9qK/iddGYwWS6g==",
+      "dependencies": {
+        "@firebase/component": "0.6.13",
+        "@firebase/installations": "0.6.13",
+        "@firebase/messaging-interop-types": "0.2.3",
+        "@firebase/util": "1.11.0",
+        "idb": "7.1.1",
+        "tslib": "^2.1.0"
+      },
+      "peerDependencies": {
+        "@firebase/app": "0.x"
+      }
+    },
+    "node_modules/@firebase/messaging-compat": {
+      "version": "0.2.17",
+      "resolved": "https://registry.npmjs.org/@firebase/messaging-compat/-/messaging-compat-0.2.17.tgz",
+      "integrity": "sha512-5Q+9IG7FuedusdWHVQRjpA3OVD9KUWp/IPegcv0s5qSqRLBjib7FlAeWxN+VL0Ew43tuPJBY2HKhEecuizmO1Q==",
+      "dependencies": {
+        "@firebase/component": "0.6.13",
+        "@firebase/messaging": "0.12.17",
+        "@firebase/util": "1.11.0",
+        "tslib": "^2.1.0"
+      },
+      "peerDependencies": {
+        "@firebase/app-compat": "0.x"
+      }
+    },
+    "node_modules/@firebase/messaging-interop-types": {
+      "version": "0.2.3",
+      "resolved": "https://registry.npmjs.org/@firebase/messaging-interop-types/-/messaging-interop-types-0.2.3.tgz",
+      "integrity": "sha512-xfzFaJpzcmtDjycpDeCUj0Ge10ATFi/VHVIvEEjDNc3hodVBQADZ7BWQU7CuFpjSHE+eLuBI13z5F/9xOoGX8Q=="
+    },
+    "node_modules/@firebase/performance": {
+      "version": "0.7.1",
+      "resolved": "https://registry.npmjs.org/@firebase/performance/-/performance-0.7.1.tgz",
+      "integrity": "sha512-SkEUurawojCjav2V2AXo6BQLDtv02NxgXPLCiAvrkn95IAKI4W/UbLKYQvMbEez/nqvmnucLyklcMlB0Q5a1iw==",
+      "dependencies": {
+        "@firebase/component": "0.6.13",
+        "@firebase/installations": "0.6.13",
+        "@firebase/logger": "0.4.4",
+        "@firebase/util": "1.11.0",
+        "tslib": "^2.1.0",
+        "web-vitals": "^4.2.4"
+      },
+      "peerDependencies": {
+        "@firebase/app": "0.x"
+      }
+    },
+    "node_modules/@firebase/performance-compat": {
+      "version": "0.2.14",
+      "resolved": "https://registry.npmjs.org/@firebase/performance-compat/-/performance-compat-0.2.14.tgz",
+      "integrity": "sha512-/crPg0fDqHIx+FjFoEqWxNp+lJSF40ZG7x43AAJGRaUaWLJDncQm3UJB5/mABaRZb7obs1CQAcRtd4phZFkmZg==",
+      "dependencies": {
+        "@firebase/component": "0.6.13",
+        "@firebase/logger": "0.4.4",
+        "@firebase/performance": "0.7.1",
+        "@firebase/performance-types": "0.2.3",
+        "@firebase/util": "1.11.0",
+        "tslib": "^2.1.0"
+      },
+      "peerDependencies": {
+        "@firebase/app-compat": "0.x"
+      }
+    },
+    "node_modules/@firebase/performance-types": {
+      "version": "0.2.3",
+      "resolved": "https://registry.npmjs.org/@firebase/performance-types/-/performance-types-0.2.3.tgz",
+      "integrity": "sha512-IgkyTz6QZVPAq8GSkLYJvwSLr3LS9+V6vNPQr0x4YozZJiLF5jYixj0amDtATf1X0EtYHqoPO48a9ija8GocxQ=="
+    },
+    "node_modules/@firebase/remote-config": {
+      "version": "0.6.0",
+      "resolved": "https://registry.npmjs.org/@firebase/remote-config/-/remote-config-0.6.0.tgz",
+      "integrity": "sha512-Yrk4l5+6FJLPHC6irNHMzgTtJ3NfHXlAXVChCBdNFtgmzyGmufNs/sr8oA0auEfIJ5VpXCaThRh3P4OdQxiAlQ==",
+      "dependencies": {
+        "@firebase/component": "0.6.13",
+        "@firebase/installations": "0.6.13",
+        "@firebase/logger": "0.4.4",
+        "@firebase/util": "1.11.0",
+        "tslib": "^2.1.0"
+      },
+      "peerDependencies": {
+        "@firebase/app": "0.x"
+      }
+    },
+    "node_modules/@firebase/remote-config-compat": {
+      "version": "0.2.13",
+      "resolved": "https://registry.npmjs.org/@firebase/remote-config-compat/-/remote-config-compat-0.2.13.tgz",
+      "integrity": "sha512-UmHoO7TxAEJPIZf8e1Hy6CeFGMeyjqSCpgoBkQZYXFI2JHhzxIyDpr8jVKJJN1dmAePKZ5EX7dC13CmcdTOl7Q==",
+      "dependencies": {
+        "@firebase/component": "0.6.13",
+        "@firebase/logger": "0.4.4",
+        "@firebase/remote-config": "0.6.0",
+        "@firebase/remote-config-types": "0.4.0",
+        "@firebase/util": "1.11.0",
+        "tslib": "^2.1.0"
+      },
+      "peerDependencies": {
+        "@firebase/app-compat": "0.x"
+      }
+    },
+    "node_modules/@firebase/remote-config-types": {
+      "version": "0.4.0",
+      "resolved": "https://registry.npmjs.org/@firebase/remote-config-types/-/remote-config-types-0.4.0.tgz",
+      "integrity": "sha512-7p3mRE/ldCNYt8fmWMQ/MSGRmXYlJ15Rvs9Rk17t8p0WwZDbeK7eRmoI1tvCPaDzn9Oqh+yD6Lw+sGLsLg4kKg=="
+    },
+    "node_modules/@firebase/storage": {
+      "version": "0.13.7",
+      "resolved": "https://registry.npmjs.org/@firebase/storage/-/storage-0.13.7.tgz",
+      "integrity": "sha512-FkRyc24rK+Y6EaQ1tYFm3TevBnnfSNA0VyTfew2hrYyL/aYfatBg7HOgktUdB4kWMHNA9VoTotzZTGoLuK92wg==",
+      "dependencies": {
+        "@firebase/component": "0.6.13",
+        "@firebase/util": "1.11.0",
+        "tslib": "^2.1.0"
+      },
+      "engines": {
+        "node": ">=18.0.0"
+      },
+      "peerDependencies": {
+        "@firebase/app": "0.x"
+      }
+    },
+    "node_modules/@firebase/storage-compat": {
+      "version": "0.3.17",
+      "resolved": "https://registry.npmjs.org/@firebase/storage-compat/-/storage-compat-0.3.17.tgz",
+      "integrity": "sha512-CBlODWEZ5b6MJWVh21VZioxwxNwVfPA9CAdsk+ZgVocJQQbE2oDW1XJoRcgthRY1HOitgbn4cVrM+NlQtuUYhw==",
+      "dependencies": {
+        "@firebase/component": "0.6.13",
+        "@firebase/storage": "0.13.7",
+        "@firebase/storage-types": "0.8.3",
+        "@firebase/util": "1.11.0",
+        "tslib": "^2.1.0"
+      },
+      "engines": {
+        "node": ">=18.0.0"
+      },
+      "peerDependencies": {
+        "@firebase/app-compat": "0.x"
+      }
+    },
+    "node_modules/@firebase/storage-types": {
+      "version": "0.8.3",
+      "resolved": "https://registry.npmjs.org/@firebase/storage-types/-/storage-types-0.8.3.tgz",
+      "integrity": "sha512-+Muk7g9uwngTpd8xn9OdF/D48uiQ7I1Fae7ULsWPuKoCH3HU7bfFPhxtJYzyhjdniowhuDpQcfPmuNRAqZEfvg==",
+      "peerDependencies": {
+        "@firebase/app-types": "0.x",
+        "@firebase/util": "1.x"
+      }
+    },
+    "node_modules/@firebase/util": {
+      "version": "1.11.0",
+      "resolved": "https://registry.npmjs.org/@firebase/util/-/util-1.11.0.tgz",
+      "integrity": "sha512-PzSrhIr++KI6y4P6C/IdgBNMkEx0Ex6554/cYd0Hm+ovyFSJtJXqb/3OSIdnBoa2cpwZT1/GW56EmRc5qEc5fQ==",
+      "hasInstallScript": true,
+      "dependencies": {
+        "tslib": "^2.1.0"
+      },
+      "engines": {
+        "node": ">=18.0.0"
+      }
+    },
+    "node_modules/@firebase/vertexai": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/@firebase/vertexai/-/vertexai-1.1.0.tgz",
+      "integrity": "sha512-K8CgIFKJrfrf5lYhKnDXOu08FEmIzVExK+ApUZx4Bw2GAmLEA3wDVrsjuupuvpXZSp8QlzvEiXwqshqqc4v0pA==",
+      "dependencies": {
+        "@firebase/app-check-interop-types": "0.3.3",
+        "@firebase/component": "0.6.13",
+        "@firebase/logger": "0.4.4",
+        "@firebase/util": "1.11.0",
+        "tslib": "^2.1.0"
+      },
+      "engines": {
+        "node": ">=18.0.0"
+      },
+      "peerDependencies": {
+        "@firebase/app": "0.x",
+        "@firebase/app-types": "0.x"
+      }
+    },
+    "node_modules/@firebase/webchannel-wrapper": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmjs.org/@firebase/webchannel-wrapper/-/webchannel-wrapper-1.0.3.tgz",
+      "integrity": "sha512-2xCRM9q9FlzGZCdgDMJwc0gyUkWFtkosy7Xxr6sFgQwn+wMNIWd7xIvYNauU1r64B5L5rsGKy/n9TKJ0aAFeqQ=="
+    },
+    "node_modules/@grpc/grpc-js": {
+      "version": "1.9.15",
+      "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.9.15.tgz",
+      "integrity": "sha512-nqE7Hc0AzI+euzUwDAy0aY5hCp10r734gMGRdU+qOPX0XSceI2ULrcXB5U2xSc5VkWwalCj4M7GzCAygZl2KoQ==",
+      "dependencies": {
+        "@grpc/proto-loader": "^0.7.8",
+        "@types/node": ">=12.12.47"
+      },
+      "engines": {
+        "node": "^8.13.0 || >=10.10.0"
+      }
+    },
+    "node_modules/@grpc/proto-loader": {
+      "version": "0.7.13",
+      "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.7.13.tgz",
+      "integrity": "sha512-AiXO/bfe9bmxBjxxtYxFAXGZvMaN5s8kO+jBHAJCON8rJoB5YS/D6X7ZNc6XQkuHNmyl4CYaMI1fJ/Gn27RGGw==",
+      "dependencies": {
+        "lodash.camelcase": "^4.3.0",
+        "long": "^5.0.0",
+        "protobufjs": "^7.2.5",
+        "yargs": "^17.7.2"
+      },
+      "bin": {
+        "proto-loader-gen-types": "build/bin/proto-loader-gen-types.js"
+      },
+      "engines": {
+        "node": ">=6"
+      }
+    },
+    "node_modules/@headlessui/vue": {
+      "version": "1.7.23",
+      "resolved": "https://registry.npmjs.org/@headlessui/vue/-/vue-1.7.23.tgz",
+      "integrity": "sha512-JzdCNqurrtuu0YW6QaDtR2PIYCKPUWq28csDyMvN4zmGccmE7lz40Is6hc3LA4HFeCI7sekZ/PQMTNmn9I/4Wg==",
+      "dependencies": {
+        "@tanstack/vue-virtual": "^3.0.0-beta.60"
+      },
+      "engines": {
+        "node": ">=10"
+      },
+      "peerDependencies": {
+        "vue": "^3.2.0"
+      }
+    },
+    "node_modules/@heroicons/vue": {
+      "version": "2.2.0",
+      "resolved": "https://registry.npmjs.org/@heroicons/vue/-/vue-2.2.0.tgz",
+      "integrity": "sha512-G3dbSxoeEKqbi/DFalhRxJU4mTXJn7GwZ7ae8NuEQzd1bqdd0jAbdaBZlHPcvPD2xI1iGzNVB4k20Un2AguYPw==",
+      "peerDependencies": {
+        "vue": ">= 3"
+      }
+    },
+    "node_modules/@jridgewell/sourcemap-codec": {
+      "version": "1.5.0",
+      "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz",
+      "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ=="
+    },
+    "node_modules/@protobufjs/aspromise": {
+      "version": "1.1.2",
+      "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz",
+      "integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ=="
+    },
+    "node_modules/@protobufjs/base64": {
+      "version": "1.1.2",
+      "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz",
+      "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg=="
+    },
+    "node_modules/@protobufjs/codegen": {
+      "version": "2.0.4",
+      "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.4.tgz",
+      "integrity": "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg=="
+    },
+    "node_modules/@protobufjs/eventemitter": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz",
+      "integrity": "sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q=="
+    },
+    "node_modules/@protobufjs/fetch": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.0.tgz",
+      "integrity": "sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==",
+      "dependencies": {
+        "@protobufjs/aspromise": "^1.1.1",
+        "@protobufjs/inquire": "^1.1.0"
+      }
+    },
+    "node_modules/@protobufjs/float": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz",
+      "integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ=="
+    },
+    "node_modules/@protobufjs/inquire": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.0.tgz",
+      "integrity": "sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q=="
+    },
+    "node_modules/@protobufjs/path": {
+      "version": "1.1.2",
+      "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz",
+      "integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA=="
+    },
+    "node_modules/@protobufjs/pool": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz",
+      "integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw=="
+    },
+    "node_modules/@protobufjs/utf8": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz",
+      "integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw=="
+    },
+    "node_modules/@rollup/rollup-android-arm-eabi": {
+      "version": "4.36.0",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.36.0.tgz",
+      "integrity": "sha512-jgrXjjcEwN6XpZXL0HUeOVGfjXhPyxAbbhD0BlXUB+abTOpbPiN5Wb3kOT7yb+uEtATNYF5x5gIfwutmuBA26w==",
+      "cpu": [
+        "arm"
+      ],
+      "optional": true,
+      "os": [
+        "android"
+      ]
+    },
+    "node_modules/@rollup/rollup-android-arm64": {
+      "version": "4.36.0",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.36.0.tgz",
+      "integrity": "sha512-NyfuLvdPdNUfUNeYKUwPwKsE5SXa2J6bCt2LdB/N+AxShnkpiczi3tcLJrm5mA+eqpy0HmaIY9F6XCa32N5yzg==",
+      "cpu": [
+        "arm64"
+      ],
+      "optional": true,
+      "os": [
+        "android"
+      ]
+    },
+    "node_modules/@rollup/rollup-darwin-arm64": {
+      "version": "4.36.0",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.36.0.tgz",
+      "integrity": "sha512-JQ1Jk5G4bGrD4pWJQzWsD8I1n1mgPXq33+/vP4sk8j/z/C2siRuxZtaUA7yMTf71TCZTZl/4e1bfzwUmFb3+rw==",
+      "cpu": [
+        "arm64"
+      ],
+      "optional": true,
+      "os": [
+        "darwin"
+      ]
+    },
+    "node_modules/@rollup/rollup-darwin-x64": {
+      "version": "4.36.0",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.36.0.tgz",
+      "integrity": "sha512-6c6wMZa1lrtiRsbDziCmjE53YbTkxMYhhnWnSW8R/yqsM7a6mSJ3uAVT0t8Y/DGt7gxUWYuFM4bwWk9XCJrFKA==",
+      "cpu": [
+        "x64"
+      ],
+      "optional": true,
+      "os": [
+        "darwin"
+      ]
+    },
+    "node_modules/@rollup/rollup-freebsd-arm64": {
+      "version": "4.36.0",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.36.0.tgz",
+      "integrity": "sha512-KXVsijKeJXOl8QzXTsA+sHVDsFOmMCdBRgFmBb+mfEb/7geR7+C8ypAml4fquUt14ZyVXaw2o1FWhqAfOvA4sg==",
+      "cpu": [
+        "arm64"
+      ],
+      "optional": true,
+      "os": [
+        "freebsd"
+      ]
+    },
+    "node_modules/@rollup/rollup-freebsd-x64": {
+      "version": "4.36.0",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.36.0.tgz",
+      "integrity": "sha512-dVeWq1ebbvByI+ndz4IJcD4a09RJgRYmLccwlQ8bPd4olz3Y213uf1iwvc7ZaxNn2ab7bjc08PrtBgMu6nb4pQ==",
+      "cpu": [
+        "x64"
+      ],
+      "optional": true,
+      "os": [
+        "freebsd"
+      ]
+    },
+    "node_modules/@rollup/rollup-linux-arm-gnueabihf": {
+      "version": "4.36.0",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.36.0.tgz",
+      "integrity": "sha512-bvXVU42mOVcF4le6XSjscdXjqx8okv4n5vmwgzcmtvFdifQ5U4dXFYaCB87namDRKlUL9ybVtLQ9ztnawaSzvg==",
+      "cpu": [
+        "arm"
+      ],
+      "optional": true,
+      "os": [
+        "linux"
+      ]
+    },
+    "node_modules/@rollup/rollup-linux-arm-musleabihf": {
+      "version": "4.36.0",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.36.0.tgz",
+      "integrity": "sha512-JFIQrDJYrxOnyDQGYkqnNBtjDwTgbasdbUiQvcU8JmGDfValfH1lNpng+4FWlhaVIR4KPkeddYjsVVbmJYvDcg==",
+      "cpu": [
+        "arm"
+      ],
+      "optional": true,
+      "os": [
+        "linux"
+      ]
+    },
+    "node_modules/@rollup/rollup-linux-arm64-gnu": {
+      "version": "4.36.0",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.36.0.tgz",
+      "integrity": "sha512-KqjYVh3oM1bj//5X7k79PSCZ6CvaVzb7Qs7VMWS+SlWB5M8p3FqufLP9VNp4CazJ0CsPDLwVD9r3vX7Ci4J56A==",
+      "cpu": [
+        "arm64"
+      ],
+      "optional": true,
+      "os": [
+        "linux"
+      ]
+    },
+    "node_modules/@rollup/rollup-linux-arm64-musl": {
+      "version": "4.36.0",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.36.0.tgz",
+      "integrity": "sha512-QiGnhScND+mAAtfHqeT+cB1S9yFnNQ/EwCg5yE3MzoaZZnIV0RV9O5alJAoJKX/sBONVKeZdMfO8QSaWEygMhw==",
+      "cpu": [
+        "arm64"
+      ],
+      "optional": true,
+      "os": [
+        "linux"
+      ]
+    },
+    "node_modules/@rollup/rollup-linux-loongarch64-gnu": {
+      "version": "4.36.0",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.36.0.tgz",
+      "integrity": "sha512-1ZPyEDWF8phd4FQtTzMh8FQwqzvIjLsl6/84gzUxnMNFBtExBtpL51H67mV9xipuxl1AEAerRBgBwFNpkw8+Lg==",
+      "cpu": [
+        "loong64"
+      ],
+      "optional": true,
+      "os": [
+        "linux"
+      ]
+    },
+    "node_modules/@rollup/rollup-linux-powerpc64le-gnu": {
+      "version": "4.36.0",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.36.0.tgz",
+      "integrity": "sha512-VMPMEIUpPFKpPI9GZMhJrtu8rxnp6mJR3ZzQPykq4xc2GmdHj3Q4cA+7avMyegXy4n1v+Qynr9fR88BmyO74tg==",
+      "cpu": [
+        "ppc64"
+      ],
+      "optional": true,
+      "os": [
+        "linux"
+      ]
+    },
+    "node_modules/@rollup/rollup-linux-riscv64-gnu": {
+      "version": "4.36.0",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.36.0.tgz",
+      "integrity": "sha512-ttE6ayb/kHwNRJGYLpuAvB7SMtOeQnVXEIpMtAvx3kepFQeowVED0n1K9nAdraHUPJ5hydEMxBpIR7o4nrm8uA==",
+      "cpu": [
+        "riscv64"
+      ],
+      "optional": true,
+      "os": [
+        "linux"
+      ]
+    },
+    "node_modules/@rollup/rollup-linux-s390x-gnu": {
+      "version": "4.36.0",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.36.0.tgz",
+      "integrity": "sha512-4a5gf2jpS0AIe7uBjxDeUMNcFmaRTbNv7NxI5xOCs4lhzsVyGR/0qBXduPnoWf6dGC365saTiwag8hP1imTgag==",
+      "cpu": [
+        "s390x"
+      ],
+      "optional": true,
+      "os": [
+        "linux"
+      ]
+    },
+    "node_modules/@rollup/rollup-linux-x64-gnu": {
+      "version": "4.36.0",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.36.0.tgz",
+      "integrity": "sha512-5KtoW8UWmwFKQ96aQL3LlRXX16IMwyzMq/jSSVIIyAANiE1doaQsx/KRyhAvpHlPjPiSU/AYX/8m+lQ9VToxFQ==",
+      "cpu": [
+        "x64"
+      ],
+      "optional": true,
+      "os": [
+        "linux"
+      ]
+    },
+    "node_modules/@rollup/rollup-linux-x64-musl": {
+      "version": "4.36.0",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.36.0.tgz",
+      "integrity": "sha512-sycrYZPrv2ag4OCvaN5js+f01eoZ2U+RmT5as8vhxiFz+kxwlHrsxOwKPSA8WyS+Wc6Epid9QeI/IkQ9NkgYyQ==",
+      "cpu": [
+        "x64"
+      ],
+      "optional": true,
+      "os": [
+        "linux"
+      ]
+    },
+    "node_modules/@rollup/rollup-win32-arm64-msvc": {
+      "version": "4.36.0",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.36.0.tgz",
+      "integrity": "sha512-qbqt4N7tokFwwSVlWDsjfoHgviS3n/vZ8LK0h1uLG9TYIRuUTJC88E1xb3LM2iqZ/WTqNQjYrtmtGmrmmawB6A==",
+      "cpu": [
+        "arm64"
+      ],
+      "optional": true,
+      "os": [
+        "win32"
+      ]
+    },
+    "node_modules/@rollup/rollup-win32-ia32-msvc": {
+      "version": "4.36.0",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.36.0.tgz",
+      "integrity": "sha512-t+RY0JuRamIocMuQcfwYSOkmdX9dtkr1PbhKW42AMvaDQa+jOdpUYysroTF/nuPpAaQMWp7ye+ndlmmthieJrQ==",
+      "cpu": [
+        "ia32"
+      ],
+      "optional": true,
+      "os": [
+        "win32"
+      ]
+    },
+    "node_modules/@rollup/rollup-win32-x64-msvc": {
+      "version": "4.36.0",
+      "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.36.0.tgz",
+      "integrity": "sha512-aRXd7tRZkWLqGbChgcMMDEHjOKudo1kChb1Jt1IfR8cY/KIpgNviLeJy5FUb9IpSuQj8dU2fAYNMPW/hLKOSTw==",
+      "cpu": [
+        "x64"
+      ],
+      "optional": true,
+      "os": [
+        "win32"
+      ]
+    },
+    "node_modules/@tailwindcss/node": {
+      "version": "4.0.14",
+      "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.0.14.tgz",
+      "integrity": "sha512-Ux9NbFkKWYE4rfUFz6M5JFLs/GEYP6ysxT8uSyPn6aTbh2K3xDE1zz++eVK4Vwx799fzMF8CID9sdHn4j/Ab8w==",
+      "dependencies": {
+        "enhanced-resolve": "^5.18.1",
+        "jiti": "^2.4.2",
+        "tailwindcss": "4.0.14"
+      }
+    },
+    "node_modules/@tailwindcss/oxide": {
+      "version": "4.0.14",
+      "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.0.14.tgz",
+      "integrity": "sha512-M8VCNyO/NBi5vJ2cRcI9u8w7Si+i76a7o1vveoGtbbjpEYJZYiyc7f2VGps/DqawO56l3tImIbq2OT/533jcrA==",
+      "engines": {
+        "node": ">= 10"
+      },
+      "optionalDependencies": {
+        "@tailwindcss/oxide-android-arm64": "4.0.14",
+        "@tailwindcss/oxide-darwin-arm64": "4.0.14",
+        "@tailwindcss/oxide-darwin-x64": "4.0.14",
+        "@tailwindcss/oxide-freebsd-x64": "4.0.14",
+        "@tailwindcss/oxide-linux-arm-gnueabihf": "4.0.14",
+        "@tailwindcss/oxide-linux-arm64-gnu": "4.0.14",
+        "@tailwindcss/oxide-linux-arm64-musl": "4.0.14",
+        "@tailwindcss/oxide-linux-x64-gnu": "4.0.14",
+        "@tailwindcss/oxide-linux-x64-musl": "4.0.14",
+        "@tailwindcss/oxide-win32-arm64-msvc": "4.0.14",
+        "@tailwindcss/oxide-win32-x64-msvc": "4.0.14"
+      }
+    },
+    "node_modules/@tailwindcss/oxide-android-arm64": {
+      "version": "4.0.14",
+      "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.0.14.tgz",
+      "integrity": "sha512-VBFKC2rFyfJ5J8lRwjy6ub3rgpY186kAcYgiUr8ArR8BAZzMruyeKJ6mlsD22Zp5ZLcPW/FXMasJiJBx0WsdQg==",
+      "cpu": [
+        "arm64"
+      ],
+      "optional": true,
+      "os": [
+        "android"
+      ],
+      "engines": {
+        "node": ">= 10"
+      }
+    },
+    "node_modules/@tailwindcss/oxide-darwin-arm64": {
+      "version": "4.0.14",
+      "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.0.14.tgz",
+      "integrity": "sha512-U3XOwLrefGr2YQZ9DXasDSNWGPZBCh8F62+AExBEDMLDfvLLgI/HDzY8Oq8p/JtqkAY38sWPOaNnRwEGKU5Zmg==",
+      "cpu": [
+        "arm64"
+      ],
+      "optional": true,
+      "os": [
+        "darwin"
+      ],
+      "engines": {
+        "node": ">= 10"
+      }
+    },
+    "node_modules/@tailwindcss/oxide-darwin-x64": {
+      "version": "4.0.14",
+      "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.0.14.tgz",
+      "integrity": "sha512-V5AjFuc3ndWGnOi1d379UsODb0TzAS2DYIP/lwEbfvafUaD2aNZIcbwJtYu2DQqO2+s/XBvDVA+w4yUyaewRwg==",
+      "cpu": [
+        "x64"
+      ],
+      "optional": true,
+      "os": [
+        "darwin"
+      ],
+      "engines": {
+        "node": ">= 10"
+      }
+    },
+    "node_modules/@tailwindcss/oxide-freebsd-x64": {
+      "version": "4.0.14",
+      "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.0.14.tgz",
+      "integrity": "sha512-tXvtxbaZfcPfqBwW3f53lTcyH6EDT+1eT7yabwcfcxTs+8yTPqxsDUhrqe9MrnEzpNkd+R/QAjJapfd4tjWdLg==",
+      "cpu": [
+        "x64"
+      ],
+      "optional": true,
+      "os": [
+        "freebsd"
+      ],
+      "engines": {
+        "node": ">= 10"
+      }
+    },
+    "node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": {
+      "version": "4.0.14",
+      "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.0.14.tgz",
+      "integrity": "sha512-cSeLNWWqIWeSTmBntQvyY2/2gcLX8rkPFfDDTQVF8qbRcRMVPLxBvFVJyfSAYRNch6ZyVH2GI6dtgALOBDpdNA==",
+      "cpu": [
+        "arm"
+      ],
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">= 10"
+      }
+    },
+    "node_modules/@tailwindcss/oxide-linux-arm64-gnu": {
+      "version": "4.0.14",
+      "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.0.14.tgz",
+      "integrity": "sha512-bwDWLBalXFMDItcSXzFk6y7QKvj6oFlaY9vM+agTlwFL1n1OhDHYLZkSjaYsh6KCeG0VB0r7H8PUJVOM1LRZyg==",
+      "cpu": [
+        "arm64"
+      ],
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">= 10"
+      }
+    },
+    "node_modules/@tailwindcss/oxide-linux-arm64-musl": {
+      "version": "4.0.14",
+      "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.0.14.tgz",
+      "integrity": "sha512-gVkJdnR/L6iIcGYXx64HGJRmlme2FGr/aZH0W6u4A3RgPMAb+6ELRLi+UBiH83RXBm9vwCfkIC/q8T51h8vUJQ==",
+      "cpu": [
+        "arm64"
+      ],
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">= 10"
+      }
+    },
+    "node_modules/@tailwindcss/oxide-linux-x64-gnu": {
+      "version": "4.0.14",
+      "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.0.14.tgz",
+      "integrity": "sha512-EE+EQ+c6tTpzsg+LGO1uuusjXxYx0Q00JE5ubcIGfsogSKth8n8i2BcS2wYTQe4jXGs+BQs35l78BIPzgwLddw==",
+      "cpu": [
+        "x64"
+      ],
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">= 10"
+      }
+    },
+    "node_modules/@tailwindcss/oxide-linux-x64-musl": {
+      "version": "4.0.14",
+      "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.0.14.tgz",
+      "integrity": "sha512-KCCOzo+L6XPT0oUp2Jwh233ETRQ/F6cwUnMnR0FvMUCbkDAzHbcyOgpfuAtRa5HD0WbTbH4pVD+S0pn1EhNfbw==",
+      "cpu": [
+        "x64"
+      ],
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">= 10"
+      }
+    },
+    "node_modules/@tailwindcss/oxide-win32-arm64-msvc": {
+      "version": "4.0.14",
+      "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.0.14.tgz",
+      "integrity": "sha512-AHObFiFL9lNYcm3tZSPqa/cHGpM5wOrNmM2uOMoKppp+0Hom5uuyRh0QkOp7jftsHZdrZUpmoz0Mp6vhh2XtUg==",
+      "cpu": [
+        "arm64"
+      ],
+      "optional": true,
+      "os": [
+        "win32"
+      ],
+      "engines": {
+        "node": ">= 10"
+      }
+    },
+    "node_modules/@tailwindcss/oxide-win32-x64-msvc": {
+      "version": "4.0.14",
+      "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.0.14.tgz",
+      "integrity": "sha512-rNXXMDJfCJLw/ZaFTOLOHoGULxyXfh2iXTGiChFiYTSgKBKQHIGEpV0yn5N25WGzJJ+VBnRjHzlmDqRV+d//oQ==",
+      "cpu": [
+        "x64"
+      ],
+      "optional": true,
+      "os": [
+        "win32"
+      ],
+      "engines": {
+        "node": ">= 10"
+      }
+    },
+    "node_modules/@tailwindcss/vite": {
+      "version": "4.0.14",
+      "resolved": "https://registry.npmjs.org/@tailwindcss/vite/-/vite-4.0.14.tgz",
+      "integrity": "sha512-y69ztPTRFy+13EPS/7dEFVl7q2Goh1pQueVO8IfGeyqSpcx/joNJXFk0lLhMgUbF0VFJotwRSb9ZY7Xoq3r26Q==",
+      "dependencies": {
+        "@tailwindcss/node": "4.0.14",
+        "@tailwindcss/oxide": "4.0.14",
+        "lightningcss": "1.29.2",
+        "tailwindcss": "4.0.14"
+      },
+      "peerDependencies": {
+        "vite": "^5.2.0 || ^6"
+      }
+    },
+    "node_modules/@tanstack/virtual-core": {
+      "version": "3.13.4",
+      "resolved": "https://registry.npmjs.org/@tanstack/virtual-core/-/virtual-core-3.13.4.tgz",
+      "integrity": "sha512-fNGO9fjjSLns87tlcto106enQQLycCKR4DPNpgq3djP5IdcPFdPAmaKjsgzIeRhH7hWrELgW12hYnRthS5kLUw==",
+      "funding": {
+        "type": "github",
+        "url": "https://github.com/sponsors/tannerlinsley"
+      }
+    },
+    "node_modules/@tanstack/vue-virtual": {
+      "version": "3.13.4",
+      "resolved": "https://registry.npmjs.org/@tanstack/vue-virtual/-/vue-virtual-3.13.4.tgz",
+      "integrity": "sha512-1fPrd3hE1SS4R/9JbX1AlzueY4duCK7ixuLcMW5GMnk9N6WbLo9MioNKiv22V+UaXKOLNy8tLdzT8NYerOFTOQ==",
+      "dependencies": {
+        "@tanstack/virtual-core": "3.13.4"
+      },
+      "funding": {
+        "type": "github",
+        "url": "https://github.com/sponsors/tannerlinsley"
+      },
+      "peerDependencies": {
+        "vue": "^2.7.0 || ^3.0.0"
+      }
+    },
+    "node_modules/@types/estree": {
+      "version": "1.0.6",
+      "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz",
+      "integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw=="
+    },
+    "node_modules/@types/node": {
+      "version": "22.13.10",
+      "resolved": "https://registry.npmjs.org/@types/node/-/node-22.13.10.tgz",
+      "integrity": "sha512-I6LPUvlRH+O6VRUqYOcMudhaIdUVWfsjnZavnsraHvpBwaEyMN29ry+0UVJhImYL16xsscu0aske3yA+uPOWfw==",
+      "dependencies": {
+        "undici-types": "~6.20.0"
+      }
+    },
+    "node_modules/@vitejs/plugin-vue": {
+      "version": "5.2.3",
+      "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-5.2.3.tgz",
+      "integrity": "sha512-IYSLEQj4LgZZuoVpdSUCw3dIynTWQgPlaRP6iAvMle4My0HdYwr5g5wQAfwOeHQBmYwEkqF70nRpSilr6PoUDg==",
+      "dev": true,
+      "engines": {
+        "node": "^18.0.0 || >=20.0.0"
+      },
+      "peerDependencies": {
+        "vite": "^5.0.0 || ^6.0.0",
+        "vue": "^3.2.25"
+      }
+    },
+    "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==",
+      "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/compiler-dom": {
+      "version": "3.5.13",
+      "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.13.tgz",
+      "integrity": "sha512-ZOJ46sMOKUjO3e94wPdCzQ6P1Lx/vhp2RSvfaab88Ajexs0AHeV0uasYhi99WPaogmBlRHNRuly8xV75cNTMDA==",
+      "dependencies": {
+        "@vue/compiler-core": "3.5.13",
+        "@vue/shared": "3.5.13"
+      }
+    },
+    "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==",
+      "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/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==",
+      "dependencies": {
+        "@vue/compiler-dom": "3.5.13",
+        "@vue/shared": "3.5.13"
+      }
+    },
+    "node_modules/@vue/devtools-api": {
+      "version": "6.6.4",
+      "resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-6.6.4.tgz",
+      "integrity": "sha512-sGhTPMuXqZ1rVOk32RylztWkfXTRhuS7vgAKv0zjqk8gbsHkJ7xfFf+jbySxt7tWObEJwyKaHMikV/WGDiQm8g=="
+    },
+    "node_modules/@vue/reactivity": {
+      "version": "3.5.13",
+      "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.13.tgz",
+      "integrity": "sha512-NaCwtw8o48B9I6L1zl2p41OHo/2Z4wqYGGIK1Khu5T7yxrn+ATOixn/Udn2m+6kZKB/J7cuT9DbWWhRxqixACg==",
+      "dependencies": {
+        "@vue/shared": "3.5.13"
+      }
+    },
+    "node_modules/@vue/runtime-core": {
+      "version": "3.5.13",
+      "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.5.13.tgz",
+      "integrity": "sha512-Fj4YRQ3Az0WTZw1sFe+QDb0aXCerigEpw418pw1HBUKFtnQHWzwojaukAs2X/c9DQz4MQ4bsXTGlcpGxU/RCIw==",
+      "dependencies": {
+        "@vue/reactivity": "3.5.13",
+        "@vue/shared": "3.5.13"
+      }
+    },
+    "node_modules/@vue/runtime-dom": {
+      "version": "3.5.13",
+      "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.5.13.tgz",
+      "integrity": "sha512-dLaj94s93NYLqjLiyFzVs9X6dWhTdAlEAciC3Moq7gzAc13VJUdCnjjRurNM6uTLFATRHexHCTu/Xp3eW6yoog==",
+      "dependencies": {
+        "@vue/reactivity": "3.5.13",
+        "@vue/runtime-core": "3.5.13",
+        "@vue/shared": "3.5.13",
+        "csstype": "^3.1.3"
+      }
+    },
+    "node_modules/@vue/server-renderer": {
+      "version": "3.5.13",
+      "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.5.13.tgz",
+      "integrity": "sha512-wAi4IRJV/2SAW3htkTlB+dHeRmpTiVIK1OGLWV1yeStVSebSQQOwGwIq0D3ZIoBj2C2qpgz5+vX9iEBkTdk5YA==",
+      "dependencies": {
+        "@vue/compiler-ssr": "3.5.13",
+        "@vue/shared": "3.5.13"
+      },
+      "peerDependencies": {
+        "vue": "3.5.13"
+      }
+    },
+    "node_modules/@vue/shared": {
+      "version": "3.5.13",
+      "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.13.tgz",
+      "integrity": "sha512-/hnE/qP5ZoGpol0a5mDi45bOd7t3tjYJBjsgCsivow7D48cJeV5l05RD82lPqi7gRiphZM37rnhW1l6ZoCNNnQ=="
+    },
+    "node_modules/ansi-regex": {
+      "version": "5.0.1",
+      "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
+      "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/ansi-styles": {
+      "version": "4.3.0",
+      "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
+      "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
+      "dependencies": {
+        "color-convert": "^2.0.1"
+      },
+      "engines": {
+        "node": ">=8"
+      },
+      "funding": {
+        "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+      }
+    },
+    "node_modules/asynckit": {
+      "version": "0.4.0",
+      "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
+      "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",
+      "license": "MIT"
+    },
+    "node_modules/axios": {
+      "version": "1.9.0",
+      "resolved": "https://registry.npmjs.org/axios/-/axios-1.9.0.tgz",
+      "integrity": "sha512-re4CqKTJaURpzbLHtIi6XpDv20/CnpXOtjRY5/CU32L8gU8ek9UIivcfvSWvmKEngmVbrUtPpdDwWDWL7DNHvg==",
+      "license": "MIT",
+      "dependencies": {
+        "follow-redirects": "^1.15.6",
+        "form-data": "^4.0.0",
+        "proxy-from-env": "^1.1.0"
+      }
+    },
+    "node_modules/call-bind-apply-helpers": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
+      "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
+      "license": "MIT",
+      "dependencies": {
+        "es-errors": "^1.3.0",
+        "function-bind": "^1.1.2"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      }
+    },
+    "node_modules/cliui": {
+      "version": "8.0.1",
+      "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz",
+      "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==",
+      "dependencies": {
+        "string-width": "^4.2.0",
+        "strip-ansi": "^6.0.1",
+        "wrap-ansi": "^7.0.0"
+      },
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/color-convert": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
+      "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
+      "dependencies": {
+        "color-name": "~1.1.4"
+      },
+      "engines": {
+        "node": ">=7.0.0"
+      }
+    },
+    "node_modules/color-name": {
+      "version": "1.1.4",
+      "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
+      "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="
+    },
+    "node_modules/combined-stream": {
+      "version": "1.0.8",
+      "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
+      "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
+      "license": "MIT",
+      "dependencies": {
+        "delayed-stream": "~1.0.0"
+      },
+      "engines": {
+        "node": ">= 0.8"
+      }
+    },
+    "node_modules/csstype": {
+      "version": "3.1.3",
+      "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz",
+      "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw=="
+    },
+    "node_modules/delayed-stream": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
+      "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=0.4.0"
+      }
+    },
+    "node_modules/detect-libc": {
+      "version": "2.0.3",
+      "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.3.tgz",
+      "integrity": "sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==",
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/dunder-proto": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
+      "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
+      "license": "MIT",
+      "dependencies": {
+        "call-bind-apply-helpers": "^1.0.1",
+        "es-errors": "^1.3.0",
+        "gopd": "^1.2.0"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      }
+    },
+    "node_modules/emoji-regex": {
+      "version": "8.0.0",
+      "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
+      "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="
+    },
+    "node_modules/enhanced-resolve": {
+      "version": "5.18.1",
+      "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.1.tgz",
+      "integrity": "sha512-ZSW3ma5GkcQBIpwZTSRAI8N71Uuwgs93IezB7mf7R60tC8ZbJideoDNKjHn2O9KIlx6rkGTTEk1xUCK2E1Y2Yg==",
+      "dependencies": {
+        "graceful-fs": "^4.2.4",
+        "tapable": "^2.2.0"
+      },
+      "engines": {
+        "node": ">=10.13.0"
+      }
+    },
+    "node_modules/entities": {
+      "version": "4.5.0",
+      "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz",
+      "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==",
+      "engines": {
+        "node": ">=0.12"
+      },
+      "funding": {
+        "url": "https://github.com/fb55/entities?sponsor=1"
+      }
+    },
+    "node_modules/es-define-property": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
+      "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.4"
+      }
+    },
+    "node_modules/es-errors": {
+      "version": "1.3.0",
+      "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
+      "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.4"
+      }
+    },
+    "node_modules/es-object-atoms": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
+      "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==",
+      "license": "MIT",
+      "dependencies": {
+        "es-errors": "^1.3.0"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      }
+    },
+    "node_modules/es-set-tostringtag": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz",
+      "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==",
+      "license": "MIT",
+      "dependencies": {
+        "es-errors": "^1.3.0",
+        "get-intrinsic": "^1.2.6",
+        "has-tostringtag": "^1.0.2",
+        "hasown": "^2.0.2"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      }
+    },
+    "node_modules/esbuild": {
+      "version": "0.25.1",
+      "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.1.tgz",
+      "integrity": "sha512-BGO5LtrGC7vxnqucAe/rmvKdJllfGaYWdyABvyMoXQlfYMb2bbRuReWR5tEGE//4LcNJj9XrkovTqNYRFZHAMQ==",
+      "hasInstallScript": true,
+      "bin": {
+        "esbuild": "bin/esbuild"
+      },
+      "engines": {
+        "node": ">=18"
+      },
+      "optionalDependencies": {
+        "@esbuild/aix-ppc64": "0.25.1",
+        "@esbuild/android-arm": "0.25.1",
+        "@esbuild/android-arm64": "0.25.1",
+        "@esbuild/android-x64": "0.25.1",
+        "@esbuild/darwin-arm64": "0.25.1",
+        "@esbuild/darwin-x64": "0.25.1",
+        "@esbuild/freebsd-arm64": "0.25.1",
+        "@esbuild/freebsd-x64": "0.25.1",
+        "@esbuild/linux-arm": "0.25.1",
+        "@esbuild/linux-arm64": "0.25.1",
+        "@esbuild/linux-ia32": "0.25.1",
+        "@esbuild/linux-loong64": "0.25.1",
+        "@esbuild/linux-mips64el": "0.25.1",
+        "@esbuild/linux-ppc64": "0.25.1",
+        "@esbuild/linux-riscv64": "0.25.1",
+        "@esbuild/linux-s390x": "0.25.1",
+        "@esbuild/linux-x64": "0.25.1",
+        "@esbuild/netbsd-arm64": "0.25.1",
+        "@esbuild/netbsd-x64": "0.25.1",
+        "@esbuild/openbsd-arm64": "0.25.1",
+        "@esbuild/openbsd-x64": "0.25.1",
+        "@esbuild/sunos-x64": "0.25.1",
+        "@esbuild/win32-arm64": "0.25.1",
+        "@esbuild/win32-ia32": "0.25.1",
+        "@esbuild/win32-x64": "0.25.1"
+      }
+    },
+    "node_modules/escalade": {
+      "version": "3.2.0",
+      "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz",
+      "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==",
+      "engines": {
+        "node": ">=6"
+      }
+    },
+    "node_modules/estree-walker": {
+      "version": "2.0.2",
+      "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz",
+      "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="
+    },
+    "node_modules/faye-websocket": {
+      "version": "0.11.4",
+      "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.11.4.tgz",
+      "integrity": "sha512-CzbClwlXAuiRQAlUyfqPgvPoNKTckTPGfwZV4ZdAhVcP2lh9KUxJg2b5GkE7XbjKQ3YJnQ9z6D9ntLAlB+tP8g==",
+      "dependencies": {
+        "websocket-driver": ">=0.5.1"
+      },
+      "engines": {
+        "node": ">=0.8.0"
+      }
+    },
+    "node_modules/fdir": {
+      "version": "6.4.4",
+      "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.4.tgz",
+      "integrity": "sha512-1NZP+GK4GfuAv3PqKvxQRDMjdSRZjnkq7KfhlNrCNNlZ0ygQFpebfrnfnq/W7fpUnAv9aGWmY1zKx7FYL3gwhg==",
+      "license": "MIT",
+      "peerDependencies": {
+        "picomatch": "^3 || ^4"
+      },
+      "peerDependenciesMeta": {
+        "picomatch": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/firebase": {
+      "version": "11.4.0",
+      "resolved": "https://registry.npmjs.org/firebase/-/firebase-11.4.0.tgz",
+      "integrity": "sha512-Z6kwhWIPDgIm0+NUEQxwjH14hMP7t42WSFnf/78R0Vh59VovLYTOCTM3MIdY3jlSZ9uKz56FhXrvsNXNhAn/Xg==",
+      "dependencies": {
+        "@firebase/analytics": "0.10.12",
+        "@firebase/analytics-compat": "0.2.18",
+        "@firebase/app": "0.11.2",
+        "@firebase/app-check": "0.8.12",
+        "@firebase/app-check-compat": "0.3.19",
+        "@firebase/app-compat": "0.2.51",
+        "@firebase/app-types": "0.9.3",
+        "@firebase/auth": "1.9.1",
+        "@firebase/auth-compat": "0.5.19",
+        "@firebase/data-connect": "0.3.1",
+        "@firebase/database": "1.0.13",
+        "@firebase/database-compat": "2.0.4",
+        "@firebase/firestore": "4.7.9",
+        "@firebase/firestore-compat": "0.3.44",
+        "@firebase/functions": "0.12.3",
+        "@firebase/functions-compat": "0.3.20",
+        "@firebase/installations": "0.6.13",
+        "@firebase/installations-compat": "0.2.13",
+        "@firebase/messaging": "0.12.17",
+        "@firebase/messaging-compat": "0.2.17",
+        "@firebase/performance": "0.7.1",
+        "@firebase/performance-compat": "0.2.14",
+        "@firebase/remote-config": "0.6.0",
+        "@firebase/remote-config-compat": "0.2.13",
+        "@firebase/storage": "0.13.7",
+        "@firebase/storage-compat": "0.3.17",
+        "@firebase/util": "1.11.0",
+        "@firebase/vertexai": "1.1.0"
+      }
+    },
+    "node_modules/follow-redirects": {
+      "version": "1.15.9",
+      "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz",
+      "integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==",
+      "funding": [
+        {
+          "type": "individual",
+          "url": "https://github.com/sponsors/RubenVerborgh"
+        }
+      ],
+      "license": "MIT",
+      "engines": {
+        "node": ">=4.0"
+      },
+      "peerDependenciesMeta": {
+        "debug": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/form-data": {
+      "version": "4.0.2",
+      "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.2.tgz",
+      "integrity": "sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w==",
+      "license": "MIT",
+      "dependencies": {
+        "asynckit": "^0.4.0",
+        "combined-stream": "^1.0.8",
+        "es-set-tostringtag": "^2.1.0",
+        "mime-types": "^2.1.12"
+      },
+      "engines": {
+        "node": ">= 6"
+      }
+    },
+    "node_modules/fsevents": {
+      "version": "2.3.3",
+      "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
+      "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
+      "hasInstallScript": true,
+      "optional": true,
+      "os": [
+        "darwin"
+      ],
+      "engines": {
+        "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
+      }
+    },
+    "node_modules/function-bind": {
+      "version": "1.1.2",
+      "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
+      "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
+      "license": "MIT",
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
+    "node_modules/get-caller-file": {
+      "version": "2.0.5",
+      "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
+      "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==",
+      "engines": {
+        "node": "6.* || 8.* || >= 10.*"
+      }
+    },
+    "node_modules/get-intrinsic": {
+      "version": "1.3.0",
+      "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
+      "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
+      "license": "MIT",
+      "dependencies": {
+        "call-bind-apply-helpers": "^1.0.2",
+        "es-define-property": "^1.0.1",
+        "es-errors": "^1.3.0",
+        "es-object-atoms": "^1.1.1",
+        "function-bind": "^1.1.2",
+        "get-proto": "^1.0.1",
+        "gopd": "^1.2.0",
+        "has-symbols": "^1.1.0",
+        "hasown": "^2.0.2",
+        "math-intrinsics": "^1.1.0"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
+    "node_modules/get-proto": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
+      "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
+      "license": "MIT",
+      "dependencies": {
+        "dunder-proto": "^1.0.1",
+        "es-object-atoms": "^1.0.0"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      }
+    },
+    "node_modules/gopd": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
+      "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.4"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
+    "node_modules/graceful-fs": {
+      "version": "4.2.11",
+      "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
+      "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="
+    },
+    "node_modules/has-symbols": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
+      "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.4"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
+    "node_modules/has-tostringtag": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz",
+      "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==",
+      "license": "MIT",
+      "dependencies": {
+        "has-symbols": "^1.0.3"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
+    "node_modules/hasown": {
+      "version": "2.0.2",
+      "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
+      "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
+      "license": "MIT",
+      "dependencies": {
+        "function-bind": "^1.1.2"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      }
+    },
+    "node_modules/http-parser-js": {
+      "version": "0.5.9",
+      "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.9.tgz",
+      "integrity": "sha512-n1XsPy3rXVxlqxVioEWdC+0+M+SQw0DpJynwtOPo1X+ZlvdzTLtDBIJJlDQTnwZIFJrZSzSGmIOUdP8tu+SgLw=="
+    },
+    "node_modules/idb": {
+      "version": "7.1.1",
+      "resolved": "https://registry.npmjs.org/idb/-/idb-7.1.1.tgz",
+      "integrity": "sha512-gchesWBzyvGHRO9W8tzUWFDycow5gwjvFKfyV9FF32Y7F50yZMp7mP+T2mJIWFx49zicqyC4uefHM17o6xKIVQ=="
+    },
+    "node_modules/is-fullwidth-code-point": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
+      "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/jiti": {
+      "version": "2.4.2",
+      "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.4.2.tgz",
+      "integrity": "sha512-rg9zJN+G4n2nfJl5MW3BMygZX56zKPNVEYYqq7adpmMh4Jn2QNEwhvQlFy6jPVdcod7txZtKHWnyZiA3a0zP7A==",
+      "bin": {
+        "jiti": "lib/jiti-cli.mjs"
+      }
+    },
+    "node_modules/lightningcss": {
+      "version": "1.29.2",
+      "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.29.2.tgz",
+      "integrity": "sha512-6b6gd/RUXKaw5keVdSEtqFVdzWnU5jMxTUjA2bVcMNPLwSQ08Sv/UodBVtETLCn7k4S1Ibxwh7k68IwLZPgKaA==",
+      "dependencies": {
+        "detect-libc": "^2.0.3"
+      },
+      "engines": {
+        "node": ">= 12.0.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/parcel"
+      },
+      "optionalDependencies": {
+        "lightningcss-darwin-arm64": "1.29.2",
+        "lightningcss-darwin-x64": "1.29.2",
+        "lightningcss-freebsd-x64": "1.29.2",
+        "lightningcss-linux-arm-gnueabihf": "1.29.2",
+        "lightningcss-linux-arm64-gnu": "1.29.2",
+        "lightningcss-linux-arm64-musl": "1.29.2",
+        "lightningcss-linux-x64-gnu": "1.29.2",
+        "lightningcss-linux-x64-musl": "1.29.2",
+        "lightningcss-win32-arm64-msvc": "1.29.2",
+        "lightningcss-win32-x64-msvc": "1.29.2"
+      }
+    },
+    "node_modules/lightningcss-darwin-arm64": {
+      "version": "1.29.2",
+      "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.29.2.tgz",
+      "integrity": "sha512-cK/eMabSViKn/PG8U/a7aCorpeKLMlK0bQeNHmdb7qUnBkNPnL+oV5DjJUo0kqWsJUapZsM4jCfYItbqBDvlcA==",
+      "cpu": [
+        "arm64"
+      ],
+      "optional": true,
+      "os": [
+        "darwin"
+      ],
+      "engines": {
+        "node": ">= 12.0.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/parcel"
+      }
+    },
+    "node_modules/lightningcss-darwin-x64": {
+      "version": "1.29.2",
+      "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.29.2.tgz",
+      "integrity": "sha512-j5qYxamyQw4kDXX5hnnCKMf3mLlHvG44f24Qyi2965/Ycz829MYqjrVg2H8BidybHBp9kom4D7DR5VqCKDXS0w==",
+      "cpu": [
+        "x64"
+      ],
+      "optional": true,
+      "os": [
+        "darwin"
+      ],
+      "engines": {
+        "node": ">= 12.0.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/parcel"
+      }
+    },
+    "node_modules/lightningcss-freebsd-x64": {
+      "version": "1.29.2",
+      "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.29.2.tgz",
+      "integrity": "sha512-wDk7M2tM78Ii8ek9YjnY8MjV5f5JN2qNVO+/0BAGZRvXKtQrBC4/cn4ssQIpKIPP44YXw6gFdpUF+Ps+RGsCwg==",
+      "cpu": [
+        "x64"
+      ],
+      "optional": true,
+      "os": [
+        "freebsd"
+      ],
+      "engines": {
+        "node": ">= 12.0.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/parcel"
+      }
+    },
+    "node_modules/lightningcss-linux-arm-gnueabihf": {
+      "version": "1.29.2",
+      "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.29.2.tgz",
+      "integrity": "sha512-IRUrOrAF2Z+KExdExe3Rz7NSTuuJ2HvCGlMKoquK5pjvo2JY4Rybr+NrKnq0U0hZnx5AnGsuFHjGnNT14w26sg==",
+      "cpu": [
+        "arm"
+      ],
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">= 12.0.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/parcel"
+      }
+    },
+    "node_modules/lightningcss-linux-arm64-gnu": {
+      "version": "1.29.2",
+      "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.29.2.tgz",
+      "integrity": "sha512-KKCpOlmhdjvUTX/mBuaKemp0oeDIBBLFiU5Fnqxh1/DZ4JPZi4evEH7TKoSBFOSOV3J7iEmmBaw/8dpiUvRKlQ==",
+      "cpu": [
+        "arm64"
+      ],
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">= 12.0.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/parcel"
+      }
+    },
+    "node_modules/lightningcss-linux-arm64-musl": {
+      "version": "1.29.2",
+      "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.29.2.tgz",
+      "integrity": "sha512-Q64eM1bPlOOUgxFmoPUefqzY1yV3ctFPE6d/Vt7WzLW4rKTv7MyYNky+FWxRpLkNASTnKQUaiMJ87zNODIrrKQ==",
+      "cpu": [
+        "arm64"
+      ],
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">= 12.0.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/parcel"
+      }
+    },
+    "node_modules/lightningcss-linux-x64-gnu": {
+      "version": "1.29.2",
+      "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.29.2.tgz",
+      "integrity": "sha512-0v6idDCPG6epLXtBH/RPkHvYx74CVziHo6TMYga8O2EiQApnUPZsbR9nFNrg2cgBzk1AYqEd95TlrsL7nYABQg==",
+      "cpu": [
+        "x64"
+      ],
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">= 12.0.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/parcel"
+      }
+    },
+    "node_modules/lightningcss-linux-x64-musl": {
+      "version": "1.29.2",
+      "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.29.2.tgz",
+      "integrity": "sha512-rMpz2yawkgGT8RULc5S4WiZopVMOFWjiItBT7aSfDX4NQav6M44rhn5hjtkKzB+wMTRlLLqxkeYEtQ3dd9696w==",
+      "cpu": [
+        "x64"
+      ],
+      "optional": true,
+      "os": [
+        "linux"
+      ],
+      "engines": {
+        "node": ">= 12.0.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/parcel"
+      }
+    },
+    "node_modules/lightningcss-win32-arm64-msvc": {
+      "version": "1.29.2",
+      "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.29.2.tgz",
+      "integrity": "sha512-nL7zRW6evGQqYVu/bKGK+zShyz8OVzsCotFgc7judbt6wnB2KbiKKJwBE4SGoDBQ1O94RjW4asrCjQL4i8Fhbw==",
+      "cpu": [
+        "arm64"
+      ],
+      "optional": true,
+      "os": [
+        "win32"
+      ],
+      "engines": {
+        "node": ">= 12.0.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/parcel"
+      }
+    },
+    "node_modules/lightningcss-win32-x64-msvc": {
+      "version": "1.29.2",
+      "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.29.2.tgz",
+      "integrity": "sha512-EdIUW3B2vLuHmv7urfzMI/h2fmlnOQBk1xlsDxkN1tCWKjNFjfLhGxYk8C8mzpSfr+A6jFFIi8fU6LbQGsRWjA==",
+      "cpu": [
+        "x64"
+      ],
+      "optional": true,
+      "os": [
+        "win32"
+      ],
+      "engines": {
+        "node": ">= 12.0.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/parcel"
+      }
+    },
+    "node_modules/lodash.camelcase": {
+      "version": "4.3.0",
+      "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz",
+      "integrity": "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA=="
+    },
+    "node_modules/long": {
+      "version": "5.3.1",
+      "resolved": "https://registry.npmjs.org/long/-/long-5.3.1.tgz",
+      "integrity": "sha512-ka87Jz3gcx/I7Hal94xaN2tZEOPoUOEVftkQqZx2EeQRN7LGdfLlI3FvZ+7WDplm+vK2Urx9ULrvSowtdCieng=="
+    },
+    "node_modules/magic-string": {
+      "version": "0.30.17",
+      "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.17.tgz",
+      "integrity": "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==",
+      "dependencies": {
+        "@jridgewell/sourcemap-codec": "^1.5.0"
+      }
+    },
+    "node_modules/math-intrinsics": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
+      "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.4"
+      }
+    },
+    "node_modules/mime-db": {
+      "version": "1.52.0",
+      "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
+      "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
+    "node_modules/mime-types": {
+      "version": "2.1.35",
+      "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
+      "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
+      "license": "MIT",
+      "dependencies": {
+        "mime-db": "1.52.0"
+      },
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
+    "node_modules/movie-group-8": {
+      "resolved": "",
+      "link": true
+    },
+    "node_modules/nanoid": {
+      "version": "3.3.10",
+      "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.10.tgz",
+      "integrity": "sha512-vSJJTG+t/dIKAUhUDw/dLdZ9s//5OxcHqLaDWWrW4Cdq7o6tdLIczUkMXt2MBNmk6sJRZBZRXVixs7URY1CmIg==",
+      "funding": [
+        {
+          "type": "github",
+          "url": "https://github.com/sponsors/ai"
+        }
+      ],
+      "bin": {
+        "nanoid": "bin/nanoid.cjs"
+      },
+      "engines": {
+        "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
+      }
+    },
+    "node_modules/picocolors": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
+      "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="
+    },
+    "node_modules/picomatch": {
+      "version": "4.0.2",
+      "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz",
+      "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=12"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/jonschlinkert"
+      }
+    },
+    "node_modules/postcss": {
+      "version": "8.5.3",
+      "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.3.tgz",
+      "integrity": "sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A==",
+      "funding": [
+        {
+          "type": "opencollective",
+          "url": "https://opencollective.com/postcss/"
+        },
+        {
+          "type": "tidelift",
+          "url": "https://tidelift.com/funding/github/npm/postcss"
+        },
+        {
+          "type": "github",
+          "url": "https://github.com/sponsors/ai"
+        }
+      ],
+      "dependencies": {
+        "nanoid": "^3.3.8",
+        "picocolors": "^1.1.1",
+        "source-map-js": "^1.2.1"
+      },
+      "engines": {
+        "node": "^10 || ^12 || >=14"
+      }
+    },
+    "node_modules/protobufjs": {
+      "version": "7.4.0",
+      "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.4.0.tgz",
+      "integrity": "sha512-mRUWCc3KUU4w1jU8sGxICXH/gNS94DvI1gxqDvBzhj1JpcsimQkYiOJfwsPUykUI5ZaspFbSgmBLER8IrQ3tqw==",
+      "hasInstallScript": true,
+      "dependencies": {
+        "@protobufjs/aspromise": "^1.1.2",
+        "@protobufjs/base64": "^1.1.2",
+        "@protobufjs/codegen": "^2.0.4",
+        "@protobufjs/eventemitter": "^1.1.0",
+        "@protobufjs/fetch": "^1.1.0",
+        "@protobufjs/float": "^1.0.2",
+        "@protobufjs/inquire": "^1.1.0",
+        "@protobufjs/path": "^1.1.2",
+        "@protobufjs/pool": "^1.1.0",
+        "@protobufjs/utf8": "^1.1.0",
+        "@types/node": ">=13.7.0",
+        "long": "^5.0.0"
+      },
+      "engines": {
+        "node": ">=12.0.0"
+      }
+    },
+    "node_modules/proxy-from-env": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
+      "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==",
+      "license": "MIT"
+    },
+    "node_modules/require-directory": {
+      "version": "2.1.1",
+      "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
+      "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==",
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
+    "node_modules/rollup": {
+      "version": "4.36.0",
+      "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.36.0.tgz",
+      "integrity": "sha512-zwATAXNQxUcd40zgtQG0ZafcRK4g004WtEl7kbuhTWPvf07PsfohXl39jVUvPF7jvNAIkKPQ2XrsDlWuxBd++Q==",
+      "dependencies": {
+        "@types/estree": "1.0.6"
+      },
+      "bin": {
+        "rollup": "dist/bin/rollup"
+      },
+      "engines": {
+        "node": ">=18.0.0",
+        "npm": ">=8.0.0"
+      },
+      "optionalDependencies": {
+        "@rollup/rollup-android-arm-eabi": "4.36.0",
+        "@rollup/rollup-android-arm64": "4.36.0",
+        "@rollup/rollup-darwin-arm64": "4.36.0",
+        "@rollup/rollup-darwin-x64": "4.36.0",
+        "@rollup/rollup-freebsd-arm64": "4.36.0",
+        "@rollup/rollup-freebsd-x64": "4.36.0",
+        "@rollup/rollup-linux-arm-gnueabihf": "4.36.0",
+        "@rollup/rollup-linux-arm-musleabihf": "4.36.0",
+        "@rollup/rollup-linux-arm64-gnu": "4.36.0",
+        "@rollup/rollup-linux-arm64-musl": "4.36.0",
+        "@rollup/rollup-linux-loongarch64-gnu": "4.36.0",
+        "@rollup/rollup-linux-powerpc64le-gnu": "4.36.0",
+        "@rollup/rollup-linux-riscv64-gnu": "4.36.0",
+        "@rollup/rollup-linux-s390x-gnu": "4.36.0",
+        "@rollup/rollup-linux-x64-gnu": "4.36.0",
+        "@rollup/rollup-linux-x64-musl": "4.36.0",
+        "@rollup/rollup-win32-arm64-msvc": "4.36.0",
+        "@rollup/rollup-win32-ia32-msvc": "4.36.0",
+        "@rollup/rollup-win32-x64-msvc": "4.36.0",
+        "fsevents": "~2.3.2"
+      }
+    },
+    "node_modules/safe-buffer": {
+      "version": "5.2.1",
+      "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
+      "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
+      "funding": [
+        {
+          "type": "github",
+          "url": "https://github.com/sponsors/feross"
+        },
+        {
+          "type": "patreon",
+          "url": "https://www.patreon.com/feross"
+        },
+        {
+          "type": "consulting",
+          "url": "https://feross.org/support"
+        }
+      ]
+    },
+    "node_modules/source-map-js": {
+      "version": "1.2.1",
+      "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
+      "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
+    "node_modules/string-width": {
+      "version": "4.2.3",
+      "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
+      "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
+      "dependencies": {
+        "emoji-regex": "^8.0.0",
+        "is-fullwidth-code-point": "^3.0.0",
+        "strip-ansi": "^6.0.1"
+      },
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/strip-ansi": {
+      "version": "6.0.1",
+      "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
+      "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
+      "dependencies": {
+        "ansi-regex": "^5.0.1"
+      },
+      "engines": {
+        "node": ">=8"
+      }
+    },
+    "node_modules/tailwindcss": {
+      "version": "4.0.14",
+      "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.0.14.tgz",
+      "integrity": "sha512-92YT2dpt671tFiHH/e1ok9D987N9fHD5VWoly1CdPD/Cd1HMglvZwP3nx2yTj2lbXDAHt8QssZkxTLCCTNL+xw=="
+    },
+    "node_modules/tapable": {
+      "version": "2.2.1",
+      "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz",
+      "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==",
+      "engines": {
+        "node": ">=6"
+      }
+    },
+    "node_modules/tinyglobby": {
+      "version": "0.2.13",
+      "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.13.tgz",
+      "integrity": "sha512-mEwzpUgrLySlveBwEVDMKk5B57bhLPYovRfPAXD5gA/98Opn0rCDj3GtLwFvCvH5RK9uPCExUROW5NjDwvqkxw==",
+      "license": "MIT",
+      "dependencies": {
+        "fdir": "^6.4.4",
+        "picomatch": "^4.0.2"
+      },
+      "engines": {
+        "node": ">=12.0.0"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/SuperchupuDev"
+      }
+    },
+    "node_modules/tslib": {
+      "version": "2.8.1",
+      "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
+      "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="
+    },
+    "node_modules/undici-types": {
+      "version": "6.20.0",
+      "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz",
+      "integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg=="
+    },
+    "node_modules/vite": {
+      "version": "6.3.5",
+      "resolved": "https://registry.npmjs.org/vite/-/vite-6.3.5.tgz",
+      "integrity": "sha512-cZn6NDFE7wdTpINgs++ZJ4N49W2vRp8LCKrn3Ob1kYNtOo21vfDoaV5GzBfLU4MovSAB8uNRm4jgzVQZ+mBzPQ==",
+      "license": "MIT",
+      "dependencies": {
+        "esbuild": "^0.25.0",
+        "fdir": "^6.4.4",
+        "picomatch": "^4.0.2",
+        "postcss": "^8.5.3",
+        "rollup": "^4.34.9",
+        "tinyglobby": "^0.2.13"
+      },
+      "bin": {
+        "vite": "bin/vite.js"
+      },
+      "engines": {
+        "node": "^18.0.0 || ^20.0.0 || >=22.0.0"
+      },
+      "funding": {
+        "url": "https://github.com/vitejs/vite?sponsor=1"
+      },
+      "optionalDependencies": {
+        "fsevents": "~2.3.3"
+      },
+      "peerDependencies": {
+        "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0",
+        "jiti": ">=1.21.0",
+        "less": "*",
+        "lightningcss": "^1.21.0",
+        "sass": "*",
+        "sass-embedded": "*",
+        "stylus": "*",
+        "sugarss": "*",
+        "terser": "^5.16.0",
+        "tsx": "^4.8.1",
+        "yaml": "^2.4.2"
+      },
+      "peerDependenciesMeta": {
+        "@types/node": {
+          "optional": true
+        },
+        "jiti": {
+          "optional": true
+        },
+        "less": {
+          "optional": true
+        },
+        "lightningcss": {
+          "optional": true
+        },
+        "sass": {
+          "optional": true
+        },
+        "sass-embedded": {
+          "optional": true
+        },
+        "stylus": {
+          "optional": true
+        },
+        "sugarss": {
+          "optional": true
+        },
+        "terser": {
+          "optional": true
+        },
+        "tsx": {
+          "optional": true
+        },
+        "yaml": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/vue": {
+      "version": "3.5.13",
+      "resolved": "https://registry.npmjs.org/vue/-/vue-3.5.13.tgz",
+      "integrity": "sha512-wmeiSMxkZCSc+PM2w2VRsOYAZC8GdipNFRTsLSfodVqI9mbejKeXEGr8SckuLnrQPGe3oJN5c3K0vpoU9q/wCQ==",
+      "dependencies": {
+        "@vue/compiler-dom": "3.5.13",
+        "@vue/compiler-sfc": "3.5.13",
+        "@vue/runtime-dom": "3.5.13",
+        "@vue/server-renderer": "3.5.13",
+        "@vue/shared": "3.5.13"
+      },
+      "peerDependencies": {
+        "typescript": "*"
+      },
+      "peerDependenciesMeta": {
+        "typescript": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/vue-router": {
+      "version": "4.5.0",
+      "resolved": "https://registry.npmjs.org/vue-router/-/vue-router-4.5.0.tgz",
+      "integrity": "sha512-HDuk+PuH5monfNuY+ct49mNmkCRK4xJAV9Ts4z9UFc4rzdDnxQLyCMGGc8pKhZhHTVzfanpNwB/lwqevcBwI4w==",
+      "dependencies": {
+        "@vue/devtools-api": "^6.6.4"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/posva"
+      },
+      "peerDependencies": {
+        "vue": "^3.2.0"
+      }
+    },
+    "node_modules/web-vitals": {
+      "version": "4.2.4",
+      "resolved": "https://registry.npmjs.org/web-vitals/-/web-vitals-4.2.4.tgz",
+      "integrity": "sha512-r4DIlprAGwJ7YM11VZp4R884m0Vmgr6EAKe3P+kO0PPj3Unqyvv59rczf6UiGcb9Z8QxZVcqKNwv/g0WNdWwsw=="
+    },
+    "node_modules/websocket-driver": {
+      "version": "0.7.4",
+      "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.4.tgz",
+      "integrity": "sha512-b17KeDIQVjvb0ssuSDF2cYXSg2iztliJ4B9WdsuB6J952qCPKmnVq4DyW5motImXHDC1cBT/1UezrJVsKw5zjg==",
+      "dependencies": {
+        "http-parser-js": ">=0.5.1",
+        "safe-buffer": ">=5.1.0",
+        "websocket-extensions": ">=0.1.1"
+      },
+      "engines": {
+        "node": ">=0.8.0"
+      }
+    },
+    "node_modules/websocket-extensions": {
+      "version": "0.1.4",
+      "resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.4.tgz",
+      "integrity": "sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==",
+      "engines": {
+        "node": ">=0.8.0"
+      }
+    },
+    "node_modules/wrap-ansi": {
+      "version": "7.0.0",
+      "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
+      "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
+      "dependencies": {
+        "ansi-styles": "^4.0.0",
+        "string-width": "^4.1.0",
+        "strip-ansi": "^6.0.0"
+      },
+      "engines": {
+        "node": ">=10"
+      },
+      "funding": {
+        "url": "https://github.com/chalk/wrap-ansi?sponsor=1"
+      }
+    },
+    "node_modules/y18n": {
+      "version": "5.0.8",
+      "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz",
+      "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==",
+      "engines": {
+        "node": ">=10"
+      }
+    },
+    "node_modules/yargs": {
+      "version": "17.7.2",
+      "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz",
+      "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==",
+      "dependencies": {
+        "cliui": "^8.0.1",
+        "escalade": "^3.1.1",
+        "get-caller-file": "^2.0.5",
+        "require-directory": "^2.1.1",
+        "string-width": "^4.2.3",
+        "y18n": "^5.0.5",
+        "yargs-parser": "^21.1.1"
+      },
+      "engines": {
+        "node": ">=12"
+      }
+    },
+    "node_modules/yargs-parser": {
+      "version": "21.1.1",
+      "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz",
+      "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==",
+      "engines": {
+        "node": ">=12"
+      }
+    }
+  }
+}
diff --git a/movie-group-8/package.json b/movie-group-8/package.json
new file mode 100644
index 0000000000000000000000000000000000000000..22af383cd1cebd8a1c2023b2f9e7938c34cbac5d
--- /dev/null
+++ b/movie-group-8/package.json
@@ -0,0 +1,26 @@
+{
+  "name": "movie-group-8",
+  "private": true,
+  "version": "0.0.0",
+  "type": "module",
+  "scripts": {
+    "dev": "vite",
+    "build": "vite build",
+    "preview": "vite preview"
+  },
+  "dependencies": {
+    "@headlessui/vue": "^1.7.23",
+    "@heroicons/vue": "^2.2.0",
+    "@tailwindcss/vite": "^4.0.14",
+    "axios": "^1.9.0",
+    "firebase": "^11.4.0",
+    "movie-group-8": "file:",
+    "tailwindcss": "^4.0.14",
+    "vue": "^3.5.13",
+    "vue-router": "^4.5.0"
+  },
+  "devDependencies": {
+    "@vitejs/plugin-vue": "^5.2.1",
+    "vite": "^6.2.0"
+  }
+}
diff --git a/movie-group-8/public/vite.svg b/movie-group-8/public/vite.svg
new file mode 100644
index 0000000000000000000000000000000000000000..e7b8dfb1b2a60bd50538bec9f876511b9cac21e3
--- /dev/null
+++ b/movie-group-8/public/vite.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>
\ No newline at end of file
diff --git a/movie-group-8/src/App.vue b/movie-group-8/src/App.vue
new file mode 100644
index 0000000000000000000000000000000000000000..06a35b97d34a73ade52d72d75981b01ddec76847
--- /dev/null
+++ b/movie-group-8/src/App.vue
@@ -0,0 +1,16 @@
+<script setup>
+import { RouterLink, RouterView } from 'vue-router';
+import Navbar from './components/Navbar.vue';
+</script>
+
+<template>
+  <Navbar />
+  <div class="z-10 bg-neutral-900">
+    <RouterView />
+  </div>
+</template>
+
+<style scoped>
+</style>
+
+
diff --git a/movie-group-8/src/assets/Dark_Mode.png b/movie-group-8/src/assets/Dark_Mode.png
new file mode 100644
index 0000000000000000000000000000000000000000..5bdc467c008da01416a4764c34f69c1855c9420b
Binary files /dev/null and b/movie-group-8/src/assets/Dark_Mode.png differ
diff --git a/movie-group-8/src/assets/Light_Mode.png b/movie-group-8/src/assets/Light_Mode.png
new file mode 100644
index 0000000000000000000000000000000000000000..d345b08cf1fcdb1107bda38811905070fa5206d3
Binary files /dev/null and b/movie-group-8/src/assets/Light_Mode.png differ
diff --git a/movie-group-8/src/assets/TMDB.svg b/movie-group-8/src/assets/TMDB.svg
new file mode 100644
index 0000000000000000000000000000000000000000..42f31f15442e3afe6ae8b38024637112e508bf7a
--- /dev/null
+++ b/movie-group-8/src/assets/TMDB.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 190.24 81.52"><defs><style>.cls-1{fill:url(#linear-gradient);}</style><linearGradient id="linear-gradient" y1="40.76" x2="190.24" y2="40.76" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#90cea1"/><stop offset="0.56" stop-color="#3cbec9"/><stop offset="1" stop-color="#00b3e5"/></linearGradient></defs><title>Asset 2</title><g id="Layer_2" data-name="Layer 2"><g id="Layer_1-2" data-name="Layer 1"><path class="cls-1" d="M105.67,36.06h66.9A17.67,17.67,0,0,0,190.24,18.4h0A17.67,17.67,0,0,0,172.57.73h-66.9A17.67,17.67,0,0,0,88,18.4h0A17.67,17.67,0,0,0,105.67,36.06Zm-88,45h76.9A17.67,17.67,0,0,0,112.24,63.4h0A17.67,17.67,0,0,0,94.57,45.73H17.67A17.67,17.67,0,0,0,0,63.4H0A17.67,17.67,0,0,0,17.67,81.06ZM10.41,35.42h7.8V6.92h10.1V0H.31v6.9h10.1Zm28.1,0h7.8V8.25h.1l9,27.15h6l9.3-27.15h.1V35.4h7.8V0H66.76l-8.2,23.1h-.1L50.31,0H38.51ZM152.43,55.67a15.07,15.07,0,0,0-4.52-5.52,18.57,18.57,0,0,0-6.68-3.08,33.54,33.54,0,0,0-8.07-1h-11.7v35.4h12.75a24.58,24.58,0,0,0,7.55-1.15A19.34,19.34,0,0,0,148.11,77a16.27,16.27,0,0,0,4.37-5.5,16.91,16.91,0,0,0,1.63-7.58A18.5,18.5,0,0,0,152.43,55.67ZM145,68.6A8.8,8.8,0,0,1,142.36,72a10.7,10.7,0,0,1-4,1.82,21.57,21.57,0,0,1-5,.55h-4.05v-21h4.6a17,17,0,0,1,4.67.63,11.66,11.66,0,0,1,3.88,1.87A9.14,9.14,0,0,1,145,59a9.87,9.87,0,0,1,1,4.52A11.89,11.89,0,0,1,145,68.6Zm44.63-.13a8,8,0,0,0-1.58-2.62A8.38,8.38,0,0,0,185.63,64a10.31,10.31,0,0,0-3.17-1v-.1a9.22,9.22,0,0,0,4.42-2.82,7.43,7.43,0,0,0,1.68-5,8.42,8.42,0,0,0-1.15-4.65,8.09,8.09,0,0,0-3-2.72,12.56,12.56,0,0,0-4.18-1.3,32.84,32.84,0,0,0-4.62-.33h-13.2v35.4h14.5a22.41,22.41,0,0,0,4.72-.5,13.53,13.53,0,0,0,4.28-1.65,9.42,9.42,0,0,0,3.1-3,8.52,8.52,0,0,0,1.2-4.68A9.39,9.39,0,0,0,189.66,68.47ZM170.21,52.72h5.3a10,10,0,0,1,1.85.18,6.18,6.18,0,0,1,1.7.57,3.39,3.39,0,0,1,1.22,1.13,3.22,3.22,0,0,1,.48,1.82,3.63,3.63,0,0,1-.43,1.8,3.4,3.4,0,0,1-1.12,1.2,4.92,4.92,0,0,1-1.58.65,7.51,7.51,0,0,1-1.77.2h-5.65Zm11.72,20a3.9,3.9,0,0,1-1.22,1.3,4.64,4.64,0,0,1-1.68.7,8.18,8.18,0,0,1-1.82.2h-7v-8h5.9a15.35,15.35,0,0,1,2,.15,8.47,8.47,0,0,1,2.05.55,4,4,0,0,1,1.57,1.18,3.11,3.11,0,0,1,.63,2A3.71,3.71,0,0,1,181.93,72.72Z"/></g></g></svg>
\ No newline at end of file
diff --git a/movie-group-8/src/assets/default_avatar.png b/movie-group-8/src/assets/default_avatar.png
new file mode 100644
index 0000000000000000000000000000000000000000..778e051529c32dc9e845d961a9229d0addaf09ea
Binary files /dev/null and b/movie-group-8/src/assets/default_avatar.png differ
diff --git a/movie-group-8/src/assets/home_poster.png b/movie-group-8/src/assets/home_poster.png
new file mode 100644
index 0000000000000000000000000000000000000000..f5eb71833364c1f645d9d9a373d302325f6ab944
Binary files /dev/null and b/movie-group-8/src/assets/home_poster.png differ
diff --git a/movie-group-8/src/assets/review_poster.png b/movie-group-8/src/assets/review_poster.png
new file mode 100644
index 0000000000000000000000000000000000000000..64d338c03a1f9c348fbebc5fd6b36b13eb37bd29
Binary files /dev/null and b/movie-group-8/src/assets/review_poster.png differ
diff --git a/movie-group-8/src/assets/tmdb_poster.png b/movie-group-8/src/assets/tmdb_poster.png
new file mode 100644
index 0000000000000000000000000000000000000000..b3caa85d45a1b549aba2128421978d23fd5f0325
Binary files /dev/null and b/movie-group-8/src/assets/tmdb_poster.png differ
diff --git a/movie-group-8/src/components/MovieCard.vue b/movie-group-8/src/components/MovieCard.vue
new file mode 100644
index 0000000000000000000000000000000000000000..15e6322b2e79283be5c16313a8484ba56dd24151
--- /dev/null
+++ b/movie-group-8/src/components/MovieCard.vue
@@ -0,0 +1,57 @@
+<template>
+  <div class="bg-white dark:bg-neutral-800 border dark:border-neutral-700 rounded-lg p-4 flex gap-4 items-start">
+    <!-- Clickable poster -->
+    <router-link
+      :to="`/films/${movie.id}`"
+      class="shrink-0 hover:opacity-90 transition"
+    >
+      <img
+        v-if="movie.poster_path"
+        :src="'https://image.tmdb.org/t/p/w154' + movie.poster_path"
+        alt="Poster"
+        class="w-24 h-36 object-cover rounded"
+      />
+    </router-link>
+
+    <div class="flex-1">
+      <!-- Clickable title -->
+      <router-link
+        :to="`/films/${movie.id}`"
+        class="text-lg font-semibold text-gray-800 dark:text-white hover:underline block"
+      >
+        {{ movie.title }}
+      </router-link>
+
+      <p class="text-sm text-gray-600 dark:text-neutral-400">⭐ {{ movie.vote_average }}</p>
+
+      <!-- Interactive status dropdown -->
+      <label class="text-xs text-gray-500 dark:text-gray-400 block mt-2">
+        Status:
+        <select
+          v-model="selectedStatus"
+          @change="emitChange"
+          class="ml-2 bg-white dark:bg-neutral-700 border border-gray-300 dark:border-neutral-600 rounded px-2 py-1 text-sm"
+        >
+          <option value="planned">Planned</option>
+          <option value="watched">Watched</option>
+        </select>
+      </label>
+    </div>
+  </div>
+</template>
+
+<script setup>
+import { ref, watch } from 'vue'
+
+const props = defineProps({
+  movie: Object,
+})
+
+const emit = defineEmits(['status-changed'])
+
+const selectedStatus = ref(props.movie.status)
+
+const emitChange = () => {
+  emit('status-changed', props.movie.id, selectedStatus.value)
+}
+</script>
\ No newline at end of file
diff --git a/movie-group-8/src/components/MovieList.vue b/movie-group-8/src/components/MovieList.vue
new file mode 100644
index 0000000000000000000000000000000000000000..78b0a9e2d4e619722113da6978d27ccaae015ff9
--- /dev/null
+++ b/movie-group-8/src/components/MovieList.vue
@@ -0,0 +1,119 @@
+<template>
+  <div class="movie-list">
+    <div v-if="!movies.length" class="no-results">
+      <slot name="empty">No movies found.</slot>
+    </div>
+    <ul v-else class="movies">
+      <li v-for="movie in movies" :key="movie.id" class="movie-item">
+        <router-link
+            :to="{ name: 'FilmDetails', params: { id: movie.id } }"
+            class="movie-link"
+        >
+            <img
+            v-if="movie.poster_path"
+            :src="getPosterUrl(movie.poster_path)"
+            :alt="`${movie.title} poster`"
+            class="poster"
+            />
+            <div class="details">
+            <h2 class="title">{{ movie.title }}</h2>
+            <p class="release-date">{{ formatDate(movie.release_date) }}</p>
+            <p class="overview">{{ movie.overview || 'No overview available.' }}</p>
+            </div>
+        </router-link>
+
+      </li>
+    </ul>
+  </div>
+</template>
+
+<script setup>
+
+const props = defineProps({
+  movies: {
+    type: Array,
+    required: true
+  }
+})
+
+// TMDB image base URL from Vite env or fallback
+const IMAGE_BASE = import.meta.env.VITE_TMDB_IMAGE_BASE || 'https://image.tmdb.org/t/p/w200'
+
+/**
+ * Build full poster URL
+ */
+function getPosterUrl(path) {
+  return `${IMAGE_BASE}${path}`
+}
+
+/**
+ * Format a date string to a more readable form
+ */
+function formatDate(dateString) {
+  if (!dateString) return 'Unknown'
+  const options = { year: 'numeric', month: 'long', day: 'numeric' }
+  return new Date(dateString).toLocaleDateString(undefined, options)
+}
+</script>
+
+<style scoped>
+.movie-list {
+  display: flex;
+  flex-direction: column;
+}
+
+.no-results {
+  text-align: center;
+  color: #666;
+  font-style: italic;
+}
+
+.movies {
+  list-style: none;
+  padding: 0;
+  display: grid;
+  grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
+  gap: 1rem;
+}
+
+.movie-item {
+  background: #fff;
+  border-radius: 8px;
+  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
+  overflow: hidden;
+  display: flex;
+  flex-direction: column;
+}
+
+.poster {
+  width: 100%;
+  object-fit: cover;
+  aspect-ratio: 2 / 3;
+}
+
+.details {
+  padding: 0.75rem;
+  display: flex;
+  flex-direction: column;
+  flex-grow: 1;
+}
+
+.title {
+  font-size: 1.1rem;
+  margin: 0 0 0.5rem;
+}
+
+.release-date {
+  font-size: 0.875rem;
+  color: #888;
+  margin: 0 0 0.5rem;
+}
+
+.overview {
+  font-size: 0.9rem;
+  color: #444;
+  flex-grow: 1;
+}
+
+
+</style>
diff --git a/movie-group-8/src/components/Navbar.vue b/movie-group-8/src/components/Navbar.vue
new file mode 100644
index 0000000000000000000000000000000000000000..64d7e5700bc6baec7c66c76e9357bbc787de6f9e
--- /dev/null
+++ b/movie-group-8/src/components/Navbar.vue
@@ -0,0 +1,130 @@
+<template>
+  <nav class="fixed top-0 left-0 right-0 min-w-11/12 mx-6 bg-neutral-800 text-white shadow-md px-4 flex justify-between items-center z-50 rounded-2xl mt-3">
+    <!-- Logo -->
+    <div class="flex items-center">
+      <RouterLink to="/">
+        <img class="h-14 w-auto" src="../assets/Dark_Mode.png" alt="Logo" />
+      </RouterLink>
+    </div>
+
+    <!-- Search Bar (Perfectly Centered) -->
+    <div class="absolute left-1/2 transform -translate-x-1/2 w-full max-w-md">
+      <input
+        v-model="searchQuery"
+        type="text"
+        placeholder="Search..."
+        class="w-full px-4 py-2 pr-10 rounded-full bg-neutral-700 text-white placeholder-gray-400 focus:ring-2 focus:ring-white focus:outline-none"
+      />
+      <!-- Search Icon inside input -->
+      <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"
+        class="size-6 absolute right-3 top-1/2 -translate-y-1/2 text-gray-400">
+        <path stroke-linecap="round" stroke-linejoin="round"
+          d="m21 21-5.197-5.197m0 0A7.5 7.5 0 1 0 5.196 5.196a7.5 7.5 0 0 0 10.607 10.607Z" />
+      </svg>
+    </div>
+
+    <!-- Navigation & Profile Section -->
+    <div class="flex items-center space-x-4 ml-auto">
+      <!-- Navigation Links -->
+      <div class="flex space-x-3">
+        <RouterLink
+          v-for="item in filteredNavigation"
+          :key="item.name"
+          :to="item.href"
+          class="px-3 py-2 rounded-md text-sm font-medium transition hover:bg-neutral-700"
+          :class="{ 'bg-neutral-900': item.href === currentRoute }">
+          {{ item.name }}
+        </RouterLink>
+
+        <RouterLink
+          v-if="!isLoggedIn"
+          to="/login"
+          class="px-4 py-2 rounded-xl bg-blue-600 text-white font-semibold text-sm transition hover:bg-blue-700 shadow-md">
+          Login
+        </RouterLink>
+      </div>
+
+      <!-- Profile Dropdown (Only show if logged in) -->
+      <Menu as="div" class="relative" v-if="isLoggedIn">
+        <div>
+          <MenuButton class="flex rounded-full bg-gray-800 text-sm focus:ring-1 focus:ring-white focus:ring-offset-2">
+            <img class="h-8 w-8 rounded-full" :src="userPhotoURL" alt="User Avatar" />
+          </MenuButton>
+        </div>
+        <transition enter-active-class="transition ease-out duration-100" enter-from-class="transform opacity-0 scale-95" enter-to-class="transform opacity-100 scale-100" leave-active-class="transition ease-in duration-75" leave-from-class="transform opacity-100 scale-100" leave-to-class="transform opacity-0 scale-95">
+          <MenuItems class="absolute right-0 mt-2 w-48 bg-neutral-800 rounded-md shadow-lg ring-1 ring-black/5 py-1">
+            <MenuItem v-slot="{ active }">
+              <RouterLink to="/profile" :class="[active ? 'bg-neutral-700' : '', 'block px-4 py-2 text-sm text-white']">Your Profile</RouterLink>
+            </MenuItem>
+            <MenuItem v-slot="{ active }">
+              <RouterLink to="/settings" :class="[active ? 'bg-neutral-700' : '', 'block px-4 py-2 text-sm text-white']">Settings</RouterLink>
+            </MenuItem>
+            <MenuItem @click="handleSignOut" v-slot="{ active }">
+              <a href="#" @click.prevent="handleSignOut" :class="[active ? 'bg-neutral-700' : '', 'block px-4 py-2 text-sm text-white']">Sign out</a>
+            </MenuItem>
+          </MenuItems>
+        </transition>
+      </Menu>
+    </div>
+  </nav>
+</template>
+
+<script setup>
+import { ref, computed, onMounted } from 'vue';
+import { useRoute, useRouter } from 'vue-router';
+import { getAuth, onAuthStateChanged, signOut } from 'firebase/auth';
+import { Menu, MenuButton, MenuItem, MenuItems } from '@headlessui/vue';
+import defaultAvatar from '@/assets/default_avatar.png'
+
+// Firebase Authentication instance
+const auth = getAuth(); 
+
+const route = useRoute();
+const router = useRouter();
+const isLoggedIn = ref(false);
+const userPhotoURL = ref(''); 
+
+const navigation = ref([
+  { name: 'Films', href: '/films', authRequired: true },
+  { name: 'Watchlist', href: '/watchlist', authRequired: true },
+  { name: 'Top Rated', href: '/top-rated', authRequired: true },
+  { name: 'Social', href: '/social', authRequired: true },
+
+]);
+
+// Compute the current route to match against the navigation links
+const currentRoute = computed(() => route.path);
+
+// Filter navigation based on auth state
+const filteredNavigation = computed(() => {
+  return navigation.value.filter(item => {
+    if (item.authRequired && !isLoggedIn.value) return false; // Hide if user is not logged in
+    if (item.guestOnly && isLoggedIn.value) return false; // Hide if user is logged in
+    return true;
+  });
+});
+
+// Check authentication state
+onMounted(() => {
+  onAuthStateChanged(auth, (user) => {
+    isLoggedIn.value = !!user; // Sets to true if user exists, false otherwise
+    if(user && user.photoURL) {
+      userPhotoURL.value = user.photoURL;
+    } else {
+      userPhotoURL.value = defaultAvatar;
+    }
+  });
+});
+
+// Sign-out function
+const handleSignOut = () => {
+  signOut(auth)
+    .then(() => {
+      isLoggedIn.value = false; // Ensure UI updates correctly
+      router.push('/'); // Redirect to home
+    })
+    .catch((error) => {
+      console.error("Error signing out:", error);
+    });
+};
+</script>
diff --git a/movie-group-8/src/components/TopRatedMovies.vue b/movie-group-8/src/components/TopRatedMovies.vue
new file mode 100644
index 0000000000000000000000000000000000000000..090e315b6344a73ba91f116b5f0b5aad23b124a1
--- /dev/null
+++ b/movie-group-8/src/components/TopRatedMovies.vue
@@ -0,0 +1,145 @@
+<script setup>
+import { ref, onMounted, watch } from 'vue'
+
+const year = ref(2000)
+const genre = ref('')
+const genres = ref([])
+const movies = ref([])
+
+const TMDB_API_KEY = import.meta.env.VITE_TMDB_API_KEY
+
+// Fetch genres on component mount
+onMounted(async () => {
+  const genreRes = await fetch(`https://api.themoviedb.org/3/genre/movie/list?api_key=${TMDB_API_KEY}`)
+  const genreData = await genreRes.json()
+  genres.value = genreData.genres
+})
+
+// Watch for changes in year or genre and fetch movies
+watch([year, genre], async () => {
+  const url = `https://api.themoviedb.org/3/discover/movie?api_key=${TMDB_API_KEY}&sort_by=vote_average.desc&vote_count.gte=50&primary_release_year=${year.value}` +
+              (genre.value ? `&with_genres=${genre.value}` : '')
+
+  const res = await fetch(url)
+  const data = await res.json()
+  movies.value = data.results
+}, { immediate: true })
+</script>
+
+<template>
+  <div class="nyt-header">
+    <h1>The Movies We've Loved Since 2000</h1>
+    <p>
+      Explore top-rated films using the filters below — by genre and year — and rediscover hidden gems.
+    </p>
+  </div>
+
+  <div class="filters">
+    <select v-model="genre">
+      <option value="">Pick a genre ...</option>
+      <option v-for="g in genres" :key="g.id" :value="g.id">{{ g.name }}</option>
+    </select>
+
+    <select v-model="year">
+      <option v-for="y in Array.from({length: 25}, (_, i) => 2000 + i)" :key="y" :value="y">
+        {{ y }}
+      </option>
+    </select>
+  </div>
+
+  <h2 class="section-title">Our favorite movies from {{ year }}</h2>
+
+  <div class="movie-grid">
+    <router-link
+      v-for="movie in movies"
+      :key="movie.id"
+      :to="`/films/${movie.id}`"
+      class="movie-card block hover:opacity-90 transition"
+    >
+      <img
+        v-if="movie.poster_path"
+        :src="'https://image.tmdb.org/t/p/w342' + movie.poster_path"
+        alt="poster"
+      />
+      <p class="title">{{ movie.title }}</p>
+      <p class="rating">⭐ {{ movie.vote_average }}</p>
+    </router-link>
+  </div>
+</template>
+  
+<style scoped>
+.nyt-header {
+  text-align: center;
+  max-width: 700px;
+  margin: 3rem auto 2rem auto;
+  padding: 0 1rem;
+}
+.nyt-header h1 {
+  font-size: 2.4rem;
+  font-weight: 700;
+  line-height: 1.2;
+}
+.nyt-header p {
+  font-size: 1.1rem;
+  margin-top: 0.75rem;
+  color: #555;
+}
+
+.filters {
+  display: flex;
+  justify-content: center;
+  flex-wrap: wrap;
+  gap: 1rem;
+  margin-bottom: 2rem;
+}
+
+.filters select {
+  padding: 0.5rem 1.2rem;
+  background-color: #cce8ff;
+  border: none;
+  border-radius: 8px;
+  font-weight: bold;
+  font-size: 1rem;
+  color: #003366;
+  cursor: pointer;
+  transition: background-color 0.2s ease;
+}
+.filters select:hover {
+  background-color: #b2dcff;
+}
+
+.section-title {
+  text-align: center;
+  font-size: 1.6rem;
+  font-weight: bold;
+  margin-bottom: 1.5rem;
+}
+
+.movie-grid {
+  display: grid;
+  grid-template-columns: repeat(auto-fill, minmax(160px, 1fr));
+  gap: 1.2rem;
+  padding: 0 1rem;
+}
+
+.movie-card {
+  text-align: center;
+}
+
+.movie-card img {
+  width: 100%;
+  border-radius: 12px;
+  box-shadow: 0 4px 10px rgba(0, 0, 0, 0.1);
+}
+
+.title {
+  font-weight: 600;
+  margin-top: 0.5rem;
+}
+
+.rating {
+  color: #ffa500;
+  font-size: 0.9rem;
+}
+
+</style>
diff --git a/movie-group-8/src/components/Watchlist.vue b/movie-group-8/src/components/Watchlist.vue
new file mode 100644
index 0000000000000000000000000000000000000000..724082258be8667655500d595f6361458acab0c9
--- /dev/null
+++ b/movie-group-8/src/components/Watchlist.vue
@@ -0,0 +1,89 @@
+<template>
+    <div class="watchlist">
+        <h1>Your Watchlist</h1>
+  
+        <div class="filters">
+            <select v-model="filter">
+                <option value="">All</option>
+                <option value="planned">Plan To Watch</option>
+                <option value="watched">Watched</option>
+            </select>
+        </div>
+  
+        <div class="movie-grid">
+            <div 
+                v-for="movie in filteredWatchlist" 
+                :key="movie.id" 
+                class="movie-card"
+            >
+            <img :src="'https://image.tmdb.org/t/p/w342' + movie.poster_path" alt="poster" />
+            <p class="title">{{ movie.title }}</p>
+            <p class="rating">⭐ {{ movie.vote_average }}</p>
+            <p class="status">📌 {{ movie.status }}</p>
+            <button
+                @click="removeFromWatchlist(movie)"
+                class="mt-2 px-3 py-1 bg-red-600 text-white rounded hover:bg-red-700 transition"
+                >
+                Remove
+            </button>
+            </div>
+        </div>
+    </div>
+</template>
+  
+<script setup>
+    import { ref, computed, onMounted } from 'vue'
+    import { getFirestore, collection, getDocs, doc, deleteDoc } from 'firebase/firestore'
+    import { getAuth } from 'firebase/auth'
+  
+    const db = getFirestore()
+    const auth = getAuth()
+  
+    const watchlist = ref([])
+    const filter = ref('')
+  
+    const fetchWatchlist = async () => {
+        const user = auth.currentUser
+        if (!user) return
+  
+        const snapshot = await getDocs(collection(db, 'users', user.uid, 'watchlist'))
+        watchlist.value = snapshot.docs.map(doc => ({ id: doc.id, ...doc.data() }))
+    }
+
+    const removeFromWatchlist = async (movie) => {
+        const user = auth.currentUser
+        const movieRef = doc(db, 'users', user.uid, 'watchlist', String(movie.id))
+        await deleteDoc(movieRef)
+
+        // remove locally
+        watchlist.value = watchlist.value.filter(m => m.id !== movie.id)
+        alert(`Removed "${movie.title}" from your watchlist.`)
+    }
+  
+    const filteredWatchlist = computed(() => {
+        if (!filter.value) return watchlist.value
+        return watchlist.value.filter(movie => movie.status === filter.value)
+    })
+  
+    onMounted(() => {
+        fetchWatchlist()
+    })
+</script>
+  
+<style scoped>
+    .watchlist {
+        padding: 2rem;
+        text-align: center;
+    }
+  
+    .filters {
+        margin-bottom: 1rem;
+    }
+  
+    .movie-grid {
+        display: grid;
+        grid-template-columns: repeat(auto-fill, minmax(160px, 1fr));
+        gap: 1.2rem;
+    }
+</style>
+  
\ No newline at end of file
diff --git a/movie-group-8/src/components/WatchlistButton.vue b/movie-group-8/src/components/WatchlistButton.vue
new file mode 100644
index 0000000000000000000000000000000000000000..91458232887237079c738f99224113fc42242682
--- /dev/null
+++ b/movie-group-8/src/components/WatchlistButton.vue
@@ -0,0 +1,37 @@
+<template>
+  <button
+    type="button"
+    @click="toggleWatchlist"
+    class="mt-2 px-3 py-1 rounded text-white text-sm transition"
+    :class="isInWatchlist
+      ? 'bg-red-600 hover:bg-red-700'
+      : 'bg-green-600 hover:bg-green-700'"
+  >
+    {{ isInWatchlist ? 'Remove from Watchlist' : 'Add to Watchlist' }}
+  </button>
+</template>
+
+<script setup>
+import { ref, watch } from 'vue'
+import { useWatchlist } from '@/composables/useWatchlist.js'
+
+const props = defineProps({
+  movie: {
+    type: Object,
+    required: true
+  }
+})
+
+// We need a ref for the ID and a ref for the data
+const movieId = ref(props.movie.id)
+const movieData = ref(props.movie)
+
+// Pull in the composable
+const { isInWatchlist, toggleWatchlist } = useWatchlist(movieId, movieData)
+
+// If parent updates the prop, keep our ref in sync
+watch(() => props.movie, m => {
+  movieId.value = m.id
+  movieData.value = m
+})
+</script>
\ No newline at end of file
diff --git a/movie-group-8/src/composables/useMovies,js b/movie-group-8/src/composables/useMovies,js
new file mode 100644
index 0000000000000000000000000000000000000000..673d1e96b8d09008d2879a81c74758a1d19ac4a8
--- /dev/null
+++ b/movie-group-8/src/composables/useMovies,js
@@ -0,0 +1,29 @@
+import axios from 'axios'
+import { ref } from 'vue'
+
+const API_KEY = process.env.VITE_TMDB_API_KEY
+const BASE   = process.env.VUE_APP_TMDB_BASE
+
+export function useMovies() {
+  const results = ref([])
+  const loading = ref(false)
+  const error   = ref(null)
+
+  async function search(query) {
+    loading.value = true
+    error.value   = null
+    try {
+      const url = `${BASE}/search/movie`
+      const { data } = await axios.get(url, {
+        params: { api_key: API_KEY, query }
+      })
+      results.value = data.results
+    } catch (e) {
+      error.value = e
+    } finally {
+      loading.value = false
+    }
+  }
+
+  return { results, loading, error, search }
+}
\ No newline at end of file
diff --git a/movie-group-8/src/composables/useReviews.js b/movie-group-8/src/composables/useReviews.js
new file mode 100644
index 0000000000000000000000000000000000000000..04f72c7ee314c80235aaeccadff5d23ae4bb073d
--- /dev/null
+++ b/movie-group-8/src/composables/useReviews.js
@@ -0,0 +1,29 @@
+// src/composables/useReviews.js
+import { doc, getDoc, setDoc, collectionGroup, query, where, getDocs } from 'firebase/firestore'
+import { auth, db } from '@/firebase.js'
+
+export async function getUserReview(movieId) {
+  const user = auth.currentUser
+  if (!user) throw new Error('User not authenticated')
+
+  const ref  = doc(db, 'users', user.uid, 'reviews', movieId)
+  const snap = await getDoc(ref)
+  return snap.exists() ? snap.data() : null
+}
+
+export async function submitReview({ movieId, rating, text }) {
+  const user = auth.currentUser
+  if (!user) throw new Error('User not authenticated')
+
+  const ref = doc(db, 'users', user.uid, 'reviews', movieId)
+  await setDoc(ref, { movieId, rating, text, authorName: user.displayName, authorPFP: user.photoURL }, { merge: true })
+}
+
+export async function getAllReviews(movieId) {
+  const q  = query(
+    collectionGroup(db, 'reviews'),
+    where('movieId', '==', movieId)
+  )
+  const snaps = await getDocs(q)
+  return snaps.docs.map(d => ({ id: d.id, ...d.data() }))
+}
diff --git a/movie-group-8/src/composables/useWatchlist.js b/movie-group-8/src/composables/useWatchlist.js
new file mode 100644
index 0000000000000000000000000000000000000000..7dc09f0315ba6d966362d22a40f381bbf7b30da6
--- /dev/null
+++ b/movie-group-8/src/composables/useWatchlist.js
@@ -0,0 +1,47 @@
+import { ref, watchEffect } from 'vue'
+import { getAuth } from 'firebase/auth'
+import { getFirestore, doc, getDoc, setDoc, deleteDoc } from 'firebase/firestore'
+
+export function useWatchlist(movieId, movieData) {
+  const auth = getAuth()
+  const db = getFirestore()
+  const isInWatchlist = ref(false)
+
+  // Check on load & whenever movieId or user changes
+  watchEffect(async (onInvalidate) => {
+    const user = auth.currentUser
+    if (!user.value ?? user) {
+      isInWatchlist.value = false
+      return
+    }
+    const ref = doc(db, 'users', user.uid, 'watchlist', String(movieId.value ?? movieId))
+    const snap = await getDoc(ref)
+    isInWatchlist.value = snap.exists()
+  })
+
+  // Toggle function
+  const toggleWatchlist = async () => {
+    const user = auth.currentUser
+    if (!user) {
+      return alert('You need to log in.')
+    }
+    const ref = doc(db, 'users', user.uid, 'watchlist', String(movieId.value ?? movieId))
+
+    if (isInWatchlist.value) {
+      await deleteDoc(ref)
+      isInWatchlist.value = false
+      alert('Removed from watchlist')
+    } else {
+      await setDoc(ref, {
+        title: movieData.value.title,
+        poster_path: movieData.value.poster_path,
+        vote_average: movieData.value.vote_average,
+        status: 'planned'
+      })
+      isInWatchlist.value = true
+      alert('Added to watchlist')
+    }
+  }
+
+  return { isInWatchlist, toggleWatchlist }
+}
\ No newline at end of file
diff --git a/movie-group-8/src/firebase.js b/movie-group-8/src/firebase.js
new file mode 100644
index 0000000000000000000000000000000000000000..46b814efeb1edf7ccb8b1467ae6ea10211ee0d36
--- /dev/null
+++ b/movie-group-8/src/firebase.js
@@ -0,0 +1,20 @@
+import { initializeApp }    from 'firebase/app'
+import { getAuth }          from 'firebase/auth'
+import { getFirestore }     from 'firebase/firestore'
+
+// Replace with your actual config (you can move the object here or import it)
+const firebaseConfig = {
+    apiKey: "AIzaSyDiAEv_XsHelEiuvJsPDOEBZRcsr4Mg4R4",
+    authDomain: "movie-group-8.firebaseapp.com",
+    projectId: "movie-group-8",
+    storageBucket: "movie-group-8.firebasestorage.app",
+    messagingSenderId: "767837078763",
+    appId: "1:767837078763:web:e64ea7235d421e46852aae"
+  };
+
+// Initialize (idempotent if called twice)
+const app = initializeApp(firebaseConfig)
+
+// Export the singleton instances
+export const auth = getAuth(app)
+export const db   = getFirestore(app)
\ No newline at end of file
diff --git a/movie-group-8/src/main.js b/movie-group-8/src/main.js
new file mode 100644
index 0000000000000000000000000000000000000000..30fe276854aea0d9445bf901ad30693350a32187
--- /dev/null
+++ b/movie-group-8/src/main.js
@@ -0,0 +1,13 @@
+// main.js
+import { createApp } from 'vue'
+import App           from './App.vue'
+import router        from './router'
+
+// Ensure the Firebase singleton is initialized
+import '@/firebase.js'
+
+import './style.css'
+
+createApp(App)
+  .use(router)
+  .mount('#app')
\ No newline at end of file
diff --git a/movie-group-8/src/router/index.js b/movie-group-8/src/router/index.js
new file mode 100644
index 0000000000000000000000000000000000000000..fcbec63700fcb695109d7a419820f479345ec6d5
--- /dev/null
+++ b/movie-group-8/src/router/index.js
@@ -0,0 +1,90 @@
+import { getAuth, onAuthStateChanged } from 'firebase/auth';
+import { createRouter, createWebHistory } from 'vue-router';
+import Films from '@/views/Films.vue'
+import FilmDetails from '@/views/FilmDetails.vue'
+import ReviewPage from '@/views/ReviewPage.vue'
+
+const router = createRouter({
+    history: createWebHistory(),
+    routes: [
+        
+        
+        { path: '/', component: () => import('../views/Home.vue') },
+        { path: '/top-rated', component: () => import('../views/TopRated.vue'), meta: { requiresAuth: true }},
+        { path: '/register', component: () => import('../views/Register.vue') },
+        { path: '/login', component: () => import('../views/Login.vue') },
+        { path: '/recover-account', component: () => import('../views/RecoverAccount.vue') },
+        {
+            path: '/films',
+            name: 'Films',
+            component: Films,
+            meta: { requiresAuth: true }  
+        },
+        {
+            path: '/films/:id',
+            name: 'FilmDetails',
+            component: FilmDetails,
+        },
+
+        {
+            path: '/films/:id/review',
+            name: 'ReviewFilm',
+            component: ReviewPage,
+            props: true,
+        },
+
+        { 
+            path: '/watchlist', 
+            component: () => import('../views/WatchlistView.vue'), 
+            meta: { requiresAuth: true },
+        },
+        { 
+            path: '/profile', 
+            component: () => import('../views/Profile.vue'), 
+            meta: { requiresAuth: true },
+        },
+        { 
+            path: '/settings', 
+            component: () => import('../views/Settings.vue'), 
+            meta: { requiresAuth: true },
+        },
+        {
+            path: '/social',
+            name: 'Social',
+            component: () => import('../views/Social.vue'),
+            meta: { requiresAuth: true },
+        },
+        {
+            path: '/user/:id',
+            name: 'UserProfile',
+            component: () => import('../views/UserProfile.vue')
+        },
+    ],
+});
+
+const getCurrentUser = () => {
+    return new Promise((resolve, reject) => {
+        const removeListener = onAuthStateChanged(
+            getAuth(),
+            user => {
+                removeListener();
+                resolve(user);
+            },
+            reject);
+    });
+};
+
+router.beforeEach(async (to, from, next) => {
+    if (to.matched.some(record => record.meta.requiresAuth)) {
+        if (await getCurrentUser()) {
+            next();
+        } else {
+            alert('You must be logged in to see this page');
+            next("/");
+        }
+    } else {
+        next();
+    }
+});
+
+export default router;
\ No newline at end of file
diff --git a/movie-group-8/src/style.css b/movie-group-8/src/style.css
new file mode 100644
index 0000000000000000000000000000000000000000..a461c505f1f0c24ab12240ac3f7fa374dfa237fb
--- /dev/null
+++ b/movie-group-8/src/style.css
@@ -0,0 +1 @@
+@import "tailwindcss";
\ No newline at end of file
diff --git a/movie-group-8/src/views/FilmDetails.vue b/movie-group-8/src/views/FilmDetails.vue
new file mode 100644
index 0000000000000000000000000000000000000000..ebc1be66bed09a4459ba2f28e3a0edf377d69d5b
--- /dev/null
+++ b/movie-group-8/src/views/FilmDetails.vue
@@ -0,0 +1,226 @@
+<template>
+  <div class="film-details">
+    <button class="back-button" @click="$router.back()">← Back</button>
+
+    <div v-if="loading" class="loading">Loading movie...</div>
+    <div v-else-if="error" class="error">Error: {{ error }}</div>
+
+    <div v-else class="details-container">
+      <div class="poster-container">
+        <img
+          v-if="movie.poster_path"
+          :src="`https://image.tmdb.org/t/p/w500${movie.poster_path}`"
+          :alt="`${movie.title} poster`"
+          class="poster"
+        />
+      </div>
+
+      <div class="info-container">
+        <h1 class="title">{{ movie.title }}</h1>
+        <p class="release-date">{{ formatDate(movie.release_date) }}</p>
+        <p class="overview">{{ movie.overview }}</p>
+
+        <div v-if="trailerUrl" class="trailer">
+          <h2>Trailer</h2>
+          <iframe
+            :src="trailerUrl"
+            frameborder="0"
+            allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
+            allowfullscreen
+          ></iframe>
+        </div>
+
+        <button
+          type="button"
+          @click="toggleWatchlist"
+          class="mt-4 px-4 py-2 rounded text-white transition"
+          :class="isInWatchlist
+            ? 'bg-red-600 hover:bg-red-700'
+            : 'bg-green-600 hover:bg-green-700'"
+        >
+          {{ isInWatchlist ? 'Remove from Watchlist' : 'Add to Watchlist' }}
+        </button>
+
+        <RouterLink
+          :to="{ name: 'ReviewFilm', params: { id: movie.id } }"
+          class="mt-2 inline-block px-4 py-2 bg-blue-600 rounded text-white hover:bg-blue-700 text-center"
+        >
+          Review this Film
+        </RouterLink>
+        <div class="mt-6 reviews-section">
+          <h2 class="text-xl mb-2 text-white">User Reviews</h2>
+          <div v-if="reviewsLoading" class="text-gray-400">Loading reviews…</div>
+          <div v-else-if="!reviews.length" class="text-gray-400">No reviews yet.</div>
+          <ul v-else class="space-y-4">
+            <li v-for="(r, i) in reviews" :key="i" class="bg-neutral-800 p-4 rounded">
+              <div class="flex items-center mb-2">
+                <img
+                 v-if="r.authorPFP"
+                 :src="r.authorPFP"
+                  alt=""
+                  class="h-8 w-8 rounded-full mr-2"/>
+                  <span class="font-semibold text-white">{{ r.authorName }}</span>
+                  <span class="text-gray-400 ml-2">{{ r.rating }}★</span>
+                </div>
+                <p class="text-gray-200">{{ r.text }}</p>
+              </li>
+          </ul>
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script setup>
+import { ref, onMounted } from 'vue'
+import { useRoute } from 'vue-router'
+import { useWatchlist } from '@/composables/useWatchlist.js'
+import { getAuth } from 'firebase/auth'
+import { getFirestore, doc, getDoc } from 'firebase/firestore'
+import { getAllReviews }  from '@/composables/useReviews.js'
+
+const route = useRoute()
+const movie = ref(null)
+const trailerUrl = ref('')
+const loading = ref(true)
+const error = ref('')
+const movieId = ref(route.params.id)
+const { isInWatchlist, toggleWatchlist } = useWatchlist(movieId, movie)
+const reviews = ref([])
+const reviewsLoading = ref(true)
+
+function formatDate(dateString) {
+  if (!dateString) return 'Unknown'
+  return new Date(dateString).toLocaleDateString(undefined, {
+    year: 'numeric',
+    month: 'long',
+    day: 'numeric'
+  })
+}
+
+async function fetchMovieDetails(id) {
+  try {
+    const res = await fetch(
+      `${import.meta.env.VITE_TMDB_BASE}/movie/${id}?api_key=${import.meta.env.VITE_TMDB_API_KEY}`
+    )
+    if (!res.ok) throw new Error('Failed to fetch movie details')
+    movie.value = await res.json()
+  } catch (err) {
+    error.value = err.message
+  }
+}
+
+async function fetchMovieVideos(id) {
+  try {
+    const res = await fetch(
+      `${import.meta.env.VITE_TMDB_BASE}/movie/${id}/videos?api_key=${import.meta.env.VITE_TMDB_API_KEY}`
+    )
+    if (!res.ok) throw new Error('Failed to fetch movie videos')
+    const data = await res.json()
+    const trailer = data.results.find(v => v.type === 'Trailer' && v.site === 'YouTube')
+    if (trailer) {
+      trailerUrl.value = `https://www.youtube.com/embed/${trailer.key}`
+    }
+  } catch {
+    // ignore trailer errors
+  }
+}
+
+onMounted(async () => {
+  const id = route.params.id
+  await fetchMovieDetails(id)
+  await fetchMovieVideos(id)
+
+  const res = await getAllReviews(id)
+  reviews.value = res
+
+  const user = getAuth().currentUser
+  if (user) {
+    const db = getFirestore()
+    const watchRef = doc(db, 'users', user.uid, 'watchlist', String(id))
+    const snap = await getDoc(watchRef)
+    isInWatchlist.value = snap.exists()
+  }
+
+  reviewsLoading.value = false
+  loading.value = false
+})
+</script>
+
+<style scoped>
+.film-details {
+  background-color: #121212;
+  color: #ffffff;
+  min-height: 100vh;
+  padding: 2rem;
+  box-sizing: border-box;
+}
+
+.back-button {
+  color: #ffffff;
+  background: transparent;
+  margin-bottom: 1rem;
+  font-size: 1rem;
+}
+
+.loading,
+.error {
+  color: #ffffff;
+  text-align: center;
+  margin: 2rem 0;
+}
+
+.details-container {
+  display: flex;
+  flex-wrap: wrap;
+  gap: 2rem;
+  background-color: #1e1e1e;
+  padding: 2rem;
+  border-radius: 8px;
+  max-width: 900px;
+  margin: 2rem auto 0;
+}
+
+.poster-container {
+  flex: 0 0 300px;
+}
+
+.poster {
+  width: 100%;
+  border-radius: 4px;
+}
+
+.info-container {
+  flex: 1;
+  display: flex;
+  flex-direction: column;
+}
+
+.title {
+  font-size: 2rem;
+  margin: 0 0 0.5rem;
+}
+
+.release-date {
+  color: #bbbbbb;
+  margin-bottom: 1rem;
+}
+
+.overview {
+  color: #dddddd;
+  line-height: 1.6;
+  margin-bottom: 1.5rem;
+}
+
+.trailer h2 {
+  margin-bottom: 0.5rem;
+  color: #ffffff;
+}
+
+.trailer iframe {
+  width: 100%;
+  height: 300px;
+  border-radius: 8px;
+  border: none;
+}
+</style>
diff --git a/movie-group-8/src/views/Films.vue b/movie-group-8/src/views/Films.vue
new file mode 100644
index 0000000000000000000000000000000000000000..31474cbb988dd844fe1c2a8548fef94c70db2bfa
--- /dev/null
+++ b/movie-group-8/src/views/Films.vue
@@ -0,0 +1,106 @@
+<template>
+  <div class="films-container">
+    <!-- Centered search bar pushed down slightly -->
+    <div class="search-wrapper">
+      <input
+        v-model="term"
+        @keyup.enter="onSearch"
+        placeholder="Search movies…"
+        class="search-input"
+      />
+    </div>
+
+    <div v-if="loading" class="loading">Loading…</div>
+    <div v-else-if="error" class="error">Error: {{ error.message }}</div>
+
+    <!-- Movie grid -->
+    <MovieList :movies="results" />
+  </div>
+</template>
+
+<script setup>
+import { ref, onMounted } from 'vue'
+import axios from 'axios'
+import MovieList from '../components/MovieList.vue'
+
+const API_KEY = import.meta.env.VITE_TMDB_API_KEY
+const BASE    = import.meta.env.VITE_TMDB_BASE
+
+const term    = ref('')
+const results = ref([])
+const loading = ref(false)
+const error   = ref(null)
+
+async function search(query) {
+  loading.value = true
+  error.value   = null
+  try {
+    const { data } = await axios.get(`${BASE}/search/movie`, {
+      params: { api_key: API_KEY, query }
+    })
+    results.value = data.results
+  } catch (e) {
+    error.value = e
+  } finally {
+    loading.value = false
+  }
+}
+
+async function fetchPopular() {
+  loading.value = true
+  error.value   = null
+  try {
+    const { data } = await axios.get(`${BASE}/movie/popular`, {
+      params: { api_key: API_KEY }
+    })
+    results.value = data.results
+  } catch (e) {
+    error.value = e
+  } finally {
+    loading.value = false
+  }
+}
+
+function onSearch() {
+  if (term.value.trim()) {
+    search(term.value)
+  } else {
+    fetchPopular()
+  }
+}
+
+onMounted(fetchPopular)
+</script>
+
+<style scoped>
+.films-container {
+  padding-top: 6rem; /* adjust to nav height + spacing */
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  width: 100%;
+}
+
+.search-wrapper {
+  width: 100%;
+  max-width: 500px;
+  margin-bottom: 1.5rem;
+  display: flex;
+  justify-content: center;
+}
+
+.search-input {
+  width: 100%;
+  padding: 0.75rem 1rem;
+  font-size: 1.1rem;
+  border: 1px solid #ccc;
+  border-radius: 4px;
+  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
+}
+
+.loading,
+.error {
+  margin: 1rem 0;
+  font-weight: bold;
+}
+</style>
diff --git a/movie-group-8/src/views/Home.vue b/movie-group-8/src/views/Home.vue
new file mode 100644
index 0000000000000000000000000000000000000000..90271525376142187d497587ad17fb21e1fc930b
--- /dev/null
+++ b/movie-group-8/src/views/Home.vue
@@ -0,0 +1,75 @@
+<template>
+    <div class="h-screen bg-neutral-900 p-6">
+      <div class="max-w-[85rem] px-4 py-10 sm:px-6 lg:px-8 lg:py-14 mx-auto dark">
+        <div class="relative p-6 md:p-16">
+          <div class="relative z-10 lg:grid lg:grid-cols-12 lg:gap-16 lg:items-center">
+            <div class="mb-10 lg:mb-0 lg:col-span-6 lg:col-start-8 lg:order-2">
+              <h2 class="text-2xl text-neutral-200 font-bold sm:text-3xl">
+                Welcome to SceneIt! Your new portal for all your movie needs.
+              </h2>
+              <nav class="grid gap-4 mt-5 md:mt-10" aria-label="Tabs" role="tablist" aria-orientation="vertical">
+                <button @click="setTab('list')" :class="{ 'bg-neutral-700 shadow-md': activeTab === 'list' }" class="text-start hover:bg-neutral-700 focus:bg-neutral-700 p-4 md:p-5 rounded-xl">
+                  <span class="flex gap-x-6">
+                    <svg class="shrink-0 mt-2 size-6 md:size-7 text-blue-500" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M5 5.5A3.5 3.5 0 0 1 8.5 2H12v7H8.5A3.5 3.5 0 0 1 5 5.5z"/><path d="M12 2h3.5a3.5 3.5 0 1 1 0 7H12V2z"/><path d="M12 12.5a3.5 3.5 0 1 1 7 0 3.5 3.5 0 1 1-7 0z"/><path d="M5 19.5A3.5 3.5 0 0 1 8.5 16H12v3.5a3.5 3.5 0 1 1-7 0z"/><path d="M5 12.5A3.5 3.5 0 0 1 8.5 9H12v7H8.5A3.5 3.5 0 0 1 5 12.5z"/></svg>
+                    <span class="grow">
+                      <span class="block text-lg font-semibold text-blue-500">Movie Lists</span>
+                      <span class="block mt-1 text-neutral-200">Organize your favorites, track what you've seen, and never forget a great film again!</span>
+                    </span>
+                  </span>
+                </button>
+                <button @click="setTab('reviews')" :class="{ 'bg-neutral-700 shadow-md': activeTab === 'reviews' }" class="text-start hover:bg-neutral-700 focus:bg-neutral-700 p-4 md:p-5 rounded-xl">
+                  <span class="flex gap-x-6">
+                    <svg class="shrink-0 mt-2 size-6 md:size-7 text-blue-500" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m12 3-1.912 5.813a2 2 0 0 1-1.275 1.275L3 12l5.813 1.912a2 2 0 0 1 1.275 1.275L12 21l1.912-5.813a2 2 0 0 1 1.275-1.275L21 12l-5.813-1.912a2 2 0 0 1-1.275-1.275L12 3Z"/><path d="M5 3v4"/><path d="M19 17v4"/><path d="M3 5h4"/><path d="M17 19h4"/></svg>
+                    <span class="grow">
+                      <span class="block text-lg font-semibold text-blue-500">Movie Reviews</span>
+                      <span class="block mt-1 text-neutral-200">Read reviews from fellow movie lovers or share your own.</span>
+                    </span>
+                  </span>
+                </button>
+                <button @click="setTab('tmdb')" :class="{ 'bg-neutral-700 shadow-md': activeTab === 'tmdb' }" class="text-start hover:bg-neutral-700 focus:bg-neutral-700 p-4 md:p-5 rounded-xl">
+                  <span class="flex gap-x-6">
+                    <img class="h-10 w-auto" src="../assets/TMDB.svg" alt="TMDb Logo" />
+                    <span class="grow">
+                      <span class="block text-lg font-semibold text-blue-500">Powered by TMDB</span>
+                      <span class="block mt-1 text-neutral-200">Data provided by The Movie Database.</span>
+                    </span>
+                  </span>
+                </button>
+              </nav>
+            </div>
+            <div class="lg:col-span-6">
+              <div class="relative">
+                <img v-if="activeTab === 'list'" class="shadow-xl shadow-gray-900/20 rounded-xl object-cover w-[560px] h-[720px]" src="../assets/home_poster.png" alt="Movie Lists">
+                <img v-if="activeTab === 'reviews'" class="shadow-xl shadow-gray-900/20 rounded-xl object-cover w-[560px] h-[720px]" src="../assets/review_poster.png" alt="Movie Reviews">
+                <img v-if="activeTab === 'tmdb'" class="shadow-xl shadow-gray-900/20 rounded-xl object-cover w-[560px] h-[720px]" src="../assets/tmdb_poster.png" alt="Powered by TMDB">
+              </div>
+            </div>
+          </div>
+          <div class="absolute inset-0 bg-neutral-800 rounded-xl"></div>
+        </div>
+      </div>
+    </div>
+</template>
+  
+<script>
+export default {
+data() {
+    return {
+    activeTab: 'list',
+    tabs: ['list', 'reviews', 'tmdb'],
+    };
+},
+methods: {
+    setTab(tab) {
+    this.activeTab = tab;
+    }
+},
+mounted() {
+    setInterval(() => {
+    const currentIndex = this.tabs.indexOf(this.activeTab);
+    const nextIndex = (currentIndex + 1) % this.tabs.length;
+    this.activeTab = this.tabs[nextIndex];
+    }, 5000);
+}
+};
+</script>
\ No newline at end of file
diff --git a/movie-group-8/src/views/Login.vue b/movie-group-8/src/views/Login.vue
new file mode 100644
index 0000000000000000000000000000000000000000..0b539b1f8a6880c589d20a59837fc7afc0c1f5fc
--- /dev/null
+++ b/movie-group-8/src/views/Login.vue
@@ -0,0 +1,143 @@
+<template>
+    <div class="flex items-center justify-center min-h-screen bg-gray-100 dark:bg-neutral-900">
+      <div class="mt-7 bg-white border border-gray-200 rounded-xl shadow-2xs dark:bg-neutral-900 dark:border-neutral-700 min-w-96">
+        <div class="p-4 sm:p-7">
+          <div class="text-center">
+            <h1 class="block text-2xl font-bold text-gray-800 dark:text-white">Login</h1>
+            <p class="mt-2 text-sm text-gray-600 dark:text-neutral-400">
+              Don't have an account?
+              <a class="text-blue-600 decoration-2 hover:underline focus:outline-hidden focus:underline font-medium dark:text-blue-500" href="/register">
+                Sign up here
+              </a>
+            </p>
+          </div>
+  
+          <div class="mt-5">
+            <button @click="signInWithGoogle" type="button" class="w-full py-3 px-4 inline-flex justify-center items-center gap-x-2 text-sm font-medium rounded-lg border border-gray-200 bg-white text-gray-800 shadow-2xs hover:bg-gray-50 focus:outline-hidden focus:bg-gray-50 dark:bg-neutral-900 dark:border-neutral-700 dark:text-white dark:hover:bg-neutral-800">
+              <svg class="w-4 h-auto" width="46" height="47" viewBox="0 0 46 47" fill="none">
+                <path d="M46 24.0287C46 22.09 45.8533 20.68 45.5013 19.2112H23.4694V27.9356H36.4069C36.1429 30.1094 34.7347 33.37 31.5957 35.5731L31.5663 35.8669L38.5191 41.2719L38.9885 41.3306C43.4477 37.2181 46 31.1669 46 24.0287Z" fill="#4285F4"/>
+                <path d="M23.4694 47C29.8061 47 35.1161 44.9144 39.0179 41.3012L31.625 35.5437C29.6301 36.9244 26.9898 37.8937 23.4987 37.8937C17.2793 37.8937 12.0281 33.7812 10.1505 28.1412L9.88649 28.1706L2.61097 33.7812L2.52296 34.0456C6.36608 41.7125 14.287 47 23.4694 47Z" fill="#34A853"/>
+                <path d="M10.1212 28.1413C9.62245 26.6725 9.32908 25.1156 9.32908 23.5C9.32908 21.8844 9.62245 20.3275 10.0918 18.8588V18.5356L2.75765 12.8369L2.52296 12.9544C0.909439 16.1269 0 19.7106 0 23.5C0 27.2894 0.909439 30.8731 2.49362 34.0456L10.1212 28.1413Z" fill="#FBBC05"/>
+                <path d="M23.4694 9.07688C27.8699 9.07688 30.8622 10.9863 32.5344 12.5725L39.1645 6.11C35.0867 2.32063 29.8061 0 23.4694 0C14.287 0 6.36607 5.2875 2.49362 12.9544L10.0918 18.8588C11.9987 13.1894 17.25 9.07688 23.4694 9.07688Z" fill="#EB4335"/>
+              </svg>
+              Sign in with Google
+            </button>
+  
+            <div class="py-3 flex items-center text-xs text-gray-400 uppercase before:flex-1 before:border-t before:border-gray-200 before:me-6 after:flex-1 after:border-t after:border-gray-200 after:ms-6 dark:text-neutral-500 dark:before:border-neutral-600 dark:after:border-neutral-600">Or</div>
+  
+            <form @submit.prevent="login">
+              <div class="grid gap-y-4">
+                <div>
+                  <label class="block text-sm mb-2 dark:text-white">Email Address</label>
+                  <input type="email" v-model="email" class="w-full border border-gray-400 rounded-lg p-2 dark:text-white" required />
+                </div>
+
+                <div>
+                    <div class="flex justify-between items-center mb-2">
+                        <label class="block text-sm dark:text-white">Password</label>
+                        <a class="text-sm text-blue-600 decoration-2 hover:underline focus:outline-hidden focus:underline font-medium dark:text-blue-500" href="/recover-account">
+                        Forgot password?
+                        </a>
+                    </div>
+                    <input type="password" v-model="password" class="w-full border border-gray-400 rounded-lg p-2 dark:text-white" required />
+                </div>
+
+                <button 
+                    type="submit" 
+                    @click="login"
+                    :disabled="loading"
+                    class="w-full py-3 bg-blue-600 text-white rounded-lg flex justify-center items-center gap-2 hover:bg-blue-700 transition duration-300 disabled:opacity-50 disabled:pointer-events-none"
+                    >
+                    <svg v-if="loading" class="w-5 h-5 animate-spin text-white" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
+                        <circle cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4" stroke-linecap="round" class="opacity-25"/>
+                        <path d="M4 12a8 8 0 018-8" stroke="currentColor" stroke-width="4" stroke-linecap="round" class="opacity-75"/>
+                    </svg>
+                    <span v-if="!loading">Sign in</span>
+                    <span v-else>Loading...</span>
+                </button> 
+              </div>
+            </form>
+          </div>
+        </div>
+      </div>
+    </div>
+</template>
+  
+<script setup>
+import { ref } from 'vue';
+import {
+  getAuth,
+  signInWithEmailAndPassword,
+  signInWithPopup,
+  GoogleAuthProvider
+} from 'firebase/auth';
+import {
+  getFirestore,
+  doc,
+  getDoc,
+  setDoc
+} from 'firebase/firestore';
+import { useRouter } from 'vue-router';
+import defaultAvatar from '@/assets/default_avatar.png'
+
+const email = ref('');
+const password = ref('');
+const router = useRouter();
+const loading = ref(false);
+
+const db = getFirestore();
+
+const createUserIfNotExists = async (user) => {
+  const userRef = doc(db, 'users', user.uid)
+  const userSnap = await getDoc(userRef)
+
+  if (!userSnap.exists()) {
+    const photoURLToSave = user.photoURL || defaultAvatar
+
+    await setDoc(userRef, {
+      uid: user.uid,
+      displayName: user.displayName || 'Anonymous',
+      photoURL: photoURLToSave
+    })
+  }
+}
+
+const login = async () => {
+  try {
+    loading.value = true;
+    const auth = getAuth();
+    const userCredential = await signInWithEmailAndPassword(auth, email.value, password.value);
+    const user = userCredential.user;
+
+    await createUserIfNotExists(user);
+
+    console.log('Successfully logged in!');
+    router.push('/films');
+  } catch (error) {
+    console.error(error);
+    alert(error.message);
+  } finally {
+    loading.value = false;
+  }
+};
+
+const signInWithGoogle = async () => {
+  try {
+    loading.value = true;
+    const auth = getAuth();
+    const provider = new GoogleAuthProvider();
+    const result = await signInWithPopup(auth, provider);
+    const user = result.user;
+
+    await createUserIfNotExists(user);
+
+    console.log('Successfully signed in with Google!');
+    router.push('/films');
+  } catch (error) {
+    console.error(error);
+    alert(error.message);
+  } finally {
+    loading.value = false;
+  }
+};
+</script>
\ No newline at end of file
diff --git a/movie-group-8/src/views/Profile.vue b/movie-group-8/src/views/Profile.vue
new file mode 100644
index 0000000000000000000000000000000000000000..94358a61e2fbb94778cbbba0fb77cd7cdc36f920
--- /dev/null
+++ b/movie-group-8/src/views/Profile.vue
@@ -0,0 +1,321 @@
+<template>
+  <div class="min-h-screen bg-gray-100 dark:bg-neutral-900 text-center px-4 py-8">
+    <div class="text-center mb-10">
+      <img
+        v-if="userPhotoURL"
+        :src="userPhotoURL"
+        alt="Profile Picture"
+        class="w-28 h-28 mx-auto rounded-full object-cover mb-4"
+      />
+      <h1 class="text-3xl font-bold text-gray-800 dark:text-white">{{ displayName || 'Anonymous' }}</h1>
+      <p class="text-sm text-gray-600 dark:text-neutral-400">{{ userEmail }}</p>
+      <p class="mt-2 text-base text-gray-600 dark:text-neutral-400">{{ description }}</p>
+
+      <button
+        @click="showForm = !showForm"
+        class="mt-4 px-5 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition"
+      >
+        {{ showForm ? 'Close Update Form' : 'Update Profile' }}
+      </button>
+    </div>
+
+    <!-- Update Form -->
+    <div v-if="showForm" class="max-w-xl mx-auto mb-12 bg-white dark:bg-neutral-800 p-6 rounded-xl border dark:border-neutral-700">
+      <form @submit.prevent="handleUpdateProfile" class="grid gap-6">
+        <div>
+          <label class="block text-sm mb-2 dark:text-white">Display Name</label>
+          <input type="text" v-model="displayName" class="w-full border border-gray-400 rounded-lg p-2 dark:text-white" required />
+        </div>
+
+        <div>
+          <label class="block text-sm mb-2 dark:text-white">Description</label>
+          <textarea v-model="description" rows="3" class="w-full border border-gray-400 rounded-lg p-2 dark:text-white" />
+        </div>
+
+        <div>
+          <label class="block text-sm mb-2 dark:text-white">New Password</label>
+          <input type="password" v-model="newPassword" class="w-full border border-gray-400 rounded-lg p-2 dark:text-white" />
+          <p v-if="newPassword && passwordError" class="text-red-500 text-sm mt-1">{{ passwordError }}</p>
+        </div>
+
+        <button
+          type="submit"
+          :disabled="loading || !!passwordError"
+          class="w-full py-3 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition disabled:opacity-50"
+        >
+          <span v-if="!loading">Save Changes</span>
+          <span v-else>Saving...</span>
+        </button>
+      </form>
+    </div>
+
+    <!-- Tabs -->
+    <div class="flex justify-center gap-4 mb-6">
+      <button
+        v-for="tab in ['watchlist', 'followers', 'following', 'reviews']"
+        :key="tab"
+        @click="activeTab = tab"
+        :class="[
+          'px-4 py-2 rounded-lg text-sm font-medium',
+          activeTab === tab
+            ? 'bg-blue-600 text-white'
+            : 'bg-white dark:bg-neutral-700 text-gray-800 dark:text-white border border-gray-300 dark:border-neutral-600'
+        ]"
+      >
+        {{ tab.charAt(0).toUpperCase() + tab.slice(1) }}
+      </button>
+    </div>
+
+    <!-- Tab Content -->
+    <div v-if="activeTab === 'watchlist'" class="grid gap-4 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4">
+      <router-link
+        v-for="movie in watchlist"
+        :key="movie.id"
+        :to="`/films/${movie.id}`"
+        class="block bg-white dark:bg-neutral-800 border dark:border-neutral-700 rounded-lg p-3 hover:opacity-90 transition"
+      >
+        <img
+          v-if="movie.poster_path"
+          :src="'https://image.tmdb.org/t/p/w342' + movie.poster_path"
+          alt="Poster"
+          class="w-full h-60 object-cover rounded mb-3"
+        />
+        <p class="font-semibold text-gray-800 dark:text-white">{{ movie.title }}</p>
+        <p class="text-sm text-gray-600 dark:text-neutral-400">⭐ {{ movie.vote_average }} — {{ movie.status }}</p>
+      </router-link>
+    </div>
+
+    <!-- Followers -->
+    <div v-else-if="activeTab === 'followers'" class="grid gap-4 max-w-xl mx-auto">
+      <div
+        v-for="follower in followers"
+        :key="follower.uid"
+        class="flex items-center justify-between p-4 bg-white dark:bg-neutral-800 border dark:border-neutral-700 rounded-lg"
+      >
+        <router-link :to="`/user/${follower.uid}`" class="flex items-center gap-4">
+          <img
+            :src="follower.photoURL || 'https://via.placeholder.com/150'"
+            alt="Avatar"
+            class="w-12 h-12 rounded-full object-cover"
+          />
+          <span class="text-lg font-semibold text-gray-800 dark:text-white">{{ follower.displayName }}</span>
+        </router-link>
+      </div>
+    </div>
+
+    <!-- Following -->
+    <div v-else-if="activeTab === 'following'" class="grid gap-4 max-w-xl mx-auto">
+      <div
+        v-for="followed in following"
+        :key="followed.uid"
+        class="flex items-center justify-between p-4 bg-white dark:bg-neutral-800 border dark:border-neutral-700 rounded-lg"
+      >
+        <router-link :to="`/user/${followed.uid}`" class="flex items-center gap-4">
+          <img
+            :src="followed.photoURL || 'https://via.placeholder.com/150'"
+            alt="Avatar"
+            class="w-12 h-12 rounded-full object-cover"
+          />
+          <span class="text-lg font-semibold text-gray-800 dark:text-white">{{ followed.displayName }}</span>
+        </router-link>
+      </div>
+    </div>
+
+    <!-- Reviews -->
+    <div v-else-if="activeTab === 'reviews'" class="max-w-2xl mx-auto space-y-6">
+      <div v-if="reviewsLoading" class="text-gray-600 dark:text-gray-400">Loading reviews…</div>
+      <div v-else-if="!reviews.length" class="text-gray-600 dark:text-gray-400">No reviews yet.</div>
+      <div v-else class="space-y-4">
+        <div v-for="r in reviews" :key="r.id" class="bg-white dark:bg-neutral-800 p-4 rounded-lg border dark:border-neutral-700 text-left">
+          <router-link :to="`/films/${r.movieId}`" class="font-semibold text-lg text-blue-600 dark:text-blue-400 hover:underline">
+            {{ r.movieTitle }}
+          </router-link>
+          <div class="mt-1 text-sm text-gray-800 dark:text-gray-200">⭐ {{ r.rating }}</div>
+          <p class="mt-2 text-gray-700 dark:text-neutral-300">{{ r.text }}</p>
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script setup>
+import {
+  collection,
+  getDocs,
+  doc,
+  getDoc,
+  updateDoc,
+  getFirestore,
+} from 'firebase/firestore'
+
+import {
+  getAuth,
+  updateProfile as firebaseUpdateProfile,
+  updatePassword
+} from 'firebase/auth'
+
+import { ref, computed, onMounted } from 'vue'
+
+const TMDB_API_KEY = import.meta.env.VITE_TMDB_API_KEY
+
+const auth = getAuth()
+const db = getFirestore()
+
+const user = ref(null)
+const userEmail = ref('')
+const displayName = ref('')
+const description = ref('')
+const userPhotoURL = ref('')
+const newPassword = ref('')
+const showForm = ref(false)
+const activeTab = ref('watchlist')
+const loading = ref(false)
+
+const watchlist = ref([])
+const followers = ref([])
+const following = ref([])
+const reviews = ref([])
+const reviewsLoading = ref(true)
+
+const passwordError = computed(() => {
+  if (!newPassword.value) return ''
+  if (newPassword.value.length < 8) return 'Password must be at least 8 characters.'
+  if (!/[A-Z]/.test(newPassword.value)) return 'Must include at least 1 uppercase letter.'
+  if (!/[0-9]/.test(newPassword.value)) return 'Must include at least 1 number.'
+  if (!/[@$!%*?&]/.test(newPassword.value)) return 'Must include at least 1 special character (@$!%*?&).'
+  return ''
+})
+
+const fetchFollowing = async () => {
+  const followingSnap = await getDocs(collection(db, 'users', user.value.uid, 'following'))
+  const followedIds = followingSnap.docs.map(doc => doc.id)
+
+  const usersList = []
+
+  for (const id of followedIds) {
+    const docSnap = await getDoc(doc(db, 'users', id))
+    if (docSnap.exists()) {
+      const data = docSnap.data()
+      // Only push if the user has a displayName or isn’t hidden
+      if (!data.hidden && data.displayName) {
+        usersList.push({ uid: id, ...data })
+      }
+    }
+  }
+
+  following.value = usersList
+}
+
+const fetchFollowers = async () => {
+  if (!user.value) return
+
+  const allUsersSnap = await getDocs(collection(db, 'users'))
+  const followersList = []
+
+  for (const docSnap of allUsersSnap.docs) {
+    const otherUid = docSnap.id
+    if (otherUid === user.value.uid) continue
+
+    const followDocRef = doc(db, 'users', otherUid, 'following', user.value.uid)
+    const followDocSnap = await getDoc(followDocRef)
+
+    if (followDocSnap.exists()) {
+      const otherUserData = docSnap.data()
+      if (otherUserData.displayName && !otherUserData.hidden) {
+        followersList.push({ uid: otherUid, ...otherUserData })
+      }
+    }
+  }
+
+  followers.value = followersList
+}
+
+const fetchReviews = async () => {
+  const uid = user.value.uid
+  const snap = await getDocs(collection(db, 'users', uid, 'reviews'))
+  const revDocs = snap.docs.map(d => ({ id: d.id, ...d.data() }))
+
+  // Fetch all movie titles in parallel
+  const withTitles = await Promise.all(
+    revDocs.map(async r => {
+      // Call TMDB for each movieId
+      const res = await fetch(
+        `https://api.themoviedb.org/3/movie/${r.movieId}?api_key=${TMDB_API_KEY}`
+      )
+      const movieData = await res.json()
+      return {
+        id: r.id,
+        movieId: r.movieId,
+        movieTitle: movieData.title,
+        rating: r.rating,
+        text: r.text,
+      }
+    })
+  )
+
+  reviews.value = withTitles
+  reviewsLoading.value = false
+}
+
+const handleUpdateProfile = async () => {
+  if (passwordError.value) return
+
+  loading.value = true
+  try {
+    const authUser = auth.currentUser
+    if (!authUser) throw new Error('No authenticated user')
+
+    // Update the Firebase Auth profile
+    await firebaseUpdateProfile(authUser, {
+      displayName: displayName.value
+    })
+
+    if (newPassword.value) {
+      await updatePassword(authUser, newPassword.value)
+    }
+
+    // Update your Firestore user document
+    const userDocRef = doc(db, 'users', authUser.uid)
+    await updateDoc(userDocRef, {
+      displayName: displayName.value,
+      description: description.value
+    })
+
+    alert('Profile updated successfully!')
+    showForm.value = false
+
+  } catch (error) {
+    alert(error.message)
+  } finally {
+    loading.value = false
+  }
+}
+
+onMounted(async () => {
+  const authUser = auth.currentUser
+  if (!authUser) return
+
+  user.value = authUser
+  userEmail.value = authUser.email
+  displayName.value = authUser.displayName || ''
+  userPhotoURL.value = authUser.photoURL || ''
+
+  const userDoc = await getDoc(doc(db, 'users', user.value.uid))
+  if (userDoc.exists()) {
+    description.value = userDoc.data().description || ''
+  }
+
+  const snapshot = await getDocs(collection(db, 'users', user.value.uid, 'watchlist'))
+  watchlist.value = snapshot.docs.map(doc => ({ id: doc.id, ...doc.data() }))
+
+  await fetchFollowing()
+  await fetchFollowers()
+  await fetchReviews()
+})
+</script>
+
+<style scoped>
+input:disabled {
+  cursor: not-allowed;
+}
+</style>
\ No newline at end of file
diff --git a/movie-group-8/src/views/RecoverAccount.vue b/movie-group-8/src/views/RecoverAccount.vue
new file mode 100644
index 0000000000000000000000000000000000000000..409f2f1d58396b37b9e8881937ccb84ec23d2a38
--- /dev/null
+++ b/movie-group-8/src/views/RecoverAccount.vue
@@ -0,0 +1 @@
+<template>Recover Account</template>
\ No newline at end of file
diff --git a/movie-group-8/src/views/Register.vue b/movie-group-8/src/views/Register.vue
new file mode 100644
index 0000000000000000000000000000000000000000..1f4d10208d7c4e78d2394a6ef467358234f9f5f4
--- /dev/null
+++ b/movie-group-8/src/views/Register.vue
@@ -0,0 +1,132 @@
+<template>
+  <div class="flex items-center justify-center min-h-screen bg-gray-100 dark:bg-neutral-900">
+    <div class="mt-7 bg-white border border-gray-200 rounded-xl shadow-2xs dark:bg-neutral-900 dark:border-neutral-700 min-w-96">
+      <div class="p-4 sm:p-7">
+        <div class="text-center">
+          <h1 class="block text-2xl font-bold text-gray-800 dark:text-white">Register</h1>
+          <p class="mt-2 text-sm text-gray-600 dark:text-neutral-400">
+            Already have an account?
+            <a class="text-blue-600 decoration-2 hover:underline font-medium dark:text-blue-500" href="/login">
+              Sign in here
+            </a>
+          </p>
+        </div>
+
+        <div class="mt-5">
+          <button @click="signInWithGoogle" type="button" class="w-full py-3 px-4 flex justify-center items-center gap-2 text-sm font-medium rounded-lg border border-gray-200 bg-white text-gray-800 shadow-2xs hover:bg-gray-50 dark:bg-neutral-900 dark:border-neutral-700 dark:text-white dark:hover:bg-neutral-800">
+            <svg class="w-4 h-auto" width="46" height="47" viewBox="0 0 46 47" fill="none">
+              <path d="M46 24.0287C46 22.09 45.8533 20.68 45.5013 19.2112H23.4694V27.9356H36.4069C36.1429 30.1094 34.7347 33.37 31.5957 35.5731L31.5663 35.8669L38.5191 41.2719L38.9885 41.3306C43.4477 37.2181 46 31.1669 46 24.0287Z" fill="#4285F4"/>
+              <path d="M23.4694 47C29.8061 47 35.1161 44.9144 39.0179 41.3012L31.625 35.5437C29.6301 36.9244 26.9898 37.8937 23.4987 37.8937C17.2793 37.8937 12.0281 33.7812 10.1505 28.1412L9.88649 28.1706L2.61097 33.7812L2.52296 34.0456C6.36608 41.7125 14.287 47 23.4694 47Z" fill="#34A853"/>
+              <path d="M10.1212 28.1413C9.62245 26.6725 9.32908 25.1156 9.32908 23.5C9.32908 21.8844 9.62245 20.3275 10.0918 18.8588V18.5356L2.75765 12.8369L2.52296 12.9544C0.909439 16.1269 0 19.7106 0 23.5C0 27.2894 0.909439 30.8731 2.49362 34.0456L10.1212 28.1413Z" fill="#FBBC05"/>
+              <path d="M23.4694 9.07688C27.8699 9.07688 30.8622 10.9863 32.5344 12.5725L39.1645 6.11C35.0867 2.32063 29.8061 0 23.4694 0C14.287 0 6.36607 5.2875 2.49362 12.9544L10.0918 18.8588C11.9987 13.1894 17.25 9.07688 23.4694 9.07688Z" fill="#EB4335"/>
+            </svg>
+            Sign up with Google
+          </button>
+
+          <div class="py-3 flex items-center text-xs text-gray-400 uppercase before:flex-1 before:border-t before:border-gray-200 after:flex-1 after:border-t after:border-gray-200 dark:text-neutral-500 dark:before:border-neutral-600 dark:after:border-neutral-600">Or</div>
+
+          <form @submit.prevent="register">
+            <div class="grid gap-y-4">
+              <div>
+                <label class="block text-sm mb-2 dark:text-white">Email address</label>
+                <input type="email" v-model="email" class="w-full border border-gray-400 rounded-lg p-2 dark:text-white" required />
+              </div>
+
+              <div>
+                <label class="block text-sm mb-2 dark:text-white">Password</label>
+                <input type="password" v-model="password" class="w-full border border-gray-400 rounded-lg p-2 dark:text-white" required />
+                <p v-if="password && passwordError" class="text-red-500 text-sm mt-1">{{ passwordError }}</p>
+              </div>
+
+              <div>
+                <label class="block text-sm mb-2 dark:text-white">Confirm Password</label>
+                <input type="password" v-model="confirmPassword" class="w-full border border-gray-400 rounded-lg p-2 dark:text-white" required />
+                <p v-if="confirmPassword && confirmPasswordError" class="text-red-500 text-sm mt-1">{{ confirmPasswordError }}</p>
+              </div>
+
+              <button 
+                  type="submit" 
+                  :disabled="loading || !!passwordError || !!confirmPasswordError"
+                  class="w-full py-3 bg-blue-600 text-white rounded-lg flex justify-center items-center gap-2 hover:bg-blue-700 transition duration-300 disabled:opacity-50 disabled:pointer-events-none"
+                  >
+                  <span v-if="!loading">Sign up</span>
+                  <span v-else>Loading...</span>
+              </button>
+            </div>
+          </form>
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script setup>
+import { ref, computed } from 'vue';
+import { useRouter } from 'vue-router';
+import defaultAvatar from '@/assets/default_avatar.png'
+
+import { 
+  getAuth,
+  createUserWithEmailAndPassword,
+ } from 'firebase/auth';
+import {
+  getFirestore,
+  doc,
+  setDoc
+} from 'firebase/firestore';
+
+const email = ref('');
+const password = ref('');
+const confirmPassword = ref('');
+const router = useRouter();
+const loading = ref(false);
+
+// Password validation
+const passwordError = computed(() => {
+if (password.value.length < 8) return "Password must be at least 8 characters.";
+if (!/[A-Z]/.test(password.value)) return "Must include at least 1 uppercase letter.";
+if (!/[0-9]/.test(password.value)) return "Must include at least 1 number.";
+if (!/[@$!%*?&]/.test(password.value)) return "Must include at least 1 special character (@$!%*?&).";
+return "";
+});
+
+// Confirm password validation
+const confirmPasswordError = computed(() => {
+if (confirmPassword.value && confirmPassword.value !== password.value) {
+  return "Passwords do not match.";
+}
+return "";
+});
+
+// Register function
+const register = async () => {
+if (passwordError.value || confirmPasswordError.value) return;
+loading.value = true;
+try {
+  const auth = getAuth()
+  const db = getFirestore()
+  const userCredential = await createUserWithEmailAndPassword(auth, email.value, password.value)
+  const user = userCredential.user
+
+  // Set default profile info
+  await updateProfile(user, {
+    displayName: 'New User',
+    photoURL: defaultAvatar
+  })
+
+  // Create Firestore document for this user
+  await setDoc(doc(db, 'users', user.uid), {
+    displayName: 'New User',
+    photoURL: defaultAvatar,
+    description: '',
+    hidden: false
+  })
+
+  router.push('/films')
+} catch (error) {
+  alert(error.message);
+} finally {
+  loading.value = false;
+}
+};
+</script>
diff --git a/movie-group-8/src/views/ReviewPage.vue b/movie-group-8/src/views/ReviewPage.vue
new file mode 100644
index 0000000000000000000000000000000000000000..7ea921a2631c922299401a5883b07c1a71d559f7
--- /dev/null
+++ b/movie-group-8/src/views/ReviewPage.vue
@@ -0,0 +1,218 @@
+<template>
+    <div class="review-page">
+      <!-- Back -->
+      <button class="back-button" @click="$router.back()">← Back</button>
+  
+      <!-- Loading / Error states -->
+      <div v-if="loading" class="loading">Loading movie...</div>
+      <div v-else-if="error" class="error">Error: {{ error }}</div>
+  
+      <!-- Main content -->
+      <div v-else class="content">
+        <!-- Poster & Title -->
+        <div class="header">
+          <img
+            v-if="movie.poster_path"
+            :src="`https://image.tmdb.org/t/p/w300${movie.poster_path}`"
+            :alt="movie.title + ' poster'"
+            class="poster"
+          />
+          <h1 class="title">{{ movie.title }}</h1>
+        </div>
+  
+        <!-- Star Rating -->
+        <div class="rating">
+          <span
+            v-for="star in 5"
+            :key="star"
+            class="star"
+            :class="{ filled: star <= userRating }"
+            @click="setRating(star)"
+          >
+            ★
+          </span>
+          <span class="rating-value">{{ userRating }} / 5</span>
+        </div>
+  
+        <!-- Review Text -->
+        <textarea
+          v-model="reviewText"
+          placeholder="Write your review…"
+          class="review-text"
+          rows="6"
+        ></textarea>
+  
+        <!-- Submit -->
+        <button class="submit-btn" @click="handleSubmit">
+          Submit Review
+        </button>
+      </div>
+    </div>
+  </template>
+  
+  <script setup>
+  import { ref, onMounted } from 'vue'
+  import { useRoute, useRouter } from 'vue-router'
+  import { getUserReview, submitReview } from '@/composables/useReviews.js'
+  
+  const route = useRoute()
+  const router = useRouter()
+  
+  // State
+  const movie      = ref(null)
+  const loading    = ref(true)
+  const error      = ref('')
+  const userRating = ref(0)
+  const reviewText = ref('')
+  
+  // Fetch movie details
+  async function fetchMovie() {
+    try {
+      const id = route.params.id
+      const res = await fetch(
+        `${import.meta.env.VITE_TMDB_BASE}/movie/${id}?api_key=${import.meta.env.VITE_TMDB_API_KEY}`
+      )
+      if (!res.ok) throw new Error('Failed to load movie')
+      movie.value = await res.json()
+    } catch (err) {
+      error.value = err.message
+    }
+  }
+  
+  // Load existing user review
+  async function loadExistingReview() {
+    try {
+      const existing = await getUserReview(route.params.id)
+      if (existing) {
+        userRating.value = existing.rating || 0
+        reviewText.value = existing.text   || ''
+      }
+    } catch (err) {
+      console.error('Error loading review:', err)
+    }
+  }
+  
+  // Rating handler
+  function setRating(star) {
+    userRating.value = star
+  }
+  
+  // Submit handler
+  async function handleSubmit() {
+    try {
+      await submitReview({
+        movieId: route.params.id,
+        rating:  userRating.value,
+        text:    reviewText.value
+      })
+      router.back()
+    } catch (err) {
+      console.error('Error submitting review:', err)
+      error.value = err.message
+    }
+  }
+  
+  onMounted(async () => {
+    await fetchMovie()
+    await loadExistingReview()
+    loading.value = false
+  })
+  </script>
+  
+  <style scoped>
+  .review-page {
+    background: #121212;
+    color: #fff;
+    min-height: 100vh;
+    padding: 2rem;
+    box-sizing: border-box;
+  }
+  
+  .back-button {
+    background: transparent;
+    border: none;
+    color: #fff;
+    font-size: 1rem;
+  }
+  
+  .loading,
+  .error {
+    text-align: center;
+    margin: 2rem 0;
+  }
+  
+  .content {
+    max-width: 600px;
+    margin: 2rem auto;
+    background: #1e1e1e;
+    padding: 2rem;
+    border-radius: 8px;
+  }
+  
+  .header {
+    display: flex;
+    align-items: center;
+    gap: 1rem;
+    margin-bottom: 1.5rem;
+  }
+  
+  .poster {
+    width: 100px;
+    border-radius: 4px;
+  }
+  
+  .title {
+    font-size: 1.75rem;
+    margin: 0;
+  }
+  
+  .rating {
+    display: flex;
+    align-items: center;
+    margin-bottom: 1rem;
+  }
+  
+  .star {
+    font-size: 2rem;
+    cursor: pointer;
+    transition: transform 0.1s;
+    margin-right: 0.25rem;
+    color: #555;
+  }
+  .star.filled {
+    color: #f5c518;
+  }
+  .star:hover {
+    transform: scale(1.2);
+  }
+  
+  .rating-value {
+    margin-left: 0.5rem;
+    color: #bbb;
+  }
+  
+  .review-text {
+    width: 100%;
+    padding: 0.75rem;
+    border: none;
+    border-radius: 4px;
+    background: #2a2a2a;
+    color: #fff;
+    margin-bottom: 1rem;
+    resize: vertical;
+  }
+  
+  .submit-btn {
+    background: #1976d2;
+    border: none;
+    color: #fff;
+    padding: 0.75rem 1.5rem;
+    border-radius: 4px;
+    cursor: pointer;
+    width: 100%;
+  }
+  .submit-btn:hover {
+    background: #1565c0;
+  }
+  </style>
+  
\ No newline at end of file
diff --git a/movie-group-8/src/views/Settings.vue b/movie-group-8/src/views/Settings.vue
new file mode 100644
index 0000000000000000000000000000000000000000..4c5d407983bd7e51dd0c6b32680470d99bd2029a
--- /dev/null
+++ b/movie-group-8/src/views/Settings.vue
@@ -0,0 +1 @@
+<template>Settings</template>
\ No newline at end of file
diff --git a/movie-group-8/src/views/Social.vue b/movie-group-8/src/views/Social.vue
new file mode 100644
index 0000000000000000000000000000000000000000..ed3b9b1e253cf69fa6e2c191f9b1ebea19fcb48f
--- /dev/null
+++ b/movie-group-8/src/views/Social.vue
@@ -0,0 +1,99 @@
+<template>
+  <div class="min-h-screen bg-gray-100 dark:bg-neutral-900 p-6">
+    <h1 class="text-3xl font-bold text-gray-900 dark:text-white mb-6 text-center">Find and Follow Users</h1>
+
+    <div class="max-w-xl mx-auto mb-6">
+      <input
+        type="text"
+        v-model="searchTerm"
+        placeholder="Search by display name..."
+        class="w-full p-3 rounded-lg border border-gray-300 dark:border-neutral-700 dark:bg-neutral-800 dark:text-white"
+      />
+    </div>
+
+    <div class="grid gap-4 max-w-3xl mx-auto">
+      <div
+        v-for="user in filteredUsers"
+        :key="user.uid"
+        class="flex items-center justify-between p-4 bg-white dark:bg-neutral-800 border dark:border-neutral-700 rounded-lg"
+      >
+        <router-link :to="`/user/${user.uid}`" class="flex items-center gap-4">
+          <img
+            :src="user.photoURL"
+            class="w-12 h-12 rounded-full object-cover"
+            alt="Avatar"
+          />
+          <span class="text-lg font-semibold text-gray-800 dark:text-white">{{ user.displayName }}</span>
+        </router-link>
+        <button
+          @click="toggleFollow(user.uid)"
+          class="px-4 py-1.5 text-sm rounded-lg font-medium"
+          :class="isFollowing(user.uid)
+            ? 'bg-red-500 text-white hover:bg-red-600'
+            : 'bg-blue-600 text-white hover:bg-blue-700'"
+        >
+          {{ isFollowing(user.uid) ? 'Unfollow' : 'Follow' }}
+        </button>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script setup>
+import { ref, computed, onMounted } from 'vue'
+import { getAuth } from 'firebase/auth'
+import {
+  getFirestore,
+  collection,
+  getDocs,
+  setDoc,
+  deleteDoc,
+  doc
+} from 'firebase/firestore'
+
+const auth = getAuth()
+const db = getFirestore()
+const currentUser = auth.currentUser
+
+const searchTerm = ref('')
+const users = ref([])
+const following = ref([])
+
+onMounted(async () => {
+  await fetchUsers()
+  await fetchFollowing()
+})
+
+const fetchUsers = async () => {
+  const snapshot = await getDocs(collection(db, 'users'))
+  users.value = snapshot.docs
+    .map(doc => doc.data())
+    .filter(u => u.uid !== currentUser?.uid && !u.hidden) // exclude self and test user
+}
+
+const fetchFollowing = async () => {
+  const snapshot = await getDocs(collection(db, 'users', currentUser.uid, 'following'))
+  following.value = snapshot.docs.map(doc => doc.id)
+}
+
+const isFollowing = (uid) => following.value.includes(uid)
+
+const toggleFollow = async (uid) => {
+  const followRef = doc(db, 'users', currentUser.uid, 'following', uid)
+
+  if (isFollowing(uid)) {
+    await deleteDoc(followRef)
+    following.value = following.value.filter(id => id !== uid)
+  } else {
+    await setDoc(followRef, { followedAt: Date.now() })
+    following.value.push(uid)
+  }
+}
+
+const filteredUsers = computed(() => {
+  if (!searchTerm.value.trim()) return users.value
+  return users.value.filter(u =>
+    u.displayName.toLowerCase().includes(searchTerm.value.toLowerCase())
+  )
+})
+</script>
\ No newline at end of file
diff --git a/movie-group-8/src/views/TopRated.vue b/movie-group-8/src/views/TopRated.vue
new file mode 100644
index 0000000000000000000000000000000000000000..a8e7c29534e976e832e311de90f8230cd613b57a
--- /dev/null
+++ b/movie-group-8/src/views/TopRated.vue
@@ -0,0 +1,44 @@
+<script setup>
+import TopRatedMovies from '@/components/TopRatedMovies.vue'
+
+</script>
+
+<template>
+  <TopRatedMovies />
+  <template>
+  <div class="nyt-header">
+    <h1>The Movies We've Loved Since 2000</h1>
+    <p>
+      Explore top-rated films using the filters below — by genre and year — and rediscover hidden gems.
+    </p>
+  </div>
+
+  <div class="filters">
+    <select v-model="genre">
+      <option value="">Pick a genre ...</option>
+      <option v-for="g in genres" :key="g.id" :value="g.id">{{ g.name }}</option>
+    </select>
+
+    <select v-model="year">
+      <option v-for="y in Array.from({length: 25}, (_, i) => 2000 + i)" :key="y" :value="y">
+        {{ y }}
+      </option>
+    </select>
+  </div>
+
+  <h2 class="section-title">Our favorite movies from {{ year }}</h2>
+
+  <div class="movie-grid">
+    <div v-for="movie in movies" :key="movie.id" class="movie-card">
+      <img
+        v-if="movie.poster_path"
+        :src="'https://image.tmdb.org/t/p/w342' + movie.poster_path"
+        alt="poster"
+      />
+      <p class="title">{{ movie.title }}</p>
+      <p class="rating">⭐ {{ movie.vote_average }}</p>
+    </div>
+  </div>
+</template>
+
+</template>
diff --git a/movie-group-8/src/views/UserProfile.vue b/movie-group-8/src/views/UserProfile.vue
new file mode 100644
index 0000000000000000000000000000000000000000..e3b650ebab1cd98051851e8b453fffa22cf2fe62
--- /dev/null
+++ b/movie-group-8/src/views/UserProfile.vue
@@ -0,0 +1,190 @@
+<template>
+  <div class="min-h-screen bg-gray-100 dark:bg-neutral-900 p-6">
+    <div class="text-center mb-10">
+      <img
+        v-if="user.photoURL"
+        :src="user.photoURL"
+        alt="Profile"
+        class="w-24 h-24 mx-auto rounded-full object-cover"
+      />
+      <h1 class="text-2xl font-bold mt-4 text-gray-800 dark:text-white">
+        {{ user.displayName || 'Unknown User' }}
+      </h1>
+
+      <button
+        v-if="!isOwnProfile"
+        @click="toggleFollow"
+        class="mt-3 px-4 py-2 rounded-lg text-white text-sm"
+        :class="isFollowing ? 'bg-red-500 hover:bg-red-600' : 'bg-blue-600 hover:bg-blue-700'"
+      >
+        {{ isFollowing ? 'Unfollow' : 'Follow' }}
+      </button>
+    </div>
+
+    <h2 class="text-xl font-semibold text-gray-800 dark:text-white mb-4 text-center">
+      Watchlist
+    </h2>
+
+    <div v-if="watchlist.length === 0" class="text-center text-gray-500 dark:text-gray-400">
+      No public watchlist found.
+    </div>
+
+    <div class="grid gap-4 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 mb-12">
+      <router-link
+        v-for="movie in watchlist"
+        :key="movie.id"
+        :to="`/films/${movie.id}`"
+        class="block bg-white dark:bg-neutral-800 border dark:border-neutral-700 rounded-lg p-4 hover:opacity-90 transition"
+      >
+        <img
+          v-if="movie.poster_path"
+          :src="'https://image.tmdb.org/t/p/w342' + movie.poster_path"
+          alt="Poster"
+          class="w-full h-60 object-cover rounded mb-2"
+        />
+        <p class="font-semibold text-gray-800 dark:text-white">{{ movie.title }}</p>
+        <p class="text-sm text-gray-600 dark:text-neutral-400">⭐ {{ movie.vote_average }}</p>
+        <p class="text-xs text-gray-400 dark:text-neutral-500">{{ movie.status }}</p>
+      </router-link>
+    </div>
+
+    <h2 class="text-xl font-semibold text-gray-800 dark:text-white mb-4 text-center">
+      Reviews
+    </h2>
+    <div v-if="reviewsLoading" class="text-center text-gray-500 dark:text-gray-400">
+      Loading reviews…
+    </div>
+    <div v-else-if="!reviews.length" class="text-gray-400">
+        No reviews yet.
+    </div>
+    <div class="space-y-6 max-w-3xl mx-auto">
+      <div
+        v-for="r in reviews"
+        :key="r.id"
+        class="bg-white dark:bg-neutral-800 p-4 rounded-lg border dark:border-neutral-700 text-left"
+      >
+        <router-link
+          :to="`/films/${r.movieId}`"
+          class="block text-lg font-semibold text-blue-600 dark:text-blue-400 hover:underline"
+        >
+          {{ r.movieTitle }}
+        </router-link>
+        <div class="mt-1 text-sm text-gray-800 dark:text-gray-200">⭐ {{ r.rating }}</div>
+        <p class="mt-2 text-gray-700 dark:text-neutral-300">{{ r.text }}</p>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script setup>
+import { ref, onMounted } from 'vue'
+import { useRoute } from 'vue-router'
+import { getAuth, onAuthStateChanged } from 'firebase/auth'
+import {
+  getFirestore,
+  doc,
+  getDoc,
+  collection,
+  getDocs,
+  setDoc,
+  deleteDoc
+} from 'firebase/firestore'
+
+const route = useRoute()
+const db = getFirestore()
+const auth = getAuth()
+
+const profileUserId = route.params.id
+
+const user = ref({})
+const watchlist = ref([])
+const isFollowing = ref(false)
+const isOwnProfile = ref(false)
+const TMDB_API_KEY = import.meta.env.VITE_TMDB_API_KEY
+const reviews = ref([])
+const reviewsLoading = ref(true)
+
+onMounted(async () => {
+    onAuthStateChanged(auth, async (u) => {
+    if (!u) {
+        // not logged in → just show public data
+        await fetchUserProfile()
+        await fetchWatchlist()
+        await fetchReviews()
+        return
+    }
+
+    // now we have a valid user
+    isOwnProfile.value = (u.uid === profileUserId)
+    await fetchUserProfile()
+    await fetchWatchlist()
+    if (!isOwnProfile.value) {
+        await checkIfFollowing(u.uid)
+    }
+    await fetchReviews()
+    })
+})
+
+const fetchUserProfile = async () => {
+  const docRef = doc(db, 'users', profileUserId)
+  const docSnap = await getDoc(docRef)
+  if (docSnap.exists()) {
+    user.value = docSnap.data()
+  } else {
+    user.value = { displayName: 'User Not Found' }
+  }
+}
+
+async function fetchReviews() {
+  const snap = await getDocs(collection(db, 'users', profileUserId, 'reviews'))
+  const revs = snap.docs.map(d => ({
+    id: d.id,
+    ...d.data(),
+    movieId: d.data().movieId,
+    text: d.data().text,
+    rating: d.data().rating,
+  }))
+
+  // lookup titles in parallel
+  const withTitles = await Promise.all(
+    revs.map(async r => {
+      const res = await fetch(
+        `https://api.themoviedb.org/3/movie/${r.movieId}?api_key=${TMDB_API_KEY}`
+      )
+      const md = await res.json()
+      return {
+        ...r,
+        movieTitle: md.title
+      }
+    })
+  )
+
+  reviews.value = withTitles
+  reviewsLoading.value = false
+}
+
+const fetchWatchlist = async () => {
+  const snapshot = await getDocs(collection(db, 'users', profileUserId, 'watchlist'))
+  watchlist.value = snapshot.docs.map(doc => ({ id: doc.id, ...doc.data() }))
+}
+
+async function checkIfFollowing(currentUid) {
+  const followRef = doc(db, 'users', currentUid, 'following', profileUserId)
+  const followSnap = await getDoc(followRef)
+  isFollowing.value = followSnap.exists()
+}
+
+const toggleFollow = async () => {
+  const u = auth.currentUser
+  if (!u) return
+  const followRef = doc(db, 'users', u.uid, 'following', profileUserId)
+
+  if (isFollowing.value) {
+    await deleteDoc(followRef)
+    isFollowing.value = false
+  } else {
+    await setDoc(followRef, { followedAt: Date.now() })
+    isFollowing.value = true
+  }
+}
+</script>
\ No newline at end of file
diff --git a/movie-group-8/src/views/WatchlistView.vue b/movie-group-8/src/views/WatchlistView.vue
new file mode 100644
index 0000000000000000000000000000000000000000..071b02ebe222a37ce5d458cfbc3781c56c890380
--- /dev/null
+++ b/movie-group-8/src/views/WatchlistView.vue
@@ -0,0 +1,95 @@
+<template>
+  <div class="min-h-screen bg-gray-100 dark:bg-neutral-900 p-6">
+    <h1 class="text-3xl font-bold mb-6 text-gray-900 dark:text-white text-center">Your Watchlist</h1>
+
+    <div class="grid grid-cols-1 lg:grid-cols-2 gap-8">
+      <!-- Planned Movies -->
+      <div>
+        <h2 class="text-2xl font-semibold mb-4 text-gray-800 dark:text-white">📌 Planned to Watch</h2>
+        <div v-if="plannedMovies.length === 0" class="text-gray-500 dark:text-gray-400 text-sm">
+          No movies planned yet.
+        </div>
+        <div class="grid gap-4">
+          <div v-for="movie in plannedMovies" :key="movie.id" class="relative bg-white dark:bg-neutral-800 p-4 rounded">
+            <MovieCard
+              :movie="movie"
+              @status-changed="updateMovieStatus"
+            />
+            <button
+              @click="removeFromWatchlist(movie.id)"
+              class="absolute top-2 right-2 px-2 py-1 bg-red-600 text-white text-xs rounded hover:bg-red-700 transition"
+            >
+              Remove
+            </button>
+          </div>
+        </div>
+      </div>
+
+      <!-- Watched Movies -->
+      <div>
+        <h2 class="text-2xl font-semibold mb-4 text-gray-800 dark:text-white">✅ Watched</h2>
+        <div v-if="watchedMovies.length === 0" class="text-gray-500 dark:text-gray-400 text-sm">
+          No watched movies yet.
+        </div>
+        <div class="grid gap-4">
+          <div v-for="movie in watchedMovies" :key="movie.id" class="relative bg-white dark:bg-neutral-800 p-4 rounded">
+            <MovieCard
+              :movie="movie"
+              @status-changed="updateMovieStatus"
+            />
+            <button
+              @click="removeFromWatchlist(movie.id)"
+              class="absolute top-2 right-2 px-2 py-1 bg-red-600 text-white text-xs rounded hover:bg-red-700 transition"
+            >
+              Remove
+            </button>
+          </div>
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script setup>
+import { ref, onMounted } from 'vue'
+import { getAuth } from 'firebase/auth'
+import { getFirestore, collection, getDocs, doc, updateDoc, deleteDoc } from 'firebase/firestore'
+import MovieCard from '@/components/MovieCard.vue'
+
+const auth = getAuth()
+const db = getFirestore()
+
+const plannedMovies = ref([])
+const watchedMovies = ref([])
+
+const fetchWatchlist = async () => {
+  const user = auth.currentUser
+  if (!user) return
+
+  const snapshot = await getDocs(collection(db, 'users', user.uid, 'watchlist'))
+  const all = snapshot.docs.map(doc => ({ id: doc.id, ...doc.data() }))
+  plannedMovies.value = all.filter(m => m.status === 'planned')
+  watchedMovies.value = all.filter(m => m.status === 'watched')
+}
+
+const removeFromWatchlist = async (movieId) => {
+  const user = auth.currentUser
+  if (!user) return
+
+  const movieRef = doc(db, 'users', user.uid, 'watchlist', movieId)
+  await deleteDoc(movieRef)
+  await fetchWatchlist()
+}
+
+const updateMovieStatus = async (movieId, newStatus) => {
+  const user = auth.currentUser
+  if (!user) return
+
+  const movieRef = doc(db, 'users', user.uid, 'watchlist', movieId)
+  await updateDoc(movieRef, { status: newStatus })
+
+  await fetchWatchlist() // Refresh lists
+}
+
+onMounted(fetchWatchlist)
+</script>
diff --git a/movie-group-8/vite.config.js b/movie-group-8/vite.config.js
new file mode 100644
index 0000000000000000000000000000000000000000..8bbf4703424d6582b10489e90bdad6599bfa8345
--- /dev/null
+++ b/movie-group-8/vite.config.js
@@ -0,0 +1,18 @@
+import { defineConfig } from 'vite'
+import vue from '@vitejs/plugin-vue'
+import tailwindcss from '@tailwindcss/vite'
+import path from 'path'
+
+
+// https://vite.dev/config/
+export default defineConfig({
+  plugins: [vue(),
+    tailwindcss()
+  ],
+  resolve: {
+    alias: {
+      '@': path.resolve(__dirname, './src'),
+    },
+  },
+
+})