diff --git a/package-lock.json b/package-lock.json
index a66bb42..e0209b6 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1763,9 +1763,9 @@
       }
     },
     "node_modules/@types/node": {
-      "version": "20.14.11",
-      "resolved": "https://registry.npmjs.org/@types/node/-/node-20.14.11.tgz",
-      "integrity": "sha512-kprQpL8MMeszbz6ojB5/tU8PLN4kesnN8Gjzw349rDlNgsSzg90lAVj3llK99Dh7JON+t9AuscPPFW6mPbTnSA==",
+      "version": "20.14.12",
+      "resolved": "https://registry.npmjs.org/@types/node/-/node-20.14.12.tgz",
+      "integrity": "sha512-r7wNXakLeSsGT0H1AU863vS2wa5wBOK4bWMjZz2wj+8nBx+m5PeIn0k8AloSLpRuiwdRQZwarZqHE4FNArPuJQ==",
       "dev": true,
       "license": "MIT",
       "dependencies": {
@@ -1986,9 +1986,9 @@
       "license": "ISC"
     },
     "node_modules/@vitejs/plugin-vue": {
-      "version": "5.0.5",
-      "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-5.0.5.tgz",
-      "integrity": "sha512-LOjm7XeIimLBZyzinBQ6OSm3UBCNVCpLkxGC0oWmm2YPzVZoxMsdvNVimLTBzpAnR9hl/yn1SHGuRfe6/Td9rQ==",
+      "version": "5.1.0",
+      "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-5.1.0.tgz",
+      "integrity": "sha512-QMRxARyrdiwi1mj3AW4fLByoHTavreXq0itdEW696EihXglf1MB3D4C2gBvE0jMPH29ZjC3iK8aIaUMLf4EOGA==",
       "dev": true,
       "license": "MIT",
       "engines": {
@@ -2260,14 +2260,14 @@
       "license": "MIT"
     },
     "node_modules/@vue/devtools-core": {
-      "version": "7.3.6",
-      "resolved": "https://registry.npmjs.org/@vue/devtools-core/-/devtools-core-7.3.6.tgz",
-      "integrity": "sha512-XqFYVkyS3eySHF4bgLt+KF6yL6nYzVY/JTJHnK6KIJXIE4GIAxmn5Gxfsb4cUG9sl0FGiMqRCnM37Q+P08wr8A==",
+      "version": "7.3.7",
+      "resolved": "https://registry.npmjs.org/@vue/devtools-core/-/devtools-core-7.3.7.tgz",
+      "integrity": "sha512-IapWbHUqvO6n+p5JFTCE5JyNjpsZ5IS1GYIRX0P7/SqYPgFCOdH0dG+u8PbBHYdnp+VPxHLO+GGZ/WBZFCZnsA==",
       "dev": true,
       "license": "MIT",
       "dependencies": {
-        "@vue/devtools-kit": "^7.3.6",
-        "@vue/devtools-shared": "^7.3.6",
+        "@vue/devtools-kit": "^7.3.7",
+        "@vue/devtools-shared": "^7.3.7",
         "mitt": "^3.0.1",
         "nanoid": "^3.3.4",
         "pathe": "^1.1.2",
@@ -2278,13 +2278,13 @@
       }
     },
     "node_modules/@vue/devtools-kit": {
-      "version": "7.3.6",
-      "resolved": "https://registry.npmjs.org/@vue/devtools-kit/-/devtools-kit-7.3.6.tgz",
-      "integrity": "sha512-5Ym9V3fkJenEoptqKoo+cgY5RTVwrSssFdzRsuyIgaeiskCT+rRJeQdwoo81tyrQ1mfS7Er1rYZlSzr3Y3L/ew==",
+      "version": "7.3.7",
+      "resolved": "https://registry.npmjs.org/@vue/devtools-kit/-/devtools-kit-7.3.7.tgz",
+      "integrity": "sha512-ktHhhjI4CoUrwdSUF5b/MFfjrtAtK8r4vhOkFyRN5Yp9kdXTwsRBYcwarHuP+wFPKf4/KM7DVBj2ELO8SBwdsw==",
       "dev": true,
       "license": "MIT",
       "dependencies": {
-        "@vue/devtools-shared": "^7.3.6",
+        "@vue/devtools-shared": "^7.3.7",
         "birpc": "^0.2.17",
         "hookable": "^5.5.3",
         "mitt": "^3.0.1",
@@ -2294,9 +2294,9 @@
       }
     },
     "node_modules/@vue/devtools-shared": {
-      "version": "7.3.6",
-      "resolved": "https://registry.npmjs.org/@vue/devtools-shared/-/devtools-shared-7.3.6.tgz",
-      "integrity": "sha512-R/FOmdJV+hhuwcNoxp6e87RRkEeDMVhWH+nOsnHUrwjjsyeXJ2W1475Ozmw+cbZhejWQzftkHVKO28Fuo1yqCw==",
+      "version": "7.3.7",
+      "resolved": "https://registry.npmjs.org/@vue/devtools-shared/-/devtools-shared-7.3.7.tgz",
+      "integrity": "sha512-M9EU1/bWi5GNS/+IZrAhwGOVZmUTN4MH22Hvh35nUZZg9AZP2R2OhfCb+MG4EtAsrUEYlu3R43/SIj3G7EZYtQ==",
       "dev": true,
       "license": "MIT",
       "dependencies": {
@@ -2452,9 +2452,9 @@
       }
     },
     "node_modules/@vueuse/core/node_modules/vue-demi": {
-      "version": "0.14.8",
-      "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.8.tgz",
-      "integrity": "sha512-Uuqnk9YE9SsWeReYqK2alDI5YzciATE0r2SkA6iMAtuXvNTMNACJLJEXNXaEy94ECuBe4Sk6RzRU80kjdbIo1Q==",
+      "version": "0.14.9",
+      "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.9.tgz",
+      "integrity": "sha512-dC1TJMODGM8lxhP6wLToncaDPPNB3biVxxRDuNCYpuXwi70ou7NsGd97KVTJ2omepGId429JZt8oaZKeXbqxwg==",
       "hasInstallScript": true,
       "license": "MIT",
       "bin": {
@@ -2544,9 +2544,9 @@
       }
     },
     "node_modules/@vueuse/integrations/node_modules/vue-demi": {
-      "version": "0.14.8",
-      "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.8.tgz",
-      "integrity": "sha512-Uuqnk9YE9SsWeReYqK2alDI5YzciATE0r2SkA6iMAtuXvNTMNACJLJEXNXaEy94ECuBe4Sk6RzRU80kjdbIo1Q==",
+      "version": "0.14.9",
+      "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.9.tgz",
+      "integrity": "sha512-dC1TJMODGM8lxhP6wLToncaDPPNB3biVxxRDuNCYpuXwi70ou7NsGd97KVTJ2omepGId429JZt8oaZKeXbqxwg==",
       "hasInstallScript": true,
       "license": "MIT",
       "bin": {
@@ -2591,9 +2591,9 @@
       }
     },
     "node_modules/@vueuse/shared/node_modules/vue-demi": {
-      "version": "0.14.8",
-      "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.8.tgz",
-      "integrity": "sha512-Uuqnk9YE9SsWeReYqK2alDI5YzciATE0r2SkA6iMAtuXvNTMNACJLJEXNXaEy94ECuBe4Sk6RzRU80kjdbIo1Q==",
+      "version": "0.14.9",
+      "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.9.tgz",
+      "integrity": "sha512-dC1TJMODGM8lxhP6wLToncaDPPNB3biVxxRDuNCYpuXwi70ou7NsGd97KVTJ2omepGId429JZt8oaZKeXbqxwg==",
       "hasInstallScript": true,
       "license": "MIT",
       "bin": {
@@ -3440,9 +3440,9 @@
       }
     },
     "node_modules/electron-to-chromium": {
-      "version": "1.4.832",
-      "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.832.tgz",
-      "integrity": "sha512-cTen3SB0H2SGU7x467NRe1eVcQgcuS6jckKfWJHia2eo0cHIGOqHoAxevIYZD4eRHcWjkvFzo93bi3vJ9W+1lA==",
+      "version": "1.5.0",
+      "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.0.tgz",
+      "integrity": "sha512-Vb3xHHYnLseK8vlMJQKJYXJ++t4u1/qJ3vykuVrVjvdiOEhYyT1AuP4x03G8EnPmYvYOhe9T+dADTmthjRQMkA==",
       "dev": true,
       "license": "ISC"
     },
@@ -5669,9 +5669,9 @@
       }
     },
     "node_modules/pinia/node_modules/vue-demi": {
-      "version": "0.14.8",
-      "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.8.tgz",
-      "integrity": "sha512-Uuqnk9YE9SsWeReYqK2alDI5YzciATE0r2SkA6iMAtuXvNTMNACJLJEXNXaEy94ECuBe4Sk6RzRU80kjdbIo1Q==",
+      "version": "0.14.9",
+      "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.9.tgz",
+      "integrity": "sha512-dC1TJMODGM8lxhP6wLToncaDPPNB3biVxxRDuNCYpuXwi70ou7NsGd97KVTJ2omepGId429JZt8oaZKeXbqxwg==",
       "hasInstallScript": true,
       "license": "MIT",
       "bin": {
@@ -6839,9 +6839,9 @@
       }
     },
     "node_modules/typescript": {
-      "version": "5.5.3",
-      "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.5.3.tgz",
-      "integrity": "sha512-/hreyEujaB0w76zKo6717l3L0o/qEUtRgdvUBvlkhoWeOVMjMuHNHk0BRBzikzuGDqNmPQbg5ifMEqsHLiIUcQ==",
+      "version": "5.5.4",
+      "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.5.4.tgz",
+      "integrity": "sha512-Mtq29sKDAEYP7aljRgtPOpTvOfbwRWlS6dPRzwjdE+C0R4brX/GUyhHSecbHMFLNBLcJIPt9nl9yG5TZ1weH+Q==",
       "devOptional": true,
       "license": "Apache-2.0",
       "bin": {
@@ -7063,19 +7063,19 @@
       }
     },
     "node_modules/vite-plugin-vue-devtools": {
-      "version": "7.3.6",
-      "resolved": "https://registry.npmjs.org/vite-plugin-vue-devtools/-/vite-plugin-vue-devtools-7.3.6.tgz",
-      "integrity": "sha512-j4Cssv6DVBtMZfyVBEm/4MZy7BiL6RedEn+f9jT3zFyGZKG1vNuEpTO86XvPPbHbYdITFyrkWb7VQuWyhfSgqA==",
+      "version": "7.3.7",
+      "resolved": "https://registry.npmjs.org/vite-plugin-vue-devtools/-/vite-plugin-vue-devtools-7.3.7.tgz",
+      "integrity": "sha512-pPv6YJYrCIlWP+wwRk9gzDp2rK5M5jQ5oz//Nci3C3FDvORL1btKQqGvgthx3hs6xbx5acToJtkMGgDnZg8smw==",
       "dev": true,
       "license": "MIT",
       "dependencies": {
-        "@vue/devtools-core": "^7.3.6",
-        "@vue/devtools-kit": "^7.3.6",
-        "@vue/devtools-shared": "^7.3.6",
+        "@vue/devtools-core": "^7.3.7",
+        "@vue/devtools-kit": "^7.3.7",
+        "@vue/devtools-shared": "^7.3.7",
         "execa": "^8.0.1",
         "sirv": "^2.0.4",
         "vite-plugin-inspect": "^0.8.4",
-        "vite-plugin-vue-inspector": "^5.1.2"
+        "vite-plugin-vue-inspector": "^5.1.3"
       },
       "engines": {
         "node": ">=v14.21.3"
@@ -7085,9 +7085,9 @@
       }
     },
     "node_modules/vite-plugin-vue-inspector": {
-      "version": "5.1.2",
-      "resolved": "https://registry.npmjs.org/vite-plugin-vue-inspector/-/vite-plugin-vue-inspector-5.1.2.tgz",
-      "integrity": "sha512-M+yH2LlQtVNzJAljQM+61CqDXBvHim8dU5ImGaQuwlo13tMDHue5D7IC20YwDJuWDODiYc/cZBUYspVlyPf2vQ==",
+      "version": "5.1.3",
+      "resolved": "https://registry.npmjs.org/vite-plugin-vue-inspector/-/vite-plugin-vue-inspector-5.1.3.tgz",
+      "integrity": "sha512-pMrseXIDP1Gb38mOevY+BvtNGNqiqmqa2pKB99lnLsADQww9w9xMbAfT4GB6RUoaOkSPrtlXqpq2Fq+Dj2AgFg==",
       "dev": true,
       "license": "MIT",
       "dependencies": {
diff --git a/src/components/utilities/Accordion.vue b/src/components/utilities/Accordion.vue
new file mode 100644
index 0000000..9ac55cb
--- /dev/null
+++ b/src/components/utilities/Accordion.vue
@@ -0,0 +1,26 @@
+<template>
+  <div class="border border-gray-300 rounded mb-4">
+    <button @click="toggle" class="w-full p-3 bg-gray-100 rounded hover:bg-gray-200 text-left cursor-pointer transition-colors duration-200 ease-in-out">
+      {{ props.title }}
+    </button>
+    <transition enter-active-class="transition-all duration-300 ease-in-out" leave-active-class="transition-all duration-300 ease-in-out" enter-from-class="opacity-0 max-h-0" enter-to-class="opacity-100 max-h-96" leave-from-class="opacity-100 max-h-96" leave-to-class="opacity-0 max-h-0">
+      <div v-if="isOpen" class="p-3 overflow-hidden">
+        <slot></slot>
+      </div>
+    </transition>
+  </div>
+</template>
+
+<script setup lang="ts">
+import { ref } from 'vue'
+
+const isOpen = ref(false)
+
+const toggle = () => {
+  isOpen.value = !isOpen.value
+}
+
+const props = defineProps({
+  title: String
+})
+</script>
diff --git a/src/components/utilities/assetManager/partials/object/ObjectList.vue b/src/components/utilities/assetManager/partials/object/ObjectList.vue
index 03baba3..09ab2ef 100644
--- a/src/components/utilities/assetManager/partials/object/ObjectList.vue
+++ b/src/components/utilities/assetManager/partials/object/ObjectList.vue
@@ -1,10 +1,12 @@
 <template>
-  <div class="relative p-2.5 cursor-pointer flex gap-y-2.5 gap-x-5 flex-wrap">
-    <label for="upload-asset" class="bg-cyan/50 border border-solid border-white/25 rounded drop-shadow-20 py-1.5 px-4 inline-flex hover:bg-cyan hover:cursor-pointer">
+  <div class="relative p-2.5 flex items-center gap-x-2.5">
+    <input v-model="searchQuery" class="input-cyan flex-grow" placeholder="Search..." @input="handleSearch" />
+    <label for="upload-asset" class="bg-cyan/50 border border-solid border-white/25 rounded drop-shadow-20 p-1.5 inline-flex items-center justify-center hover:bg-cyan hover:cursor-pointer w-6 h-6">
       <input class="hidden" id="upload-asset" ref="objectUploadField" type="file" accept="image/png" multiple @change="handleFileUpload" />
-      Upload object(s)
+      <svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
+        <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4v16m8-8H4" />
+      </svg>
     </label>
-    <input v-model="searchQuery" class="input-cyan w-full" placeholder="Search..." @input="handleSearch" />
     <div class="absolute left-0 bottom-0 w-full h-px bg-cyan-200"></div>
   </div>
   <div v-bind="containerProps" class="overflow-y-auto relative" @scroll="onScroll">
diff --git a/src/components/utilities/assetManager/partials/sprite/SpriteDetails.vue b/src/components/utilities/assetManager/partials/sprite/SpriteDetails.vue
index 2eab3ac..dbeff23 100644
--- a/src/components/utilities/assetManager/partials/sprite/SpriteDetails.vue
+++ b/src/components/utilities/assetManager/partials/sprite/SpriteDetails.vue
@@ -1,56 +1,64 @@
 <template>
   <div class="h-full overflow-auto">
-    <div class="relative p-2.5 flex flex-col items-center justify-between h-72">
-      <div class="filler"></div>
-      <img class="max-h-56" :src="`${config.server_endpoint}/assets/sprites/${selectedSprite?.id}.png`" :alt="'Sprite ' + selectedSprite?.id" />
-      <button class="btn-bordeaux px-4 py-1.5 min-w-24" type="button" @click.prevent="removeSprite">Remove</button>
-      <div class="absolute left-0 bottom-0 w-full h-px bg-cyan-200"></div>
+    <div class="relative p-2.5 flex flex-col items-center justify-between">
+      <div class="w-full flex flex-col sm:flex-row items-center gap-9 mb-5">
+        <div class="w-full sm:flex-grow">
+          <input v-model="spriteName" class="input-cyan w-full" type="text" name="name" placeholder="New sprite" />
+        </div>
+        <div class="flex gap-2 w-full sm:w-auto">
+          <button class="btn-cyan px-4 py-1.5 flex-1 sm:flex-none sm:min-w-24" type="button" @click.prevent="saveSprite">Save</button>
+          <button class="btn-bordeaux px-4 py-1.5 flex-1 sm:flex-none sm:min-w-24" type="button" @click.prevent="removeSprite">Remove</button>
+        </div>
+      </div>
+      <div class="w-full h-px bg-cyan-200"></div>
     </div>
-    <div class="m-2.5 p-2.5 block">
-      <form class="flex gap-2.5 flex-wrap" @submit.prevent="saveSprite">
-        <div class="w-full flex flex-col mb-5">
-          <label class="mb-1.5 font-titles" for="name">Name</label>
-          <input v-model="spriteName" class="input-cyan" type="text" name="name" placeholder="Wall #1" />
-        </div>
-        <div class="w-[calc(50%_-_5px)] flex flex-col mb-5">
-          <label class="mb-1.5 font-titles" for="origin-x">Origin X</label>
-          <input v-model="spriteOriginX" class="input-cyan" type="number" step="any" name="origin-x" placeholder="Origin X" />
-        </div>
-        <div class="w-[calc(50%_-_5px)] flex flex-col mb-5">
-          <label class="mb-1.5 font-titles" for="origin-y">Origin Y</label>
-          <input v-model="spriteOriginY" class="input-cyan" type="number" step="any" name="origin-y" placeholder="Origin Y" />
-        </div>
-        <div class="w-full flex flex-col mb-5">
-          <label class="mb-1.5 font-titles" for="frame-speed">Frame speed</label>
-          <input v-model="spriteFrameSpeed" class="input-cyan" type="number" step="any" name="frame-speed" placeholder="Frame speed" />
-        </div>
-        <div class="w-[calc(50%_-_5px)] flex flex-col mb-5">
-          <label class="mb-1.5 font-titles" for="frame-width">Frame width</label>
-          <input v-model="spriteFrameWidth" class="input-cyan" type="number" step="any" name="frame-width" placeholder="Frame width" />
-        </div>
-        <div class="w-[calc(50%_-_5px)] flex flex-col mb-5">
-          <label class="mb-1.5 font-titles" for="frame-height">Frame height</label>
-          <input v-model="spriteFrameHeight" class="input-cyan" type="number" step="any" name="frame-height" placeholder="Frame height" />
-        </div>
-        <div class="w-full flex flex-col mb-5">
-          <label class="mb-1.5 font-titles" for="is-looping">Is looping</label>
-          <select v-model="spriteIsLooping" class="input-cyan" name="is-looping">
-            <option :value="false">No</option>
-            <option :value="true">Yes</option>
-          </select>
-        </div>
-        <button class="btn-cyan px-4 py-1.5 min-w-24" type="submit">Save</button>
-      </form>
+    <div class="m-2.5 px-2.5 block">
+      <button class="btn-cyan px-4 py-1.5 flex-1 sm:flex-none sm:min-w-24 mb-5" type="button" @click.prevent="addNewImage">New IMG</button>
+      <Accordion v-for="image in spriteImages" :key="image.id" :title="image.name">
+        <form class="flex gap-2.5 flex-wrap" @submit.prevent="saveSprite">
+          <div class="w-full flex flex-col mb-5">
+            <label class="mb-1.5 font-titles" for="name">Name</label>
+            <input v-model="image.name" class="input-cyan" type="text" name="name" placeholder="Wall #1" />
+          </div>
+          <div class="w-[calc(50%_-_5px)] flex flex-col mb-5">
+            <label class="mb-1.5 font-titles" for="origin-x">Origin X</label>
+            <input v-model.number="image.origin_x" class="input-cyan" type="number" step="any" name="origin-x" placeholder="Origin X" />
+          </div>
+          <div class="w-[calc(50%_-_5px)] flex flex-col mb-5">
+            <label class="mb-1.5 font-titles" for="origin-y">Origin Y</label>
+            <input v-model.number="image.origin_y" class="input-cyan" type="number" step="any" name="origin-y" placeholder="Origin Y" />
+          </div>
+          <div class="w-full flex flex-col mb-5">
+            <label class="mb-1.5 font-titles" for="frame-speed">Frame speed</label>
+            <input v-model.number="image.frameSpeed" class="input-cyan" type="number" step="any" name="frame-speed" placeholder="Frame speed" />
+          </div>
+          <div class="w-[calc(50%_-_5px)] flex flex-col mb-5">
+            <label class="mb-1.5 font-titles" for="frame-width">Frame width</label>
+            <input v-model.number="image.frameWidth" class="input-cyan" type="number" step="any" name="frame-width" placeholder="Frame width" />
+          </div>
+          <div class="w-[calc(50%_-_5px)] flex flex-col mb-5">
+            <label class="mb-1.5 font-titles" for="frame-height">Frame height</label>
+            <input v-model.number="image.frameHeight" class="input-cyan" type="number" step="any" name="frame-height" placeholder="Frame height" />
+          </div>
+          <div class="w-full flex flex-col mb-5">
+            <label class="mb-1.5 font-titles" for="is-looping">Is looping</label>
+            <select v-model="image.isLooping" class="input-cyan" name="is-looping">
+              <option :value="false">No</option>
+              <option :value="true">Yes</option>
+            </select>
+          </div>
+        </form>
+      </Accordion>
     </div>
   </div>
 </template>
 
 <script setup lang="ts">
-import type { Sprite } from '@/types'
+import type { Sprite, SpriteImage } from '@/types'
 import { computed, onBeforeUnmount, onMounted, ref, watch } from 'vue'
 import { useAssetManagerStore } from '@/stores/assetManager'
 import { useGameStore } from '@/stores/game'
-import config from '@/config'
+import Accordion from '@/components/utilities/Accordion.vue'
 
 const gameStore = useGameStore()
 const assetManagerStore = useAssetManagerStore()
@@ -58,12 +66,7 @@ const assetManagerStore = useAssetManagerStore()
 const selectedSprite = computed(() => assetManagerStore.selectedSprite)
 
 const spriteName = ref('')
-const spriteOriginX = ref(0)
-const spriteOriginY = ref(0)
-const spriteFrameSpeed = ref(0)
-const spriteFrameWidth = ref(0)
-const spriteFrameHeight = ref(0)
-const spriteIsLooping = ref(false)
+const spriteImages = ref<SpriteImage[]>([])
 
 if (!selectedSprite.value) {
   console.error('No sprite selected')
@@ -71,12 +74,7 @@ if (!selectedSprite.value) {
 
 if (selectedSprite.value) {
   spriteName.value = selectedSprite.value.name
-  spriteOriginX.value = selectedSprite.value.origin_x
-  spriteOriginY.value = selectedSprite.value.origin_y
-  spriteFrameSpeed.value = selectedSprite.value.frameSpeed
-  spriteFrameWidth.value = selectedSprite.value.frameWidth
-  spriteFrameHeight.value = selectedSprite.value.frameHeight
-  spriteIsLooping.value = selectedSprite.value.isLooping
+  spriteImages.value = selectedSprite.value.spriteImages
 }
 
 function removeSprite() {
@@ -105,37 +103,51 @@ function saveSprite() {
     return
   }
 
-  gameStore.connection?.emit(
-    'gm:sprite:update',
-    {
-      id: selectedSprite.value.id,
-      name: spriteName.value,
-      origin_x: spriteOriginX.value,
-      origin_y: spriteOriginY.value,
-      frameSpeed: spriteFrameSpeed.value,
-      frameWidth: spriteFrameWidth.value,
-      frameHeight: spriteFrameHeight.value,
-      isLooping: spriteIsLooping.value
-    },
-    (response: boolean) => {
-      if (!response) {
-        console.error('Failed to save sprite')
-        return
-      }
-      refreshSpriteList(false)
+  const updatedSprite = {
+    id: selectedSprite.value.id,
+    name: spriteName.value,
+    spriteImages: selectedSprite.value.spriteImages
+  }
+
+  gameStore.connection?.emit('gm:sprite:update', updatedSprite, (response: boolean) => {
+    if (!response) {
+      console.error('Failed to save sprite')
+      return
     }
-  )
+    refreshSpriteList(false)
+  })
+}
+
+function addNewImage() {
+  if (!selectedSprite.value) return
+
+  const newImage: SpriteImage = {
+    id: Date.now().toString(), // Temporary ID, should be replaced by server-generated ID
+    name: 'New image',
+    origin_x: 0,
+    origin_y: 0,
+    frameSpeed: 0,
+    frameWidth: 0,
+    frameHeight: 0,
+    isAnimated: false,
+    spriteId: selectedSprite.value.id,
+    sprite: selectedSprite.value,
+    action: '',
+    isLooping: false
+  }
+
+  // spriteimages value can be undefined
+  if (!spriteImages.value) {
+    spriteImages.value = []
+  }
+
+  spriteImages.value.push(newImage)
 }
 
 watch(selectedSprite, (sprite: Sprite | null) => {
   if (!sprite) return
   spriteName.value = sprite.name
-  spriteOriginX.value = sprite.origin_x
-  spriteOriginY.value = sprite.origin_y
-  spriteFrameSpeed.value = sprite.frameSpeed
-  spriteFrameWidth.value = sprite.frameWidth
-  spriteFrameHeight.value = sprite.frameHeight
-  spriteIsLooping.value = sprite.isLooping
+  spriteImages.value = sprite.spriteImages
 })
 
 onMounted(() => {
diff --git a/src/components/utilities/assetManager/partials/sprite/SpriteList.vue b/src/components/utilities/assetManager/partials/sprite/SpriteList.vue
index 42840d8..3200b78 100644
--- a/src/components/utilities/assetManager/partials/sprite/SpriteList.vue
+++ b/src/components/utilities/assetManager/partials/sprite/SpriteList.vue
@@ -1,19 +1,17 @@
 <template>
-  <div class="relative p-2.5 cursor-pointer flex gap-y-2.5 gap-x-5 flex-wrap">
-    <label for="upload-asset" class="bg-cyan/50 border border-solid border-white/25 rounded drop-shadow-20 py-1.5 px-4 inline-flex hover:bg-cyan hover:cursor-pointer">
-      <input class="hidden" id="upload-asset" ref="spriteUploadField" type="file" accept="image/png" multiple @change="handleFileUpload" />
-      Upload sprite(s)
-    </label>
-    <input v-model="searchQuery" class="input-cyan w-full" placeholder="Search..." @input="handleSearch" />
+  <div class="relative p-2.5 flex items-center gap-x-2.5">
+    <input v-model="searchQuery" class="input-cyan flex-grow" placeholder="Search..." @input="handleSearch" />
+    <button @click.prevent="newButtonClickHandler" class="bg-cyan/50 border border-solid border-white/25 rounded drop-shadow-20 p-1.5 inline-flex items-center justify-center hover:bg-cyan hover:cursor-pointer w-9 h-9">
+      <svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
+        <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4v16m8-8H4" />
+      </svg>
+    </button>
     <div class="absolute left-0 bottom-0 w-full h-px bg-cyan-200"></div>
   </div>
   <div v-bind="containerProps" class="overflow-y-auto relative" @scroll="onScroll">
     <div v-bind="wrapperProps" ref="elementToScroll">
       <a v-for="{ data: sprite } in list" :key="sprite.id" class="relative p-2.5 cursor-pointer block" :class="{ 'bg-cyan/80': assetManagerStore.selectedSprite?.id === sprite.id }" @click="assetManagerStore.setSelectedSprite(sprite as Sprite)">
         <div class="flex items-center gap-2.5">
-          <div class="h-7 w-16 max-w-16 flex justify-center">
-            <img class="h-7" :src="`${config.server_endpoint}/assets/sprites/${sprite.id}.png`" alt="Sprite" />
-          </div>
           <span>{{ sprite.name }}</span>
         </div>
         <div class="absolute left-0 bottom-0 w-full h-px bg-cyan-200"></div>
@@ -35,7 +33,6 @@ import { useVirtualList } from '@vueuse/core'
 import type { Sprite } from '@/types'
 
 const gameStore = useGameStore()
-const spriteUploadField = ref(null)
 const assetManagerStore = useAssetManagerStore()
 const assetStore = useAssetStore()
 
@@ -44,17 +41,13 @@ const searchQuery = ref('')
 const hasScrolled = ref(false)
 const elementToScroll = ref()
 
-const handleFileUpload = (e: Event) => {
-  const files = (e.target as HTMLInputElement).files
-  if (!files) return
-  gameStore.connection?.emit('gm:sprite:upload', files, (response: boolean) => {
+function newButtonClickHandler() {
+  gameStore.connection?.emit('gm:sprite:create', {}, (response: boolean) => {
     if (!response) {
-      if (config.development) console.error('Failed to upload sprite')
+      if (config.development) console.error('Failed to create new sprite')
       return
     }
 
-    assetStore.fetchAssets()
-
     gameStore.connection?.emit('gm:sprite:list', {}, (response: Sprite[]) => {
       assetManagerStore.setSpriteList(response)
     })
diff --git a/src/components/utilities/assetManager/partials/tile/TileList.vue b/src/components/utilities/assetManager/partials/tile/TileList.vue
index 4f9c1cb..0fcb043 100644
--- a/src/components/utilities/assetManager/partials/tile/TileList.vue
+++ b/src/components/utilities/assetManager/partials/tile/TileList.vue
@@ -1,10 +1,12 @@
 <template>
-  <div class="relative p-2.5 cursor-pointer flex gap-y-2.5 gap-x-5 flex-wrap">
-    <label for="upload-asset" class="bg-cyan/50 border border-solid border-white/25 rounded drop-shadow-20 py-1.5 px-4 inline-flex hover:bg-cyan hover:cursor-pointer">
+  <div class="relative p-2.5 flex items-center gap-x-2.5">
+    <input v-model="searchQuery" class="input-cyan flex-grow" placeholder="Search..." @input="handleSearch" />
+    <label for="upload-asset" class="bg-cyan/50 border border-solid border-white/25 rounded drop-shadow-20 p-1.5 inline-flex items-center justify-center hover:bg-cyan hover:cursor-pointer w-6 h-6">
       <input class="hidden" id="upload-asset" ref="tileUploadField" type="file" accept="image/png" multiple @change="handleFileUpload" />
-      Upload tile(s)
+      <svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
+        <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4v16m8-8H4" />
+      </svg>
     </label>
-    <input v-model="searchQuery" class="input-cyan w-full" placeholder="Search..." @input="handleSearch" />
     <div class="absolute left-0 bottom-0 w-full h-px bg-cyan-200"></div>
   </div>
   <div v-bind="containerProps" class="overflow-y-auto relative" @scroll="onScroll">
diff --git a/src/screens/Login.vue b/src/screens/Login.vue
index c02aa68..16a122c 100644
--- a/src/screens/Login.vue
+++ b/src/screens/Login.vue
@@ -67,6 +67,7 @@ async function loginFunc() {
 
   gameStore.setToken(response.token)
   gameStore.initConnection()
+  return true // Indicate success
 }
 
 async function registerFunc() {
@@ -84,6 +85,9 @@ async function registerFunc() {
     return
   }
 
-  await loginFunc()
+  const loginSuccess = await loginFunc()
+  if (!loginSuccess) {
+    notifications.addNotification({ message: 'Login after registration failed. Please try logging in manually.' })
+  }
 }
 </script>
diff --git a/src/types.ts b/src/types.ts
index 069e77a..88a63ce 100644
--- a/src/types.ts
+++ b/src/types.ts
@@ -10,23 +10,10 @@ export type Asset = {
   type: 'base64' | 'link'
 }
 
-export type Sprite = {
-  id: string
-  name: string
-  origin_x: number
-  origin_y: number
-  frameSpeed: number
-  frameWidth: number
-  frameHeight: number
-  isLooping: boolean
-  createdAt: Date
-  updatedAt: Date
-}
-
 export type Tile = {
   id: string
   name: string
-  tags?: any
+  tags: any | null
   createdAt: Date
   updatedAt: Date
 }
@@ -34,7 +21,7 @@ export type Tile = {
 export type Object = {
   id: string
   name: string
-  tags?: any
+  tags: any | null
   origin_x: number
   origin_y: number
   isAnimated: boolean
@@ -49,54 +36,19 @@ export type Object = {
 export type Item = {
   id: string
   name: string
-  description: string
+  description: string | null
   stackable: boolean
   createdAt: Date
   updatedAt: Date
   characters: CharacterItem[]
 }
 
-export type User = {
-  id: number
-  username: string
-  password: string
-  characters: Character[]
-}
-
-export type Character = {
-  id: number
-  userId: number
-  user: User
-  name: string
-  hitpoints: number
-  mana: number
-  level: number
-  experience: number
-  role: string
-  position_x: number
-  position_y: number
-  rotation: number
-  zoneId: number
-  zone: Zone
-  chats: Chat[]
-  items: CharacterItem[]
-}
-
-export type CharacterItem = {
-  id: number
-  characterId: number
-  character: Character
-  itemId: string
-  item: Item
-  quantity: number
-}
-
 export type Zone = {
   id: number
   name: string
   width: number
   height: number
-  tiles: string[][]
+  tiles: any | null
   pvp: boolean
   zoneEventTiles: ZoneEventTile[]
   zoneObjects: ZoneObject[]
@@ -133,6 +85,92 @@ export type ZoneEventTile = {
   position_y: number
 }
 
+export type User = {
+  id: number
+  username: string
+  password: string
+  characters: Character[]
+}
+
+export enum CharacterGender {
+  MALE = 'MALE',
+  FEMALE = 'FEMALE'
+}
+
+export enum CharacterRace {
+  HUMAN = 'HUMAN',
+  ELF = 'ELF',
+  DWARF = 'DWARF',
+  ORC = 'ORC',
+  GOBLIN = 'GOBLIN'
+}
+
+export type CharacterType = {
+  id: number
+  name: string
+  gender: CharacterGender
+  race: CharacterRace
+  characters: Character[]
+  spriteId: string
+  sprite: Sprite
+  createdAt: Date
+  updatedAt: Date
+}
+
+export type Character = {
+  id: number
+  userId: number
+  user: User
+  name: string
+  hitpoints: number
+  mana: number
+  level: number
+  experience: number
+  role: string
+  position_x: number
+  position_y: number
+  rotation: number
+  zoneId: number
+  zone: Zone
+  characterTypeId: number | null
+  characterType: CharacterType | null
+  chats: Chat[]
+  items: CharacterItem[]
+}
+
+export type CharacterItem = {
+  id: number
+  characterId: number
+  character: Character
+  itemId: string
+  item: Item
+  quantity: number
+}
+
+export type Sprite = {
+  id: string
+  name: string
+  createdAt: Date
+  updatedAt: Date
+  spriteImages: SpriteImage[]
+  characterTypes: CharacterType[]
+}
+
+export type SpriteImage = {
+  id: string
+  spriteId: string
+  sprite: Sprite
+  name: string
+  action: string
+  origin_x: number
+  origin_y: number
+  isAnimated: boolean
+  isLooping: boolean
+  frameWidth: number
+  frameHeight: number
+  frameSpeed: number
+}
+
 export type Chat = {
   id: number
   characterId: number