diff --git a/README.md b/README.md
index 10ced067a3e993f5be230d2dffbc8cf45cef214e..1f3157336ae49f456ae425a4bc6ad0f010574357 100644
--- a/README.md
+++ b/README.md
@@ -7,10 +7,24 @@ Look at the [Nuxt documentation](https://nuxt.com/docs/getting-started/introduct
 Make sure to install dependencies:
 
 ```bash
-# npm
 npm install
 ```
 
+## Environment Variables
+
+Create a `.env` file at the root of the project with the following variables:
+
+```bash
+# either latest or dev; used for docker-compose deployment (default is latest)
+TAG=latest
+# depends of your API configuration (default is 3d)
+JWT_EXPIRES=3d
+# depends of your API url (default is http://localhost:3001)
+API_BASE_URL=http://localhost:3001
+# generate a random string for the secret key (https://www.random.org/strings/)
+SECRET_KEY=AZERTY
+```
+
 ## Development Server
 
 Be sure there are no new dependency updates:
@@ -23,31 +37,34 @@ npm install
 Start the development server on `http://localhost:3000`:
 
 ```bash
-# npm
 npm run dev
 ```
 
 ## Production
 
-Build the application for production:
+Locally preview production build:
 
 ```bash
-# npm
 npm run build
+npm run preview
 ```
 
-Locally preview production build:
+With docker for a local build with the API:
 
 ```bash
-# npm
-npm run preview
+# Start the API
+docker pull gitlab-registry.imt-atlantique.fr/3spa/api:latest
+docker run -d -p 3001:3000 --env-file .env --name 3spa-api gitlab-registry.imt-atlantique.fr/3spa/api:latest
+
+# Start the app
+docker build -t 3spa_web-admin .
+docker run -d -p 3000:3000 --env-file .env --name 3spa_web-admin 3spa_web-admin
 ```
 
-With docker
+With docker-compose
 
 ```bash
-docker build -t 3spa .
-docker run --rm -it -p 3000:3000 --env-file .env --name 3spa 3spa
+docker-compose up -d
 ```
 
 Check out the [deployment documentation](https://nuxt.com/docs/getting-started/deployment) for more information.
diff --git a/app.config.ts b/app.config.ts
index d1630e5b59a00987386673bc48b2794c65d90cae..c7a91d6c7c28278a78992a084996423e3a8c2900 100644
--- a/app.config.ts
+++ b/app.config.ts
@@ -30,16 +30,17 @@ export default defineAppConfig({
         }
     },
     toaster: {
-        position: 'bottom-right' as const,
+        position: 'top-right' as const,
         expand: true,
         duration: 5000
     },
     ui: {
         colors: {
-            primary: 'primary',
-            secondary: 'secondary',
-            success: 'success',
-            error: 'error',
+            primary: '3spa_primary',
+            secondary: '3spa_secondary',
+            success: '3spa_success',
+            error: '3spa_error',
+            accent: 'accent',
         }
     }
 })
diff --git a/assets/css/colors.css b/assets/css/colors.css
index 4481e0ca981b3f52f6c716ebc641eb435aca72ea..4cea775ef70516f7f85218cb87bb04ac118d4f15 100644
--- a/assets/css/colors.css
+++ b/assets/css/colors.css
@@ -4,29 +4,30 @@
 @theme {
    /*created with https://www.tailwindshades.com/*/
 
-    --color-primary-50: #9DF5D8;
-    --color-primary-100: #8BF3D0;
-    --color-primary-200: #66F0C1;
-    --color-primary-300: #41ECB3;
-    --color-primary-400: #1CE8A4;
-    --color-primary-500: #14C78B;
-    --color-primary-600: #10A271;
-    --color-primary-700: #0D7D57;
-    --color-primary-800: #09583D;
-    --color-primary-900: #053323;
-    --color-primary-950: #032016;
+    /*FIXME: add issue in nuxt/ui git to ask if there is a bug while using color-primary-X*/
+    --color-3spa_primary-50: #9DF5D8;
+    --color-3spa_primary-100: #8BF3D0;
+    --color-3spa_primary-200: #66F0C1;
+    --color-3spa_primary-300: #41ECB3;
+    --color-3spa_primary-400: #1CE8A4;
+    --color-3spa_primary-500: #14C78B;
+    --color-3spa_primary-600: #10A271;
+    --color-3spa_primary-700: #0D7D57;
+    --color-3spa_primary-800: #09583D;
+    --color-3spa_primary-900: #053323;
+    --color-3spa_primary-950: #032016;
 
-    --color-secondary-50: light-dark(#427684, #FDFEF8);
-    --color-secondary-100: light-dark(#3E6F7C, #FDFDF6);
-    --color-secondary-200: light-dark(#36606B, #FBFCF2);
-    --color-secondary-300: light-dark(#2D505A, #FAFCEE);
-    --color-secondary-400: light-dark(#254149, #F9FBE9);
-    --color-secondary-500: light-dark(#1C3238, #F8FAE5);
-    --color-secondary-600: light-dark(#192C31, #F7F9E1);
-    --color-secondary-700: light-dark(#15262A, #F6F8DC);
-    --color-secondary-800: light-dark(#122024, #F5F8D8);
-    --color-secondary-900: light-dark(#0E1A1D, #F3F7D4);
-    --color-secondary-950: light-dark(#0D1719, #F3F6D2);
+    --color-3spa_secondary-50: light-dark(#427684, #FDFEF8);
+    --color-3spa_secondary-100: light-dark(#3E6F7C, #FDFDF6);
+    --color-3spa_secondary-200: light-dark(#36606B, #FBFCF2);
+    --color-3spa_secondary-300: light-dark(#2D505A, #FAFCEE);
+    --color-3spa_secondary-400: light-dark(#254149, #F9FBE9);
+    --color-3spa_secondary-500: light-dark(#1C3238, #F8FAE5);
+    --color-3spa_secondary-600: light-dark(#192C31, #F7F9E1);
+    --color-3spa_secondary-700: light-dark(#15262A, #F6F8DC);
+    --color-3spa_secondary-800: light-dark(#122024, #F5F8D8);
+    --color-3spa_secondary-900: light-dark(#0E1A1D, #F3F7D4);
+    --color-3spa_secondary-950: light-dark(#0D1719, #F3F6D2);
 
     --color-background-50: light-dark(#FDFEF8, #427684);
     --color-background-100: light-dark(#FDFDF6, #3E6F7C);
@@ -52,27 +53,27 @@
     --color-accent-900: #8F1B00;
     --color-accent-950: #7A1700;
     
-    --color-error-50: #FFB8B8;
-    --color-error-100: #FFA3A3;
-    --color-error-200: #FF7A7A;
-    --color-error-300: #FF5252;
-    --color-error-400: #FF2929;
-    --color-error-500: #FF0000;
-    --color-error-600: #D60000;
-    --color-error-700: #AD0000;
-    --color-error-800: #850000;
-    --color-error-900: #5C0000;
-    --color-error-950: #470000;
+    --color-3spa_error-50: #FFB8B8;
+    --color-3spa_error-100: #FFA3A3;
+    --color-3spa_error-200: #FF7A7A;
+    --color-3spa_error-300: #FF5252;
+    --color-3spa_error-400: #FF2929;
+    --color-3spa_error-500: #FF0000;
+    --color-3spa_error-600: #D60000;
+    --color-3spa_error-700: #AD0000;
+    --color-3spa_error-800: #850000;
+    --color-3spa_error-900: #5C0000;
+    --color-3spa_error-950: #470000;
 
-    --color-success-50: #07FF07;
-    --color-success-100: #00FC00;
-    --color-success-200: #00E700;
-    --color-success-300: #00D300;
-    --color-success-400: #00BE00;
-    --color-success-500: #00AA00;
-    --color-success-600: #009600;
-    --color-success-700: #008100;
-    --color-success-800: #006D00;
-    --color-success-900: #005800;
-    --color-success-950: #004E00;
+    --color-3spa_success-50: #07FF07;
+    --color-3spa_success-100: #00FC00;
+    --color-3spa_success-200: #00E700;
+    --color-3spa_success-300: #00D300;
+    --color-3spa_success-400: #00BE00;
+    --color-3spa_success-500: #00AA00;
+    --color-3spa_success-600: #009600;
+    --color-3spa_success-700: #008100;
+    --color-3spa_success-800: #006D00;
+    --color-3spa_success-900: #005800;
+    --color-3spa_success-950: #004E00;
 }
\ No newline at end of file
diff --git a/components/AccountPlaceholder.vue b/components/AccountPlaceholder.vue
index 51689686a4c36bb9bc2d6dab1695b699c8d90127..6b61f929ff9729c0150b69d70f3393d79d892071 100644
--- a/components/AccountPlaceholder.vue
+++ b/components/AccountPlaceholder.vue
@@ -1,5 +1,5 @@
 <script setup lang="ts">
-import userData from '../data/mock/user.json';
+import userData from '~/data/mock/user.json';
 import {UserMapper} from "~/utils/mapper/UserMapper";
 
 const user = ref<User>(UserMapper.from(userData));
diff --git a/components/AppFooter.vue b/components/AppFooter.vue
index bdd9e88e71bdbe1d2c50611bdb6d49417ce9c145..d48e5dd2c9220369f379c8cff6d0808ef3cee65f 100644
--- a/components/AppFooter.vue
+++ b/components/AppFooter.vue
@@ -23,6 +23,6 @@ footer {
 
 footer > p {
     margin: 1vh;
-    color: var(--color-secondary-500);
+    color: var(--color-3spa_secondary-500);
 }
 </style>
\ No newline at end of file
diff --git a/components/AppHeader.vue b/components/AppHeader.vue
index a1ee0be0d5d4550ea47b61dfec6d6ab87146ba54..15d612b74550511d60b4326d37bba00a0aedc671 100644
--- a/components/AppHeader.vue
+++ b/components/AppHeader.vue
@@ -43,7 +43,7 @@ header {
 
 .title {
     margin: 1vh;
-    color: var(--color-primary-500);
+    color: var(--color-3spa_primary-500);
 }
 
 header span {
diff --git a/components/ExerciceItem.vue b/components/ExerciceItem.vue
new file mode 100644
index 0000000000000000000000000000000000000000..8134feb85d4130b363f0f88a465054e12ec181a0
--- /dev/null
+++ b/components/ExerciceItem.vue
@@ -0,0 +1,40 @@
+<script setup lang="ts">
+import type {Exercise} from "~/types/Exercise";
+import {getExerciseCategoryIcon, translateExerciseCategoryToFrench} from "~/types/enum/ExerciseCategory";
+
+const props = defineProps<{
+    exercise: Exercise
+}>()
+</script>
+
+<template>
+    <div class="exercise-item" :class="{ 'exercise-selected' : exercise.selected }">
+        <UIcon :name="getExerciseCategoryIcon(exercise.category)" class="h-9 w-9 m-1.5"/>
+        <div class="grid gap-2 w-full">
+            <p>{{ props.exercise.name }}</p>
+            <small>
+                {{ translateExerciseCategoryToFrench(props.exercise.category) }}
+                {{ props.exercise.subCategory ? '> ' + props.exercise.subCategory : '' }}
+            </small>
+        </div>
+    </div>
+</template>
+
+<style>
+div.exercise-item {
+    transition: 0.4s;
+}
+
+div.exercise-item:hover {
+    background-color: var(--color-background-800);
+    border-radius: 25px;
+}
+
+.exercise-item > span {
+    color: var(--color-accent-500)
+}
+
+.exercise-selected > span {
+    color: var(--color-3spa_success-500)
+}
+</style>
\ No newline at end of file
diff --git a/components/Modal/Confirmation.vue b/components/Modal/Confirmation.vue
new file mode 100644
index 0000000000000000000000000000000000000000..5f183021b043e33044fc53de7af6a3697a55ec7a
--- /dev/null
+++ b/components/Modal/Confirmation.vue
@@ -0,0 +1,24 @@
+<script setup lang="ts">
+defineProps<{
+    cancelIcon?: string,
+    confirmIcon?: string,
+}>();
+
+const modal = useModal();
+const emit = defineEmits(['success']);
+
+function onSuccess() {
+    emit('success');
+}
+</script>
+
+<template>
+    <UModal>
+        <template #footer>
+            <div class="flex gap-2 justify-center w-full">
+                <UButton :icon="cancelIcon" label="Non" color="secondary" @click="modal.close()"/>
+                <UButton :icon="confirmIcon" label="Confirmer" @click="onSuccess"/>
+            </div>
+        </template>
+    </UModal>
+</template>
diff --git a/components/Parcours/Details.vue b/components/Parcours/Details.vue
new file mode 100644
index 0000000000000000000000000000000000000000..8a456cf4f08ac486acf4fbe98edd86f4aa348812
--- /dev/null
+++ b/components/Parcours/Details.vue
@@ -0,0 +1,100 @@
+<script setup lang="ts">
+import type {PointTuple} from "leaflet";
+import * as v from 'valibot'
+import type {FormSubmitEvent} from '@nuxt/ui'
+
+const toast = useToast()
+
+const schema = v.object({
+    parcoursTitle: v.pipe(v.string(), v.nonEmpty('Titre obligatoire')),
+    localization: v.pipe(v.string(), v.nonEmpty('Localisation obligatoire')),
+    duration: v.pipe(v.string(), v.isoTime('Invalid time format'))
+})
+
+type Schema = v.InferOutput<typeof schema>
+
+const map = ref(null) as any;
+const state = reactive({
+    parcoursTitle: '',
+    localization: '',
+    duration: '00:30'
+})
+// TODO : calculate these datas from the data
+const zoom = ref(16.5)
+const maxBounds = [[47.283, -1.53], [47.29, -1.52]]
+const center: PointTuple = [47.287, -1.524]
+
+async function onSubmit(event: FormSubmitEvent<Schema>) {
+    toast.add({title: 'Success', description: 'The form has been submitted.', color: 'success'})
+    state.parcoursTitle = '';
+    state.localization = '';
+    state.duration = '00:30'
+    // TODO : Send the data to the API
+    // $api.parcours.createParcours(state)
+    console.log(event.data)
+}
+</script>
+
+<template>
+    <UForm :schema="v.safeParser(schema)" :state="state" class="space-y-4 grid-container" @submit="onSubmit">
+        <UFormField label="Titre du parcours" name="parcoursTitle" class="col-span-2">
+            <UInput
+                v-model="state.parcoursTitle" placeholder="Titre du parcours" class="w-full"
+                style="margin-right: 20px"/>
+        </UFormField>
+
+        <LMap
+            ref="map"
+            class="row-span-3"
+            style="height: auto;"
+            :zoom="zoom"
+            :min-zoom="zoom"
+            :max-zoom="zoom"
+            :center="center"
+            :max-bounds="maxBounds"
+            :use-global-leaflet="true"
+            :options="{ zoomSnap: 0.75 }"
+        >
+            <LTileLayer
+                url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
+                attribution="&amp;copy; <a href=&quot;https://www.openstreetmap.org/&quot;>OpenStreetMap</a>"
+                layer-type="base"
+                name="OpenStreetMap"
+            />
+        </LMap>
+
+        <UFormField label="Localisation" name="localization">
+            <UInput
+                v-model="state.localization" placeholder="Localisation" class="w-full"
+                style="margin-right: 20px"/>
+        </UFormField>
+
+        <UFormField label="Durée (hh:mm)" name="duration">
+            <UInput v-model="state.duration" type="time" class="w-full" style="margin-right: 20px"/>
+        </UFormField>
+
+        <div class="grid-item col-span-2">
+            <a href="https://github.com/kutlugsahin/vue-smooth-dnd?tab=readme-ov-file">Implements drag and drop from
+                this library</a>
+        </div>
+
+        <UButton type="submit" class="col-span-3 w-fit justify-self-center">
+            Enregistrer le parcours
+        </UButton>
+    </UForm>
+</template>
+
+<style>
+.grid-container {
+    display: grid;
+    grid-template-columns: 3fr 1fr 4fr;
+    grid-template-rows: 1fr 1fr 6fr auto;
+    height: 65vh;
+    gap: 1px;
+}
+
+.grid-item {
+    background-color: lightgray;
+    border: 1px solid gray;
+}
+</style>
\ No newline at end of file
diff --git a/pages/exercices.vue b/components/Parcours/Exercices.vue
similarity index 55%
rename from pages/exercices.vue
rename to components/Parcours/Exercices.vue
index e27fb6a01d3813389a926acc0fd1b55f730f4b4b..e53d45d2fe0292f78781b2df6926ac1b201907b8 100644
--- a/pages/exercices.vue
+++ b/components/Parcours/Exercices.vue
@@ -1,29 +1,29 @@
 <script setup lang="ts">
 import L, {type LeafletMouseEvent, Marker, type PointTuple} from 'leaflet';
 import 'leaflet.markercluster';
-import myData from '../data/mock/exercices.json';
-import type Exercise from "~/types/Exercice";
+import myData from '~/data/mock/exercices.json';
+import type {Exercise} from "~/types/Exercise";
 
+const {$api} = useNuxtApp();
 const map = ref(null) as any;
 const inCreation = ref(false);
-const exercises = ref(myData);
+const exercises = await $api.exercises.getExercises();
+const exerciseSearch = ref('');
+const selectedExercises: Exercise[] = [];
 
 // TODO : Move to a separate file
 interface MarkerCluster {
     markerCluster: L.LayerGroup;
-    // markerCluster: L.Map;
     markers: Marker[];
-    // options?: L.MarkerClusterGroupOptions;
 }
 
-let _markerCluster: MarkerCluster;
-let newMarkerCluster: MarkerCluster;
+let markerCluster: MarkerCluster;
 
 // DATA
 const zoom = ref(17)
 const chantrerie: PointTuple = [47.287, -1.524]
 
-const locations2: any = myData.map((exo) => {
+const locations: any = myData.map((exo) => {
     return {
         name: exo.id,
         lat: exo.lat,
@@ -38,30 +38,7 @@ const locations2: any = myData.map((exo) => {
     }
 })
 
-const locations = [
-    {
-        name: 'Chantrerie',
-        lat: 47.287,
-        lng: -1.524,
-        options: {
-            icon: L.icon({
-                iconUrl: '/svg/pin.svg',
-                iconSize: [30, 30],
-            })
-        },
-        popup: 'Parc de la chantrerie'
-    }, {
-        name: 'Chantrerie',
-        lat: 47.287,
-        lng: -1.523,
-        options: {
-            icon: L.icon({
-                iconUrl: '/svg/pin.svg',
-                iconSize: [30, 30],
-            })
-        }
-    }
-]
+// TODO & FIXME : menu contextuel à revoir
 const items = ref([
     [
         {
@@ -94,17 +71,10 @@ const items = ref([
 
 // Functions
 async function onMapReady() {
-    _markerCluster = await useLMarkerCluster({
+    markerCluster = await useLMarkerCluster({
         leafletObject: map?.value?.leafletObject,
         markers: locations
     });
-    newMarkerCluster = await useLMarkerCluster({
-        leafletObject: map?.value?.leafletObject,
-        markers: locations2
-    });
-
-    // newMarkerCluster = L.markerClusterGroup();
-    // map.addLayer(newMarkerCluster);
 
     map.value.leafletObject.on('moveend', () => {
         // ask api for new markers in bounds
@@ -114,7 +84,7 @@ async function onMapReady() {
 
 function deleteMarker(): void {
     if (!inCreation.value) return;
-    newMarkerCluster.markerCluster.clearLayers();
+    markerCluster.markerCluster.clearLayers();
     inCreation.value = false;
 }
 
@@ -129,9 +99,10 @@ function MapEvents(e: LeafletMouseEvent): void {
         iconUrl: '/svg/pinadd.svg',
         iconSize: [30, 30],
     }));
-    newMarkerCluster.markerCluster.addLayer(newMarker);
+    markerCluster.markerCluster.addLayer(newMarker);
 }
 
+// TODO : changement d'affichage des clusters
 const _cluster_visible = (cluster: any) => L.divIcon({
     html: `<div style="background-color: red;">${cluster.getChildCount()}</div>`,
     className: 'marker-cluster',
@@ -140,7 +111,7 @@ const _cluster_visible = (cluster: any) => L.divIcon({
 
 // FIXME : still a lot of works around this
 function hooverMarker(exo: Exercise): void {
-    newMarkerCluster.markers.find((marker: Marker) => {
+    markerCluster.markers.find((marker: Marker) => {
         if (marker.getLatLng().lat === exo.lat && marker.getLatLng().lng === exo.lng) {
             if (map.value.leafletObject.hasLayer(marker)) {
                 // Not in a cluster (ie marker is visible on map)
@@ -148,35 +119,35 @@ function hooverMarker(exo: Exercise): void {
                     iconUrl: '/svg/pin.svg',
                     iconSize: [45, 45],
                 }));
-            } else {
-                // In a cluster (ie marker is not visible on map)
-                map.value.leafletObject.eachLayer((layer: any) => {
-                    if (layer instanceof L.FeatureGroup && layer.hasLayer(marker)) {
-                        (layer as any).options = {
-                            iconCreateFunction: (cluster: any) => {
-                                return L.divIcon({
-                                    html: `<div style="background-color: red;">${cluster.getChildCount()}</div>`,
-                                    className: 'marker-cluster',
-                                    iconSize: L.point(40, 40)
-                                });
-                            }
-                        }
-                        // console.log((layer as any));
-                        // console.log((layer as any)._markerCluster);
-                        // (newMarkerCluster as any).refreshClusters();
-                        // (layer as any).setIcon(cluster_visible);
-                        // map.value.leafletObject.removeLayer(layer);
-                        // map.value.leafletObject.addLayer(layer);
-                        // (map.value.leafletObject as L.LayerGroup)
-                    }
-                });
+                // } else {
+                //     In a cluster (ie marker is not visible on map)
+                //     map.value.leafletObject.eachLayer((layer: any) => {
+                //         if (layer instanceof L.FeatureGroup && layer.hasLayer(marker)) {
+                //             (layer as any).options = {
+                //                 iconCreateFunction: (cluster: any) => {
+                //                     return L.divIcon({
+                //                         html: `<div style="background-color: red;">${cluster.getChildCount()}</div>`,
+                //                         className: 'marker-cluster',
+                //                         iconSize: L.point(40, 40)
+                //                     });
+                //                 }
+                //             }
+                //             console.log((layer as any));
+                //             console.log((layer as any)._markerCluster);
+                //             (newMarkerCluster as any).refreshClusters();
+                //             (layer as any).setIcon(cluster_visible);
+                //             map.value.leafletObject.removeLayer(layer);
+                //             map.value.leafletObject.addLayer(layer);
+                //             (map.value.leafletObject as L.LayerGroup)
+                //         }
+                //     });
             }
         }
     });
 }
 
 function leaveMarker(exo: Exercise): void {
-    newMarkerCluster.markers.find((marker: any) => {
+    markerCluster.markers.find((marker: any) => {
         if (marker.getLatLng().lat === exo.lat && marker.getLatLng().lng === exo.lng) {
             marker.setIcon(L.icon({
                 iconUrl: '/svg/pin.svg',
@@ -185,34 +156,59 @@ function leaveMarker(exo: Exercise): void {
         }
     });
 }
+
+function filterExercise(): Exercise[] {
+    return exercises.filter((exo: Exercise) => {
+        return exo.name.toLowerCase().includes(exerciseSearch.value.toLowerCase());
+    });
+}
+
+function onExerciceClick(exo: Exercise): void {
+    if (exo.selected) {
+        selectedExercises.splice(selectedExercises.indexOf(exo), 1);
+        document.getElementById(`exercise-item-${exo.id}`)?.classList.remove('exercise-selected');
+        exercises.find((exercise: Exercise) => {
+            if (exercise.id === exo.id) {
+                exercise.selected = false;
+            }
+        });
+    } else {
+        selectedExercises.push(exo);
+        document.getElementById(`exercise-item-${exo.id}`)?.classList.add('exercise-selected');
+        exercises.find((exercise: Exercise) => {
+            if (exercise.id === exo.id) {
+                exercise.selected = true;
+            }
+        })
+    }
+}
 </script>
 
 <template>
     <div id="exercices">
         <UContextMenu :items="items" :ui="{ content: 'w-48' }">
-            <div>
-                <LMap
-                    ref="map"
-                    :zoom="zoom"
-                    :min-zoom="15"
-                    :center="chantrerie"
-                    :use-global-leaflet="true"
-                    :options="{ zoomSnap: 0.75 }"
-                    @ready="onMapReady"
-                    @click="MapEvents"
-                >
-                    <LTileLayer
-                        url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
-                        attribution="&amp;copy; <a href=&quot;https://www.openstreetmap.org/&quot;>OpenStreetMap</a>"
-                        layer-type="base"
-                        name="OpenStreetMap"
-                    />
-                </LMap>
-            </div>
+            <LMap
+                ref="map"
+                :zoom="zoom"
+                :min-zoom="15"
+                :center="chantrerie"
+                :use-global-leaflet="true"
+                :options="{ zoomSnap: 0.75 }"
+                @ready="onMapReady"
+                @click="MapEvents"
+            >
+                <LTileLayer
+                    url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
+                    attribution="&amp;copy; <a href=&quot;https://www.openstreetmap.org/&quot;>OpenStreetMap</a>"
+                    layer-type="base"
+                    name="OpenStreetMap"
+                />
+            </LMap>
         </UContextMenu>
         <div id="side" class="flex flex-col w-full h-full p-2 overflow-hidden">
             <UInput
                 id="search-input"
+                v-model="exerciseSearch"
                 icon="material-symbols:search"
                 size="md" variant="outline" placeholder=""
                 :ui="{ base: 'peer' }">
@@ -226,9 +222,12 @@ function leaveMarker(exo: Exercise): void {
             </UInput>
             <USeparator class="m-1 w-full"/>
             <div id="exercices-list" class="flex flex-col w-full h-full overflow-auto gap-1">
-                <exerciceItem
-                    v-for="exercise in exercises" :key="exercise.id" :exercise="exercise as Exercise"
+                <ExerciceItem
+                    v-for="exercise in filterExercise()" :id="'exercise-item-'+exercise.id"
+                    :key="exercise.id" ref="list"
+                    :exercise="exercise as Exercise"
                     class="skeletton gap-4"
+                    @click="onExerciceClick(exercise)"
                     @mouseover="hooverMarker(exercise as Exercise)" @mouseleave="leaveMarker(exercise as Exercise)"/>
                 <div class="skeletton gap-4">
                     <USkeleton class="h-12 w-12 rounded-full"/>
diff --git a/components/exerciceItem.vue b/components/exerciceItem.vue
deleted file mode 100644
index bcacfe17ee4d2352e684816b77ab581eae5cf2db..0000000000000000000000000000000000000000
--- a/components/exerciceItem.vue
+++ /dev/null
@@ -1,45 +0,0 @@
-<script setup lang="ts">
-import type Exercise from '../types/Exercice.js';
-
-function getIcon() {
-    switch (props.exercise.category) {
-        case ExerciseCategory.RENFORCEMENT:
-            return 'material-symbols:exercise-outline';
-        case ExerciseCategory.CARDIO:
-            return 'material-symbols:cardiology-outline';
-        case ExerciseCategory.STRETCHING:
-            return 'material-symbols:transfer-within-a-station';
-        default:
-            return 'material-symbols:question-mark';
-    }
-}
-
-const props = defineProps<{
-    exercise: Exercise
-}>()
-</script>
-
-<template>
-    <div class="exercise-item">
-        <UIcon :name="getIcon()" class="h-9 w-9 m-1.5" style="color: var(--color-accent-500)"/>
-        <div class="grid gap-2 w-full">
-            <p>{{ props.exercise.name }}</p>
-            <small>
-                {{ props.exercise.category }}
-                {{ props.exercise.subCategory ? '> ' + props.exercise.subCategory : '' }}
-            </small>
-        </div>
-    </div>
-</template>
-
-<style>
-div.exercise-item {
-    transition: 0.4s;
-}
-
-div.exercise-item:hover {
-    transform: scale(1.05);
-    background-color: var(--color-background-800);
-    border-radius: 25px;
-}
-</style>
\ No newline at end of file
diff --git a/docker-compose.yaml b/docker-compose.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..028124342e6ccc566dabbf6ef3bc034d0f072a31
--- /dev/null
+++ b/docker-compose.yaml
@@ -0,0 +1,31 @@
+services:
+  api:
+    image: gitlab-registry.imt-atlantique.fr/3spa/api:${TAG}
+    container_name: api
+    restart: always
+    env_file: .env
+    ports:
+      - "3001:3000"
+    volumes:
+      - api_data:/app/data
+    networks:
+      - app_network
+
+  web-admin:
+    image: gitlab-registry.imt-atlantique.fr/3spa/web-admin:${TAG}
+    container_name: web-admin
+    restart: always
+    env_file: .env
+    ports:
+      - "3000:3000"
+    networks:
+      - app_network
+    depends_on:
+      - api
+
+volumes:
+  api_data:
+
+networks:
+  app_network:
+    driver: bridge
diff --git a/dockerfile b/dockerfile
index 608cd8e5e4b306564ce15669032cac0bb7f6d211..e1f42b0aef9c2126d72b69f11d907cb9622dbf7f 100644
--- a/dockerfile
+++ b/dockerfile
@@ -21,7 +21,8 @@ RUN npm run build
 # Run
 FROM base
 
-ENV PORT=$PORT
+# ENV PORT=$PORT
+EXPOSE 3000
 ENV NODE_ENV=production
 
 COPY --from=build /src/.output /src/.output
diff --git a/nuxt.config.ts b/nuxt.config.ts
index ff2b7d6c2585113773558a711aa66edcda5eea4b..f7b4a86bd2311ad8f5749bb55e5281a16b0fe0e1 100644
--- a/nuxt.config.ts
+++ b/nuxt.config.ts
@@ -19,10 +19,9 @@ export default defineNuxtConfig({
 
     /** Les variables d'environnement */
     runtimeConfig: {
-        apiSecret: '123',
         // Keys within public are also exposed client-side
         public: {
-            apiBase: '/api'
+            apiBase: process.env.API_BASE_URL
         }
     },
 
diff --git a/package-lock.json b/package-lock.json
index b7b6f7eceafd487b8ac1775ad3bba87c797f8a08..d65a610b6943a840ea2fe5f078c1beb5e5361d3f 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -7,21 +7,22 @@
       "name": "nuxt-app",
       "hasInstallScript": true,
       "dependencies": {
-        "@iconify-json/material-symbols": "^1.2.14",
-        "@nuxt/eslint": "^1.0.0",
-        "@nuxt/ui": "^3.0.0-alpha.11",
+        "@nuxt/eslint": "^1.0.1",
+        "@nuxt/ui": "^3.0.0-alpha.13",
         "@nuxtjs/leaflet": "^1.2.6",
-        "eslint": "^9.19.0",
+        "eslint": "^9.20.1",
         "leaflet.markercluster": "^1.5.3",
         "nuxt": "^3.15.4",
-        "valibot": "^1.0.0-beta.15",
+        "valibot": "^1.0.0-rc.1",
         "vue": "latest",
         "vue-router": "latest"
       },
       "devDependencies": {
+        "@iconify-json/lucide": "^1.2.26",
+        "@iconify-json/material-symbols": "^1.2.14",
         "@nuxt/test-utils": "^3.15.4",
         "@vue/test-utils": "^2.4.6",
-        "happy-dom": "^16.8.1",
+        "happy-dom": "^17.1.0",
         "typescript": "^5.7.3",
         "vitest": "^3.0.5"
       }
@@ -632,9 +633,9 @@
       }
     },
     "node_modules/@esbuild/aix-ppc64": {
-      "version": "0.24.2",
-      "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.24.2.tgz",
-      "integrity": "sha512-thpVCb/rhxE/BnMLQ7GReQLLN8q9qbHmI55F4489/ByVg2aQaQ6kbcLb6FHkocZzQhxc4gx0sCk0tJkKBFzDhA==",
+      "version": "0.25.0",
+      "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.0.tgz",
+      "integrity": "sha512-O7vun9Sf8DFjH2UtqK8Ku3LkquL9SZL8OLY1T5NZkA34+wG3OQF7cl4Ql8vdNzM6fzBbYfLaiRLIOZ+2FOCgBQ==",
       "cpu": [
         "ppc64"
       ],
@@ -648,9 +649,9 @@
       }
     },
     "node_modules/@esbuild/android-arm": {
-      "version": "0.24.2",
-      "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.24.2.tgz",
-      "integrity": "sha512-tmwl4hJkCfNHwFB3nBa8z1Uy3ypZpxqxfTQOcHX+xRByyYgunVbZ9MzUUfb0RxaHIMnbHagwAxuTL+tnNM+1/Q==",
+      "version": "0.25.0",
+      "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.0.tgz",
+      "integrity": "sha512-PTyWCYYiU0+1eJKmw21lWtC+d08JDZPQ5g+kFyxP0V+es6VPPSUhM6zk8iImp2jbV6GwjX4pap0JFbUQN65X1g==",
       "cpu": [
         "arm"
       ],
@@ -664,9 +665,9 @@
       }
     },
     "node_modules/@esbuild/android-arm64": {
-      "version": "0.24.2",
-      "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.24.2.tgz",
-      "integrity": "sha512-cNLgeqCqV8WxfcTIOeL4OAtSmL8JjcN6m09XIgro1Wi7cF4t/THaWEa7eL5CMoMBdjoHOTh/vwTO/o2TRXIyzg==",
+      "version": "0.25.0",
+      "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.0.tgz",
+      "integrity": "sha512-grvv8WncGjDSyUBjN9yHXNt+cq0snxXbDxy5pJtzMKGmmpPxeAmAhWxXI+01lU5rwZomDgD3kJwulEnhTRUd6g==",
       "cpu": [
         "arm64"
       ],
@@ -680,9 +681,9 @@
       }
     },
     "node_modules/@esbuild/android-x64": {
-      "version": "0.24.2",
-      "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.24.2.tgz",
-      "integrity": "sha512-B6Q0YQDqMx9D7rvIcsXfmJfvUYLoP722bgfBlO5cGvNVb5V/+Y7nhBE3mHV9OpxBf4eAS2S68KZztiPaWq4XYw==",
+      "version": "0.25.0",
+      "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.0.tgz",
+      "integrity": "sha512-m/ix7SfKG5buCnxasr52+LI78SQ+wgdENi9CqyCXwjVR2X4Jkz+BpC3le3AoBPYTC9NHklwngVXvbJ9/Akhrfg==",
       "cpu": [
         "x64"
       ],
@@ -696,9 +697,9 @@
       }
     },
     "node_modules/@esbuild/darwin-arm64": {
-      "version": "0.24.2",
-      "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.24.2.tgz",
-      "integrity": "sha512-kj3AnYWc+CekmZnS5IPu9D+HWtUI49hbnyqk0FLEJDbzCIQt7hg7ucF1SQAilhtYpIujfaHr6O0UHlzzSPdOeA==",
+      "version": "0.25.0",
+      "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.0.tgz",
+      "integrity": "sha512-mVwdUb5SRkPayVadIOI78K7aAnPamoeFR2bT5nszFUZ9P8UpK4ratOdYbZZXYSqPKMHfS1wdHCJk1P1EZpRdvw==",
       "cpu": [
         "arm64"
       ],
@@ -712,9 +713,9 @@
       }
     },
     "node_modules/@esbuild/darwin-x64": {
-      "version": "0.24.2",
-      "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.24.2.tgz",
-      "integrity": "sha512-WeSrmwwHaPkNR5H3yYfowhZcbriGqooyu3zI/3GGpF8AyUdsrrP0X6KumITGA9WOyiJavnGZUwPGvxvwfWPHIA==",
+      "version": "0.25.0",
+      "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.0.tgz",
+      "integrity": "sha512-DgDaYsPWFTS4S3nWpFcMn/33ZZwAAeAFKNHNa1QN0rI4pUjgqf0f7ONmXf6d22tqTY+H9FNdgeaAa+YIFUn2Rg==",
       "cpu": [
         "x64"
       ],
@@ -728,9 +729,9 @@
       }
     },
     "node_modules/@esbuild/freebsd-arm64": {
-      "version": "0.24.2",
-      "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.24.2.tgz",
-      "integrity": "sha512-UN8HXjtJ0k/Mj6a9+5u6+2eZ2ERD7Edt1Q9IZiB5UZAIdPnVKDoG7mdTVGhHJIeEml60JteamR3qhsr1r8gXvg==",
+      "version": "0.25.0",
+      "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.0.tgz",
+      "integrity": "sha512-VN4ocxy6dxefN1MepBx/iD1dH5K8qNtNe227I0mnTRjry8tj5MRk4zprLEdG8WPyAPb93/e4pSgi1SoHdgOa4w==",
       "cpu": [
         "arm64"
       ],
@@ -744,9 +745,9 @@
       }
     },
     "node_modules/@esbuild/freebsd-x64": {
-      "version": "0.24.2",
-      "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.24.2.tgz",
-      "integrity": "sha512-TvW7wE/89PYW+IevEJXZ5sF6gJRDY/14hyIGFXdIucxCsbRmLUcjseQu1SyTko+2idmCw94TgyaEZi9HUSOe3Q==",
+      "version": "0.25.0",
+      "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.0.tgz",
+      "integrity": "sha512-mrSgt7lCh07FY+hDD1TxiTyIHyttn6vnjesnPoVDNmDfOmggTLXRv8Id5fNZey1gl/V2dyVK1VXXqVsQIiAk+A==",
       "cpu": [
         "x64"
       ],
@@ -760,9 +761,9 @@
       }
     },
     "node_modules/@esbuild/linux-arm": {
-      "version": "0.24.2",
-      "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.24.2.tgz",
-      "integrity": "sha512-n0WRM/gWIdU29J57hJyUdIsk0WarGd6To0s+Y+LwvlC55wt+GT/OgkwoXCXvIue1i1sSNWblHEig00GBWiJgfA==",
+      "version": "0.25.0",
+      "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.0.tgz",
+      "integrity": "sha512-vkB3IYj2IDo3g9xX7HqhPYxVkNQe8qTK55fraQyTzTX/fxaDtXiEnavv9geOsonh2Fd2RMB+i5cbhu2zMNWJwg==",
       "cpu": [
         "arm"
       ],
@@ -776,9 +777,9 @@
       }
     },
     "node_modules/@esbuild/linux-arm64": {
-      "version": "0.24.2",
-      "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.24.2.tgz",
-      "integrity": "sha512-7HnAD6074BW43YvvUmE/35Id9/NB7BeX5EoNkK9obndmZBUk8xmJJeU7DwmUeN7tkysslb2eSl6CTrYz6oEMQg==",
+      "version": "0.25.0",
+      "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.0.tgz",
+      "integrity": "sha512-9QAQjTWNDM/Vk2bgBl17yWuZxZNQIF0OUUuPZRKoDtqF2k4EtYbpyiG5/Dk7nqeK6kIJWPYldkOcBqjXjrUlmg==",
       "cpu": [
         "arm64"
       ],
@@ -792,9 +793,9 @@
       }
     },
     "node_modules/@esbuild/linux-ia32": {
-      "version": "0.24.2",
-      "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.24.2.tgz",
-      "integrity": "sha512-sfv0tGPQhcZOgTKO3oBE9xpHuUqguHvSo4jl+wjnKwFpapx+vUDcawbwPNuBIAYdRAvIDBfZVvXprIj3HA+Ugw==",
+      "version": "0.25.0",
+      "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.0.tgz",
+      "integrity": "sha512-43ET5bHbphBegyeqLb7I1eYn2P/JYGNmzzdidq/w0T8E2SsYL1U6un2NFROFRg1JZLTzdCoRomg8Rvf9M6W6Gg==",
       "cpu": [
         "ia32"
       ],
@@ -808,9 +809,9 @@
       }
     },
     "node_modules/@esbuild/linux-loong64": {
-      "version": "0.24.2",
-      "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.24.2.tgz",
-      "integrity": "sha512-CN9AZr8kEndGooS35ntToZLTQLHEjtVB5n7dl8ZcTZMonJ7CCfStrYhrzF97eAecqVbVJ7APOEe18RPI4KLhwQ==",
+      "version": "0.25.0",
+      "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.0.tgz",
+      "integrity": "sha512-fC95c/xyNFueMhClxJmeRIj2yrSMdDfmqJnyOY4ZqsALkDrrKJfIg5NTMSzVBr5YW1jf+l7/cndBfP3MSDpoHw==",
       "cpu": [
         "loong64"
       ],
@@ -824,9 +825,9 @@
       }
     },
     "node_modules/@esbuild/linux-mips64el": {
-      "version": "0.24.2",
-      "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.24.2.tgz",
-      "integrity": "sha512-iMkk7qr/wl3exJATwkISxI7kTcmHKE+BlymIAbHO8xanq/TjHaaVThFF6ipWzPHryoFsesNQJPE/3wFJw4+huw==",
+      "version": "0.25.0",
+      "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.0.tgz",
+      "integrity": "sha512-nkAMFju7KDW73T1DdH7glcyIptm95a7Le8irTQNO/qtkoyypZAnjchQgooFUDQhNAy4iu08N79W4T4pMBwhPwQ==",
       "cpu": [
         "mips64el"
       ],
@@ -840,9 +841,9 @@
       }
     },
     "node_modules/@esbuild/linux-ppc64": {
-      "version": "0.24.2",
-      "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.24.2.tgz",
-      "integrity": "sha512-shsVrgCZ57Vr2L8mm39kO5PPIb+843FStGt7sGGoqiiWYconSxwTiuswC1VJZLCjNiMLAMh34jg4VSEQb+iEbw==",
+      "version": "0.25.0",
+      "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.0.tgz",
+      "integrity": "sha512-NhyOejdhRGS8Iwv+KKR2zTq2PpysF9XqY+Zk77vQHqNbo/PwZCzB5/h7VGuREZm1fixhs4Q/qWRSi5zmAiO4Fw==",
       "cpu": [
         "ppc64"
       ],
@@ -856,9 +857,9 @@
       }
     },
     "node_modules/@esbuild/linux-riscv64": {
-      "version": "0.24.2",
-      "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.24.2.tgz",
-      "integrity": "sha512-4eSFWnU9Hhd68fW16GD0TINewo1L6dRrB+oLNNbYyMUAeOD2yCK5KXGK1GH4qD/kT+bTEXjsyTCiJGHPZ3eM9Q==",
+      "version": "0.25.0",
+      "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.0.tgz",
+      "integrity": "sha512-5S/rbP5OY+GHLC5qXp1y/Mx//e92L1YDqkiBbO9TQOvuFXM+iDqUNG5XopAnXoRH3FjIUDkeGcY1cgNvnXp/kA==",
       "cpu": [
         "riscv64"
       ],
@@ -872,9 +873,9 @@
       }
     },
     "node_modules/@esbuild/linux-s390x": {
-      "version": "0.24.2",
-      "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.24.2.tgz",
-      "integrity": "sha512-S0Bh0A53b0YHL2XEXC20bHLuGMOhFDO6GN4b3YjRLK//Ep3ql3erpNcPlEFed93hsQAjAQDNsvcK+hV90FubSw==",
+      "version": "0.25.0",
+      "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.0.tgz",
+      "integrity": "sha512-XM2BFsEBz0Fw37V0zU4CXfcfuACMrppsMFKdYY2WuTS3yi8O1nFOhil/xhKTmE1nPmVyvQJjJivgDT+xh8pXJA==",
       "cpu": [
         "s390x"
       ],
@@ -888,9 +889,9 @@
       }
     },
     "node_modules/@esbuild/linux-x64": {
-      "version": "0.24.2",
-      "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.24.2.tgz",
-      "integrity": "sha512-8Qi4nQcCTbLnK9WoMjdC9NiTG6/E38RNICU6sUNqK0QFxCYgoARqVqxdFmWkdonVsvGqWhmm7MO0jyTqLqwj0Q==",
+      "version": "0.25.0",
+      "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.0.tgz",
+      "integrity": "sha512-9yl91rHw/cpwMCNytUDxwj2XjFpxML0y9HAOH9pNVQDpQrBxHy01Dx+vaMu0N1CKa/RzBD2hB4u//nfc+Sd3Cw==",
       "cpu": [
         "x64"
       ],
@@ -904,9 +905,9 @@
       }
     },
     "node_modules/@esbuild/netbsd-arm64": {
-      "version": "0.24.2",
-      "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.24.2.tgz",
-      "integrity": "sha512-wuLK/VztRRpMt9zyHSazyCVdCXlpHkKm34WUyinD2lzK07FAHTq0KQvZZlXikNWkDGoT6x3TD51jKQ7gMVpopw==",
+      "version": "0.25.0",
+      "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.0.tgz",
+      "integrity": "sha512-RuG4PSMPFfrkH6UwCAqBzauBWTygTvb1nxWasEJooGSJ/NwRw7b2HOwyRTQIU97Hq37l3npXoZGYMy3b3xYvPw==",
       "cpu": [
         "arm64"
       ],
@@ -920,9 +921,9 @@
       }
     },
     "node_modules/@esbuild/netbsd-x64": {
-      "version": "0.24.2",
-      "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.24.2.tgz",
-      "integrity": "sha512-VefFaQUc4FMmJuAxmIHgUmfNiLXY438XrL4GDNV1Y1H/RW3qow68xTwjZKfj/+Plp9NANmzbH5R40Meudu8mmw==",
+      "version": "0.25.0",
+      "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.0.tgz",
+      "integrity": "sha512-jl+qisSB5jk01N5f7sPCsBENCOlPiS/xptD5yxOx2oqQfyourJwIKLRA2yqWdifj3owQZCL2sn6o08dBzZGQzA==",
       "cpu": [
         "x64"
       ],
@@ -936,9 +937,9 @@
       }
     },
     "node_modules/@esbuild/openbsd-arm64": {
-      "version": "0.24.2",
-      "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.24.2.tgz",
-      "integrity": "sha512-YQbi46SBct6iKnszhSvdluqDmxCJA+Pu280Av9WICNwQmMxV7nLRHZfjQzwbPs3jeWnuAhE9Jy0NrnJ12Oz+0A==",
+      "version": "0.25.0",
+      "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.0.tgz",
+      "integrity": "sha512-21sUNbq2r84YE+SJDfaQRvdgznTD8Xc0oc3p3iW/a1EVWeNj/SdUCbm5U0itZPQYRuRTW20fPMWMpcrciH2EJw==",
       "cpu": [
         "arm64"
       ],
@@ -952,9 +953,9 @@
       }
     },
     "node_modules/@esbuild/openbsd-x64": {
-      "version": "0.24.2",
-      "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.24.2.tgz",
-      "integrity": "sha512-+iDS6zpNM6EnJyWv0bMGLWSWeXGN/HTaF/LXHXHwejGsVi+ooqDfMCCTerNFxEkM3wYVcExkeGXNqshc9iMaOA==",
+      "version": "0.25.0",
+      "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.0.tgz",
+      "integrity": "sha512-2gwwriSMPcCFRlPlKx3zLQhfN/2WjJ2NSlg5TKLQOJdV0mSxIcYNTMhk3H3ulL/cak+Xj0lY1Ym9ysDV1igceg==",
       "cpu": [
         "x64"
       ],
@@ -968,9 +969,9 @@
       }
     },
     "node_modules/@esbuild/sunos-x64": {
-      "version": "0.24.2",
-      "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.24.2.tgz",
-      "integrity": "sha512-hTdsW27jcktEvpwNHJU4ZwWFGkz2zRJUz8pvddmXPtXDzVKTTINmlmga3ZzwcuMpUvLw7JkLy9QLKyGpD2Yxig==",
+      "version": "0.25.0",
+      "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.0.tgz",
+      "integrity": "sha512-bxI7ThgLzPrPz484/S9jLlvUAHYMzy6I0XiU1ZMeAEOBcS0VePBFxh1JjTQt3Xiat5b6Oh4x7UC7IwKQKIJRIg==",
       "cpu": [
         "x64"
       ],
@@ -984,9 +985,9 @@
       }
     },
     "node_modules/@esbuild/win32-arm64": {
-      "version": "0.24.2",
-      "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.24.2.tgz",
-      "integrity": "sha512-LihEQ2BBKVFLOC9ZItT9iFprsE9tqjDjnbulhHoFxYQtQfai7qfluVODIYxt1PgdoyQkz23+01rzwNwYfutxUQ==",
+      "version": "0.25.0",
+      "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.0.tgz",
+      "integrity": "sha512-ZUAc2YK6JW89xTbXvftxdnYy3m4iHIkDtK3CLce8wg8M2L+YZhIvO1DKpxrd0Yr59AeNNkTiic9YLf6FTtXWMw==",
       "cpu": [
         "arm64"
       ],
@@ -1000,9 +1001,9 @@
       }
     },
     "node_modules/@esbuild/win32-ia32": {
-      "version": "0.24.2",
-      "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.24.2.tgz",
-      "integrity": "sha512-q+iGUwfs8tncmFC9pcnD5IvRHAzmbwQ3GPS5/ceCyHdjXubwQWI12MKWSNSMYLJMq23/IUCvJMS76PDqXe1fxA==",
+      "version": "0.25.0",
+      "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.0.tgz",
+      "integrity": "sha512-eSNxISBu8XweVEWG31/JzjkIGbGIJN/TrRoiSVZwZ6pkC6VX4Im/WV2cz559/TXLcYbcrDN8JtKgd9DJVIo8GA==",
       "cpu": [
         "ia32"
       ],
@@ -1016,9 +1017,9 @@
       }
     },
     "node_modules/@esbuild/win32-x64": {
-      "version": "0.24.2",
-      "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.24.2.tgz",
-      "integrity": "sha512-7VTgWzgMGvup6aSqDPLiW5zHaxYJGTO4OokMjIlrCtf+VpEL+cXKtCvg723iguPYI5oaUNdS+/V7OU2gvXVWEg==",
+      "version": "0.25.0",
+      "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.0.tgz",
+      "integrity": "sha512-ZENoHJBxA20C2zFzh6AI4fT6RraMzjYw4xKWemRTRmRVtN9c5DcH9r/f2ihEkMjOW5eGgrwCslG/+Y/3bL+DHQ==",
       "cpu": [
         "x64"
       ],
@@ -1071,9 +1072,9 @@
       }
     },
     "node_modules/@eslint/compat": {
-      "version": "1.2.5",
-      "resolved": "https://registry.npmjs.org/@eslint/compat/-/compat-1.2.5.tgz",
-      "integrity": "sha512-5iuG/StT+7OfvhoBHPlmxkPA9om6aDUFgmD4+mWKAGsYt4vCe8rypneG03AuseyRHBmcCLXQtIH5S26tIoggLg==",
+      "version": "1.2.6",
+      "resolved": "https://registry.npmjs.org/@eslint/compat/-/compat-1.2.6.tgz",
+      "integrity": "sha512-k7HNCqApoDHM6XzT30zGoETj+D+uUcZUb+IVAJmar3u6bvHf7hhHJcWx09QHj4/a2qrKZMWU0E16tvkiAdv06Q==",
       "license": "Apache-2.0",
       "engines": {
         "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -1235,9 +1236,9 @@
       }
     },
     "node_modules/@eslint/js": {
-      "version": "9.19.0",
-      "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.19.0.tgz",
-      "integrity": "sha512-rbq9/g38qjfqFLOVPvwjIvFFdNziEC5S65jmjPw5r6A//QH+W91akh9irMwjDN8zKUTak6W9EsAv4m/7Wnw0UQ==",
+      "version": "9.20.0",
+      "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.20.0.tgz",
+      "integrity": "sha512-iZA07H9io9Wn836aVTytRaNqh00Sad+EamwOVJT12GTLw1VGMFV/4JaME+JjLtr9fiGaoWgYnS54wrfWsSs4oQ==",
       "license": "MIT",
       "engines": {
         "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -1388,10 +1389,21 @@
         "url": "https://github.com/sponsors/nzakas"
       }
     },
+    "node_modules/@iconify-json/lucide": {
+      "version": "1.2.26",
+      "resolved": "https://registry.npmjs.org/@iconify-json/lucide/-/lucide-1.2.26.tgz",
+      "integrity": "sha512-arD/8mK0lRxFY2LgLf345NhWVWiOtV8sOxJuLnq4QRz3frMiOwVwGxEgp5Xe/bRGzxO2CxxCBok0bPRpCkYZQQ==",
+      "dev": true,
+      "license": "ISC",
+      "dependencies": {
+        "@iconify/types": "*"
+      }
+    },
     "node_modules/@iconify-json/material-symbols": {
       "version": "1.2.14",
       "resolved": "https://registry.npmjs.org/@iconify-json/material-symbols/-/material-symbols-1.2.14.tgz",
       "integrity": "sha512-S0AAFFQPVr8Dkrprspz/otNjxdD3rJRXDGZjbO8a8zn8ZR5mO8jAF81lVoTfUWxPH6SCtH2lK1JQGXHGPxld7g==",
+      "dev": true,
       "license": "Apache-2.0",
       "dependencies": {
         "@iconify/types": "*"
@@ -1954,24 +1966,24 @@
       }
     },
     "node_modules/@nuxt/eslint": {
-      "version": "1.0.0",
-      "resolved": "https://registry.npmjs.org/@nuxt/eslint/-/eslint-1.0.0.tgz",
-      "integrity": "sha512-RtVvRM+rv9q4x9nZVHkVo7X0gRvockZm6b5EYshO0A0qjughD3fj7N1sSYxvyO9n8IBxrnWT5jHa+2txdzuthg==",
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/@nuxt/eslint/-/eslint-1.0.1.tgz",
+      "integrity": "sha512-Bh6M9Tjf4jyIyfobu/ekW6YHMCsWpvZHuZN/6tRVKwXgAL6fiJG6tHm3uYJ5ILcVTdp6tz/j/sW8dgP+ijHLDQ==",
       "license": "MIT",
       "dependencies": {
         "@eslint/config-inspector": "^1.0.0",
         "@nuxt/devtools-kit": "^1.7.0",
-        "@nuxt/eslint-config": "1.0.0",
-        "@nuxt/eslint-plugin": "1.0.0",
+        "@nuxt/eslint-config": "1.0.1",
+        "@nuxt/eslint-plugin": "1.0.1",
         "@nuxt/kit": "^3.15.4",
         "chokidar": "^4.0.3",
-        "eslint-flat-config-utils": "^2.0.0",
+        "eslint-flat-config-utils": "^2.0.1",
         "eslint-typegen": "^1.0.0",
         "find-up": "^7.0.0",
         "get-port-please": "^3.1.2",
         "mlly": "^1.7.4",
         "pathe": "^2.0.2",
-        "unimport": "^4.0.0"
+        "unimport": "^4.1.0"
       },
       "peerDependencies": {
         "eslint": "^9.0.0",
@@ -1988,20 +2000,20 @@
       }
     },
     "node_modules/@nuxt/eslint-config": {
-      "version": "1.0.0",
-      "resolved": "https://registry.npmjs.org/@nuxt/eslint-config/-/eslint-config-1.0.0.tgz",
-      "integrity": "sha512-Bxvx6y68WqLkubKv9zDP7mdl82ljO7Zmqi13RWveGTvFt61BwP4bQIF10s8r1rfE+2svDN8d0L9eIDoDHtHZQg==",
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/@nuxt/eslint-config/-/eslint-config-1.0.1.tgz",
+      "integrity": "sha512-N1sSvtrCWehBHOhB4VBVDxZyDxkXv5b4QN+NQC0suCGR4VekZj8mciaepWFTb66gNrpA/IHYfBId308Na++FwA==",
       "license": "MIT",
       "dependencies": {
         "@antfu/install-pkg": "^1.0.0",
         "@clack/prompts": "^0.9.1",
         "@eslint/js": "^9.19.0",
-        "@nuxt/eslint-plugin": "1.0.0",
+        "@nuxt/eslint-plugin": "1.0.1",
         "@stylistic/eslint-plugin": "^3.0.1",
-        "@typescript-eslint/eslint-plugin": "^8.22.0",
-        "@typescript-eslint/parser": "^8.22.0",
+        "@typescript-eslint/eslint-plugin": "^8.23.0",
+        "@typescript-eslint/parser": "^8.23.0",
         "eslint-config-flat-gitignore": "^2.0.0",
-        "eslint-flat-config-utils": "^2.0.0",
+        "eslint-flat-config-utils": "^2.0.1",
         "eslint-merge-processors": "^1.0.0",
         "eslint-plugin-import-x": "^4.6.1",
         "eslint-plugin-jsdoc": "^50.6.3",
@@ -2041,13 +2053,13 @@
       }
     },
     "node_modules/@nuxt/eslint-plugin": {
-      "version": "1.0.0",
-      "resolved": "https://registry.npmjs.org/@nuxt/eslint-plugin/-/eslint-plugin-1.0.0.tgz",
-      "integrity": "sha512-mYkq6V3xCVwnJxiwqTYfEe3HYV/Nxayes9cqY5maijSMJSCSI8l73FYWal2HFvsSoqilYhN4EfgzCHPhyWHqQQ==",
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/@nuxt/eslint-plugin/-/eslint-plugin-1.0.1.tgz",
+      "integrity": "sha512-pqad64otqRmYTnaUSD1Jc57iWHACdWebD6UUb1hLVl/ML1B4yFxn5xq8kXRjzQsSKX3yMs7dFTYS0ygw04n+SA==",
       "license": "MIT",
       "dependencies": {
-        "@typescript-eslint/types": "^8.22.0",
-        "@typescript-eslint/utils": "^8.22.0"
+        "@typescript-eslint/types": "^8.23.0",
+        "@typescript-eslint/utils": "^8.23.0"
       },
       "peerDependencies": {
         "eslint": "^9.0.0"
@@ -2082,12 +2094,11 @@
       }
     },
     "node_modules/@nuxt/eslint/node_modules/unimport": {
-      "version": "4.0.0",
-      "resolved": "https://registry.npmjs.org/unimport/-/unimport-4.0.0.tgz",
-      "integrity": "sha512-FH+yZ36YaVlh0ZjHesP20Q4uL+wL0EqTNxDZcUupsIn6WRYXZAbIYEMDLTaLBpkNVzFpqZXS+am51/HR3ANUNw==",
+      "version": "4.1.0",
+      "resolved": "https://registry.npmjs.org/unimport/-/unimport-4.1.0.tgz",
+      "integrity": "sha512-y5ZYDG+j7IB45+Y6CIkWIKou4E1JFigCUw6vI+h15HdYAKmT0oQWcawnxXuwJG8srJyXhIZuWz5uXB1MQ/ARZw==",
       "license": "MIT",
       "dependencies": {
-        "@rollup/pluginutils": "^5.1.4",
         "acorn": "^8.14.0",
         "escape-string-regexp": "^5.0.0",
         "estree-walker": "^3.0.3",
@@ -2100,10 +2111,11 @@
         "pkg-types": "^1.3.1",
         "scule": "^1.3.0",
         "strip-literal": "^3.0.0",
-        "unplugin": "^2.1.2"
+        "unplugin": "^2.1.2",
+        "unplugin-utils": "^0.2.3"
       },
       "engines": {
-        "node": ">=18.12.0"
+        "node": ">=18.20.6"
       }
     },
     "node_modules/@nuxt/fonts": {
@@ -2393,29 +2405,28 @@
       }
     },
     "node_modules/@nuxt/ui": {
-      "version": "3.0.0-alpha.11",
-      "resolved": "https://registry.npmjs.org/@nuxt/ui/-/ui-3.0.0-alpha.11.tgz",
-      "integrity": "sha512-J/9XhhRD6ooNUk192KcWEAioBDUsVCaLfDSUbxWEJvVKtT74fY8bXZZ38nTzgptF8Evg2dxIugFCoJXLu5xgEQ==",
+      "version": "3.0.0-alpha.13",
+      "resolved": "https://registry.npmjs.org/@nuxt/ui/-/ui-3.0.0-alpha.13.tgz",
+      "integrity": "sha512-RjCR5P1jdgsmVPgaIsaZaTT20r+EN3Qt3cQlu5yiE/9VhrF5YZWfxcNKIGdDqgRObRKgpV0GlaxYVZiDu9wAmQ==",
       "license": "MIT",
       "dependencies": {
         "@iconify/vue": "^4.3.0",
-        "@internationalized/date": "^3.6.0",
+        "@internationalized/date": "^3.7.0",
         "@internationalized/number": "^3.6.0",
-        "@nuxt/devtools-kit": "^1.7.0",
+        "@nuxt/devtools-kit": "^2.1.0",
         "@nuxt/fonts": "^0.10.3",
         "@nuxt/icon": "^1.10.3",
-        "@nuxt/kit": "^3.15.1",
-        "@nuxt/schema": "^3.15.1",
+        "@nuxt/kit": "^3.15.4",
+        "@nuxt/schema": "^3.15.4",
         "@nuxtjs/color-mode": "^3.5.2",
-        "@tailwindcss/postcss": "4.0.0-beta.9",
-        "@tailwindcss/vite": "4.0.0-beta.9",
-        "@tanstack/vue-table": "^8.20.5",
-        "@types/color": "^4.2.0",
-        "@unhead/vue": "^1.11.16",
-        "@vueuse/core": "^12.4.0",
-        "@vueuse/integrations": "^12.4.0",
-        "color": "^4.2.3",
-        "consola": "^3.3.3",
+        "@tailwindcss/postcss": "^4.0.6",
+        "@tailwindcss/vite": "^4.0.6",
+        "@tanstack/vue-table": "^8.21.2",
+        "@unhead/vue": "^1.11.19",
+        "@vueuse/core": "^12.7.0",
+        "@vueuse/integrations": "^12.7.0",
+        "colortranslator": "^4.1.0",
+        "consola": "^3.4.0",
         "defu": "^6.1.4",
         "embla-carousel-auto-height": "^8.5.2",
         "embla-carousel-auto-scroll": "^8.5.2",
@@ -2424,23 +2435,23 @@
         "embla-carousel-fade": "^8.5.2",
         "embla-carousel-vue": "^8.5.2",
         "embla-carousel-wheel-gestures": "^8.0.1",
-        "fuse.js": "^7.0.0",
+        "fuse.js": "^7.1.0",
         "get-port-please": "^3.1.2",
         "knitwork": "^1.2.0",
         "magic-string": "^0.30.17",
         "mlly": "^1.7.4",
         "ohash": "^1.1.4",
-        "pathe": "^2.0.1",
-        "reka-ui": "1.0.0-alpha.8",
+        "pathe": "^2.0.3",
+        "reka-ui": "1.0.0-alpha.10",
         "scule": "^1.3.0",
         "sirv": "^3.0.0",
-        "tailwind-variants": "^0.3.0",
-        "tailwindcss": "4.0.0-beta.9",
+        "tailwind-variants": "^0.3.1",
+        "tailwindcss": "^4.0.6",
         "tinyglobby": "^0.2.10",
-        "unplugin": "^2.1.2",
-        "unplugin-auto-import": "^19.0.0",
-        "unplugin-vue-components": "^28.0.0",
-        "vaul-vue": "^0.2.0"
+        "unplugin": "^2.2.0",
+        "unplugin-auto-import": "^19.1.0",
+        "unplugin-vue-components": "^28.1.0",
+        "vaul-vue": "^0.2.1"
       },
       "bin": {
         "nuxt-ui": "cli/index.mjs"
@@ -2449,6 +2460,172 @@
         "typescript": "^5.6.3"
       }
     },
+    "node_modules/@nuxt/ui/node_modules/@nuxt/devtools-kit": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmjs.org/@nuxt/devtools-kit/-/devtools-kit-2.1.0.tgz",
+      "integrity": "sha512-1fhwU7dDq/vIpjpNRwjEmTllRT1O0nzyBEhY187bQ8xBpoCC93t3zG3iTKcl8XkpT1aK9SqcgmXOnj5fNIAaYA==",
+      "license": "MIT",
+      "dependencies": {
+        "@nuxt/kit": "^3.15.4",
+        "@nuxt/schema": "^3.15.4",
+        "execa": "^9.5.2"
+      },
+      "peerDependencies": {
+        "vite": ">=6.0"
+      }
+    },
+    "node_modules/@nuxt/ui/node_modules/@sindresorhus/merge-streams": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmjs.org/@sindresorhus/merge-streams/-/merge-streams-4.0.0.tgz",
+      "integrity": "sha512-tlqY9xq5ukxTUZBmoOp+m61cqwQD5pHJtFY3Mn8CA8ps6yghLH/Hw8UPdqg4OLmFW3IFlcXnQNmo/dh8HzXYIQ==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=18"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
+    "node_modules/@nuxt/ui/node_modules/execa": {
+      "version": "9.5.2",
+      "resolved": "https://registry.npmjs.org/execa/-/execa-9.5.2.tgz",
+      "integrity": "sha512-EHlpxMCpHWSAh1dgS6bVeoLAXGnJNdR93aabr4QCGbzOM73o5XmRfM/e5FUqsw3aagP8S8XEWUWFAxnRBnAF0Q==",
+      "license": "MIT",
+      "dependencies": {
+        "@sindresorhus/merge-streams": "^4.0.0",
+        "cross-spawn": "^7.0.3",
+        "figures": "^6.1.0",
+        "get-stream": "^9.0.0",
+        "human-signals": "^8.0.0",
+        "is-plain-obj": "^4.1.0",
+        "is-stream": "^4.0.1",
+        "npm-run-path": "^6.0.0",
+        "pretty-ms": "^9.0.0",
+        "signal-exit": "^4.1.0",
+        "strip-final-newline": "^4.0.0",
+        "yoctocolors": "^2.0.0"
+      },
+      "engines": {
+        "node": "^18.19.0 || >=20.5.0"
+      },
+      "funding": {
+        "url": "https://github.com/sindresorhus/execa?sponsor=1"
+      }
+    },
+    "node_modules/@nuxt/ui/node_modules/get-stream": {
+      "version": "9.0.1",
+      "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-9.0.1.tgz",
+      "integrity": "sha512-kVCxPF3vQM/N0B1PmoqVUqgHP+EeVjmZSQn+1oCRPxd2P21P2F19lIgbR3HBosbB1PUhOAoctJnfEn2GbN2eZA==",
+      "license": "MIT",
+      "dependencies": {
+        "@sec-ant/readable-stream": "^0.4.1",
+        "is-stream": "^4.0.1"
+      },
+      "engines": {
+        "node": ">=18"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
+    "node_modules/@nuxt/ui/node_modules/human-signals": {
+      "version": "8.0.0",
+      "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-8.0.0.tgz",
+      "integrity": "sha512-/1/GPCpDUCCYwlERiYjxoczfP0zfvZMU/OWgQPMya9AbAE24vseigFdhAMObpc8Q4lc/kjutPfUddDYyAmejnA==",
+      "license": "Apache-2.0",
+      "engines": {
+        "node": ">=18.18.0"
+      }
+    },
+    "node_modules/@nuxt/ui/node_modules/is-stream": {
+      "version": "4.0.1",
+      "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-4.0.1.tgz",
+      "integrity": "sha512-Dnz92NInDqYckGEUJv689RbRiTSEHCQ7wOVeALbkOz999YpqT46yMRIGtSNl2iCL1waAZSx40+h59NV/EwzV/A==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=18"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
+    "node_modules/@nuxt/ui/node_modules/npm-run-path": {
+      "version": "6.0.0",
+      "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-6.0.0.tgz",
+      "integrity": "sha512-9qny7Z9DsQU8Ou39ERsPU4OZQlSTP47ShQzuKZ6PRXpYLtIFgl/DEBYEXKlvcEa+9tHVcK8CF81Y2V72qaZhWA==",
+      "license": "MIT",
+      "dependencies": {
+        "path-key": "^4.0.0",
+        "unicorn-magic": "^0.3.0"
+      },
+      "engines": {
+        "node": ">=18"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
+    "node_modules/@nuxt/ui/node_modules/path-key": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz",
+      "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=12"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
+    "node_modules/@nuxt/ui/node_modules/signal-exit": {
+      "version": "4.1.0",
+      "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz",
+      "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==",
+      "license": "ISC",
+      "engines": {
+        "node": ">=14"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/isaacs"
+      }
+    },
+    "node_modules/@nuxt/ui/node_modules/strip-final-newline": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-4.0.0.tgz",
+      "integrity": "sha512-aulFJcD6YK8V1G7iRB5tigAP4TsHBZZrOV8pjV++zdUwmeV8uzbY7yn6h9MswN62adStNZFuCIx4haBnRuMDaw==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=18"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
+    "node_modules/@nuxt/ui/node_modules/unicorn-magic": {
+      "version": "0.3.0",
+      "resolved": "https://registry.npmjs.org/unicorn-magic/-/unicorn-magic-0.3.0.tgz",
+      "integrity": "sha512-+QBBXBCvifc56fsbuxZQ6Sic3wqqc3WWaqxs58gvJrcOuN83HGTCwz3oS5phzU9LthRNE9VrJCFCLUgHeeFnfA==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=18"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
+    "node_modules/@nuxt/ui/node_modules/unplugin": {
+      "version": "2.2.0",
+      "resolved": "https://registry.npmjs.org/unplugin/-/unplugin-2.2.0.tgz",
+      "integrity": "sha512-m1ekpSwuOT5hxkJeZGRxO7gXbXT3gF26NjQ7GdVHoLoF8/nopLcd/QfPigpCy7i51oFHiRJg/CyHhj4vs2+KGw==",
+      "license": "MIT",
+      "dependencies": {
+        "acorn": "^8.14.0",
+        "webpack-virtual-modules": "^0.6.2"
+      },
+      "engines": {
+        "node": ">=18.12.0"
+      }
+    },
     "node_modules/@nuxt/vite-builder": {
       "version": "3.15.4",
       "resolved": "https://registry.npmjs.org/@nuxt/vite-builder/-/vite-builder-3.15.4.tgz",
@@ -3382,6 +3559,12 @@
         "win32"
       ]
     },
+    "node_modules/@sec-ant/readable-stream": {
+      "version": "0.4.1",
+      "resolved": "https://registry.npmjs.org/@sec-ant/readable-stream/-/readable-stream-0.4.1.tgz",
+      "integrity": "sha512-831qok9r2t8AlxLko40y2ebgSDhenenCatLVeW/uBtnHPyhHOvG0C7TvfgecV+wHzIm5KUICgzmVpWS+IMEAeg==",
+      "license": "MIT"
+    },
     "node_modules/@sindresorhus/merge-streams": {
       "version": "2.3.0",
       "resolved": "https://registry.npmjs.org/@sindresorhus/merge-streams/-/merge-streams-2.3.0.tgz",
@@ -3423,42 +3606,42 @@
       }
     },
     "node_modules/@tailwindcss/node": {
-      "version": "4.0.0-beta.9",
-      "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.0.0-beta.9.tgz",
-      "integrity": "sha512-KuKNhNVU5hd2L5BkXE/twBKkMnHG4wQiHes6axhDbdcRew0/YZtvlWvMIy7QmtBWnR1lM8scPhp0RXmxK/hZdw==",
+      "version": "4.0.6",
+      "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.0.6.tgz",
+      "integrity": "sha512-jb6E0WeSq7OQbVYcIJ6LxnZTeC4HjMvbzFBMCrQff4R50HBlo/obmYNk6V2GCUXDeqiXtvtrQgcIbT+/boB03Q==",
       "license": "MIT",
       "dependencies": {
         "enhanced-resolve": "^5.18.0",
         "jiti": "^2.4.2",
-        "tailwindcss": "4.0.0-beta.9"
+        "tailwindcss": "4.0.6"
       }
     },
     "node_modules/@tailwindcss/oxide": {
-      "version": "4.0.0-beta.9",
-      "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.0.0-beta.9.tgz",
-      "integrity": "sha512-1bpui84CDnrjB6TI3AGR9jYUA28+VIfkrM4BH3+VXA9B80+cARtd3ON06ouA5/r/2xs4qe+T85Z1c0k5X6vLeA==",
+      "version": "4.0.6",
+      "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.0.6.tgz",
+      "integrity": "sha512-lVyKV2y58UE9CeKVcYykULe9QaE1dtKdxDEdrTPIdbzRgBk6bdxHNAoDqvcqXbIGXubn3VOl1O/CFF77v/EqSA==",
       "license": "MIT",
       "engines": {
         "node": ">= 10"
       },
       "optionalDependencies": {
-        "@tailwindcss/oxide-android-arm64": "4.0.0-beta.9",
-        "@tailwindcss/oxide-darwin-arm64": "4.0.0-beta.9",
-        "@tailwindcss/oxide-darwin-x64": "4.0.0-beta.9",
-        "@tailwindcss/oxide-freebsd-x64": "4.0.0-beta.9",
-        "@tailwindcss/oxide-linux-arm-gnueabihf": "4.0.0-beta.9",
-        "@tailwindcss/oxide-linux-arm64-gnu": "4.0.0-beta.9",
-        "@tailwindcss/oxide-linux-arm64-musl": "4.0.0-beta.9",
-        "@tailwindcss/oxide-linux-x64-gnu": "4.0.0-beta.9",
-        "@tailwindcss/oxide-linux-x64-musl": "4.0.0-beta.9",
-        "@tailwindcss/oxide-win32-arm64-msvc": "4.0.0-beta.9",
-        "@tailwindcss/oxide-win32-x64-msvc": "4.0.0-beta.9"
+        "@tailwindcss/oxide-android-arm64": "4.0.6",
+        "@tailwindcss/oxide-darwin-arm64": "4.0.6",
+        "@tailwindcss/oxide-darwin-x64": "4.0.6",
+        "@tailwindcss/oxide-freebsd-x64": "4.0.6",
+        "@tailwindcss/oxide-linux-arm-gnueabihf": "4.0.6",
+        "@tailwindcss/oxide-linux-arm64-gnu": "4.0.6",
+        "@tailwindcss/oxide-linux-arm64-musl": "4.0.6",
+        "@tailwindcss/oxide-linux-x64-gnu": "4.0.6",
+        "@tailwindcss/oxide-linux-x64-musl": "4.0.6",
+        "@tailwindcss/oxide-win32-arm64-msvc": "4.0.6",
+        "@tailwindcss/oxide-win32-x64-msvc": "4.0.6"
       }
     },
     "node_modules/@tailwindcss/oxide-android-arm64": {
-      "version": "4.0.0-beta.9",
-      "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.0.0-beta.9.tgz",
-      "integrity": "sha512-MiDpTfYvRozM+40mV2wh7GCxyEj7zIOtX3bRNaJgu0adxzZaKkylks46kBY8X91NV3ch6CQSf9Zlr0vi4U5qdw==",
+      "version": "4.0.6",
+      "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.0.6.tgz",
+      "integrity": "sha512-xDbym6bDPW3D2XqQqX3PjqW3CKGe1KXH7Fdkc60sX5ZLVUbzPkFeunQaoP+BuYlLc2cC1FoClrIRYnRzof9Sow==",
       "cpu": [
         "arm64"
       ],
@@ -3472,9 +3655,9 @@
       }
     },
     "node_modules/@tailwindcss/oxide-darwin-arm64": {
-      "version": "4.0.0-beta.9",
-      "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.0.0-beta.9.tgz",
-      "integrity": "sha512-SjdLul42NElqSHO5uINXylMNDx4KjtN3iB2o5nv0dFJV119DB0rxSCswgSEfigqyMXLyOAw3dwdoJIUFiw5Sdg==",
+      "version": "4.0.6",
+      "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.0.6.tgz",
+      "integrity": "sha512-1f71/ju/tvyGl5c2bDkchZHy8p8EK/tDHCxlpYJ1hGNvsYihZNurxVpZ0DefpN7cNc9RTT8DjrRoV8xXZKKRjg==",
       "cpu": [
         "arm64"
       ],
@@ -3488,9 +3671,9 @@
       }
     },
     "node_modules/@tailwindcss/oxide-darwin-x64": {
-      "version": "4.0.0-beta.9",
-      "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.0.0-beta.9.tgz",
-      "integrity": "sha512-pmAs3H+pYUxAYbz2y7Q2tIfcNVlnPiikZN0SejF7JaDROg4PhQsWWpvlzHZZvD6CuyFCRXayudG8PwpJSk29dg==",
+      "version": "4.0.6",
+      "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.0.6.tgz",
+      "integrity": "sha512-s/hg/ZPgxFIrGMb0kqyeaqZt505P891buUkSezmrDY6lxv2ixIELAlOcUVTkVh245SeaeEiUVUPiUN37cwoL2g==",
       "cpu": [
         "x64"
       ],
@@ -3504,9 +3687,9 @@
       }
     },
     "node_modules/@tailwindcss/oxide-freebsd-x64": {
-      "version": "4.0.0-beta.9",
-      "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.0.0-beta.9.tgz",
-      "integrity": "sha512-l39LttvdeeueMxuVNn1Z/cNK1YMWNzoIUgTsHCgF2vhY9tl4R+QcSwlviAkvw4AkiAC4El84pGBBVGswyWa8Rw==",
+      "version": "4.0.6",
+      "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.0.6.tgz",
+      "integrity": "sha512-Z3Wo8FWZnmio8+xlcbb7JUo/hqRMSmhQw8IGIRoRJ7GmLR0C+25Wq+bEX/135xe/yEle2lFkhu9JBHd4wZYiig==",
       "cpu": [
         "x64"
       ],
@@ -3520,9 +3703,9 @@
       }
     },
     "node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": {
-      "version": "4.0.0-beta.9",
-      "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.0.0-beta.9.tgz",
-      "integrity": "sha512-sISzLGpVXNqOYJTo7KcdtUWQulZnW7cqFanBNbe8tCkS1KvlIuckC3MWAihLxpLrmobKh/Wv+wB1aE08VEfCww==",
+      "version": "4.0.6",
+      "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.0.6.tgz",
+      "integrity": "sha512-SNSwkkim1myAgmnbHs4EjXsPL7rQbVGtjcok5EaIzkHkCAVK9QBQsWeP2Jm2/JJhq4wdx8tZB9Y7psMzHYWCkA==",
       "cpu": [
         "arm"
       ],
@@ -3536,9 +3719,9 @@
       }
     },
     "node_modules/@tailwindcss/oxide-linux-arm64-gnu": {
-      "version": "4.0.0-beta.9",
-      "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.0.0-beta.9.tgz",
-      "integrity": "sha512-8nmeXyBchcqzQtyqjnmMxlLyxBPd+bwlnr5tDr3w6yol0z7Yrfz3T6L4QoZ4TbfhE26t6qWsUa+WQlzMJKsazg==",
+      "version": "4.0.6",
+      "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.0.6.tgz",
+      "integrity": "sha512-tJ+mevtSDMQhKlwCCuhsFEFg058kBiSy4TkoeBG921EfrHKmexOaCyFKYhVXy4JtkaeeOcjJnCLasEeqml4i+Q==",
       "cpu": [
         "arm64"
       ],
@@ -3552,9 +3735,9 @@
       }
     },
     "node_modules/@tailwindcss/oxide-linux-arm64-musl": {
-      "version": "4.0.0-beta.9",
-      "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.0.0-beta.9.tgz",
-      "integrity": "sha512-x+Vr4SnZayMj5PEFHL7MczrvjK7fYuv2LvakPfXoDYnAOmjhrjX5go3I0Q65uUPWiZxGcS/y0JgAtQqgHSKU8A==",
+      "version": "4.0.6",
+      "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.0.6.tgz",
+      "integrity": "sha512-IoArz1vfuTR4rALXMUXI/GWWfx2EaO4gFNtBNkDNOYhlTD4NVEwE45nbBoojYiTulajI4c2XH8UmVEVJTOJKxA==",
       "cpu": [
         "arm64"
       ],
@@ -3568,9 +3751,9 @@
       }
     },
     "node_modules/@tailwindcss/oxide-linux-x64-gnu": {
-      "version": "4.0.0-beta.9",
-      "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.0.0-beta.9.tgz",
-      "integrity": "sha512-4HpvDn3k5P623exDRbo9rjEXcIuHBj3ZV9YcnWJNE9QZ2vzKXGXxCxPuShTAg25JmH8z+b2whmFsnbxDqtgKhA==",
+      "version": "4.0.6",
+      "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.0.6.tgz",
+      "integrity": "sha512-QtsUfLkEAeWAC3Owx9Kg+7JdzE+k9drPhwTAXbXugYB9RZUnEWWx5x3q/au6TvUYcL+n0RBqDEO2gucZRvRFgQ==",
       "cpu": [
         "x64"
       ],
@@ -3584,9 +3767,9 @@
       }
     },
     "node_modules/@tailwindcss/oxide-linux-x64-musl": {
-      "version": "4.0.0-beta.9",
-      "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.0.0-beta.9.tgz",
-      "integrity": "sha512-RgJrSk7uAt5QC7ez0p0uNcd/Z0yoXuBL9VvMnZVdEMDA7dcf1/zMCcFt3p2nGsGY7q2qp0hULdBEhsRP2Gq0cw==",
+      "version": "4.0.6",
+      "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.0.6.tgz",
+      "integrity": "sha512-QthvJqIji2KlGNwLcK/PPYo7w1Wsi/8NK0wAtRGbv4eOPdZHkQ9KUk+oCoP20oPO7i2a6X1aBAFQEL7i08nNMA==",
       "cpu": [
         "x64"
       ],
@@ -3600,9 +3783,9 @@
       }
     },
     "node_modules/@tailwindcss/oxide-win32-arm64-msvc": {
-      "version": "4.0.0-beta.9",
-      "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.0.0-beta.9.tgz",
-      "integrity": "sha512-FCpprAxJqDT27C2OaJTAR06+BsmHS2gW7Wu0lC9E6DwiizYP0YjSVFeYvnkluE5O2J4uVR3X2GAaqxbtG4z9Ug==",
+      "version": "4.0.6",
+      "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.0.6.tgz",
+      "integrity": "sha512-+oka+dYX8jy9iP00DJ9Y100XsqvbqR5s0yfMZJuPR1H/lDVtDfsZiSix1UFBQ3X1HWxoEEl6iXNJHWd56TocVw==",
       "cpu": [
         "arm64"
       ],
@@ -3616,9 +3799,9 @@
       }
     },
     "node_modules/@tailwindcss/oxide-win32-x64-msvc": {
-      "version": "4.0.0-beta.9",
-      "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.0.0-beta.9.tgz",
-      "integrity": "sha512-KOf2YKFwrvFVX+RNJsYVC6tsWBxDMTX7/u4SpUepqkwVgq2yCObx/Sqt820lXuKgGJ9dKsTYF2wvMUGom7B71A==",
+      "version": "4.0.6",
+      "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.0.6.tgz",
+      "integrity": "sha512-+o+juAkik4p8Ue/0LiflQXPmVatl6Av3LEZXpBTfg4qkMIbZdhCGWFzHdt2NjoMiLOJCFDddoV6GYaimvK1Olw==",
       "cpu": [
         "x64"
       ],
@@ -3632,38 +3815,38 @@
       }
     },
     "node_modules/@tailwindcss/postcss": {
-      "version": "4.0.0-beta.9",
-      "resolved": "https://registry.npmjs.org/@tailwindcss/postcss/-/postcss-4.0.0-beta.9.tgz",
-      "integrity": "sha512-xXqMnXtg8K2FgrIlqSf3PPHgyAuSiGe7BJ6+6wma96s7VXArsN5UtTwDuksAedJtCymk1liTvLa2eRNrumlavA==",
+      "version": "4.0.6",
+      "resolved": "https://registry.npmjs.org/@tailwindcss/postcss/-/postcss-4.0.6.tgz",
+      "integrity": "sha512-noTaGPHjGCXTCc487TWnfAEN0VMjqDAecssWDOsfxV2hFrcZR0AHthX7IdY/0xHTg/EtpmIPdssddlZ5/B7JnQ==",
       "license": "MIT",
       "dependencies": {
         "@alloc/quick-lru": "^5.2.0",
-        "@tailwindcss/node": "4.0.0-beta.9",
-        "@tailwindcss/oxide": "4.0.0-beta.9",
-        "lightningcss": "^1.29.0",
+        "@tailwindcss/node": "^4.0.6",
+        "@tailwindcss/oxide": "^4.0.6",
+        "lightningcss": "^1.29.1",
         "postcss": "^8.4.41",
-        "tailwindcss": "4.0.0-beta.9"
+        "tailwindcss": "4.0.6"
       }
     },
     "node_modules/@tailwindcss/vite": {
-      "version": "4.0.0-beta.9",
-      "resolved": "https://registry.npmjs.org/@tailwindcss/vite/-/vite-4.0.0-beta.9.tgz",
-      "integrity": "sha512-Hf28QkwSLM6bbOkcTQk1iEEOB37v+9vfqdpHUaLSluZpEGCVAFc0i+p2Gvp6MlK840tyixQu5L39VjL2lAZFFQ==",
+      "version": "4.0.6",
+      "resolved": "https://registry.npmjs.org/@tailwindcss/vite/-/vite-4.0.6.tgz",
+      "integrity": "sha512-O25vZ/URWbZ2JHdk2o8wH7jOKqEGCsYmX3GwGmYS5DjE4X3mpf93a72Rn7VRnefldNauBzr5z2hfZptmBNtTUQ==",
       "license": "MIT",
       "dependencies": {
-        "@tailwindcss/node": "4.0.0-beta.9",
-        "@tailwindcss/oxide": "4.0.0-beta.9",
-        "lightningcss": "^1.29.0",
-        "tailwindcss": "4.0.0-beta.9"
+        "@tailwindcss/node": "^4.0.6",
+        "@tailwindcss/oxide": "^4.0.6",
+        "lightningcss": "^1.29.1",
+        "tailwindcss": "4.0.6"
       },
       "peerDependencies": {
         "vite": "^5.2.0 || ^6"
       }
     },
     "node_modules/@tanstack/table-core": {
-      "version": "8.20.5",
-      "resolved": "https://registry.npmjs.org/@tanstack/table-core/-/table-core-8.20.5.tgz",
-      "integrity": "sha512-P9dF7XbibHph2PFRz8gfBKEXEY/HJPOhym8CHmjF8y3q5mWpKx9xtZapXQUWCgkqvsK0R46Azuz+VaxD4Xl+Tg==",
+      "version": "8.21.2",
+      "resolved": "https://registry.npmjs.org/@tanstack/table-core/-/table-core-8.21.2.tgz",
+      "integrity": "sha512-uvXk/U4cBiFMxt+p9/G7yUWI/UbHYbyghLCjlpWZ3mLeIZiUBSKcUnw9UnKkdRz7Z/N4UBuFLWQdJCjUe7HjvA==",
       "license": "MIT",
       "engines": {
         "node": ">=12"
@@ -3674,9 +3857,9 @@
       }
     },
     "node_modules/@tanstack/virtual-core": {
-      "version": "3.11.2",
-      "resolved": "https://registry.npmjs.org/@tanstack/virtual-core/-/virtual-core-3.11.2.tgz",
-      "integrity": "sha512-vTtpNt7mKCiZ1pwU9hfKPhpdVO2sVzFQsxoVBGtOSHxlrRRzYr8iQ2TlwbAcRYCcEiZ9ECAM8kBzH0v2+VzfKw==",
+      "version": "3.13.0",
+      "resolved": "https://registry.npmjs.org/@tanstack/virtual-core/-/virtual-core-3.13.0.tgz",
+      "integrity": "sha512-NBKJP3OIdmZY3COJdWkSonr50FMVIi+aj5ZJ7hI/DTpEKg2RMfo/KvP8A3B/zOSpMgIe52B5E2yn7rryULzA6g==",
       "license": "MIT",
       "funding": {
         "type": "github",
@@ -3684,12 +3867,12 @@
       }
     },
     "node_modules/@tanstack/vue-table": {
-      "version": "8.20.5",
-      "resolved": "https://registry.npmjs.org/@tanstack/vue-table/-/vue-table-8.20.5.tgz",
-      "integrity": "sha512-2xixT3BEgSDw+jOSqPt6ylO/eutDI107t2WdFMVYIZZ45UmTHLySqNriNs0+dMaKR56K5z3t+97P6VuVnI2L+Q==",
+      "version": "8.21.2",
+      "resolved": "https://registry.npmjs.org/@tanstack/vue-table/-/vue-table-8.21.2.tgz",
+      "integrity": "sha512-KBgOWxha/x4m1EdhVWxOpqHb661UjqAxzPcmXR3QiA7aShZ547x19Gw0UJX9we+m+tVcPuLRZ61JsYW47QZFfQ==",
       "license": "MIT",
       "dependencies": {
-        "@tanstack/table-core": "8.20.5"
+        "@tanstack/table-core": "8.21.2"
       },
       "engines": {
         "node": ">=12"
@@ -3703,12 +3886,12 @@
       }
     },
     "node_modules/@tanstack/vue-virtual": {
-      "version": "3.11.2",
-      "resolved": "https://registry.npmjs.org/@tanstack/vue-virtual/-/vue-virtual-3.11.2.tgz",
-      "integrity": "sha512-y0b1p1FTlzxcSt/ZdGWY1AZ52ddwSU69pvFRYAELUSdLLxV8QOPe9dyT/KATO43UCb3DAwiyzi96h2IoYstBOQ==",
+      "version": "3.13.0",
+      "resolved": "https://registry.npmjs.org/@tanstack/vue-virtual/-/vue-virtual-3.13.0.tgz",
+      "integrity": "sha512-EPgcTc41KGJAK2N2Ux2PeUnG3cPpdkldTib05nwq+0zdS2Ihpbq8BsWXz/eXPyNc5noDBh1GBgAe36yMYiW6WA==",
       "license": "MIT",
       "dependencies": {
-        "@tanstack/virtual-core": "3.11.2"
+        "@tanstack/virtual-core": "3.13.0"
       },
       "funding": {
         "type": "github",
@@ -3727,30 +3910,6 @@
         "node": ">=10.13.0"
       }
     },
-    "node_modules/@types/color": {
-      "version": "4.2.0",
-      "resolved": "https://registry.npmjs.org/@types/color/-/color-4.2.0.tgz",
-      "integrity": "sha512-6+xrIRImMtGAL2X3qYkd02Mgs+gFGs+WsK0b7VVMaO4mYRISwyTjcqNrO0mNSmYEoq++rSLDB2F5HDNmqfOe+A==",
-      "license": "MIT",
-      "dependencies": {
-        "@types/color-convert": "*"
-      }
-    },
-    "node_modules/@types/color-convert": {
-      "version": "2.0.4",
-      "resolved": "https://registry.npmjs.org/@types/color-convert/-/color-convert-2.0.4.tgz",
-      "integrity": "sha512-Ub1MmDdyZ7mX//g25uBAoH/mWGd9swVbt8BseymnaE18SU4po/PjmCrHxqIIRjBo3hV/vh1KGr0eMxUhp+t+dQ==",
-      "license": "MIT",
-      "dependencies": {
-        "@types/color-name": "^1.1.0"
-      }
-    },
-    "node_modules/@types/color-name": {
-      "version": "1.1.5",
-      "resolved": "https://registry.npmjs.org/@types/color-name/-/color-name-1.1.5.tgz",
-      "integrity": "sha512-j2K5UJqGTxeesj6oQuGpMgifpT5k9HprgQd8D1Y0lOFqKHl3PJu5GMeS4Y5EgjS55AE6OQxf8mPED9uaGbf4Cg==",
-      "license": "MIT"
-    },
     "node_modules/@types/doctrine": {
       "version": "0.0.9",
       "resolved": "https://registry.npmjs.org/@types/doctrine/-/doctrine-0.0.9.tgz",
@@ -3827,20 +3986,20 @@
       "license": "MIT"
     },
     "node_modules/@typescript-eslint/eslint-plugin": {
-      "version": "8.22.0",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.22.0.tgz",
-      "integrity": "sha512-4Uta6REnz/xEJMvwf72wdUnC3rr4jAQf5jnTkeRQ9b6soxLxhDEbS/pfMPoJLDfFPNVRdryqWUIV/2GZzDJFZw==",
+      "version": "8.23.0",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.23.0.tgz",
+      "integrity": "sha512-vBz65tJgRrA1Q5gWlRfvoH+w943dq9K1p1yDBY2pc+a1nbBLZp7fB9+Hk8DaALUbzjqlMfgaqlVPT1REJdkt/w==",
       "license": "MIT",
       "dependencies": {
         "@eslint-community/regexpp": "^4.10.0",
-        "@typescript-eslint/scope-manager": "8.22.0",
-        "@typescript-eslint/type-utils": "8.22.0",
-        "@typescript-eslint/utils": "8.22.0",
-        "@typescript-eslint/visitor-keys": "8.22.0",
+        "@typescript-eslint/scope-manager": "8.23.0",
+        "@typescript-eslint/type-utils": "8.23.0",
+        "@typescript-eslint/utils": "8.23.0",
+        "@typescript-eslint/visitor-keys": "8.23.0",
         "graphemer": "^1.4.0",
         "ignore": "^5.3.1",
         "natural-compare": "^1.4.0",
-        "ts-api-utils": "^2.0.0"
+        "ts-api-utils": "^2.0.1"
       },
       "engines": {
         "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -3865,15 +4024,15 @@
       }
     },
     "node_modules/@typescript-eslint/parser": {
-      "version": "8.22.0",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.22.0.tgz",
-      "integrity": "sha512-MqtmbdNEdoNxTPzpWiWnqNac54h8JDAmkWtJExBVVnSrSmi9z+sZUt0LfKqk9rjqmKOIeRhO4fHHJ1nQIjduIQ==",
+      "version": "8.23.0",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.23.0.tgz",
+      "integrity": "sha512-h2lUByouOXFAlMec2mILeELUbME5SZRN/7R9Cw2RD2lRQQY08MWMM+PmVVKKJNK1aIwqTo9t/0CvOxwPbRIE2Q==",
       "license": "MIT",
       "dependencies": {
-        "@typescript-eslint/scope-manager": "8.22.0",
-        "@typescript-eslint/types": "8.22.0",
-        "@typescript-eslint/typescript-estree": "8.22.0",
-        "@typescript-eslint/visitor-keys": "8.22.0",
+        "@typescript-eslint/scope-manager": "8.23.0",
+        "@typescript-eslint/types": "8.23.0",
+        "@typescript-eslint/typescript-estree": "8.23.0",
+        "@typescript-eslint/visitor-keys": "8.23.0",
         "debug": "^4.3.4"
       },
       "engines": {
@@ -3889,13 +4048,13 @@
       }
     },
     "node_modules/@typescript-eslint/scope-manager": {
-      "version": "8.22.0",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.22.0.tgz",
-      "integrity": "sha512-/lwVV0UYgkj7wPSw0o8URy6YI64QmcOdwHuGuxWIYznO6d45ER0wXUbksr9pYdViAofpUCNJx/tAzNukgvaaiQ==",
+      "version": "8.23.0",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.23.0.tgz",
+      "integrity": "sha512-OGqo7+dXHqI7Hfm+WqkZjKjsiRtFUQHPdGMXzk5mYXhJUedO7e/Y7i8AK3MyLMgZR93TX4bIzYrfyVjLC+0VSw==",
       "license": "MIT",
       "dependencies": {
-        "@typescript-eslint/types": "8.22.0",
-        "@typescript-eslint/visitor-keys": "8.22.0"
+        "@typescript-eslint/types": "8.23.0",
+        "@typescript-eslint/visitor-keys": "8.23.0"
       },
       "engines": {
         "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -3906,15 +4065,15 @@
       }
     },
     "node_modules/@typescript-eslint/type-utils": {
-      "version": "8.22.0",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.22.0.tgz",
-      "integrity": "sha512-NzE3aB62fDEaGjaAYZE4LH7I1MUwHooQ98Byq0G0y3kkibPJQIXVUspzlFOmOfHhiDLwKzMlWxaNv+/qcZurJA==",
+      "version": "8.23.0",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.23.0.tgz",
+      "integrity": "sha512-iIuLdYpQWZKbiH+RkCGc6iu+VwscP5rCtQ1lyQ7TYuKLrcZoeJVpcLiG8DliXVkUxirW/PWlmS+d6yD51L9jvA==",
       "license": "MIT",
       "dependencies": {
-        "@typescript-eslint/typescript-estree": "8.22.0",
-        "@typescript-eslint/utils": "8.22.0",
+        "@typescript-eslint/typescript-estree": "8.23.0",
+        "@typescript-eslint/utils": "8.23.0",
         "debug": "^4.3.4",
-        "ts-api-utils": "^2.0.0"
+        "ts-api-utils": "^2.0.1"
       },
       "engines": {
         "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -3929,9 +4088,9 @@
       }
     },
     "node_modules/@typescript-eslint/types": {
-      "version": "8.22.0",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.22.0.tgz",
-      "integrity": "sha512-0S4M4baNzp612zwpD4YOieP3VowOARgK2EkN/GBn95hpyF8E2fbMT55sRHWBq+Huaqk3b3XK+rxxlM8sPgGM6A==",
+      "version": "8.23.0",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.23.0.tgz",
+      "integrity": "sha512-1sK4ILJbCmZOTt9k4vkoulT6/y5CHJ1qUYxqpF1K/DBAd8+ZUL4LlSCxOssuH5m4rUaaN0uS0HlVPvd45zjduQ==",
       "license": "MIT",
       "engines": {
         "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -3942,19 +4101,19 @@
       }
     },
     "node_modules/@typescript-eslint/typescript-estree": {
-      "version": "8.22.0",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.22.0.tgz",
-      "integrity": "sha512-SJX99NAS2ugGOzpyhMza/tX+zDwjvwAtQFLsBo3GQxiGcvaKlqGBkmZ+Y1IdiSi9h4Q0Lr5ey+Cp9CGWNY/F/w==",
+      "version": "8.23.0",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.23.0.tgz",
+      "integrity": "sha512-LcqzfipsB8RTvH8FX24W4UUFk1bl+0yTOf9ZA08XngFwMg4Kj8A+9hwz8Cr/ZS4KwHrmo9PJiLZkOt49vPnuvQ==",
       "license": "MIT",
       "dependencies": {
-        "@typescript-eslint/types": "8.22.0",
-        "@typescript-eslint/visitor-keys": "8.22.0",
+        "@typescript-eslint/types": "8.23.0",
+        "@typescript-eslint/visitor-keys": "8.23.0",
         "debug": "^4.3.4",
         "fast-glob": "^3.3.2",
         "is-glob": "^4.0.3",
         "minimatch": "^9.0.4",
         "semver": "^7.6.0",
-        "ts-api-utils": "^2.0.0"
+        "ts-api-utils": "^2.0.1"
       },
       "engines": {
         "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -3968,15 +4127,15 @@
       }
     },
     "node_modules/@typescript-eslint/utils": {
-      "version": "8.22.0",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.22.0.tgz",
-      "integrity": "sha512-T8oc1MbF8L+Bk2msAvCUzjxVB2Z2f+vXYfcucE2wOmYs7ZUwco5Ep0fYZw8quNwOiw9K8GYVL+Kgc2pETNTLOg==",
+      "version": "8.23.0",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.23.0.tgz",
+      "integrity": "sha512-uB/+PSo6Exu02b5ZEiVtmY6RVYO7YU5xqgzTIVZwTHvvK3HsL8tZZHFaTLFtRG3CsV4A5mhOv+NZx5BlhXPyIA==",
       "license": "MIT",
       "dependencies": {
         "@eslint-community/eslint-utils": "^4.4.0",
-        "@typescript-eslint/scope-manager": "8.22.0",
-        "@typescript-eslint/types": "8.22.0",
-        "@typescript-eslint/typescript-estree": "8.22.0"
+        "@typescript-eslint/scope-manager": "8.23.0",
+        "@typescript-eslint/types": "8.23.0",
+        "@typescript-eslint/typescript-estree": "8.23.0"
       },
       "engines": {
         "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -3991,12 +4150,12 @@
       }
     },
     "node_modules/@typescript-eslint/visitor-keys": {
-      "version": "8.22.0",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.22.0.tgz",
-      "integrity": "sha512-AWpYAXnUgvLNabGTy3uBylkgZoosva/miNd1I8Bz3SjotmQPbVqhO4Cczo8AsZ44XVErEBPr/CRSgaj8sG7g0w==",
+      "version": "8.23.0",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.23.0.tgz",
+      "integrity": "sha512-oWWhcWDLwDfu++BGTZcmXWqpwtkwb5o7fxUIGksMQQDSdPW9prsSnfIOZMlsj4vBOSrcnjIUZMiIjODgGosFhQ==",
       "license": "MIT",
       "dependencies": {
-        "@typescript-eslint/types": "8.22.0",
+        "@typescript-eslint/types": "8.23.0",
         "eslint-visitor-keys": "^4.2.0"
       },
       "engines": {
@@ -4008,13 +4167,39 @@
       }
     },
     "node_modules/@unhead/dom": {
-      "version": "1.11.18",
-      "resolved": "https://registry.npmjs.org/@unhead/dom/-/dom-1.11.18.tgz",
-      "integrity": "sha512-zQuJUw/et9zYEV0SZWTDX23IgurwMaXycAuxt4L6OgNL0T4TWP3a0J/Vm3Q02hmdNo/cPKeVBrwBdnFUXjGU4w==",
+      "version": "1.11.19",
+      "resolved": "https://registry.npmjs.org/@unhead/dom/-/dom-1.11.19.tgz",
+      "integrity": "sha512-udkgITdIblEWH3hsoFQMKW+6QXNO2qFZlZ2FI37bVAplQSnK/PytTPt/5oA1GWkoVwT0DsQNGHbU6kOg/3SlNg==",
       "license": "MIT",
       "dependencies": {
-        "@unhead/schema": "1.11.18",
-        "@unhead/shared": "1.11.18"
+        "@unhead/schema": "1.11.19",
+        "@unhead/shared": "1.11.19"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/harlan-zw"
+      }
+    },
+    "node_modules/@unhead/dom/node_modules/@unhead/schema": {
+      "version": "1.11.19",
+      "resolved": "https://registry.npmjs.org/@unhead/schema/-/schema-1.11.19.tgz",
+      "integrity": "sha512-7VhYHWK7xHgljdv+C01MepCSYZO2v6OhgsfKWPxRQBDDGfUKCUaChox0XMq3tFvXP6u4zSp6yzcDw2yxCfVMwg==",
+      "license": "MIT",
+      "dependencies": {
+        "hookable": "^5.5.3",
+        "zhead": "^2.2.4"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/harlan-zw"
+      }
+    },
+    "node_modules/@unhead/dom/node_modules/@unhead/shared": {
+      "version": "1.11.19",
+      "resolved": "https://registry.npmjs.org/@unhead/shared/-/shared-1.11.19.tgz",
+      "integrity": "sha512-UYE9EIeQLJOhx8vC71bWGkAGY4Zzq/H8qYlihowUg4NiFOfL+KKMnj96datb74PRxSDvHac9V3OLktNcsX2NuA==",
+      "license": "MIT",
+      "dependencies": {
+        "@unhead/schema": "1.11.19",
+        "packrup": "^0.1.2"
       },
       "funding": {
         "url": "https://github.com/sponsors/harlan-zw"
@@ -4060,15 +4245,15 @@
       }
     },
     "node_modules/@unhead/vue": {
-      "version": "1.11.18",
-      "resolved": "https://registry.npmjs.org/@unhead/vue/-/vue-1.11.18.tgz",
-      "integrity": "sha512-Jfi7t/XNBnlcauP9UTH3VHBcS69G70ikFd2e5zdgULLDRWpOlLs1sSTH1V2juNptc93DOk9RQfC5jLWbLcivFw==",
+      "version": "1.11.19",
+      "resolved": "https://registry.npmjs.org/@unhead/vue/-/vue-1.11.19.tgz",
+      "integrity": "sha512-/XATTP8wVLs3+2Pkj2crvr/Z55nybVQyOwISh+sAlr/48/9n3jGNiCZHKpHgL4MpOnGT4krwzWzbfhBO/G2BSQ==",
       "license": "MIT",
       "dependencies": {
-        "@unhead/schema": "1.11.18",
-        "@unhead/shared": "1.11.18",
+        "@unhead/schema": "1.11.19",
+        "@unhead/shared": "1.11.19",
         "hookable": "^5.5.3",
-        "unhead": "1.11.18"
+        "unhead": "1.11.19"
       },
       "funding": {
         "url": "https://github.com/sponsors/harlan-zw"
@@ -4077,6 +4262,32 @@
         "vue": ">=2.7 || >=3"
       }
     },
+    "node_modules/@unhead/vue/node_modules/@unhead/schema": {
+      "version": "1.11.19",
+      "resolved": "https://registry.npmjs.org/@unhead/schema/-/schema-1.11.19.tgz",
+      "integrity": "sha512-7VhYHWK7xHgljdv+C01MepCSYZO2v6OhgsfKWPxRQBDDGfUKCUaChox0XMq3tFvXP6u4zSp6yzcDw2yxCfVMwg==",
+      "license": "MIT",
+      "dependencies": {
+        "hookable": "^5.5.3",
+        "zhead": "^2.2.4"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/harlan-zw"
+      }
+    },
+    "node_modules/@unhead/vue/node_modules/@unhead/shared": {
+      "version": "1.11.19",
+      "resolved": "https://registry.npmjs.org/@unhead/shared/-/shared-1.11.19.tgz",
+      "integrity": "sha512-UYE9EIeQLJOhx8vC71bWGkAGY4Zzq/H8qYlihowUg4NiFOfL+KKMnj96datb74PRxSDvHac9V3OLktNcsX2NuA==",
+      "license": "MIT",
+      "dependencies": {
+        "@unhead/schema": "1.11.19",
+        "packrup": "^0.1.2"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/harlan-zw"
+      }
+    },
     "node_modules/@vercel/nft": {
       "version": "0.27.10",
       "resolved": "https://registry.npmjs.org/@vercel/nft/-/nft-0.27.10.tgz",
@@ -4546,14 +4757,14 @@
       }
     },
     "node_modules/@vueuse/core": {
-      "version": "12.5.0",
-      "resolved": "https://registry.npmjs.org/@vueuse/core/-/core-12.5.0.tgz",
-      "integrity": "sha512-GVyH1iYqNANwcahAx8JBm6awaNgvR/SwZ1fjr10b8l1HIgDp82ngNbfzJUgOgWEoxjL+URAggnlilAEXwCOZtg==",
+      "version": "12.7.0",
+      "resolved": "https://registry.npmjs.org/@vueuse/core/-/core-12.7.0.tgz",
+      "integrity": "sha512-jtK5B7YjZXmkGNHjviyGO4s3ZtEhbzSgrbX+s5o+Lr8i2nYqNyHuPVOeTdM1/hZ5Tkxg/KktAuAVDDiHMraMVA==",
       "license": "MIT",
       "dependencies": {
         "@types/web-bluetooth": "^0.0.20",
-        "@vueuse/metadata": "12.5.0",
-        "@vueuse/shared": "12.5.0",
+        "@vueuse/metadata": "12.7.0",
+        "@vueuse/shared": "12.7.0",
         "vue": "^3.5.13"
       },
       "funding": {
@@ -4561,13 +4772,13 @@
       }
     },
     "node_modules/@vueuse/integrations": {
-      "version": "12.5.0",
-      "resolved": "https://registry.npmjs.org/@vueuse/integrations/-/integrations-12.5.0.tgz",
-      "integrity": "sha512-HYLt8M6mjUfcoUOzyBcX2RjpfapIwHPBmQJtTmXOQW845Y/Osu9VuTJ5kPvnmWJ6IUa05WpblfOwZ+P0G4iZsQ==",
+      "version": "12.7.0",
+      "resolved": "https://registry.npmjs.org/@vueuse/integrations/-/integrations-12.7.0.tgz",
+      "integrity": "sha512-IEq7K4bCl7mn3uKJaWtNXnd1CAPaHLUMuyj5K1/k/pVcItt0VONZW8xiGxdIovJcQjkzOHjImhX5t6gija+0/g==",
       "license": "MIT",
       "dependencies": {
-        "@vueuse/core": "12.5.0",
-        "@vueuse/shared": "12.5.0",
+        "@vueuse/core": "12.7.0",
+        "@vueuse/shared": "12.7.0",
         "vue": "^3.5.13"
       },
       "funding": {
@@ -4627,18 +4838,18 @@
       }
     },
     "node_modules/@vueuse/metadata": {
-      "version": "12.5.0",
-      "resolved": "https://registry.npmjs.org/@vueuse/metadata/-/metadata-12.5.0.tgz",
-      "integrity": "sha512-Ui7Lo2a7AxrMAXRF+fAp9QsXuwTeeZ8fIB9wsLHqzq9MQk+2gMYE2IGJW48VMJ8ecvCB3z3GsGLKLbSasQ5Qlg==",
+      "version": "12.7.0",
+      "resolved": "https://registry.npmjs.org/@vueuse/metadata/-/metadata-12.7.0.tgz",
+      "integrity": "sha512-4VvTH9mrjXqFN5LYa5YfqHVRI6j7R00Vy4995Rw7PQxyCL3z0Lli86iN4UemWqixxEvYfRjG+hF9wL8oLOn+3g==",
       "license": "MIT",
       "funding": {
         "url": "https://github.com/sponsors/antfu"
       }
     },
     "node_modules/@vueuse/shared": {
-      "version": "12.5.0",
-      "resolved": "https://registry.npmjs.org/@vueuse/shared/-/shared-12.5.0.tgz",
-      "integrity": "sha512-vMpcL1lStUU6O+kdj6YdHDixh0odjPAUM15uJ9f7MY781jcYkIwFA4iv2EfoIPO6vBmvutI1HxxAwmf0cx5ISQ==",
+      "version": "12.7.0",
+      "resolved": "https://registry.npmjs.org/@vueuse/shared/-/shared-12.7.0.tgz",
+      "integrity": "sha512-coLlUw2HHKsm7rPN6WqHJQr18WymN4wkA/3ThFaJ4v4gWGWAQQGK+MJxLuJTBs4mojQiazlVWAKNJNpUWGRkNw==",
       "license": "MIT",
       "dependencies": {
         "vue": "^3.5.13"
@@ -5583,19 +5794,6 @@
         "node": ">=0.10.0"
       }
     },
-    "node_modules/color": {
-      "version": "4.2.3",
-      "resolved": "https://registry.npmjs.org/color/-/color-4.2.3.tgz",
-      "integrity": "sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==",
-      "license": "MIT",
-      "dependencies": {
-        "color-convert": "^2.0.1",
-        "color-string": "^1.9.0"
-      },
-      "engines": {
-        "node": ">=12.5.0"
-      }
-    },
     "node_modules/color-convert": {
       "version": "2.0.1",
       "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
@@ -5614,16 +5812,6 @@
       "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
       "license": "MIT"
     },
-    "node_modules/color-string": {
-      "version": "1.9.1",
-      "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz",
-      "integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==",
-      "license": "MIT",
-      "dependencies": {
-        "color-name": "^1.0.0",
-        "simple-swizzle": "^0.2.2"
-      }
-    },
     "node_modules/colord": {
       "version": "2.9.3",
       "resolved": "https://registry.npmjs.org/colord/-/colord-2.9.3.tgz",
@@ -5636,6 +5824,12 @@
       "integrity": "sha512-Y2oEozpomLn7Q3HFP7dpww7AtMJplbM9lGZP6RDfHqmbeRjiwRg4n6VM6j4KLmRke85uWEI7JqF17f3pqdRA0g==",
       "license": "MIT"
     },
+    "node_modules/colortranslator": {
+      "version": "4.1.0",
+      "resolved": "https://registry.npmjs.org/colortranslator/-/colortranslator-4.1.0.tgz",
+      "integrity": "sha512-bwa5awaMnQ6dpm9D3nbsFwUr6x6FrTKmxPdolNtSYfxCNR7ZM93GG1OF5Y3Sy1LvYdalb3riKC9uTn0X5NB36g==",
+      "license": "Apache-2.0"
+    },
     "node_modules/commander": {
       "version": "10.0.1",
       "resolved": "https://registry.npmjs.org/commander/-/commander-10.0.1.tgz",
@@ -6531,9 +6725,9 @@
       "license": "MIT"
     },
     "node_modules/esbuild": {
-      "version": "0.24.2",
-      "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.24.2.tgz",
-      "integrity": "sha512-+9egpBW8I3CD5XPe0n6BfT5fxLzxrlDzqydF3aviG+9ni1lDC/OvMHcxqEFV0+LANZG5R1bFMWfUrjVsdwxJvA==",
+      "version": "0.25.0",
+      "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.0.tgz",
+      "integrity": "sha512-BXq5mqc8ltbaN34cDqWuYKyNhX8D/Z0J1xdtdQ8UcIIIyJyz+ZMKUt58tF3SrZ85jcfN/PZYhjR5uDQAYNVbuw==",
       "hasInstallScript": true,
       "license": "MIT",
       "bin": {
@@ -6543,31 +6737,31 @@
         "node": ">=18"
       },
       "optionalDependencies": {
-        "@esbuild/aix-ppc64": "0.24.2",
-        "@esbuild/android-arm": "0.24.2",
-        "@esbuild/android-arm64": "0.24.2",
-        "@esbuild/android-x64": "0.24.2",
-        "@esbuild/darwin-arm64": "0.24.2",
-        "@esbuild/darwin-x64": "0.24.2",
-        "@esbuild/freebsd-arm64": "0.24.2",
-        "@esbuild/freebsd-x64": "0.24.2",
-        "@esbuild/linux-arm": "0.24.2",
-        "@esbuild/linux-arm64": "0.24.2",
-        "@esbuild/linux-ia32": "0.24.2",
-        "@esbuild/linux-loong64": "0.24.2",
-        "@esbuild/linux-mips64el": "0.24.2",
-        "@esbuild/linux-ppc64": "0.24.2",
-        "@esbuild/linux-riscv64": "0.24.2",
-        "@esbuild/linux-s390x": "0.24.2",
-        "@esbuild/linux-x64": "0.24.2",
-        "@esbuild/netbsd-arm64": "0.24.2",
-        "@esbuild/netbsd-x64": "0.24.2",
-        "@esbuild/openbsd-arm64": "0.24.2",
-        "@esbuild/openbsd-x64": "0.24.2",
-        "@esbuild/sunos-x64": "0.24.2",
-        "@esbuild/win32-arm64": "0.24.2",
-        "@esbuild/win32-ia32": "0.24.2",
-        "@esbuild/win32-x64": "0.24.2"
+        "@esbuild/aix-ppc64": "0.25.0",
+        "@esbuild/android-arm": "0.25.0",
+        "@esbuild/android-arm64": "0.25.0",
+        "@esbuild/android-x64": "0.25.0",
+        "@esbuild/darwin-arm64": "0.25.0",
+        "@esbuild/darwin-x64": "0.25.0",
+        "@esbuild/freebsd-arm64": "0.25.0",
+        "@esbuild/freebsd-x64": "0.25.0",
+        "@esbuild/linux-arm": "0.25.0",
+        "@esbuild/linux-arm64": "0.25.0",
+        "@esbuild/linux-ia32": "0.25.0",
+        "@esbuild/linux-loong64": "0.25.0",
+        "@esbuild/linux-mips64el": "0.25.0",
+        "@esbuild/linux-ppc64": "0.25.0",
+        "@esbuild/linux-riscv64": "0.25.0",
+        "@esbuild/linux-s390x": "0.25.0",
+        "@esbuild/linux-x64": "0.25.0",
+        "@esbuild/netbsd-arm64": "0.25.0",
+        "@esbuild/netbsd-x64": "0.25.0",
+        "@esbuild/openbsd-arm64": "0.25.0",
+        "@esbuild/openbsd-x64": "0.25.0",
+        "@esbuild/sunos-x64": "0.25.0",
+        "@esbuild/win32-arm64": "0.25.0",
+        "@esbuild/win32-ia32": "0.25.0",
+        "@esbuild/win32-x64": "0.25.0"
       }
     },
     "node_modules/escalade": {
@@ -6598,17 +6792,17 @@
       }
     },
     "node_modules/eslint": {
-      "version": "9.19.0",
-      "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.19.0.tgz",
-      "integrity": "sha512-ug92j0LepKlbbEv6hD911THhoRHmbdXt2gX+VDABAW/Ir7D3nqKdv5Pf5vtlyY6HQMTEP2skXY43ueqTCWssEA==",
+      "version": "9.20.1",
+      "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.20.1.tgz",
+      "integrity": "sha512-m1mM33o6dBUjxl2qb6wv6nGNwCAsns1eKtaQ4l/NPHeTvhiUPbtdfMyktxN4B3fgHIgsYh1VT3V9txblpQHq+g==",
       "license": "MIT",
       "dependencies": {
         "@eslint-community/eslint-utils": "^4.2.0",
         "@eslint-community/regexpp": "^4.12.1",
         "@eslint/config-array": "^0.19.0",
-        "@eslint/core": "^0.10.0",
+        "@eslint/core": "^0.11.0",
         "@eslint/eslintrc": "^3.2.0",
-        "@eslint/js": "9.19.0",
+        "@eslint/js": "9.20.0",
         "@eslint/plugin-kit": "^0.2.5",
         "@humanfs/node": "^0.16.6",
         "@humanwhocodes/module-importer": "^1.0.1",
@@ -6672,9 +6866,9 @@
       }
     },
     "node_modules/eslint-flat-config-utils": {
-      "version": "2.0.0",
-      "resolved": "https://registry.npmjs.org/eslint-flat-config-utils/-/eslint-flat-config-utils-2.0.0.tgz",
-      "integrity": "sha512-AbpYwI9FBmjF6BQ8UcaDCrM750DWEB6UJzEjQEg+iWFP6UX9rGsUGJlMf7sWbW3dOA0klUEwmWGZa5FoynXU/w==",
+      "version": "2.0.1",
+      "resolved": "https://registry.npmjs.org/eslint-flat-config-utils/-/eslint-flat-config-utils-2.0.1.tgz",
+      "integrity": "sha512-brf0eAgQ6JlKj3bKfOTuuI7VcCZvi8ZCD1MMTVoEvS/d38j8cByZViLFALH/36+eqB17ukmfmKq3bWzGvizejA==",
       "license": "MIT",
       "dependencies": {
         "pathe": "^2.0.2"
@@ -6927,6 +7121,18 @@
         "url": "https://opencollective.com/eslint"
       }
     },
+    "node_modules/eslint/node_modules/@eslint/core": {
+      "version": "0.11.0",
+      "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.11.0.tgz",
+      "integrity": "sha512-DWUB2pksgNEb6Bz2fggIy1wh6fGgZP4Xyy/Mt0QZPiloKKXerbqq9D3SBQTlCRYOrcRPu4vuz+CGjwdfqxnoWA==",
+      "license": "Apache-2.0",
+      "dependencies": {
+        "@types/json-schema": "^7.0.15"
+      },
+      "engines": {
+        "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+      }
+    },
     "node_modules/eslint/node_modules/ansi-styles": {
       "version": "4.3.0",
       "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
@@ -7358,6 +7564,21 @@
         }
       }
     },
+    "node_modules/figures": {
+      "version": "6.1.0",
+      "resolved": "https://registry.npmjs.org/figures/-/figures-6.1.0.tgz",
+      "integrity": "sha512-d+l3qxjSesT4V7v2fh+QnmFnUWv9lSpjarhShNTgBOfA0ttejbQUAlHLitbjkoRiDulW0OPoQPYIGhIC8ohejg==",
+      "license": "MIT",
+      "dependencies": {
+        "is-unicode-supported": "^2.0.0"
+      },
+      "engines": {
+        "node": ">=18"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
     "node_modules/file-entry-cache": {
       "version": "8.0.0",
       "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz",
@@ -7593,9 +7814,9 @@
       }
     },
     "node_modules/fuse.js": {
-      "version": "7.0.0",
-      "resolved": "https://registry.npmjs.org/fuse.js/-/fuse.js-7.0.0.tgz",
-      "integrity": "sha512-14F4hBIxqKvD4Zz/XjDc3y94mNZN6pRv3U13Udo0lNLCWRBUsrMv2xwcF/y/Z5sV6+FQW+/ow68cHpm4sunt8Q==",
+      "version": "7.1.0",
+      "resolved": "https://registry.npmjs.org/fuse.js/-/fuse.js-7.1.0.tgz",
+      "integrity": "sha512-trLf4SzuuUxfusZADLINj+dE8clK1frKdmqiJNb1Es75fmI5oY6X2mxLVUciLLjxqw/xr72Dhy+lER6dGd02FQ==",
       "license": "Apache-2.0",
       "engines": {
         "node": ">=10"
@@ -7857,9 +8078,9 @@
       }
     },
     "node_modules/happy-dom": {
-      "version": "16.8.1",
-      "resolved": "https://registry.npmjs.org/happy-dom/-/happy-dom-16.8.1.tgz",
-      "integrity": "sha512-n0QrmT9lD81rbpKsyhnlz3DgnMZlaOkJPpgi746doA+HvaMC79bdWkwjrNnGJRvDrWTI8iOcJiVTJ5CdT/AZRw==",
+      "version": "17.1.0",
+      "resolved": "https://registry.npmjs.org/happy-dom/-/happy-dom-17.1.0.tgz",
+      "integrity": "sha512-9tUhXyePCjzUMycaHS/IzrIpF69xiq/laAT7golk4MtZ6t8ft5+Rv7U3lfrs2b4NMH0JTL3EhZzjfahrPmOnaQ==",
       "dev": true,
       "license": "MIT",
       "dependencies": {
@@ -8127,12 +8348,6 @@
         "url": "https://github.com/sponsors/brc-dd"
       }
     },
-    "node_modules/is-arrayish": {
-      "version": "0.3.2",
-      "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz",
-      "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==",
-      "license": "MIT"
-    },
     "node_modules/is-binary-path": {
       "version": "2.1.0",
       "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
@@ -8281,6 +8496,18 @@
         "url": "https://github.com/sponsors/sindresorhus"
       }
     },
+    "node_modules/is-plain-obj": {
+      "version": "4.1.0",
+      "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz",
+      "integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=12"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
     "node_modules/is-reference": {
       "version": "1.2.1",
       "resolved": "https://registry.npmjs.org/is-reference/-/is-reference-1.2.1.tgz",
@@ -8311,6 +8538,18 @@
         "url": "https://github.com/sponsors/sindresorhus"
       }
     },
+    "node_modules/is-unicode-supported": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-2.1.0.tgz",
+      "integrity": "sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=18"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
     "node_modules/is-what": {
       "version": "4.1.16",
       "resolved": "https://registry.npmjs.org/is-what/-/is-what-4.1.16.tgz",
@@ -8366,9 +8605,9 @@
       "license": "ISC"
     },
     "node_modules/jackspeak": {
-      "version": "4.0.2",
-      "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-4.0.2.tgz",
-      "integrity": "sha512-bZsjR/iRjl1Nk1UkjGpAzLNfQtzuijhn2g+pbZb98HQ1Gk8vM9hfbxeMBP+M2/UUdwj0RqGG3mlvk2MsAqwvEw==",
+      "version": "4.0.3",
+      "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-4.0.3.tgz",
+      "integrity": "sha512-oSwM7q8PTHQWuZAlp995iPpPJ4Vkl7qT0ZRD+9duL9j2oBy6KcTfyxc8mEuHJYC+z/kbps80aJLkaNzTOrf/kw==",
       "license": "BlueOak-1.0.0",
       "dependencies": {
         "@isaacs/cliui": "^8.0.2"
@@ -9997,6 +10236,18 @@
         "url": "https://github.com/sponsors/sindresorhus"
       }
     },
+    "node_modules/parse-ms": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmjs.org/parse-ms/-/parse-ms-4.0.0.tgz",
+      "integrity": "sha512-TXfryirbmq34y8QBwgqCVLi+8oA3oWx2eAnSn62ITyEhEYaWRlVZ2DvMM9eZbMs/RfxPu/PK/aBLyGj4IrqMHw==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=18"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
     "node_modules/parse-path": {
       "version": "7.0.0",
       "resolved": "https://registry.npmjs.org/parse-path/-/parse-path-7.0.0.tgz",
@@ -10090,9 +10341,9 @@
       }
     },
     "node_modules/pathe": {
-      "version": "2.0.2",
-      "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.2.tgz",
-      "integrity": "sha512-15Ztpk+nov8DR524R4BF7uEuzESgzUEAV4Ah7CUMNGXdE5ELuvxElxGXndBl32vMSsWa1jpNf22Z+Er3sKwq+w==",
+      "version": "2.0.3",
+      "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz",
+      "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==",
       "license": "MIT"
     },
     "node_modules/pathval": {
@@ -10659,6 +10910,21 @@
         "url": "https://github.com/sponsors/sindresorhus"
       }
     },
+    "node_modules/pretty-ms": {
+      "version": "9.2.0",
+      "resolved": "https://registry.npmjs.org/pretty-ms/-/pretty-ms-9.2.0.tgz",
+      "integrity": "sha512-4yf0QO/sllf/1zbZWYnvWw3NxCQwLXKzIj0G849LSufP15BXKM0rbD2Z3wVnkMfjdn/CB0Dpp444gYAACdsplg==",
+      "license": "MIT",
+      "dependencies": {
+        "parse-ms": "^4.0.0"
+      },
+      "engines": {
+        "node": ">=18"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
     "node_modules/process": {
       "version": "0.11.10",
       "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz",
@@ -10736,9 +11002,9 @@
       "license": "MIT"
     },
     "node_modules/radix-vue": {
-      "version": "1.9.12",
-      "resolved": "https://registry.npmjs.org/radix-vue/-/radix-vue-1.9.12.tgz",
-      "integrity": "sha512-zkr66Jqxbej4+oR6O/pZRzyM/VZi66ndbyIBZQjJKAXa1lIoYReZJse6W1EEDZKXknD7rXhpS+jM9Sr23lIqfg==",
+      "version": "1.9.15",
+      "resolved": "https://registry.npmjs.org/radix-vue/-/radix-vue-1.9.15.tgz",
+      "integrity": "sha512-+wz/PBlUTkrqFPS9ZUI1eZ4QHrU2EVbQnPvtnxWY7Q7jZtNE/LAAEf9IDoBoacX1hKfJXBB+SKfZSrv57uofYA==",
       "license": "MIT",
       "dependencies": {
         "@floating-ui/dom": "^1.6.7",
@@ -11134,18 +11400,18 @@
       }
     },
     "node_modules/reka-ui": {
-      "version": "1.0.0-alpha.8",
-      "resolved": "https://registry.npmjs.org/reka-ui/-/reka-ui-1.0.0-alpha.8.tgz",
-      "integrity": "sha512-FmAUxWFLWtvbheBLvjgotR/RsE1KSjciMJOLmo7wL0Sbe+sW7M35O8K6f141a0Vc1cE0mH57UHcWBuVpeJNQNA==",
-      "license": "MIT",
-      "dependencies": {
-        "@floating-ui/dom": "^1.6.11",
-        "@floating-ui/vue": "^1.1.5",
-        "@internationalized/date": "^3.5.6",
-        "@internationalized/number": "^3.5.4",
-        "@tanstack/vue-virtual": "^3.10.8",
-        "@vueuse/core": "^12.0.0",
-        "@vueuse/shared": "^12.0.0",
+      "version": "1.0.0-alpha.10",
+      "resolved": "https://registry.npmjs.org/reka-ui/-/reka-ui-1.0.0-alpha.10.tgz",
+      "integrity": "sha512-fQ+7LLKlZ9dTFOMsSmefRBDLEnFdOcstWW3ZIaV7000d8YYsteYZOY86WWRi7k64FuWn8pMIE8Ya3Aq0pkc7lw==",
+      "license": "MIT",
+      "dependencies": {
+        "@floating-ui/dom": "^1.6.13",
+        "@floating-ui/vue": "^1.1.6",
+        "@internationalized/date": "^3.5.0",
+        "@internationalized/number": "^3.5.0",
+        "@tanstack/vue-virtual": "^3.12.0",
+        "@vueuse/core": "^12.5.0",
+        "@vueuse/shared": "^12.5.0",
         "aria-hidden": "^1.2.4",
         "defu": "^6.1.4",
         "ohash": "^1.1.4"
@@ -11615,15 +11881,6 @@
         "url": "https://github.com/steveukx/git-js?sponsor=1"
       }
     },
-    "node_modules/simple-swizzle": {
-      "version": "0.2.2",
-      "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz",
-      "integrity": "sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==",
-      "license": "MIT",
-      "dependencies": {
-        "is-arrayish": "^0.3.1"
-      }
-    },
     "node_modules/sirv": {
       "version": "3.0.0",
       "resolved": "https://registry.npmjs.org/sirv/-/sirv-3.0.0.tgz",
@@ -12128,9 +12385,9 @@
       }
     },
     "node_modules/tailwindcss": {
-      "version": "4.0.0-beta.9",
-      "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.0.0-beta.9.tgz",
-      "integrity": "sha512-96KpsfQi+/sFIOfyFnGzyy5pobuzf1iMBD9NVtelerPM/lPI2XUS4Kikw9yuKRniXXw77ov1sl7gCSKLsn6CJA==",
+      "version": "4.0.6",
+      "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.0.6.tgz",
+      "integrity": "sha512-mysewHYJKaXgNOW6pp5xon/emCsfAMnO8WMaGKZZ35fomnR/T5gYnRg2/yRTTrtXiEl1tiVkeRt0eMO6HxEZqw==",
       "license": "MIT"
     },
     "node_modules/tapable": {
@@ -12323,9 +12580,9 @@
       "license": "MIT"
     },
     "node_modules/ts-api-utils": {
-      "version": "2.0.0",
-      "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.0.0.tgz",
-      "integrity": "sha512-xCt/TOAc+EOHS1XPnijD3/yzpH6qg2xppZO1YDqGoVsNXfQfzHpOdNuXwrwOU8u4ITXJyDCTyt8w5g1sZv9ynQ==",
+      "version": "2.0.1",
+      "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.0.1.tgz",
+      "integrity": "sha512-dnlgjFSVetynI8nzgJ+qF62efpglpWRk8isUEWZGWlJYySCTD6aKvbUDu+zbPeDakk3bg5H4XpitHukgfL1m9w==",
       "license": "MIT",
       "engines": {
         "node": ">=18.12"
@@ -12451,20 +12708,46 @@
       "license": "MIT"
     },
     "node_modules/unhead": {
-      "version": "1.11.18",
-      "resolved": "https://registry.npmjs.org/unhead/-/unhead-1.11.18.tgz",
-      "integrity": "sha512-TWgGUoZMpYe2yJwY6jZ0/9kpQT18ygr2h5lI6cUXdfD9UzDc0ytM9jGaleSYkj9guJWXkk7izYBnzJvxl8mRvQ==",
+      "version": "1.11.19",
+      "resolved": "https://registry.npmjs.org/unhead/-/unhead-1.11.19.tgz",
+      "integrity": "sha512-O5AYb3+xUOzBlwDmPfC/DgGp9rDMoGkB4gFkhoaz8IonQqP8W8qqetxYf5ZyEdntvXnFsMWS8lZF//5176xo6Q==",
       "license": "MIT",
       "dependencies": {
-        "@unhead/dom": "1.11.18",
-        "@unhead/schema": "1.11.18",
-        "@unhead/shared": "1.11.18",
+        "@unhead/dom": "1.11.19",
+        "@unhead/schema": "1.11.19",
+        "@unhead/shared": "1.11.19",
         "hookable": "^5.5.3"
       },
       "funding": {
         "url": "https://github.com/sponsors/harlan-zw"
       }
     },
+    "node_modules/unhead/node_modules/@unhead/schema": {
+      "version": "1.11.19",
+      "resolved": "https://registry.npmjs.org/@unhead/schema/-/schema-1.11.19.tgz",
+      "integrity": "sha512-7VhYHWK7xHgljdv+C01MepCSYZO2v6OhgsfKWPxRQBDDGfUKCUaChox0XMq3tFvXP6u4zSp6yzcDw2yxCfVMwg==",
+      "license": "MIT",
+      "dependencies": {
+        "hookable": "^5.5.3",
+        "zhead": "^2.2.4"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/harlan-zw"
+      }
+    },
+    "node_modules/unhead/node_modules/@unhead/shared": {
+      "version": "1.11.19",
+      "resolved": "https://registry.npmjs.org/@unhead/shared/-/shared-1.11.19.tgz",
+      "integrity": "sha512-UYE9EIeQLJOhx8vC71bWGkAGY4Zzq/H8qYlihowUg4NiFOfL+KKMnj96datb74PRxSDvHac9V3OLktNcsX2NuA==",
+      "license": "MIT",
+      "dependencies": {
+        "@unhead/schema": "1.11.19",
+        "packrup": "^0.1.2"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/harlan-zw"
+      }
+    },
     "node_modules/unicode-properties": {
       "version": "1.4.1",
       "resolved": "https://registry.npmjs.org/unicode-properties/-/unicode-properties-1.4.1.tgz",
@@ -12611,18 +12894,17 @@
       }
     },
     "node_modules/unplugin-auto-import": {
-      "version": "19.0.0",
-      "resolved": "https://registry.npmjs.org/unplugin-auto-import/-/unplugin-auto-import-19.0.0.tgz",
-      "integrity": "sha512-TREXtXqCM6YLy3rE2tjvKZEaCiPlP2e5bmnRKaS8AM2MlNgjV7UP4RPieWIfs4Isv0GoeHmov956PIIvJYdqpQ==",
+      "version": "19.1.0",
+      "resolved": "https://registry.npmjs.org/unplugin-auto-import/-/unplugin-auto-import-19.1.0.tgz",
+      "integrity": "sha512-B+TGBEBHqY9aR+7YfShfLujETOHstzpV+yaqgy5PkfV0QG7Py+TYMX7vJ9W4SrysHR+UzR+gzcx/nuZjmPeclA==",
       "license": "MIT",
       "dependencies": {
-        "@antfu/utils": "^0.7.10",
-        "@rollup/pluginutils": "^5.1.4",
-        "local-pkg": "^0.5.1",
+        "local-pkg": "^1.0.0",
         "magic-string": "^0.30.17",
         "picomatch": "^4.0.2",
-        "unimport": "^3.14.5",
-        "unplugin": "^2.1.2"
+        "unimport": "^4.1.1",
+        "unplugin": "^2.2.0",
+        "unplugin-utils": "^0.2.4"
       },
       "engines": {
         "node": ">=14"
@@ -12643,22 +12925,118 @@
         }
       }
     },
+    "node_modules/unplugin-auto-import/node_modules/escape-string-regexp": {
+      "version": "5.0.0",
+      "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz",
+      "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=12"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
+    "node_modules/unplugin-auto-import/node_modules/local-pkg": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/local-pkg/-/local-pkg-1.0.0.tgz",
+      "integrity": "sha512-bbgPw/wmroJsil/GgL4qjDzs5YLTBMQ99weRsok1XCDccQeehbHA/I1oRvk2NPtr7KGZgT/Y5tPRnAtMqeG2Kg==",
+      "license": "MIT",
+      "dependencies": {
+        "mlly": "^1.7.3",
+        "pkg-types": "^1.3.0"
+      },
+      "engines": {
+        "node": ">=14"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/antfu"
+      }
+    },
+    "node_modules/unplugin-auto-import/node_modules/tinyglobby": {
+      "version": "0.2.11",
+      "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.11.tgz",
+      "integrity": "sha512-32TmKeeKUahv0Go8WmQgiEp9Y21NuxjwjqiRC1nrUB51YacfSwuB44xgXD+HdIppmMRgjQNPdrHyA6vIybYZ+g==",
+      "license": "MIT",
+      "dependencies": {
+        "fdir": "^6.4.3",
+        "picomatch": "^4.0.2"
+      },
+      "engines": {
+        "node": ">=12.0.0"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/SuperchupuDev"
+      }
+    },
+    "node_modules/unplugin-auto-import/node_modules/unimport": {
+      "version": "4.1.2",
+      "resolved": "https://registry.npmjs.org/unimport/-/unimport-4.1.2.tgz",
+      "integrity": "sha512-oVUL7PSlyVV3QRhsdcyYEMaDX8HJyS/CnUonEJTYA3//bWO+o/4gG8F7auGWWWkrrxBQBYOO8DKe+C53ktpRXw==",
+      "license": "MIT",
+      "dependencies": {
+        "acorn": "^8.14.0",
+        "escape-string-regexp": "^5.0.0",
+        "estree-walker": "^3.0.3",
+        "local-pkg": "^1.0.0",
+        "magic-string": "^0.30.17",
+        "mlly": "^1.7.4",
+        "pathe": "^2.0.3",
+        "picomatch": "^4.0.2",
+        "pkg-types": "^1.3.1",
+        "scule": "^1.3.0",
+        "strip-literal": "^3.0.0",
+        "tinyglobby": "^0.2.11",
+        "unplugin": "^2.2.0",
+        "unplugin-utils": "^0.2.4"
+      },
+      "engines": {
+        "node": ">=18.12.0"
+      }
+    },
+    "node_modules/unplugin-auto-import/node_modules/unplugin": {
+      "version": "2.2.0",
+      "resolved": "https://registry.npmjs.org/unplugin/-/unplugin-2.2.0.tgz",
+      "integrity": "sha512-m1ekpSwuOT5hxkJeZGRxO7gXbXT3gF26NjQ7GdVHoLoF8/nopLcd/QfPigpCy7i51oFHiRJg/CyHhj4vs2+KGw==",
+      "license": "MIT",
+      "dependencies": {
+        "acorn": "^8.14.0",
+        "webpack-virtual-modules": "^0.6.2"
+      },
+      "engines": {
+        "node": ">=18.12.0"
+      }
+    },
+    "node_modules/unplugin-utils": {
+      "version": "0.2.4",
+      "resolved": "https://registry.npmjs.org/unplugin-utils/-/unplugin-utils-0.2.4.tgz",
+      "integrity": "sha512-8U/MtpkPkkk3Atewj1+RcKIjb5WBimZ/WSLhhR3w6SsIj8XJuKTacSP8g+2JhfSGw0Cb125Y+2zA/IzJZDVbhA==",
+      "license": "MIT",
+      "dependencies": {
+        "pathe": "^2.0.2",
+        "picomatch": "^4.0.2"
+      },
+      "engines": {
+        "node": ">=18.12.0"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sxzz"
+      }
+    },
     "node_modules/unplugin-vue-components": {
-      "version": "28.0.0",
-      "resolved": "https://registry.npmjs.org/unplugin-vue-components/-/unplugin-vue-components-28.0.0.tgz",
-      "integrity": "sha512-vYe0wSyqTVhyNFIad1iiGyQGhG++tDOMgohqenMDOAooMJP9vvzCdXTqCVx20A0rCQXFNjgoRbSeDAioLPH36Q==",
+      "version": "28.2.0",
+      "resolved": "https://registry.npmjs.org/unplugin-vue-components/-/unplugin-vue-components-28.2.0.tgz",
+      "integrity": "sha512-FMncMZRnOcVs81BligpZL4tPFHE+VX1P1SYgzzl4D6ZgLiJHoZWNx743z+Vfyz701GsZ8WX/ENvYrIZ4mvJPeA==",
       "license": "MIT",
       "dependencies": {
-        "@antfu/utils": "^0.7.10",
-        "@rollup/pluginutils": "^5.1.4",
         "chokidar": "^3.6.0",
         "debug": "^4.4.0",
-        "fast-glob": "^3.3.3",
-        "local-pkg": "^0.5.1",
+        "local-pkg": "^1.0.0",
         "magic-string": "^0.30.17",
-        "minimatch": "^9.0.5",
-        "mlly": "^1.7.3",
-        "unplugin": "^2.1.2"
+        "mlly": "^1.7.4",
+        "tinyglobby": "^0.2.10",
+        "unplugin": "^2.2.0",
+        "unplugin-utils": "^0.2.4"
       },
       "engines": {
         "node": ">=14"
@@ -12716,6 +13094,22 @@
         "node": ">= 6"
       }
     },
+    "node_modules/unplugin-vue-components/node_modules/local-pkg": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/local-pkg/-/local-pkg-1.0.0.tgz",
+      "integrity": "sha512-bbgPw/wmroJsil/GgL4qjDzs5YLTBMQ99weRsok1XCDccQeehbHA/I1oRvk2NPtr7KGZgT/Y5tPRnAtMqeG2Kg==",
+      "license": "MIT",
+      "dependencies": {
+        "mlly": "^1.7.3",
+        "pkg-types": "^1.3.0"
+      },
+      "engines": {
+        "node": ">=14"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/antfu"
+      }
+    },
     "node_modules/unplugin-vue-components/node_modules/picomatch": {
       "version": "2.3.1",
       "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
@@ -12740,6 +13134,19 @@
         "node": ">=8.10.0"
       }
     },
+    "node_modules/unplugin-vue-components/node_modules/unplugin": {
+      "version": "2.2.0",
+      "resolved": "https://registry.npmjs.org/unplugin/-/unplugin-2.2.0.tgz",
+      "integrity": "sha512-m1ekpSwuOT5hxkJeZGRxO7gXbXT3gF26NjQ7GdVHoLoF8/nopLcd/QfPigpCy7i51oFHiRJg/CyHhj4vs2+KGw==",
+      "license": "MIT",
+      "dependencies": {
+        "acorn": "^8.14.0",
+        "webpack-virtual-modules": "^0.6.2"
+      },
+      "engines": {
+        "node": ">=18.12.0"
+      }
+    },
     "node_modules/unplugin-vue-router": {
       "version": "0.11.2",
       "resolved": "https://registry.npmjs.org/unplugin-vue-router/-/unplugin-vue-router-0.11.2.tgz",
@@ -13140,9 +13547,9 @@
       "license": "MIT"
     },
     "node_modules/valibot": {
-      "version": "1.0.0-beta.15",
-      "resolved": "https://registry.npmjs.org/valibot/-/valibot-1.0.0-beta.15.tgz",
-      "integrity": "sha512-BKy8XosZkDHWmYC+cJG74LBzP++Gfntwi33pP3D3RKztz2XV9jmFWnkOi21GoqARP8wAWARwhV6eTr1JcWzjGw==",
+      "version": "1.0.0-rc.1",
+      "resolved": "https://registry.npmjs.org/valibot/-/valibot-1.0.0-rc.1.tgz",
+      "integrity": "sha512-bTHNpeeQ403xS7qGHF/tw3EC/zkZOU5VdkfIsmRDu1Sp+BJNTNCm6m5HlwOgyW/03lofP+uQiq3R+Poo9wiCEg==",
       "license": "MIT",
       "peerDependencies": {
         "typescript": ">=5"
@@ -13174,9 +13581,9 @@
       }
     },
     "node_modules/vaul-vue": {
-      "version": "0.2.0",
-      "resolved": "https://registry.npmjs.org/vaul-vue/-/vaul-vue-0.2.0.tgz",
-      "integrity": "sha512-YV0zqxc8NiVzr1z/Awwbaty0UDDchxj5BfhFbLiYu+Uz0rCfSaDK2zwmuXZvejBJKLGbWw9I5GLHJRse14lQew==",
+      "version": "0.2.1",
+      "resolved": "https://registry.npmjs.org/vaul-vue/-/vaul-vue-0.2.1.tgz",
+      "integrity": "sha512-iF91R1JQZzxb9mb9uGNHYv8rVFxR5bL5Fj51iqvyXjzMPAzNMciCrXb9OUBu2NdlhcF6rVtEADXnQoTY+pKIeA==",
       "dependencies": {
         "@vueuse/core": "^10.8.0",
         "radix-vue": "^1.4.9",
@@ -14260,6 +14667,18 @@
         "url": "https://github.com/sponsors/sindresorhus"
       }
     },
+    "node_modules/yoctocolors": {
+      "version": "2.1.1",
+      "resolved": "https://registry.npmjs.org/yoctocolors/-/yoctocolors-2.1.1.tgz",
+      "integrity": "sha512-GQHQqAopRhwU8Kt1DDM8NjibDXHC8eoh1erhGAJPEyveY9qqVeXvVikNKrDz69sHowPMorbPUrH/mx8c50eiBQ==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=18"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
     "node_modules/zhead": {
       "version": "2.2.4",
       "resolved": "https://registry.npmjs.org/zhead/-/zhead-2.2.4.tgz",
diff --git a/package.json b/package.json
index ed2f66b6c3b08714d366520ee0d18b9611f68456..4748729435c5ba2a0c1f65f8d467455f55fac6c6 100644
--- a/package.json
+++ b/package.json
@@ -4,7 +4,7 @@
   "type": "module",
   "scripts": {
     "build": "node_modules/.bin/nuxt build",
-    "dev": "node_modules/.bin/nuxt dev",
+    "dev": "node_modules/.bin/nuxt dev --dotenv .env",
     "generate": "node_modules/.bin/nuxt generate",
     "lint": "node_modules/.bin/eslint .",
     "postinstall": "node_modules/.bin/nuxt prepare",
@@ -14,25 +14,28 @@
     "test": "node_modules/.bin/vitest run --passWithNoTests"
   },
   "dependencies": {
-    "@iconify-json/material-symbols": "^1.2.14",
-    "@nuxt/eslint": "^1.0.0",
-    "@nuxt/ui": "^3.0.0-alpha.11",
+    "@nuxt/eslint": "^1.0.1",
+    "@nuxt/ui": "^3.0.0-alpha.13",
     "@nuxtjs/leaflet": "^1.2.6",
-    "eslint": "^9.19.0",
+    "eslint": "^9.20.1",
     "leaflet.markercluster": "^1.5.3",
     "nuxt": "^3.15.4",
-    "valibot": "^1.0.0-beta.15",
+    "valibot": "^1.0.0-rc.1",
     "vue": "latest",
     "vue-router": "latest"
   },
   "devDependencies": {
+    "@iconify-json/lucide": "^1.2.26",
+    "@iconify-json/material-symbols": "^1.2.14",
     "@nuxt/test-utils": "^3.15.4",
     "@vue/test-utils": "^2.4.6",
-    "happy-dom": "^16.8.1",
+    "happy-dom": "^17.1.0",
     "typescript": "^5.7.3",
     "vitest": "^3.0.5"
   },
   "overrides": {
-    "glob": "^11.0.1"
+    "glob": "^11.0.1",
+    "happy-dom": "^17.1.0",
+    "esbuild": "^0.25.0"
   }
 }
diff --git a/package.json.md b/package.json.md
new file mode 100644
index 0000000000000000000000000000000000000000..9a05fcc9eec3141c96dbc0256e1cddcadd19bd9d
--- /dev/null
+++ b/package.json.md
@@ -0,0 +1,12 @@
+Dépendances outrepassées : 
+- `glob` with version "^11.0.1", waiting update from these packages:
+  - @vue/test-utils@2.4.6
+  - nuxt@3.15.4
+- `happy-dom` with version "^17.1.0", waiting update from these packages:
+  - @nuxt/test-utils@3.15.4
+  - vitest@3.0.5
+- `esbuild` with version "^0.25.0", waiting update from these packages:
+  - @nuxt@3.15.4
+  - @nuxt/test-utils@3.15.4
+  - @nuxt/ui@3.0.0-alpha.13
+  - @nuxt/eslint@1.0.1
diff --git a/pages/exercices/[id].vue b/pages/exercices/[id].vue
deleted file mode 100644
index 8e436cc317fcf7a347f1a37530bbba0d836182fd..0000000000000000000000000000000000000000
--- a/pages/exercices/[id].vue
+++ /dev/null
@@ -1,3 +0,0 @@
-<template>
-    <div/>
-</template>
\ No newline at end of file
diff --git a/pages/exercise/[id].vue b/pages/exercise/[id].vue
new file mode 100644
index 0000000000000000000000000000000000000000..8dea90f84a54e2f29e51a20ea6bcc562e41617c4
--- /dev/null
+++ b/pages/exercise/[id].vue
@@ -0,0 +1,149 @@
+<script setup lang="ts">
+import * as v from 'valibot'
+import {ModalConfirmation} from "#components";
+import {getExerciseCategoryIcon, translateExerciseCategoryToFrench} from "~/types/enum/ExerciseCategory";
+import type {ResponseBody} from "~/types/ResponseBody";
+import type {IExercise} from "~/repository/model/IExercise";
+
+const {$api} = useNuxtApp();
+const modal = useModal();
+const route = useRoute();
+const router = useRouter();
+const toast = useToast();
+
+const isCreation = route.params.id === 'new';
+const mode = route.query.mode;
+const form = useTemplateRef('form');
+const exercise: Exercise = reactive(await $api.exercises.getExercise(parseInt(route.params.id as string)));
+const categories = Object.values(ExerciseCategory).map((category) => {
+    return {
+        label: translateExerciseCategoryToFrench(category),
+        value: category,
+        icon: getExerciseCategoryIcon(category)
+    }
+});
+const subCategories = ref<string[]>(await $api.exercises.fetchSubCategories());
+
+const schema = v.object({
+    id: v.pipe(v.number()),
+    name: v.pipe(v.string(), v.nonEmpty('Champs requis')),
+    description: v.pipe(v.string(), v.nonEmpty('Champs requis')),
+    category: v.pipe(v.string(), v.nonEmpty('Champs requis')),
+    subCategory: v.pipe(v.string(), v.nonEmpty('Champs requis')),
+    diagram: v.union([v.pipe(v.string(), v.url('URL incorrect')), v.literal('')]),
+    video: v.union([v.pipe(v.string(), v.url('URL incorrect')), v.literal('')])
+});
+
+function onCreate(item: string) {
+    subCategories.value.push(item)
+
+    // TODO : Need api update
+    // Gestion d'une nouvelle sous catégorie ?
+    // affichage d'une pop-up ?
+    exercise.subCategory = item
+}
+
+async function onSubmit() {
+    const data = await $api.exercises.saveExercise(exercise) as ResponseBody<string | IExercise>;
+
+    if (data.statusCode === 200 || data.statusCode === 201) {
+        toast.add({title: 'Success', description: 'Votre exercice a bien été enregistré.', color: 'success'})
+        await router.push('/exercises');
+    } else if (data.statusCode === 401) {
+        toast.add({title: 'Error', description: 'Vous n\'êtes pas autorisé à effectuer cette action.' , color: 'error'})
+    } else if (data.statusCode === 403) {
+        toast.add({title: 'Error', description: 'Vous devez vous identifier avant d\'effectuer cette action.', color: 'error'})
+    } else {
+        console.error('Error saving exercise', data);
+        toast.add({title: 'Error', description: 'Error updating exercise', color: 'error'})
+    }
+}
+
+function onCancel() {
+    if (form.value?.dirty) {
+        modal.open(ModalConfirmation, {
+            title: 'Quitter sans enregistrer ?',
+            description: 'Toutes vos modifications seront perdues.',
+            confirmIcon: 'material-symbols:googler-travel',
+            onSuccess() {
+                modal.close()
+                router.push('/exercises')
+            }
+        })
+    } else {
+        router.push('/exercises')
+    }
+}
+</script>
+
+<template>
+    <div id="form-exercise" class="flex flex-col items-center justify-center">
+        <UButton icon="material-symbols:arrow-back-rounded" class="self-start" @click="onCancel">Retour</UButton>
+        <UForm
+            ref="form"
+            :disabled="mode !== 'edit'"
+            :schema="v.safeParser(schema)"
+            :state="exercise"
+            :validate-on="['input', 'change']"
+            class="space-y-4 grid grid-cols-2 gap-2 h-full"
+            @submit="onSubmit"
+        >
+            <UFormField v-if="!isCreation" :inert="!isCreation" label="Identifiant" name="id">
+                <UInput :value="exercise.id" type="number" disabled/>
+            </UFormField>
+
+            <UFormField label="Nom de l'exercice" name="name" required :class=" {'col-span-2' : isCreation }">
+                <UInput v-model="exercise.name" placeholder="Nom de l'exercice" class="w-full"/>
+            </UFormField>
+
+            <UFormField label="Description" name="description" required>
+                <UTextarea v-model="exercise.description" placeholder="Description" class="w-full"/>
+            </UFormField>
+
+            <UFormField label="Instructions de sécurité" name="safety">
+                <UTextarea v-model="exercise.safetyInstructions" placeholder="Instructions de sécurité" class="w-full"/>
+            </UFormField>
+
+            <UFormField label="Consignes" name="general" class="col-span-2">
+                <UTextarea v-model="exercise.generalInstructions" placeholder="Consignes" class="w-full"/>
+            </UFormField>
+
+            <UFormField label="Illustration" name="diagram">
+                <UInput v-model="exercise.diagram" type="url" placeholder="Illustration" class="w-full"/>
+            </UFormField>
+
+            <UFormField label="Vidéo" name="video">
+                <UInput v-model="exercise.video" type="url" placeholder="Vidéo" class="w-full"/>
+            </UFormField>
+
+            <UFormField label="Catégorie" name="category" required>
+                <USelect v-model="exercise.category" :items="categories" class="w-full"/>
+            </UFormField>
+
+            <UFormField label="Sous-catégorie" name="subCategory" required>
+                <USelectMenu
+                    v-model="exercise.subCategory"
+                    :items="subCategories"
+                    create-item
+                    class="w-full"
+                    placeholder="Sous-catégorie"
+                    @create="onCreate"/>
+            </UFormField>
+
+            <UButton type="button" class="place-self-center m-auto w-fit" color="secondary" icon="material-symbols:cancel-outline" size="xl" @click="onCancel">
+                Annuler
+            </UButton>
+            <UButton type="submit" class="w-fit place-self-center" :disabled="mode !== 'edit'" icon="material-symbols:save-outline" size="xl">
+                Enregistrer
+            </UButton>
+        </UForm>
+    </div>
+</template>
+
+<style>
+div#form-exercise {
+    padding: 1vh 1vw 6vh 1vw;
+    height: 80vh;
+    overflow: auto;
+}
+</style>
\ No newline at end of file
diff --git a/pages/exercises.vue b/pages/exercises.vue
new file mode 100644
index 0000000000000000000000000000000000000000..d994b314ddfcd1db5f3dac457f2154e1e843fee2
--- /dev/null
+++ b/pages/exercises.vue
@@ -0,0 +1,211 @@
+<script setup lang="ts">
+import {h} from "vue";
+import type {DropdownMenuItem, TableColumn} from "@nuxt/ui";
+import {getPaginationRowModel, type SortDirection} from "@tanstack/vue-table";
+import {getExerciseCategoryIcon, translateExerciseCategoryToFrench} from "~/types/enum/ExerciseCategory";
+
+const UButton = resolveComponent('UButton');
+const UBadge = resolveComponent('UBadge');
+const UDropdownMenu = resolveComponent('UDropdownMenu');
+
+const {$api} = useNuxtApp();
+const router = useRouter();
+const table = useTemplateRef('table');
+const toast = useToast();
+
+const data = ref(await $api.exercises.getExercises());
+const globalFilter = ref('');
+const sorting = ref([
+    {
+        id: 'id',
+        desc: true
+    }
+]);
+const pagination = ref({
+    pageIndex: 0,
+    pageSize: 10
+});
+
+// Create the columns for the table
+const columns: TableColumn<Exercise>[] = [
+    {
+        accessorKey: 'id',
+        header: ({column}) => {
+            return h(UButton, {
+                color: 'neutral',
+                variant: 'ghost',
+                label: '#',
+                icon: getSortIcon(column.getIsSorted()),
+                class: '-mx-2.5',
+                onClick: () => column.toggleSorting(column.getIsSorted() === 'asc')
+            })
+        },
+        cell: ({row}) => `#${row.getValue('id')}`
+    },
+    {
+        accessorKey: 'name',
+        header: ({column}) => {
+            return h(UButton, {
+                color: 'neutral',
+                variant: 'ghost',
+                label: 'Nom de l\'exercice',
+                icon: getSortIcon(column.getIsSorted()),
+                class: '-mx-2.5',
+                onClick: () => column.toggleSorting(column.getIsSorted() === 'asc')
+            })
+        },
+        cell: ({row}) => {
+            return h('div', {class: 'font-medium'}, row.getValue('name'))
+        }
+    },
+    {
+        accessorKey: 'category',
+        header: 'Catégorie',
+        cell: ({row}) => {
+            return h(
+                UBadge,
+                {class: 'capitalize justify-center', variant: 'subtle', color:'neutral', icon: getExerciseCategoryIcon(row.getValue('category'))},
+                () => translateExerciseCategoryToFrench(row.getValue('category'))
+            )
+        }
+    },
+    {
+        id: 'actions',
+        cell: ({row}) => {
+            return h(
+                'div',
+                {class: 'text-right'},
+                h(
+                    UDropdownMenu,
+                    {
+                        content: {
+                            align: 'end'
+                        },
+                        items: getDropdownActions(row.original)
+                    },
+                    () =>
+                        h(UButton, {
+                            icon: 'i-lucide-ellipsis-vertical',
+                            color: 'neutral',
+                            variant: 'ghost',
+                            class: 'ml-auto'
+                        })
+                )
+            )
+        }
+    }
+];
+
+// Get the icon for the sorting direction
+function getSortIcon(isSorted:  false | SortDirection) {
+    if (!isSorted) return 'i-lucide-arrow-up-down'
+    return isSorted === 'asc' ? 'i-lucide-arrow-up-narrow-wide' : 'i-lucide-arrow-down-wide-narrow'
+}
+
+// Calculate the number of displayed elements
+function getDisplayedElementsCount(pageIndex: number, pageSize: number, totalElements: number): number {
+    const startIndex = pageIndex * pageSize;
+    const endIndex = Math.min(startIndex + pageSize, totalElements);
+    return endIndex - startIndex;
+}
+
+// Get the dropdown actions for the given exercise such as copy user id, view, edit and delete.
+function getDropdownActions(exercise: Exercise): DropdownMenuItem[][] {
+    return [
+        [
+            {
+                label: 'Copier l\'identifiant',
+                icon: 'material-symbols:content-copy-outline',
+                onSelect: () => {
+                    navigator.clipboard.writeText(exercise.id.toString())
+                    toast.add({
+                        title: 'Identifiant de l\'exercice copié dans le presse-papier.',
+                        color: 'success',
+                        icon: 'material-symbols:check-circle-outline'
+                    })
+                }
+            }
+        ],
+        [
+            {
+                label: 'Afficher',
+                icon: 'material-symbols:visibility',
+                onSelect: () => {
+                    router.push(`/exercise/${exercise.id}`)
+                }
+            },
+            {
+                label: 'Modifier',
+                icon: 'material-symbols:edit',
+                onSelect: () => {
+                    router.push(`/exercise/${exercise.id}?mode=edit`)
+                }
+            },
+            {
+                label: 'Supprimer',
+                icon: 'material-symbols:delete-forever-outline',
+                color: 'error',
+                onSelect: async () => {
+                    const response = await $api.exercises.deleteExercise(exercise.id)
+                    if (response.statusCode === 200) {
+                        data.value = await $api.exercises.getExercises()
+                        toast.add({title: 'Success', description: 'Votre exercice a bien été enregistré.', color: 'success'})
+                        await router.push('/exercises');
+                    } else if (response.statusCode === 401) {
+                        toast.add({title: 'Error', description: 'Vous n\'êtes pas autorisé à effectuer cette action.' , color: 'error'})
+                    } else if (response.statusCode === 403) {
+                        toast.add({title: 'Error', description: 'Vous devez vous identifier avant d\'effectuer cette action.', color: 'error'})
+                    } else {
+                        console.error('Error saving exercise', response);
+                        toast.add({title: 'Error', description: 'Une erreur est survenue.', color: 'error'})
+                    }
+                }
+            }
+        ]
+    ]
+}
+
+</script>
+
+<template>
+    <div id="exercises">
+        <div class="flex justify-between">
+            <UInput v-model="globalFilter" class="max-w-sm" placeholder="Filter..."/>
+            <NuxtLink to="/exercise/new?mode=edit">
+                <UButton class="ml-2" icon="material-symbols:add-circle-outline-rounded">Ajouter un exercice</UButton>
+            </NuxtLink>
+        </div>
+        <UTable
+            ref="table"
+            v-model:sorting="sorting"
+            v-model:global-filter="globalFilter"
+            v-model:pagination="pagination"
+            :data="data"
+            :columns="columns"
+            :loading="data.length < 1"
+            :pagination-options="{ getPaginationRowModel: getPaginationRowModel() }"
+            class="flex-1 m-1 max-h-[70vh]" sticky
+        />
+
+        <div class="flex flex-col items-center border-t border-(--ui-border) pt-4">
+            <UPagination
+                :default-page="(table?.tableApi?.getState().pagination.pageIndex || 0) + 1"
+                :items-per-page="table?.tableApi?.getState().pagination.pageSize"
+                :total="table?.tableApi?.getFilteredRowModel().rows.length"
+                @update:page="(p) => table?.tableApi?.setPageIndex(p - 1)"
+            />
+            <p>
+                {{getDisplayedElementsCount(pagination.pageIndex, pagination.pageSize, table?.tableApi?.getFilteredRowModel().rows.length ?? 0) }}
+                /
+                {{ table?.tableApi?.getFilteredRowModel().rows.length }}
+            </p>
+        </div>
+    </div>
+</template>
+
+<style>
+div#exercises {
+    padding: 1vh 1vw 6vh 1vw;
+    height: 80vh;
+}
+</style>
diff --git a/pages/index.vue b/pages/index.vue
index ba99005635f7ae48d137aece853cade471eace4d..c9a6593756a50d54e22aeccbdd473ee2a901b55d 100644
--- a/pages/index.vue
+++ b/pages/index.vue
@@ -16,7 +16,7 @@ definePageMeta({
             des exercices et parcours 3SPA
         </h2>
         <div class="flex flex-col items-center p-5 gap-2">
-            <NuxtLink to="/exercices">Exercices</NuxtLink>
+            <NuxtLink to="/exercises">Exercices</NuxtLink>
             <NuxtLink to="/parcours">Parcours</NuxtLink>
             <NuxtLink to="/events">Évènements</NuxtLink>
             <NuxtLink to="/login">Se connecter</NuxtLink>
diff --git a/pages/login.vue b/pages/login.vue
index f53b0920000d2c91c8a35fcac2f95a33847cef6e..848e1487357c17257946b48f1d3da61b7d727d17 100644
--- a/pages/login.vue
+++ b/pages/login.vue
@@ -1,28 +1,36 @@
 <script setup lang="ts">
-import * as v from 'valibot'
-import type {FormSubmitEvent} from '#ui/types'
+import * as v from 'valibot';
+import type {FormSubmitEvent} from '#ui/types';
+import type {LoginBody} from "~/repository/model/LoginBody";
+
+const {$api} = useNuxtApp();
 
 definePageMeta({
     layout: 'clear'
-})
+});
 
 const schema = v.object({
-    email: v.pipe(v.string(), v.email('Invalid email')),
-    password: v.pipe(v.string(), v.minLength(8, 'Must be at least 8 characters'))
-})
+    email: v.pipe(v.string(), v.nonEmpty('Invalid username')),
+    password: v.pipe(v.string(), v.minLength(3, 'Must be at least 8 characters'))
+});
 
 type Schema = v.InferOutput<typeof schema>
 
 const state = reactive({
-    email: 'a.a@a.aa',
-    password: 'azertyui'
-})
+    email: 'pam',
+    password: 'psw'
+});
 
-const toast = useToast()
-const router = useRouter()
+const router = useRouter();
+const toast = useToast();
 
 async function onSubmit(event: FormSubmitEvent<Schema>) {
-    if (event.data.email === 'a.a@a.aa') {
+    const body: LoginBody = {
+        username: event.data.email,
+        password: event.data.password
+    }
+    const data = await $api.users.postLogin(body);
+    if (data.message === 'Login successful') {
         toast.add({
             title: 'Success',
             description: 'Connexion réussie.',
@@ -31,10 +39,12 @@ async function onSubmit(event: FormSubmitEvent<Schema>) {
             icon: 'material-symbols:check'
         })
         await router.push('/')
-    } else {
-        toast.add({title: 'Error', description: 'The form has not been submitted.', color: 'error'})
+    } else if (data.error  === 'Invalid username or password') {
+        toast.add({title: 'Error', description: data.error, color: 'error'})
         state.password = ''
         return new Promise<void>(res => setTimeout(res, 1000))
+    } else {
+        toast.add({title: 'Error', description: 'Une erreur est survenue.', color: 'error'})
     }
 }
 </script>
@@ -43,13 +53,13 @@ async function onSubmit(event: FormSubmitEvent<Schema>) {
     <div class="page-login h-full">
         <h1>Login</h1><br>
         <UForm :schema="v.safeParser(schema)" :state="state" class="space-y-4 page-login" @submit="onSubmit">
-            <UFormField label="Email" name="email">
+            <UFormField label="Email" name="email" required>
                 <UInput
                     v-model="state.email" placeholder="e-mail" icon="material-symbols:account-circle"
                     variant="outline" color="primary"/>
             </UFormField>
 
-            <UFormField label="Password" name="password">
+            <UFormField label="Password" name="password" required>
                 <UInput
                     v-model="state.password" type="password" placeholder="password"
                     icon="material-symbols:key-vertical" color="primary"/>
diff --git a/pages/parcours.vue b/pages/parcours.vue
index 15e83c3df60a6d4d410948548d30c10b62304b3e..10aacf33a6a27f0524dc6c929fcf193264d7eb6b 100644
--- a/pages/parcours.vue
+++ b/pages/parcours.vue
@@ -1,5 +1,4 @@
 <script setup lang="ts">
-import Exercices from "~/pages/exercices.vue";
 
 const items = [
     {
@@ -27,7 +26,7 @@ const stepper = useTemplateRef('stepper')
     <div id="parcours" class="w-full flex flex-col justify-between">
         <UStepper ref="stepper" :items="items">
             <template #exercises>
-                <Exercices/>
+                <ParcoursExercices/>
             </template>
 
             <template #parcours>
@@ -35,7 +34,7 @@ const stepper = useTemplateRef('stepper')
             </template>
 
             <template #details>
-                Détails
+                <ParcoursDetails/>
             </template>
         </UStepper>
 
diff --git a/plugins/api.ts b/plugins/api.ts
new file mode 100644
index 0000000000000000000000000000000000000000..c24ff4cf2a743ca08c5077be3b9fd7655fbfabc6
--- /dev/null
+++ b/plugins/api.ts
@@ -0,0 +1,20 @@
+import ExerciseModule from "~/repository/module/exercise";
+import UserModule from '~/repository/module/user';
+
+interface IApiInstance {
+    users: UserModule;
+    exercises: ExerciseModule;
+}
+
+export default defineNuxtPlugin(() => {
+    const modules: IApiInstance = {
+        users: new UserModule(),
+        exercises: new ExerciseModule()
+    };
+
+    return {
+        provide: {
+            api: modules
+        }
+    };
+});
\ No newline at end of file
diff --git a/repository/model/IExercise.ts b/repository/model/IExercise.ts
new file mode 100644
index 0000000000000000000000000000000000000000..913059badcb368d10913b67981cd8f85cf809e1e
--- /dev/null
+++ b/repository/model/IExercise.ts
@@ -0,0 +1,15 @@
+export type IExercise = {
+    id: number;
+    name: string;
+    description: string;
+    safetyInstructions: string;
+    generalInstructions: string;
+    diagram: string;
+    video: string;
+    category: {
+        name: string
+    },
+    subCategory: {
+        name: string
+    }
+}
\ No newline at end of file
diff --git a/repository/model/IUser.ts b/repository/model/IUser.ts
new file mode 100644
index 0000000000000000000000000000000000000000..d50142a643911552f57a5d7acbeea06277f9ec73
--- /dev/null
+++ b/repository/model/IUser.ts
@@ -0,0 +1,4 @@
+export type IUser = {
+    id: number;
+    login: string;
+}
\ No newline at end of file
diff --git a/repository/model/LoginBody.ts b/repository/model/LoginBody.ts
new file mode 100644
index 0000000000000000000000000000000000000000..c605f012af62273fdd8a31f794b8cb2884587f4d
--- /dev/null
+++ b/repository/model/LoginBody.ts
@@ -0,0 +1,4 @@
+export type LoginBody = {
+    username: string;
+    password: string;
+}
\ No newline at end of file
diff --git a/repository/module/exercise.ts b/repository/module/exercise.ts
new file mode 100644
index 0000000000000000000000000000000000000000..79b489ca7f1f1dffedfba26769fba287cb90e949
--- /dev/null
+++ b/repository/module/exercise.ts
@@ -0,0 +1,105 @@
+import type {IExercise} from "~/repository/model/IExercise";
+import {type Exercise, ExerciseClass} from "~/types/Exercise";
+import type {ResponseBody} from "~/types/ResponseBody";
+import {ExerciseMapperFrom} from "~/utils/mapper/ExerciseMapper";
+
+class ExerciseModule {
+    private readonly RESSOURCE: string = '/api/exercise'
+    private exercises: IExercise[] = [];
+
+    /**
+     * Get an exercise by its id
+     * @param id the id of the exercise
+     * @return the exercise
+     */
+    async getExercise(id: number): Promise<Exercise> {
+        if (!this.exercises.length || !this.exercises.find((e) => e.id === id)) {
+            await this.fetchExercises();
+        }
+        const ex = this.exercises.find((e) => e.id === id)
+        if (!ex) {
+            return new ExerciseClass(this.getNewId());
+        }
+        return ExerciseMapperFrom(ex);
+    }
+
+    /**
+     * Get all exercises. If there is no exercises in local, it will get them from the server.
+     * @return a list of exercises
+     */
+    async getExercises(): Promise<Exercise[]> {
+        if (!this.exercises.length) {
+            await this.fetchExercises();
+        }
+        return this.exercises.map((e) => ExerciseMapperFrom(e));
+    }
+
+    /**
+     * Save an exercise. Could be a new one or an existing one.
+     * @param exercise the exercise to save
+     * @return either a response as a string or the saved exercise with their associated status code
+     */
+    async saveExercise(exercise: Exercise): Promise<ResponseBody<string | IExercise>> {
+        const method = this.exercises.find((e) => e.id === exercise.id) ? 'PATCH' : 'POST';
+
+        const data: ResponseBody<string | IExercise> = (await $fetch(this.RESSOURCE, {
+            method: method,
+            body: JSON.stringify(exercise)
+        }));
+
+        if (data.statusCode == 200) {
+            this.exercises = this.exercises.map((e) => e.id === exercise.id ? data.message as IExercise : e);
+        } else if (data.statusCode == 201) {
+            this.exercises.push(data.message as IExercise);
+        }
+        return data;
+    }
+
+    /**
+     * Delete an exercise by its id
+     * @param id the id of the exercise to delete
+     * @return the response from the server
+     */
+    async deleteExercise(id: number): Promise<ResponseBody<string>> {
+        const data: ResponseBody<string> = (await $fetch(`${this.RESSOURCE}?id=${id}`, {
+            method: 'DELETE'
+        }));
+
+        if (data.statusCode == 200) {
+            this.exercises = this.exercises.filter((e) => e.id !== id);
+        }
+        return data;
+    }
+
+    /**
+     * Fetch all sub-categories from the server
+     * @return a list of sub-categories
+     */
+    async fetchSubCategories(): Promise<string[]> {
+        return (await $fetch(`${this.RESSOURCE}/fetch-sub-categories`, {
+            method: 'GET'
+        })).message ?? [];
+    }
+
+    /**
+     * Get the next id for a new exercise. It is the maximum id + 1.<br>
+     * For example, if there are 3 ids : [1,3,7], the maximum id is 7 so the next id will be 8.
+     * @private
+     */
+    private getNewId(): number {
+        return Math.max(...this.exercises.map((e) => e.id)) + 1;
+    }
+
+    /**
+     * Fetch all exercises from the server
+     * @private
+     */
+    private async fetchExercises(): Promise<void> {
+        this.exercises = (await $fetch(this.RESSOURCE, {
+            method: 'GET'
+        })).message ?? [];
+    }
+
+}
+
+export default ExerciseModule;
\ No newline at end of file
diff --git a/repository/module/user.ts b/repository/module/user.ts
new file mode 100644
index 0000000000000000000000000000000000000000..ae83479158908a0406b366a9886714aaa197bc02
--- /dev/null
+++ b/repository/module/user.ts
@@ -0,0 +1,21 @@
+import type {LoginBody} from "~/repository/model/LoginBody";
+import type {ResponseBody} from "~/types/ResponseBody";
+import type {IUser} from "~/repository/model/IUser";
+
+class UserModule {
+    private readonly RESOURCE = '/api/auth';
+
+    /**
+     * Return the products as array
+     * @param body
+     * @returns
+     */
+    async postLogin(body: LoginBody): Promise<ResponseBody<string | IUser>> {
+        return await $fetch(`${this.RESOURCE}/login`, {
+            method: 'POST',
+            body: JSON.stringify(body)
+        })
+    }
+}
+
+export default UserModule;
\ No newline at end of file
diff --git a/server/api/auth/login.post.ts b/server/api/auth/login.post.ts
new file mode 100644
index 0000000000000000000000000000000000000000..86114ac3434727a37fb6bc5c623c54012e5b4359
--- /dev/null
+++ b/server/api/auth/login.post.ts
@@ -0,0 +1,20 @@
+import {appendResponseHeader, defineEventHandler, readBody} from "h3";
+import {useRuntimeConfig} from "#imports";
+
+export default defineEventHandler(async (event) => {
+    const url = event.path.replace('/api/', '');
+    const config = useRuntimeConfig(event);
+
+    const headers = new Headers();
+    headers.set('Content-Type', 'application/json');
+    headers.set('accept', '*/*');
+
+    const res = await fetch(`${config.public.apiBase}/${url}`, {
+        method: 'POST',
+        headers: headers,
+        body: JSON.stringify(await readBody(event))
+    })
+
+    appendResponseHeader(event, 'set-cookie', res.headers.getSetCookie())
+    return await res.json();
+})
\ No newline at end of file
diff --git a/server/api/exercise/.delete.ts b/server/api/exercise/.delete.ts
new file mode 100644
index 0000000000000000000000000000000000000000..aaa9f5ab2ce5dd8ee415a591bc77e23819ab405d
--- /dev/null
+++ b/server/api/exercise/.delete.ts
@@ -0,0 +1,27 @@
+import {defineEventHandler} from "h3";
+import {useRuntimeConfig} from "#imports";
+import type {ResponseBody} from "~/types/ResponseBody";
+
+// TODO : TU des différents retours de l'api
+export default defineEventHandler(async (event) => {
+    const url = event.path.replace('/api/', '');
+    const config = useRuntimeConfig(event);
+
+    const headers = new Headers();
+    headers.set('accept', '*/*');
+    headers.set('Cookie', event.headers.get('Cookie') as string);
+
+    const res = await fetch(`${config.public.apiBase}/${url}`, {
+        method: 'DELETE',
+        headers: headers
+    })
+
+    if (res.ok) {
+        return {
+            statusCode: res.status,
+            message: await res.text()
+        } as ResponseBody<string>
+    } else {
+        return await res.json() as ResponseBody<string>
+    }
+})
\ No newline at end of file
diff --git a/server/api/exercise/.get.ts b/server/api/exercise/.get.ts
new file mode 100644
index 0000000000000000000000000000000000000000..e1eda3dba0304f1b14ae993dd8a20748f5d8fd60
--- /dev/null
+++ b/server/api/exercise/.get.ts
@@ -0,0 +1,24 @@
+import {defineEventHandler} from "h3";
+import {useRuntimeConfig} from "#imports";
+import type {ResponseBody} from "~/types/ResponseBody";
+import type {IExercise} from "~/repository/model/IExercise";
+
+// TODO : TU des différents retours de l'api
+export default defineEventHandler(async (event) => {
+    const url = event.path.replace('/api/', '');
+    const config = useRuntimeConfig(event);
+
+    const headers = new Headers();
+    headers.set('Content-Type', 'application/json');
+    headers.set('accept', '*/*');
+
+    const res = await fetch(`${config.public.apiBase}/${url}`, {
+        method: 'GET',
+        headers: headers
+    })
+
+    return {
+        statusCode: res.status,
+        message: await res.json()
+    } as ResponseBody<IExercise[]>;
+})
\ No newline at end of file
diff --git a/server/api/exercise/.patch.ts b/server/api/exercise/.patch.ts
new file mode 100644
index 0000000000000000000000000000000000000000..7ae8d21f05b35fad63adb0a3c81142ada4a5e3c7
--- /dev/null
+++ b/server/api/exercise/.patch.ts
@@ -0,0 +1,45 @@
+import {defineEventHandler, readBody} from "h3";
+import {useRuntimeConfig} from "#imports";
+import type {ResponseBody} from "~/types/ResponseBody";
+import type {IExercise} from "~/repository/model/IExercise";
+
+// TODO : TU des différents retours de l'api
+export default defineEventHandler(async (event) => {
+    const url = event.path.replace('/api/', '');
+    const config = useRuntimeConfig(event);
+
+    const headers = new Headers();
+    headers.set('Content-Type', 'application/json');
+    headers.set('accept', '*/*');
+    headers.set('Cookie', event.headers.get('Cookie') as string);
+
+    const res = await fetch(`${config.public.apiBase}/${url}`, {
+        method: 'PATCH',
+        headers: headers,
+        body: ExerciseToPostBody(await readBody(event))
+    })
+
+    if(res.ok) {
+        return {
+            statusCode: res.status,
+            message: await res.json() as IExercise
+        } as ResponseBody<IExercise>
+    } else {
+        return await res.json() as ResponseBody<string>
+    }
+})
+
+// TODO : TU
+function ExerciseToPostBody(exercise: Exercise) {
+    return JSON.stringify({
+        id: exercise.id,
+        name: exercise.name,
+        description: exercise.description,
+        category: exercise.category,
+        subCategory: exercise.subCategory,
+        safetyInstructions: exercise.safetyInstructions,
+        generalInstructions: exercise.generalInstructions,
+        diagram: exercise.diagram,
+        video: exercise.video
+    });
+}
\ No newline at end of file
diff --git a/server/api/exercise/.post.ts b/server/api/exercise/.post.ts
new file mode 100644
index 0000000000000000000000000000000000000000..2dc97621310782244e69e52a93b269b120d07032
--- /dev/null
+++ b/server/api/exercise/.post.ts
@@ -0,0 +1,45 @@
+import {defineEventHandler, readBody} from "h3";
+import {useRuntimeConfig} from "#imports";
+import type {ResponseBody} from "~/types/ResponseBody";
+import type {Exercise} from "~/types/Exercise";
+import type {IExercise} from "~/repository/model/IExercise";
+
+// TODO : TU des différents retours de l'api
+export default defineEventHandler(async (event) => {
+    const url = event.path.replace('/api/', '');
+    const config = useRuntimeConfig(event);
+
+    const headers = new Headers();
+    headers.set('Content-Type', 'application/json');
+    headers.set('accept', '*/*');
+    headers.set('Cookie', event.headers.get('Cookie') as string);
+
+    const res = await fetch(`${config.public.apiBase}/${url}`, {
+        method: 'POST',
+        headers: headers,
+        body: ExerciseToPostBody(await readBody(event))
+    })
+
+    if(res.ok) {
+        return {
+            statusCode: res.status,
+            message: await res.json() as IExercise
+        } as ResponseBody<IExercise>
+    } else {
+        return await res.json() as ResponseBody<string>
+    }
+})
+
+// TODO : TU
+function ExerciseToPostBody(exercise: Exercise) {
+    return JSON.stringify({
+        name: exercise.name,
+        description: exercise.description,
+        category: exercise.category,
+        subCategory: exercise.subCategory,
+        safetyInstructions: exercise.safetyInstructions,
+        generalInstructions: exercise.generalInstructions,
+        diagram: exercise.diagram,
+        video: exercise.video
+    });
+}
\ No newline at end of file
diff --git a/server/api/exercise/fetch-sub-categories.get.ts b/server/api/exercise/fetch-sub-categories.get.ts
new file mode 100644
index 0000000000000000000000000000000000000000..892e83b3d055f53b6b579133a983d5e343a2133c
--- /dev/null
+++ b/server/api/exercise/fetch-sub-categories.get.ts
@@ -0,0 +1,29 @@
+import {defineEventHandler} from "h3";
+import {useRuntimeConfig} from "#imports";
+import type {ResponseBody} from "~/types/ResponseBody";
+
+export default defineEventHandler(async (event) => {
+    const url = event.path.replace('/api/', '');
+    const config = useRuntimeConfig(event);
+
+    const headers = new Headers();
+    headers.set('Content-Type', 'application/json');
+    headers.set('accept', '*/*');
+
+    const res = await fetch(`${config.public.apiBase}/${url}`, {
+        method: 'GET',
+        headers: headers
+    })
+
+    return {
+        statusCode: res.status,
+        message: mapSubCategoryToString(await res.json())
+    } as ResponseBody<string[]>;
+})
+
+type ISubCategory = {
+    name: string;
+}
+function mapSubCategoryToString(categories: ISubCategory[]): string[] {
+    return categories.map(c => c.name);
+}
\ No newline at end of file
diff --git a/types/Exercice.ts b/types/Exercice.ts
deleted file mode 100644
index c0945edd0b243b6ca37c9e2cbf4b022e0e38c862..0000000000000000000000000000000000000000
--- a/types/Exercice.ts
+++ /dev/null
@@ -1,16 +0,0 @@
-import type {ExerciseCategory} from "~/types/enum/ExerciseCategory";
-import type {ExerciseSubCategory} from "~/types/enum/ExerciseSubCategory";
-
-export default interface Exercise {
-    id: number;
-    name: string;
-    description: string;
-    category: ExerciseCategory;
-    subCategory?: ExerciseSubCategory;
-    safetyInstructions?: string[];
-    generalInstructions?: string[];
-    diagram?: string;
-    video?: string;
-    lat: number;
-    lng: number;
-}
\ No newline at end of file
diff --git a/types/Exercise.ts b/types/Exercise.ts
new file mode 100644
index 0000000000000000000000000000000000000000..d6d3be0245543e8da2115b05c9dcad344ffd7817
--- /dev/null
+++ b/types/Exercise.ts
@@ -0,0 +1,33 @@
+import {ExerciseCategory} from "~/types/enum/ExerciseCategory";
+
+export type Exercise = {
+    id: number;
+    name: string;
+    description: string;
+    category: ExerciseCategory;
+    subCategory: string;
+    safetyInstructions?: string;
+    generalInstructions?: string;
+    diagram?: string;
+    video?: string;
+    // FIXME: to remove
+    lat?: number;
+    lng?: number;
+    selected?: boolean;
+};
+
+export class ExerciseClass implements Exercise {
+    constructor(newId: number) {
+        this.id = newId;
+    }
+
+    id: number;
+    name: string = '';
+    description: string = '';
+    safetyInstructions: string = "";
+    generalInstructions: string = "";
+    diagram: string = '';
+    video: string = '';
+    category: ExerciseCategory = ExerciseCategory.CARDIO;
+    subCategory: string = '';
+}
\ No newline at end of file
diff --git a/types/ResponseBody.ts b/types/ResponseBody.ts
new file mode 100644
index 0000000000000000000000000000000000000000..69deddd65ab43623303bc02d7e8ddf63772d6593
--- /dev/null
+++ b/types/ResponseBody.ts
@@ -0,0 +1,4 @@
+export type ResponseBody<T> = {
+    statusCode: number;
+    message: T;
+}
\ No newline at end of file
diff --git a/types/enum/ExerciseCategory.ts b/types/enum/ExerciseCategory.ts
index c405b4d15beb392d0ddb45d4bcf8c5b1ddac6dcc..0e2f6360335f37f156f6d09fbdcd79a63b1587e6 100644
--- a/types/enum/ExerciseCategory.ts
+++ b/types/enum/ExerciseCategory.ts
@@ -1,5 +1,41 @@
 export enum ExerciseCategory {
-    RENFORCEMENT = 'Renforcement',
+    REINFORCEMENT = 'Reinforcement',
     CARDIO = 'Cardio',
-    STRETCHING = 'Stretching',
+    STRENGTHENING = 'Strengthening',
+}
+
+/**
+ * Get the icon name for a given exercise category
+ * @param category the category of the exercise
+ * @return the icon name
+ */
+export function getExerciseCategoryIcon(category: ExerciseCategory): string {
+    switch (category) {
+        case ExerciseCategory.REINFORCEMENT:
+            return 'material-symbols:exercise-outline';
+        case ExerciseCategory.CARDIO:
+            return 'material-symbols:cardiology-outline';
+        case ExerciseCategory.STRENGTHENING:
+            return 'material-symbols:transfer-within-a-station';
+        default:
+            return 'material-symbols:question-mark';
+    }
+}
+
+/**
+ * Translate an exercise category to French
+ * @param category the category of the exercise
+ * @return the translated category
+ */
+export function translateExerciseCategoryToFrench(category: ExerciseCategory): string {
+    switch (category) {
+        case ExerciseCategory.REINFORCEMENT:
+            return 'Renforcement musculaire';
+        case ExerciseCategory.CARDIO:
+            return 'Cardio';
+        case ExerciseCategory.STRENGTHENING:
+            return 'Étirement';
+        default:
+            return 'Inconnu';
+    }
 }
\ No newline at end of file
diff --git a/utils/mapper/ExerciseMapper.test.ts b/utils/mapper/ExerciseMapper.test.ts
new file mode 100644
index 0000000000000000000000000000000000000000..c6bb06bbc91bcb26974b40b03504acbda59f90fd
--- /dev/null
+++ b/utils/mapper/ExerciseMapper.test.ts
@@ -0,0 +1,105 @@
+import {expect, test} from 'vitest'
+import {ExerciseMapperFrom, ExerciseMapperTo} from './ExerciseMapper'
+import type {IExercise} from "~/repository/model/IExercise";
+
+const iExercise: IExercise = {
+    id: 1,
+    name: 'name',
+    description: 'description',
+    safetyInstructions: 'safetyInstructions',
+    generalInstructions: 'generalInstructions',
+    diagram: 'diagram',
+    video: 'video',
+    category: {
+        name: 'Cardio'
+    },
+    subCategory: {
+        name: 'subCategory'
+    }
+}
+
+const exercise: Exercise = {
+    id: 1,
+    name: 'name',
+    description: 'description',
+    safetyInstructions: 'safetyInstructions',
+    generalInstructions: 'generalInstructions',
+    diagram: 'diagram',
+    video: 'video',
+    category: ExerciseCategory.CARDIO,
+    subCategory: 'subCategory',
+    selected: false
+}
+
+// ExerciseMapperFrom
+test('ExerciseMapperFrom', () => {
+    const mappedExercise = ExerciseMapperFrom(iExercise)
+
+    expect(mappedExercise).toStrictEqual(exercise)
+})
+
+// ExerciseMapperTo
+test('ExerciseMapperTo', () => {
+    const mappedIExercise = ExerciseMapperTo(exercise)
+
+    expect(mappedIExercise).toStrictEqual(iExercise)
+})
+
+// ExerciseMapperFlipFlop
+test('ExerciseMapperFlipFlop', () => {
+    const mappedIExercise = ExerciseMapperTo(ExerciseMapperFrom(iExercise))
+    const mappedExercise = ExerciseMapperFrom(ExerciseMapperTo(exercise))
+
+    expect(mappedIExercise).toStrictEqual(iExercise)
+    expect(mappedExercise).toStrictEqual(exercise)
+})
+
+// ExerciseMapperFromNull, should throw an error
+test('ExerciseMapperNull', () => {
+    expect(() => ExerciseMapperFrom(null)).toThrow(TypeError)
+    expect(() => ExerciseMapperTo(null)).toThrow(TypeError)
+})
+
+// ExerciseMapperFromEmptyOptional
+test('ExerciseMapperEmptyOptional', () => {
+    const expected: Exercise = {
+        ...exercise,
+        safetyInstructions: '',
+        generalInstructions: '',
+        diagram: '',
+        video: ''
+    };
+
+    const toMap: IExercise = {
+        ...iExercise,
+        safetyInstructions: '',
+        generalInstructions: '',
+        diagram: '',
+        video: ''
+    }
+
+    const mappedExercise = ExerciseMapperFrom(toMap)
+    expect(mappedExercise).toStrictEqual(expected)
+})
+
+// ExerciseMapperToEmptyOptional
+test('ExerciseMapperToEmptyOptional', () => {
+    const expected: IExercise = {
+        ...iExercise,
+        safetyInstructions: '',
+        generalInstructions: '',
+        diagram: '',
+        video: ''
+    };
+
+    const toMap: Exercise = {
+        ...exercise,
+        safetyInstructions: '',
+        generalInstructions: '',
+        diagram: '',
+        video: ''
+    }
+
+    const mappedIExercise = ExerciseMapperTo(toMap)
+    expect(mappedIExercise).toStrictEqual(expected)
+})
\ No newline at end of file
diff --git a/utils/mapper/ExerciseMapper.ts b/utils/mapper/ExerciseMapper.ts
new file mode 100644
index 0000000000000000000000000000000000000000..3da9e52552e5e2cbc56e6b0a7abfdd11003b8c8d
--- /dev/null
+++ b/utils/mapper/ExerciseMapper.ts
@@ -0,0 +1,35 @@
+import type {Exercise} from "~/types/Exercise";
+import type {IExercise} from "~/repository/model/IExercise";
+
+export function ExerciseMapperFrom(iExercise: IExercise): Exercise {
+    return {
+        id: iExercise.id,
+        name: iExercise.name,
+        description: iExercise.description,
+        safetyInstructions: iExercise.safetyInstructions,
+        generalInstructions: iExercise.generalInstructions,
+        diagram: iExercise.diagram,
+        video: iExercise.video,
+        category: iExercise.category.name as ExerciseCategory,
+        subCategory: iExercise.subCategory?.name as ExerciseSubCategory,
+        selected: false
+    }
+}
+
+export function ExerciseMapperTo(exercise: Exercise): IExercise {
+    return {
+        id: exercise.id,
+        name: exercise.name,
+        description: exercise.description,
+        safetyInstructions: exercise.safetyInstructions ?? '',
+        generalInstructions: exercise.generalInstructions ?? '',
+        diagram: exercise.diagram ?? '',
+        video: exercise.video ?? '',
+        category: {
+            name: exercise.category
+        },
+        subCategory: {
+            name: exercise.subCategory ?? ''
+        }
+    }
+}
diff --git a/utils/mapper/UserMapper.ts b/utils/mapper/UserMapper.ts
index 03f9ae2a06912eb601800bac7b41b4a3d1cc9c7d..7d91f70b4406e7464c7e588e9834eb6b8916a741 100644
--- a/utils/mapper/UserMapper.ts
+++ b/utils/mapper/UserMapper.ts
@@ -1,7 +1,7 @@
 import {Role} from "~/types/enum/Role";
 import {Status} from "~/types/enum/Status";
 
-// FIXME : à déplacer dans un dossier DTO pour les retours de l'API
+// FIXME : The APi doesn't return these data, currently only mocked
 interface UserDTO {
     id: number;
     lastname: string;