From dc2089ddb90b3aeb632bc3dac1e228a3c395ebc6 Mon Sep 17 00:00:00 2001
From: valere <dev@valerebron.com>
Date: Wed, 9 Oct 2024 14:42:41 +0200
Subject: [PATCH] FEAT: styles

---
 README.md                                |   6 -
 components/comment/form.vue              |  23 +-
 components/comment/list.vue              |  13 +-
 components/list.vue                      |  22 +-
 components/ui-close.vue                  |  10 +
 components/{loader.vue => ui-loader.vue} |   0
 components/ui-person.vue                 |   7 +
 nuxt.config.ts                           |   4 +-
 package-lock.json                        | 742 +++++++++++++++++++++--
 package.json                             |   6 +-
 pages/details/[id].vue                   |  44 +-
 utils/formatPercent.ts                   |   3 +
 12 files changed, 788 insertions(+), 92 deletions(-)
 create mode 100644 components/ui-close.vue
 rename components/{loader.vue => ui-loader.vue} (100%)
 create mode 100644 components/ui-person.vue
 create mode 100644 utils/formatPercent.ts

diff --git a/README.md b/README.md
index 2561822..73d879c 100644
--- a/README.md
+++ b/README.md
@@ -1,8 +1,2 @@
-- tinymce
-- style
 - variables typées
-- Vuetify
-- Vuelidate
 - tests
-- animations
-- skeletons
diff --git a/components/comment/form.vue b/components/comment/form.vue
index 7316890..4330a97 100644
--- a/components/comment/form.vue
+++ b/components/comment/form.vue
@@ -1,9 +1,15 @@
 <template>
-  <form @submit.prevent="sendComment()" class="flex flex-col">
+  <form @submit.prevent="sendComment()" class="flex flex-col bg-slate-200 my-12 p-4 w-full rounded-md">
     <input v-model="username" type="text" placeholder="username" min="3" max="50" required id="username">
     <textarea v-model="message" type="textarea" placeholder="message" min="3" max="500" required id="message" />
-    <input type="range" name="score" min="1" max="10" id="score">
-    <button type="submit">envoyer</button>
+    <div class="scoreContainer">
+      <label for="score">
+        Note:
+      </label>
+      <input type="range" name="score" min="1" max="10" id="score" class="w-full">
+    </div>
+    <button type="submit"
+      class="bg-white rounded-md p-2 inline-flex items-center justify-center text-gray-400 hover:text-gray-500 hover:bg-gray-100 focus:outline-none focus:ring-2 focus:ring-inset focus:ring-indigo-500">envoyer</button>
   </form>
 </template>
 
@@ -21,3 +27,14 @@ const sendComment = () => {
   })
 }
 </script>
+
+<style lang="scss" scoped>
+form {
+
+  input,
+  textarea,
+  .scoreContainer {
+    @apply p-4 m-2;
+  }
+}
+</style>
\ No newline at end of file
diff --git a/components/comment/list.vue b/components/comment/list.vue
index 4303cdf..195779a 100644
--- a/components/comment/list.vue
+++ b/components/comment/list.vue
@@ -1,14 +1,19 @@
 <template>
   <ul>
-    <li v-for="comment in store.orderedComment(props.filmId)">
-      {{ comment.username }}
-      {{ comment.added }}
-      {{ comment.message }}
+    <li v-for="comment in store.orderedComment(props.filmId)" class="m-6">
+      <p class="bg-white p-4 mb-4 rounded-lg">
+        {{ comment.message }}
+      </p>
+      <p>
+        {{ comment.username }} - {{ useDateFormat(comment.added, 'D MMM YYYY') }}
+      </p>
     </li>
   </ul>
   <div class="mt-24"></div>
 </template>
 <script setup>
+import { useDateFormat } from '@vueuse/core'
+
 const props = defineProps(['filmId'])
 const store = useCommentStore()
 </script>
diff --git a/components/list.vue b/components/list.vue
index 09748f3..c14147c 100644
--- a/components/list.vue
+++ b/components/list.vue
@@ -1,6 +1,6 @@
 <template>
   <section class="flex flex-col items-center min-h-screen">
-    <loader v-if="isLoading" class="fixed z-50 backdrop-blur-sm p-4 rounded-full top-1/2" />
+    <uiLoader v-if="isLoading" class="fixed z-50 backdrop-blur-sm p-4 rounded-full top-1/2" />
     <div class="container w-full p-6 max-w-6xl grow flex flex-col items-center">
       <nav
         class="w-full flex bg-slate-800 font-semibold rounded-full backdrop-blur sticky top-0 justify-center items-center hover:ring">
@@ -14,17 +14,19 @@
       </nav>
       <main class="flex max-w-screen-2xl justify-center flex-wrap m-6">
         <NuxtLink v-for="(film, index) in films" :key="index" :href="film.title" :to="'/details/' + film.id"
-          class="hover:bg-slate-200 w-full flex flex-col bg-emerald-800 rounded-lg w-60 m-4">
+          class="hover:bg-green-500 transition flex flex-col bg-slate-400 rounded-lg w-60 m-4">
           <img @error="setPlaceholder" :src="config.public.IMG_BASE_URL + film.poster_path" :alt="film.title"
             class="w-full rounded-t-lg">
-          <span class="p-4">
-            <h2 class="text-xl font-bold">
+          <span class="p-4 text-white flex flex-col">
+            <h2 class="text-xl font-bold pb-2">
               {{ film.title }}
             </h2>
             <span v-if="film.release_date">
               {{ useDateFormat(film.release_date, 'D MMM YYYY') }}
             </span>
-            {{ formatPercentage(film.vote_average) }}%
+            <span class="font-bold">
+              {{ formatPercent(film.vote_average) }}%
+            </span>
           </span>
         </NuxtLink>
       </main>
@@ -61,16 +63,6 @@ const pressEnter = () => {
   search()
 }
 
-const fromPercentToDegree = (percent) => {
-  let deg = Math.round((percent / 10) * 180)
-  console.log(deg)
-  return [`rotate-[calc(${deg}deg-45deg)]`]
-}
-
-const formatPercentage = (percent) => {
-  return Math.round(percent * 10)
-}
-
 const setPlaceholder = (event) => {
   event.target.src = 'https://via.placeholder.com/200x300'
 }
diff --git a/components/ui-close.vue b/components/ui-close.vue
new file mode 100644
index 0000000..0f16199
--- /dev/null
+++ b/components/ui-close.vue
@@ -0,0 +1,10 @@
+<template>
+  <button type="button"
+    class="bg-white rounded-md p-2 inline-flex items-center justify-center text-gray-400 hover:text-gray-500 hover:bg-gray-100 focus:outline-none focus:ring-2 focus:ring-inset focus:ring-indigo-500">
+    <span class="sr-only">Close menu</span>
+    <svg class="h-6 w-6" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor"
+      aria-hidden="true">
+      <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
+    </svg>
+  </button>
+</template>
diff --git a/components/loader.vue b/components/ui-loader.vue
similarity index 100%
rename from components/loader.vue
rename to components/ui-loader.vue
diff --git a/components/ui-person.vue b/components/ui-person.vue
new file mode 100644
index 0000000..e74908a
--- /dev/null
+++ b/components/ui-person.vue
@@ -0,0 +1,7 @@
+<template>
+  <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"
+    class="size-6">
+    <path stroke-linecap="round" stroke-linejoin="round"
+      d="M17.982 18.725A7.488 7.488 0 0 0 12 15.75a7.488 7.488 0 0 0-5.982 2.975m11.963 0a9 9 0 1 0-11.963 0m11.963 0A8.966 8.966 0 0 1 12 21a8.966 8.966 0 0 1-5.982-2.275M15 9.75a3 3 0 1 1-6 0 3 3 0 0 1 6 0Z" />
+  </svg>
+</template>
\ No newline at end of file
diff --git a/nuxt.config.ts b/nuxt.config.ts
index af8d880..bb84619 100644
--- a/nuxt.config.ts
+++ b/nuxt.config.ts
@@ -5,6 +5,7 @@ export default defineNuxtConfig({
   modules: [
     '@pinia/nuxt',
     '@vueuse/nuxt',
+    '@nuxt/test-utils/module',
   ],
   postcss: {
     plugins: {
@@ -28,5 +29,4 @@ export default defineNuxtConfig({
   },
 
   compatibilityDate: '2024-10-06',
-  modules: ['@pinia/nuxt'],
-})
\ No newline at end of file
+})
diff --git a/package-lock.json b/package-lock.json
index 6f96d97..e1250c9 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -16,12 +16,15 @@
         "vue-router": "^4.4.0"
       },
       "devDependencies": {
+        "@nuxt/test-utils": "^3.14.3",
+        "@playwright/test": "^1.48.0",
         "@vueuse/core": "^11.1.0",
         "@vueuse/nuxt": "^11.1.0",
         "autoprefixer": "^10.4.19",
         "postcss": "^8.4.39",
         "sass": "^1.77.6",
-        "tailwindcss": "^3.4.4"
+        "tailwindcss": "^3.4.4",
+        "vitest": "^2.1.2"
       }
     },
     "node_modules/@alloc/quick-lru": {
@@ -338,17 +341,17 @@
       }
     },
     "node_modules/@babel/helper-string-parser": {
-      "version": "7.24.8",
-      "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.24.8.tgz",
-      "integrity": "sha512-pO9KhhRcuUyGnJWwyEgnRJTSIZHiT+vMD0kPeD+so0l7mxkMT19g3pjY9GTnHySck/hDzq+dtW/4VgnMkippsQ==",
+      "version": "7.25.7",
+      "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.25.7.tgz",
+      "integrity": "sha512-CbkjYdsJNHFk8uqpEkpCvRs3YRp9tY6FmFY7wLMSYuGYkrdUi7r2lc4/wqsvlHoMznX3WJ9IP8giGPq68T/Y6g==",
       "engines": {
         "node": ">=6.9.0"
       }
     },
     "node_modules/@babel/helper-validator-identifier": {
-      "version": "7.24.7",
-      "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.24.7.tgz",
-      "integrity": "sha512-rR+PBcQ1SMQDDyF6X0wxtG8QyLCgUB0eRAGguqRLfkCA87l7yAP7ehq8SNj96OOGTO8OBV70KhuFYcIkHXOg0w==",
+      "version": "7.25.7",
+      "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.7.tgz",
+      "integrity": "sha512-AM6TzwYqGChO45oiuPqwL2t20/HdMC1rTPAesnBCgPCSF1x3oN9MVUwQV2iyz4xqWrctwK5RNC8LV22kaQCNYg==",
       "engines": {
         "node": ">=6.9.0"
       }
@@ -388,9 +391,12 @@
       }
     },
     "node_modules/@babel/parser": {
-      "version": "7.24.8",
-      "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.24.8.tgz",
-      "integrity": "sha512-WzfbgXOkGzZiXXCqk43kKwZjzwx4oulxZi3nq2TYL9mOjQv6kYwul9mz6ID36njuL7Xkp6nJEfok848Zj10j/w==",
+      "version": "7.25.7",
+      "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.25.7.tgz",
+      "integrity": "sha512-aZn7ETtQsjjGG5HruveUK06cU3Hljuhd9Iojm4M8WWv3wLE6OkE5PWbDUkItmMgegmccaITudyuW5RPYrYlgWw==",
+      "dependencies": {
+        "@babel/types": "^7.25.7"
+      },
       "bin": {
         "parser": "bin/babel-parser.js"
       },
@@ -540,12 +546,12 @@
       }
     },
     "node_modules/@babel/types": {
-      "version": "7.24.9",
-      "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.24.9.tgz",
-      "integrity": "sha512-xm8XrMKz0IlUdocVbYJe0Z9xEgidU7msskG8BbhnTPK/HZ2z/7FP7ykqPgrUH+C+r414mNfNWam1f2vqOjqjYQ==",
+      "version": "7.25.7",
+      "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.25.7.tgz",
+      "integrity": "sha512-vwIVdXG+j+FOpkwqHRcBgHLYNL7XMkufrlaFvL9o6Ai9sJn9+PdyIL5qa0XzTZw084c+u9LOls53eoZWP/W5WQ==",
       "dependencies": {
-        "@babel/helper-string-parser": "^7.24.8",
-        "@babel/helper-validator-identifier": "^7.24.7",
+        "@babel/helper-string-parser": "^7.25.7",
+        "@babel/helper-validator-identifier": "^7.25.7",
         "to-fast-properties": "^2.0.0"
       },
       "engines": {
@@ -1024,6 +1030,236 @@
         "nuxt-telemetry": "bin/nuxt-telemetry.mjs"
       }
     },
+    "node_modules/@nuxt/test-utils": {
+      "version": "3.14.3",
+      "resolved": "https://registry.npmjs.org/@nuxt/test-utils/-/test-utils-3.14.3.tgz",
+      "integrity": "sha512-5SoyaR9bQG7xcyj6kSnzFVWvpAdiKSruRkq3KVDKEAbxwHhtyz2Ijqxf8iGE3W9dAM0F+omIvLmjen3ITgd3rA==",
+      "dev": true,
+      "dependencies": {
+        "@nuxt/kit": "^3.13.2",
+        "@nuxt/schema": "^3.13.2",
+        "c12": "^2.0.1",
+        "consola": "^3.2.3",
+        "defu": "^6.1.4",
+        "destr": "^2.0.3",
+        "estree-walker": "^3.0.3",
+        "fake-indexeddb": "^6.0.0",
+        "get-port-please": "^3.1.2",
+        "local-pkg": "^0.5.0",
+        "magic-string": "^0.30.11",
+        "node-fetch-native": "^1.6.4",
+        "ofetch": "^1.4.0",
+        "pathe": "^1.1.2",
+        "perfect-debounce": "^1.0.0",
+        "radix3": "^1.1.2",
+        "scule": "^1.3.0",
+        "std-env": "^3.7.0",
+        "tinyexec": "^0.3.0",
+        "ufo": "^1.5.4",
+        "unenv": "^1.10.0",
+        "unplugin": "^1.14.1",
+        "vitest-environment-nuxt": "^1.0.1"
+      },
+      "engines": {
+        "node": ">=18.20.4"
+      },
+      "peerDependencies": {
+        "@cucumber/cucumber": "^10.3.1 || ^11.0.0",
+        "@jest/globals": "^29.5.0",
+        "@playwright/test": "^1.43.1",
+        "@testing-library/vue": "^7.0.0 || ^8.0.1",
+        "@vitest/ui": "^0.34.6 || ^1.0.0 || ^2.0.0",
+        "@vue/test-utils": "^2.4.2",
+        "h3": "*",
+        "happy-dom": "^9.10.9 || ^10.0.0 || ^11.0.0 || ^12.0.0 || ^13.0.0 || ^14.0.0 || ^15.0.0",
+        "jsdom": "^22.0.0 || ^23.0.0 || ^24.0.0 || ^25.0.0",
+        "nitropack": "*",
+        "playwright-core": "^1.43.1",
+        "vite": "*",
+        "vitest": "^0.34.6 || ^1.0.0 || ^2.0.0",
+        "vue": "^3.3.4",
+        "vue-router": "^4.0.0"
+      },
+      "peerDependenciesMeta": {
+        "@cucumber/cucumber": {
+          "optional": true
+        },
+        "@jest/globals": {
+          "optional": true
+        },
+        "@playwright/test": {
+          "optional": true
+        },
+        "@testing-library/vue": {
+          "optional": true
+        },
+        "@vitest/ui": {
+          "optional": true
+        },
+        "@vue/test-utils": {
+          "optional": true
+        },
+        "happy-dom": {
+          "optional": true
+        },
+        "jsdom": {
+          "optional": true
+        },
+        "playwright-core": {
+          "optional": true
+        },
+        "vitest": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/@nuxt/test-utils/node_modules/@nuxt/kit": {
+      "version": "3.13.2",
+      "resolved": "https://registry.npmjs.org/@nuxt/kit/-/kit-3.13.2.tgz",
+      "integrity": "sha512-KvRw21zU//wdz25IeE1E5m/aFSzhJloBRAQtv+evcFeZvuroIxpIQuUqhbzuwznaUwpiWbmwlcsp5uOWmi4vwA==",
+      "dev": true,
+      "dependencies": {
+        "@nuxt/schema": "3.13.2",
+        "c12": "^1.11.2",
+        "consola": "^3.2.3",
+        "defu": "^6.1.4",
+        "destr": "^2.0.3",
+        "globby": "^14.0.2",
+        "hash-sum": "^2.0.0",
+        "ignore": "^5.3.2",
+        "jiti": "^1.21.6",
+        "klona": "^2.0.6",
+        "knitwork": "^1.1.0",
+        "mlly": "^1.7.1",
+        "pathe": "^1.1.2",
+        "pkg-types": "^1.2.0",
+        "scule": "^1.3.0",
+        "semver": "^7.6.3",
+        "ufo": "^1.5.4",
+        "unctx": "^2.3.1",
+        "unimport": "^3.12.0",
+        "untyped": "^1.4.2"
+      },
+      "engines": {
+        "node": "^14.18.0 || >=16.10.0"
+      }
+    },
+    "node_modules/@nuxt/test-utils/node_modules/@nuxt/kit/node_modules/c12": {
+      "version": "1.11.2",
+      "resolved": "https://registry.npmjs.org/c12/-/c12-1.11.2.tgz",
+      "integrity": "sha512-oBs8a4uvSDO9dm8b7OCFW7+dgtVrwmwnrVXYzLm43ta7ep2jCn/0MhoUFygIWtxhyy6+/MG7/agvpY0U1Iemew==",
+      "dev": true,
+      "dependencies": {
+        "chokidar": "^3.6.0",
+        "confbox": "^0.1.7",
+        "defu": "^6.1.4",
+        "dotenv": "^16.4.5",
+        "giget": "^1.2.3",
+        "jiti": "^1.21.6",
+        "mlly": "^1.7.1",
+        "ohash": "^1.1.3",
+        "pathe": "^1.1.2",
+        "perfect-debounce": "^1.0.0",
+        "pkg-types": "^1.2.0",
+        "rc9": "^2.1.2"
+      },
+      "peerDependencies": {
+        "magicast": "^0.3.4"
+      },
+      "peerDependenciesMeta": {
+        "magicast": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/@nuxt/test-utils/node_modules/@nuxt/schema": {
+      "version": "3.13.2",
+      "resolved": "https://registry.npmjs.org/@nuxt/schema/-/schema-3.13.2.tgz",
+      "integrity": "sha512-CCZgpm+MkqtOMDEgF9SWgGPBXlQ01hV/6+2reDEpJuqFPGzV8HYKPBcIFvn7/z5ahtgutHLzjP71Na+hYcqSpw==",
+      "dev": true,
+      "dependencies": {
+        "compatx": "^0.1.8",
+        "consola": "^3.2.3",
+        "defu": "^6.1.4",
+        "hookable": "^5.5.3",
+        "pathe": "^1.1.2",
+        "pkg-types": "^1.2.0",
+        "scule": "^1.3.0",
+        "std-env": "^3.7.0",
+        "ufo": "^1.5.4",
+        "uncrypto": "^0.1.3",
+        "unimport": "^3.12.0",
+        "untyped": "^1.4.2"
+      },
+      "engines": {
+        "node": "^14.18.0 || >=16.10.0"
+      }
+    },
+    "node_modules/@nuxt/test-utils/node_modules/c12": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmjs.org/c12/-/c12-2.0.1.tgz",
+      "integrity": "sha512-Z4JgsKXHG37C6PYUtIxCfLJZvo6FyhHJoClwwb9ftUkLpPSkuYqn6Tr+vnaN8hymm0kIbcg6Ey3kv/Q71k5w/A==",
+      "dev": true,
+      "dependencies": {
+        "chokidar": "^4.0.1",
+        "confbox": "^0.1.7",
+        "defu": "^6.1.4",
+        "dotenv": "^16.4.5",
+        "giget": "^1.2.3",
+        "jiti": "^2.3.0",
+        "mlly": "^1.7.1",
+        "ohash": "^1.1.4",
+        "pathe": "^1.1.2",
+        "perfect-debounce": "^1.0.0",
+        "pkg-types": "^1.2.0",
+        "rc9": "^2.1.2"
+      },
+      "peerDependencies": {
+        "magicast": "^0.3.5"
+      },
+      "peerDependenciesMeta": {
+        "magicast": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/@nuxt/test-utils/node_modules/c12/node_modules/chokidar": {
+      "version": "4.0.1",
+      "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.1.tgz",
+      "integrity": "sha512-n8enUVCED/KVRQlab1hr3MVpcVMvxtZjmEa956u+4YijlmQED223XMSYj2tLuKvr4jcCTzNNMpQDUer72MMmzA==",
+      "dev": true,
+      "dependencies": {
+        "readdirp": "^4.0.1"
+      },
+      "engines": {
+        "node": ">= 14.16.0"
+      },
+      "funding": {
+        "url": "https://paulmillr.com/funding/"
+      }
+    },
+    "node_modules/@nuxt/test-utils/node_modules/c12/node_modules/jiti": {
+      "version": "2.3.3",
+      "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.3.3.tgz",
+      "integrity": "sha512-EX4oNDwcXSivPrw2qKH2LB5PoFxEvgtv2JgwW0bU858HoLQ+kutSvjLMUqBd0PeJYEinLWhoI9Ol0eYMqj/wNQ==",
+      "dev": true,
+      "bin": {
+        "jiti": "lib/jiti-cli.mjs"
+      }
+    },
+    "node_modules/@nuxt/test-utils/node_modules/readdirp": {
+      "version": "4.0.2",
+      "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.0.2.tgz",
+      "integrity": "sha512-yDMz9g+VaZkqBYS/ozoBJwaBhTbZo3UNYQHNRw1D3UFQB8oHB4uS/tAODO+ZLjGWmUbKnIlOWO+aaIiAxrUWHA==",
+      "dev": true,
+      "engines": {
+        "node": ">= 14.16.0"
+      },
+      "funding": {
+        "type": "individual",
+        "url": "https://paulmillr.com/funding/"
+      }
+    },
     "node_modules/@nuxt/vite-builder": {
       "version": "3.12.3",
       "resolved": "https://registry.npmjs.org/@nuxt/vite-builder/-/vite-builder-3.12.3.tgz",
@@ -1197,6 +1433,21 @@
         "node": ">=14"
       }
     },
+    "node_modules/@playwright/test": {
+      "version": "1.48.0",
+      "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.48.0.tgz",
+      "integrity": "sha512-W5lhqPUVPqhtc/ySvZI5Q8X2ztBOUgZ8LbAFy0JQgrXZs2xaILrUcNO3rQjwbLPfGK13+rZsDa1FpG+tqYkT5w==",
+      "dev": true,
+      "dependencies": {
+        "playwright": "1.48.0"
+      },
+      "bin": {
+        "playwright": "cli.js"
+      },
+      "engines": {
+        "node": ">=18"
+      }
+    },
     "node_modules/@polka/url": {
       "version": "1.0.0-next.25",
       "resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.25.tgz",
@@ -1693,6 +1944,113 @@
         "vue": "^3.0.0"
       }
     },
+    "node_modules/@vitest/expect": {
+      "version": "2.1.2",
+      "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-2.1.2.tgz",
+      "integrity": "sha512-FEgtlN8mIUSEAAnlvn7mP8vzaWhEaAEvhSXCqrsijM7K6QqjB11qoRZYEd4AKSCDz8p0/+yH5LzhZ47qt+EyPg==",
+      "dev": true,
+      "dependencies": {
+        "@vitest/spy": "2.1.2",
+        "@vitest/utils": "2.1.2",
+        "chai": "^5.1.1",
+        "tinyrainbow": "^1.2.0"
+      },
+      "funding": {
+        "url": "https://opencollective.com/vitest"
+      }
+    },
+    "node_modules/@vitest/mocker": {
+      "version": "2.1.2",
+      "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-2.1.2.tgz",
+      "integrity": "sha512-ExElkCGMS13JAJy+812fw1aCv2QO/LBK6CyO4WOPAzLTmve50gydOlWhgdBJPx2ztbADUq3JVI0C5U+bShaeEA==",
+      "dev": true,
+      "dependencies": {
+        "@vitest/spy": "^2.1.0-beta.1",
+        "estree-walker": "^3.0.3",
+        "magic-string": "^0.30.11"
+      },
+      "funding": {
+        "url": "https://opencollective.com/vitest"
+      },
+      "peerDependencies": {
+        "@vitest/spy": "2.1.2",
+        "msw": "^2.3.5",
+        "vite": "^5.0.0"
+      },
+      "peerDependenciesMeta": {
+        "msw": {
+          "optional": true
+        },
+        "vite": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/@vitest/pretty-format": {
+      "version": "2.1.2",
+      "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-2.1.2.tgz",
+      "integrity": "sha512-FIoglbHrSUlOJPDGIrh2bjX1sNars5HbxlcsFKCtKzu4+5lpsRhOCVcuzp0fEhAGHkPZRIXVNzPcpSlkoZ3LuA==",
+      "dev": true,
+      "dependencies": {
+        "tinyrainbow": "^1.2.0"
+      },
+      "funding": {
+        "url": "https://opencollective.com/vitest"
+      }
+    },
+    "node_modules/@vitest/runner": {
+      "version": "2.1.2",
+      "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-2.1.2.tgz",
+      "integrity": "sha512-UCsPtvluHO3u7jdoONGjOSil+uON5SSvU9buQh3lP7GgUXHp78guN1wRmZDX4wGK6J10f9NUtP6pO+SFquoMlw==",
+      "dev": true,
+      "dependencies": {
+        "@vitest/utils": "2.1.2",
+        "pathe": "^1.1.2"
+      },
+      "funding": {
+        "url": "https://opencollective.com/vitest"
+      }
+    },
+    "node_modules/@vitest/snapshot": {
+      "version": "2.1.2",
+      "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-2.1.2.tgz",
+      "integrity": "sha512-xtAeNsZ++aRIYIUsek7VHzry/9AcxeULlegBvsdLncLmNCR6tR8SRjn8BbDP4naxtccvzTqZ+L1ltZlRCfBZFA==",
+      "dev": true,
+      "dependencies": {
+        "@vitest/pretty-format": "2.1.2",
+        "magic-string": "^0.30.11",
+        "pathe": "^1.1.2"
+      },
+      "funding": {
+        "url": "https://opencollective.com/vitest"
+      }
+    },
+    "node_modules/@vitest/spy": {
+      "version": "2.1.2",
+      "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-2.1.2.tgz",
+      "integrity": "sha512-GSUi5zoy+abNRJwmFhBDC0yRuVUn8WMlQscvnbbXdKLXX9dE59YbfwXxuJ/mth6eeqIzofU8BB5XDo/Ns/qK2A==",
+      "dev": true,
+      "dependencies": {
+        "tinyspy": "^3.0.0"
+      },
+      "funding": {
+        "url": "https://opencollective.com/vitest"
+      }
+    },
+    "node_modules/@vitest/utils": {
+      "version": "2.1.2",
+      "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-2.1.2.tgz",
+      "integrity": "sha512-zMO2KdYy6mx56btx9JvAqAZ6EyS3g49krMPPrgOp1yxGZiA93HumGk+bZ5jIZtOg5/VBYl5eBmGRQHqq4FG6uQ==",
+      "dev": true,
+      "dependencies": {
+        "@vitest/pretty-format": "2.1.2",
+        "loupe": "^3.1.1",
+        "tinyrainbow": "^1.2.0"
+      },
+      "funding": {
+        "url": "https://opencollective.com/vitest"
+      }
+    },
     "node_modules/@vue-macros/common": {
       "version": "1.10.4",
       "resolved": "https://registry.npmjs.org/@vue-macros/common/-/common-1.10.4.tgz",
@@ -2366,6 +2724,15 @@
       "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
       "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="
     },
+    "node_modules/assertion-error": {
+      "version": "2.0.1",
+      "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz",
+      "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==",
+      "dev": true,
+      "engines": {
+        "node": ">=12"
+      }
+    },
     "node_modules/ast-kit": {
       "version": "0.12.2",
       "resolved": "https://registry.npmjs.org/ast-kit/-/ast-kit-0.12.2.tgz",
@@ -2714,6 +3081,22 @@
         }
       ]
     },
+    "node_modules/chai": {
+      "version": "5.1.1",
+      "resolved": "https://registry.npmjs.org/chai/-/chai-5.1.1.tgz",
+      "integrity": "sha512-pT1ZgP8rPNqUgieVaEY+ryQr6Q4HXNg8Ei9UnLUrjN4IA7dvQC5JB+/kxVcPNDHyBcc/26CXPkbNzq3qwrOEKA==",
+      "dev": true,
+      "dependencies": {
+        "assertion-error": "^2.0.1",
+        "check-error": "^2.1.1",
+        "deep-eql": "^5.0.1",
+        "loupe": "^3.1.0",
+        "pathval": "^2.0.0"
+      },
+      "engines": {
+        "node": ">=12"
+      }
+    },
     "node_modules/chalk": {
       "version": "2.4.2",
       "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
@@ -2735,6 +3118,15 @@
         "node": ">=0.8.0"
       }
     },
+    "node_modules/check-error": {
+      "version": "2.1.1",
+      "resolved": "https://registry.npmjs.org/check-error/-/check-error-2.1.1.tgz",
+      "integrity": "sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw==",
+      "dev": true,
+      "engines": {
+        "node": ">= 16"
+      }
+    },
     "node_modules/chokidar": {
       "version": "3.6.0",
       "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz",
@@ -3300,11 +3692,11 @@
       }
     },
     "node_modules/debug": {
-      "version": "4.3.5",
-      "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.5.tgz",
-      "integrity": "sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg==",
+      "version": "4.3.7",
+      "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz",
+      "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==",
       "dependencies": {
-        "ms": "2.1.2"
+        "ms": "^2.1.3"
       },
       "engines": {
         "node": ">=6.0"
@@ -3315,6 +3707,15 @@
         }
       }
     },
+    "node_modules/deep-eql": {
+      "version": "5.0.2",
+      "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-5.0.2.tgz",
+      "integrity": "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==",
+      "dev": true,
+      "engines": {
+        "node": ">=6"
+      }
+    },
     "node_modules/deepmerge": {
       "version": "4.3.1",
       "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz",
@@ -3705,6 +4106,15 @@
         "ufo": "^1.1.2"
       }
     },
+    "node_modules/fake-indexeddb": {
+      "version": "6.0.0",
+      "resolved": "https://registry.npmjs.org/fake-indexeddb/-/fake-indexeddb-6.0.0.tgz",
+      "integrity": "sha512-YEboHE5VfopUclOck7LncgIqskAqnv4q0EWbYCaxKKjAvO93c+TJIaBuGy8CBFdbg9nKdpN3AuPRwVBJ4k7NrQ==",
+      "dev": true,
+      "engines": {
+        "node": ">=18"
+      }
+    },
     "node_modules/fast-fifo": {
       "version": "1.3.2",
       "resolved": "https://registry.npmjs.org/fast-fifo/-/fast-fifo-1.3.2.tgz",
@@ -3918,6 +4328,19 @@
       "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
       "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw=="
     },
+    "node_modules/fsevents": {
+      "version": "2.3.2",
+      "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
+      "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
+      "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",
@@ -4756,6 +5179,12 @@
       "resolved": "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz",
       "integrity": "sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ=="
     },
+    "node_modules/loupe": {
+      "version": "3.1.2",
+      "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.1.2.tgz",
+      "integrity": "sha512-23I4pFZHmAemUnz8WZXbYRSKYj801VDaNv9ETuMh7IrMc7VuVVSo+Z9iLE3ni30+U48iDWfi30d3twAXBYmnCg==",
+      "dev": true
+    },
     "node_modules/lru-cache": {
       "version": "5.1.1",
       "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz",
@@ -4784,12 +5213,12 @@
       }
     },
     "node_modules/magicast": {
-      "version": "0.3.4",
-      "resolved": "https://registry.npmjs.org/magicast/-/magicast-0.3.4.tgz",
-      "integrity": "sha512-TyDF/Pn36bBji9rWKHlZe+PZb6Mx5V8IHCSxk7X4aljM4e/vyDvZZYwHewdVaqiA0nb3ghfHU/6AUpDxWoER2Q==",
+      "version": "0.3.5",
+      "resolved": "https://registry.npmjs.org/magicast/-/magicast-0.3.5.tgz",
+      "integrity": "sha512-L0WhttDl+2BOsybvEOLK7fW3UA0OQ0IQ2d6Zl2x/a6vVRs3bAY0ECOSHHeL5jD+SbOpOCUEi0y1DgHEn9Qn1AQ==",
       "dependencies": {
-        "@babel/parser": "^7.24.4",
-        "@babel/types": "^7.24.0",
+        "@babel/parser": "^7.25.4",
+        "@babel/types": "^7.25.4",
         "source-map-js": "^1.2.0"
       }
     },
@@ -4980,9 +5409,9 @@
       }
     },
     "node_modules/ms": {
-      "version": "2.1.2",
-      "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
-      "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
+      "version": "2.1.3",
+      "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
+      "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="
     },
     "node_modules/mz": {
       "version": "2.7.0",
@@ -5329,6 +5758,19 @@
         "fsevents": "~2.3.3"
       }
     },
+    "node_modules/nuxi/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/nuxt": {
       "version": "3.12.3",
       "resolved": "https://registry.npmjs.org/nuxt/-/nuxt-3.12.3.tgz",
@@ -5502,19 +5944,19 @@
       }
     },
     "node_modules/ofetch": {
-      "version": "1.3.4",
-      "resolved": "https://registry.npmjs.org/ofetch/-/ofetch-1.3.4.tgz",
-      "integrity": "sha512-KLIET85ik3vhEfS+3fDlc/BAZiAp+43QEC/yCo5zkNoY2YaKvNkOaFr/6wCFgFH1kuYQM5pMNi0Tg8koiIemtw==",
+      "version": "1.4.0",
+      "resolved": "https://registry.npmjs.org/ofetch/-/ofetch-1.4.0.tgz",
+      "integrity": "sha512-MuHgsEhU6zGeX+EMh+8mSMrYTnsqJQQrpM00Q6QHMKNqQ0bKy0B43tk8tL1wg+CnsSTy1kg4Ir2T5Ig6rD+dfQ==",
       "dependencies": {
         "destr": "^2.0.3",
-        "node-fetch-native": "^1.6.3",
-        "ufo": "^1.5.3"
+        "node-fetch-native": "^1.6.4",
+        "ufo": "^1.5.4"
       }
     },
     "node_modules/ohash": {
-      "version": "1.1.3",
-      "resolved": "https://registry.npmjs.org/ohash/-/ohash-1.1.3.tgz",
-      "integrity": "sha512-zuHHiGTYTA1sYJ/wZN+t5HKZaH23i4yI1HMwbuXm24Nid7Dv0KcuRlKoNKS9UNfAVSBlnGLcuQrnOKWOZoEGaw=="
+      "version": "1.1.4",
+      "resolved": "https://registry.npmjs.org/ohash/-/ohash-1.1.4.tgz",
+      "integrity": "sha512-FlDryZAahJmEF3VR3w1KogSEdWX3WhA5GPakFx4J81kEAiHyLMpdLLElS8n8dfNadMgAne/MywcvmogzscVt4g=="
     },
     "node_modules/on-finished": {
       "version": "2.4.1",
@@ -5723,6 +6165,15 @@
       "resolved": "https://registry.npmjs.org/pathe/-/pathe-1.1.2.tgz",
       "integrity": "sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ=="
     },
+    "node_modules/pathval": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/pathval/-/pathval-2.0.0.tgz",
+      "integrity": "sha512-vE7JKRyES09KiunauX7nd2Q9/L7lhok4smP9RZTDeD4MVs72Dp2qNFVz39Nz5a0FVEW0BJR6C0DYrq6unoziZA==",
+      "dev": true,
+      "engines": {
+        "node": ">= 14.16"
+      }
+    },
     "node_modules/perfect-debounce": {
       "version": "1.0.0",
       "resolved": "https://registry.npmjs.org/perfect-debounce/-/perfect-debounce-1.0.0.tgz",
@@ -5822,6 +6273,36 @@
         "pathe": "^1.1.2"
       }
     },
+    "node_modules/playwright": {
+      "version": "1.48.0",
+      "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.48.0.tgz",
+      "integrity": "sha512-qPqFaMEHuY/ug8o0uteYJSRfMGFikhUysk8ZvAtfKmUK3kc/6oNl/y3EczF8OFGYIi/Ex2HspMfzYArk6+XQSA==",
+      "dev": true,
+      "dependencies": {
+        "playwright-core": "1.48.0"
+      },
+      "bin": {
+        "playwright": "cli.js"
+      },
+      "engines": {
+        "node": ">=18"
+      },
+      "optionalDependencies": {
+        "fsevents": "2.3.2"
+      }
+    },
+    "node_modules/playwright-core": {
+      "version": "1.48.0",
+      "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.48.0.tgz",
+      "integrity": "sha512-RBvzjM9rdpP7UUFrQzRwR8L/xR4HyC1QXMzGYTbf1vjw25/ya9NRAVnXi/0fvFopjebvyPzsmoK58xxeEOaVvA==",
+      "dev": true,
+      "bin": {
+        "playwright-core": "cli.js"
+      },
+      "engines": {
+        "node": ">=18"
+      }
+    },
     "node_modules/postcss": {
       "version": "8.4.39",
       "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.39.tgz",
@@ -6811,11 +7292,6 @@
         "node": ">=4"
       }
     },
-    "node_modules/send/node_modules/ms": {
-      "version": "2.1.3",
-      "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
-      "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="
-    },
     "node_modules/serialize-javascript": {
       "version": "6.0.2",
       "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz",
@@ -6883,6 +7359,12 @@
         "url": "https://github.com/sponsors/ljharb"
       }
     },
+    "node_modules/siginfo": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz",
+      "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==",
+      "dev": true
+    },
     "node_modules/signal-exit": {
       "version": "3.0.7",
       "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz",
@@ -6977,6 +7459,12 @@
         "node": ">=0.10.0"
       }
     },
+    "node_modules/stackback": {
+      "version": "0.0.2",
+      "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz",
+      "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==",
+      "dev": true
+    },
     "node_modules/standard-as-callback": {
       "version": "2.1.0",
       "resolved": "https://registry.npmjs.org/standard-as-callback/-/standard-as-callback-2.1.0.tgz",
@@ -7411,6 +7899,45 @@
       "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.3.tgz",
       "integrity": "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg=="
     },
+    "node_modules/tinybench": {
+      "version": "2.9.0",
+      "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz",
+      "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==",
+      "dev": true
+    },
+    "node_modules/tinyexec": {
+      "version": "0.3.0",
+      "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.0.tgz",
+      "integrity": "sha512-tVGE0mVJPGb0chKhqmsoosjsS+qUnJVGJpZgsHYQcGoPlG3B51R3PouqTgEGH2Dc9jjFyOqOpix6ZHNMXp1FZg==",
+      "dev": true
+    },
+    "node_modules/tinypool": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-1.0.1.tgz",
+      "integrity": "sha512-URZYihUbRPcGv95En+sz6MfghfIc2OJ1sv/RmhWZLouPY0/8Vo80viwPvg3dlaS9fuq7fQMEfgRRK7BBZThBEA==",
+      "dev": true,
+      "engines": {
+        "node": "^18.0.0 || >=20.0.0"
+      }
+    },
+    "node_modules/tinyrainbow": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-1.2.0.tgz",
+      "integrity": "sha512-weEDEq7Z5eTHPDh4xjX789+fHfF+P8boiFB+0vbWzpbnbsEr/GRaohi/uMKxg8RZMXnl1ItAi/IUHWMsjDV7kQ==",
+      "dev": true,
+      "engines": {
+        "node": ">=14.0.0"
+      }
+    },
+    "node_modules/tinyspy": {
+      "version": "3.0.2",
+      "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-3.0.2.tgz",
+      "integrity": "sha512-n1cw8k1k0x4pgA2+9XrOkFydTerNcJ1zWCO5Nn9scWHTD+5tp8dghT2x1uduQePZTZgd3Tupf+x9BxJjeJi77Q==",
+      "dev": true,
+      "engines": {
+        "node": ">=14.0.0"
+      }
+    },
     "node_modules/to-fast-properties": {
       "version": "2.0.0",
       "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz",
@@ -7511,15 +8038,15 @@
       "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA=="
     },
     "node_modules/unenv": {
-      "version": "1.9.0",
-      "resolved": "https://registry.npmjs.org/unenv/-/unenv-1.9.0.tgz",
-      "integrity": "sha512-QKnFNznRxmbOF1hDgzpqrlIf6NC5sbZ2OJ+5Wl3OX8uM+LUJXbj4TXvLJCtwbPTmbMHCLIz6JLKNinNsMShK9g==",
+      "version": "1.10.0",
+      "resolved": "https://registry.npmjs.org/unenv/-/unenv-1.10.0.tgz",
+      "integrity": "sha512-wY5bskBQFL9n3Eca5XnhH6KbUo/tfvkwm9OpcdCvLaeA7piBNbavbOKJySEwQ1V0RH6HvNlSAFRTpvTqgKRQXQ==",
       "dependencies": {
         "consola": "^3.2.3",
-        "defu": "^6.1.3",
+        "defu": "^6.1.4",
         "mime": "^3.0.0",
-        "node-fetch-native": "^1.6.1",
-        "pathe": "^1.1.1"
+        "node-fetch-native": "^1.6.4",
+        "pathe": "^1.1.2"
       }
     },
     "node_modules/unenv/node_modules/mime": {
@@ -8203,6 +8730,113 @@
         "@esbuild/win32-x64": "0.21.5"
       }
     },
+    "node_modules/vite/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/vitest": {
+      "version": "2.1.2",
+      "resolved": "https://registry.npmjs.org/vitest/-/vitest-2.1.2.tgz",
+      "integrity": "sha512-veNjLizOMkRrJ6xxb+pvxN6/QAWg95mzcRjtmkepXdN87FNfxAss9RKe2far/G9cQpipfgP2taqg0KiWsquj8A==",
+      "dev": true,
+      "dependencies": {
+        "@vitest/expect": "2.1.2",
+        "@vitest/mocker": "2.1.2",
+        "@vitest/pretty-format": "^2.1.2",
+        "@vitest/runner": "2.1.2",
+        "@vitest/snapshot": "2.1.2",
+        "@vitest/spy": "2.1.2",
+        "@vitest/utils": "2.1.2",
+        "chai": "^5.1.1",
+        "debug": "^4.3.6",
+        "magic-string": "^0.30.11",
+        "pathe": "^1.1.2",
+        "std-env": "^3.7.0",
+        "tinybench": "^2.9.0",
+        "tinyexec": "^0.3.0",
+        "tinypool": "^1.0.0",
+        "tinyrainbow": "^1.2.0",
+        "vite": "^5.0.0",
+        "vite-node": "2.1.2",
+        "why-is-node-running": "^2.3.0"
+      },
+      "bin": {
+        "vitest": "vitest.mjs"
+      },
+      "engines": {
+        "node": "^18.0.0 || >=20.0.0"
+      },
+      "funding": {
+        "url": "https://opencollective.com/vitest"
+      },
+      "peerDependencies": {
+        "@edge-runtime/vm": "*",
+        "@types/node": "^18.0.0 || >=20.0.0",
+        "@vitest/browser": "2.1.2",
+        "@vitest/ui": "2.1.2",
+        "happy-dom": "*",
+        "jsdom": "*"
+      },
+      "peerDependenciesMeta": {
+        "@edge-runtime/vm": {
+          "optional": true
+        },
+        "@types/node": {
+          "optional": true
+        },
+        "@vitest/browser": {
+          "optional": true
+        },
+        "@vitest/ui": {
+          "optional": true
+        },
+        "happy-dom": {
+          "optional": true
+        },
+        "jsdom": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/vitest-environment-nuxt": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/vitest-environment-nuxt/-/vitest-environment-nuxt-1.0.1.tgz",
+      "integrity": "sha512-eBCwtIQriXW5/M49FjqNKfnlJYlG2LWMSNFsRVKomc8CaMqmhQPBS5LZ9DlgYL9T8xIVsiA6RZn2lk7vxov3Ow==",
+      "dev": true,
+      "dependencies": {
+        "@nuxt/test-utils": ">=3.13.1"
+      }
+    },
+    "node_modules/vitest/node_modules/vite-node": {
+      "version": "2.1.2",
+      "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-2.1.2.tgz",
+      "integrity": "sha512-HPcGNN5g/7I2OtPjLqgOtCRu/qhVvBxTUD3qzitmL0SrG1cWFzxzhMDWussxSbrRYWqnKf8P2jiNhPMSN+ymsQ==",
+      "dev": true,
+      "dependencies": {
+        "cac": "^6.7.14",
+        "debug": "^4.3.6",
+        "pathe": "^1.1.2",
+        "vite": "^5.0.0"
+      },
+      "bin": {
+        "vite-node": "vite-node.mjs"
+      },
+      "engines": {
+        "node": "^18.0.0 || >=20.0.0"
+      },
+      "funding": {
+        "url": "https://opencollective.com/vitest"
+      }
+    },
     "node_modules/vscode-jsonrpc": {
       "version": "6.0.0",
       "resolved": "https://registry.npmjs.org/vscode-jsonrpc/-/vscode-jsonrpc-6.0.0.tgz",
@@ -8377,6 +9011,22 @@
         "node": "^14.17.0 || ^16.13.0 || >=18.0.0"
       }
     },
+    "node_modules/why-is-node-running": {
+      "version": "2.3.0",
+      "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz",
+      "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==",
+      "dev": true,
+      "dependencies": {
+        "siginfo": "^2.0.0",
+        "stackback": "0.0.2"
+      },
+      "bin": {
+        "why-is-node-running": "cli.js"
+      },
+      "engines": {
+        "node": ">=8"
+      }
+    },
     "node_modules/wide-align": {
       "version": "1.1.5",
       "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz",
diff --git a/package.json b/package.json
index e14ca31..55bbc53 100644
--- a/package.json
+++ b/package.json
@@ -5,6 +5,7 @@
   "scripts": {
     "build": "nuxt build",
     "dev": "nuxt dev --host",
+    "test": "nuxt test",
     "generate": "nuxt generate",
     "preview": "nuxt preview",
     "postinstall": "nuxt prepare"
@@ -19,11 +20,14 @@
     "vue-router": "^4.4.0"
   },
   "devDependencies": {
+    "@nuxt/test-utils": "^3.14.3",
+    "@playwright/test": "^1.48.0",
     "@vueuse/core": "^11.1.0",
     "@vueuse/nuxt": "^11.1.0",
     "autoprefixer": "^10.4.19",
     "postcss": "^8.4.39",
     "sass": "^1.77.6",
-    "tailwindcss": "^3.4.4"
+    "tailwindcss": "^3.4.4",
+    "vitest": "^2.1.2"
   }
 }
\ No newline at end of file
diff --git a/pages/details/[id].vue b/pages/details/[id].vue
index 264d487..a704b65 100644
--- a/pages/details/[id].vue
+++ b/pages/details/[id].vue
@@ -1,30 +1,39 @@
 <template>
   <section class="flex flex-col items-center min-h-screen">
-    <div class="container w-full p-6 max-w-6xl grow flex flex-col items-center">
-      <div class="flex flex-col lg:flex-row">
-        <img class="min-w-fit" :src="config.public.IMG_BASE_URL + film.poster_path" :alt="film.title">
-        <span class="ml-20 w-60">
+    <div class="container w-full p-6 max-w-6xl grow flex flex-col items-center bg-slate-300">
+      <NuxtLink class="w-full m-4 flex justify-end" :to="'/'">
+        <uiClose />
+      </NuxtLink>
+      <img class="rounded-lg mb-6" :src="config.public.IMG_BASE_URL + film.poster_path" :alt="film.title">
+
+      <div class="poster flex flex-col lg:flex-row w-full justify-center">
+        <span>
           <h1 class="text-4xl font-bold leading-relaxed">
             {{ film.title }}
           </h1>
-          <h2 class="text-2xl">
+          <h2 class="text-2xl text-slate-700">
             {{ director }}
           </h2>
           <p class="my-8">
             {{ film.overview }}
           </p>
+          <p>
+            {{ formatPercent(film.vote_average) }} %
+          </p>
+          <p>
+            {{ film.vote_count }} votes
+          </p>
+          <div class="flex justify-center">
+            <ul class="flex items-center overflow-y-hidden overflow-x-scroll w-full max-w-lg">
+              <li v-for="star in film.credits.cast" class="flex flex-col items-center p-4 bg-slate-200 rounded m-4">
+                <UiPerson class="h-10" />
+                {{ star.name }}
+              </li>
+            </ul>
+          </div>
           <ul class="flex">
-            <li v-for="star in film.credits.cast">
-              {{ star.name }}
-            </li>
+            <li v-for="genre in film.genres" class="p-8"> {{ genre.name }} </li>
           </ul>
-          <ul>
-            <li v-for="genre in film.genres">
-              {{ genre.name }}
-            </li>
-          </ul>
-          {{ film.vote_average }}
-          {{ film.vote_count }}
         </span>
       </div>
       <div>
@@ -36,6 +45,7 @@
 </template>
 
 <script setup lang="ts">
+const router = useRouter()
 const config = useRuntimeConfig()
 const film = ref()
 const route = useRoute()
@@ -50,4 +60,8 @@ if (filmId.value !== '') {
   director.value = film.value.credits.crew.filter((member) => member.job === 'Director')
   director.value = director.value[0].name
 }
+
+onMounted(() => {
+  window.scrollTo(0, 0)
+})
 </script>
diff --git a/utils/formatPercent.ts b/utils/formatPercent.ts
new file mode 100644
index 0000000..9414983
--- /dev/null
+++ b/utils/formatPercent.ts
@@ -0,0 +1,3 @@
+export default function (percent: Number) {
+  return Math.round(percent * 10)
+}