diff --git a/package-lock.json b/package-lock.json
index fc3e187..c91e10e 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -11,6 +11,7 @@
         "@vueuse/core": "^10.5.0",
         "@vueuse/integrations": "^10.5.0",
         "axios": "^1.7.7",
+        "dexie": "^4.0.8",
         "phaser": "^3.86.0",
         "pinia": "^2.1.6",
         "socket.io-client": "^4.8.0",
@@ -3549,6 +3550,12 @@
         "node": ">=0.10"
       }
     },
+    "node_modules/dexie": {
+      "version": "4.0.8",
+      "resolved": "https://registry.npmjs.org/dexie/-/dexie-4.0.8.tgz",
+      "integrity": "sha512-1G6cJevS17KMDK847V3OHvK2zei899GwpDiqfEXHP1ASvme6eWJmAp9AU4s1son2TeGkWmC0g3y8ezOBPnalgQ==",
+      "license": "Apache-2.0"
+    },
     "node_modules/didyoumean": {
       "version": "1.2.2",
       "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz",
@@ -3884,9 +3891,9 @@
       }
     },
     "node_modules/eslint-plugin-vue": {
-      "version": "9.29.0",
-      "resolved": "https://registry.npmjs.org/eslint-plugin-vue/-/eslint-plugin-vue-9.29.0.tgz",
-      "integrity": "sha512-hamyjrBhNH6Li6R1h1VF9KHfshJlKgKEg3ARbGTn72CMNDSMhWbgC7NdkRDEh25AFW+4SDATzyNM+3gWuZii8g==",
+      "version": "9.29.1",
+      "resolved": "https://registry.npmjs.org/eslint-plugin-vue/-/eslint-plugin-vue-9.29.1.tgz",
+      "integrity": "sha512-MH/MbVae4HV/tM8gKAVWMPJbYgW04CK7SuzYRrlNERpxbO0P3+Zdsa2oAcFBW6xNu7W6lIkGOsFAMCRTYmrlWQ==",
       "dev": true,
       "license": "MIT",
       "dependencies": {
diff --git a/package.json b/package.json
index 63b3c51..b54fe60 100644
--- a/package.json
+++ b/package.json
@@ -18,6 +18,7 @@
     "@vueuse/core": "^10.5.0",
     "@vueuse/integrations": "^10.5.0",
     "axios": "^1.7.7",
+    "dexie": "^4.0.8",
     "phaser": "^3.86.0",
     "pinia": "^2.1.6",
     "socket.io-client": "^4.8.0",
diff --git a/public/assets/music/click-btn.mp3 b/public/assets/music/click-btn.mp3
new file mode 100644
index 0000000..6ef1729
Binary files /dev/null and b/public/assets/music/click-btn.mp3 differ
diff --git a/public/assets/music/login.mp3 b/public/assets/music/login.mp3
new file mode 100644
index 0000000..1943d41
Binary files /dev/null and b/public/assets/music/login.mp3 differ
diff --git a/src/App.vue b/src/App.vue
index d188312..ba67548 100644
--- a/src/App.vue
+++ b/src/App.vue
@@ -15,6 +15,7 @@ import GmPanel from '@/components/gameMaster/GmPanel.vue'
 import Login from '@/screens/Login.vue'
 import Characters from '@/screens/Characters.vue'
 import Game from '@/screens/Game.vue'
+import Loading from '@/screens/Loading.vue'
 import ZoneEditor from '@/screens/ZoneEditor.vue'
 import { computed } from 'vue'
 
@@ -22,16 +23,11 @@ const gameStore = useGameStore()
 const zoneEditorStore = useZoneEditorStore()
 
 const currentScreen = computed(() => {
+  if (!gameStore.isAssetsLoaded) return Loading
   if (!gameStore.connection) return Login
   if (!gameStore.token) return Login
   if (!gameStore.character) return Characters
   if (zoneEditorStore.active) return ZoneEditor
   return Game
 })
-
-// Disable right click
-addEventListener('contextmenu', (event) => event.preventDefault())
-
-// Disable pinch zoom
-addEventListener('gesturestart', (event) => event.preventDefault())
 </script>
diff --git a/src/components/gameMaster/zoneEditor/ZoneEditor.vue b/src/components/gameMaster/zoneEditor/ZoneEditor.vue
index dc9ee28..6dad5d4 100644
--- a/src/components/gameMaster/zoneEditor/ZoneEditor.vue
+++ b/src/components/gameMaster/zoneEditor/ZoneEditor.vue
@@ -14,11 +14,9 @@
 </template>
 
 <script setup lang="ts">
-import { onBeforeMount, onUnmounted, ref } from 'vue'
-import { useScene } from 'phavuer'
+import { onUnmounted, ref } from 'vue'
 import { useGameStore } from '@/stores/gameStore'
 import { useZoneEditorStore } from '@/stores/zoneEditorStore'
-import { loadAssets } from '@/composables/zoneComposable'
 import { type Zone } from '@/types'
 
 // Components
@@ -32,7 +30,6 @@ import Tiles from '@/components/gameMaster/zoneEditor/Tiles.vue'
 import Objects from '@/components/gameMaster/zoneEditor/Objects.vue'
 import EventTiles from '@/components/gameMaster/zoneEditor/EventTiles.vue'
 
-const scene = useScene()
 const gameStore = useGameStore()
 const zoneEditorStore = useZoneEditorStore()
 
@@ -62,11 +59,6 @@ function save() {
   })
 }
 
-onBeforeMount(async () => {
-  await gameStore.fetchAllZoneAssets()
-  await loadAssets(scene)
-})
-
 onUnmounted(() => {
   zoneEditorStore.reset()
 })
diff --git a/src/components/zone/Zone.vue b/src/components/zone/Zone.vue
index f94c321..05ac949 100644
--- a/src/components/zone/Zone.vue
+++ b/src/components/zone/Zone.vue
@@ -5,19 +5,16 @@
 </template>
 
 <script setup lang="ts">
-import { useScene } from 'phavuer'
 import { useGameStore } from '@/stores/gameStore'
 import { useZoneStore } from '@/stores/zoneStore'
-import { onBeforeMount, onBeforeUnmount, ref } from 'vue'
+import { onBeforeUnmount, ref } from 'vue'
 import type { Character as CharacterT, Zone as ZoneT, ExtendedCharacter as ExtendedCharacterT } from '@/types'
 import Tiles from '@/components/zone/Tiles.vue'
 import Objects from '@/components/zone/Objects.vue'
 import Characters from '@/components/zone/Characters.vue'
-import { loadAssets } from '@/composables/zoneComposable'
 
 const gameStore = useGameStore()
 const zoneStore = useZoneStore()
-const scene = useScene()
 
 const tileMap = ref(null as Phaser.Tilemaps.Tilemap | null)
 
@@ -28,13 +25,6 @@ type zoneLoadData = {
 
 // Event listeners
 gameStore.connection!.on('zone:character:teleport', async (data: zoneLoadData) => {
-  /**
-   * This is the cause of the bug
-   */
-  // Fetch assets for new zone
-  await gameStore.fetchZoneAssets(data.zone.id)
-  await loadAssets(scene)
-
   /**
    * @TODO : Update character via global event server-side, remove this and listen for it somewhere not here
    */
@@ -61,16 +51,9 @@ gameStore.connection!.on('character:move', (data: ExtendedCharacterT) => {
   zoneStore.updateCharacter(data)
 })
 
-onBeforeMount(() => {
-  gameStore.connection!.emit('zone:character:join', async (response: zoneLoadData) => {
-    // Fetch assets for new zone
-    await gameStore.fetchZoneAssets(response.zone.id)
-    await loadAssets(scene)
-
-    // Set zone and characters
-    zoneStore.setZone(response.zone)
-    zoneStore.setCharacters(response.characters)
-  })
+gameStore.connection!.emit('zone:character:join', async (response: zoneLoadData) => {
+  zoneStore.setZone(response.zone)
+  zoneStore.setCharacters(response.characters)
 })
 
 onBeforeUnmount(() => {
diff --git a/src/composables/zoneComposable.ts b/src/composables/zoneComposable.ts
index 0879d64..af3ce03 100644
--- a/src/composables/zoneComposable.ts
+++ b/src/composables/zoneComposable.ts
@@ -4,6 +4,7 @@ import TilemapLayer = Phaser.Tilemaps.TilemapLayer
 import Tileset = Phaser.Tilemaps.Tileset
 import Tile = Phaser.Tilemaps.Tile
 import { useGameStore } from '@/stores/gameStore'
+import { useAssetManager } from '@/utilities/assets'
 
 export function getTile(layer: TilemapLayer | Tilemap, x: number, y: number): Tile | undefined {
   const tile = layer.getTileAtWorldXY(x, y)
@@ -89,17 +90,35 @@ export const clearAssets = (scene: Phaser.Scene) => {
 
 export const loadAssets = (scene: Phaser.Scene): Promise<void> => {
   return new Promise((resolve) => {
-    const gameStore = useGameStore()
+    let assetManager = useAssetManager
     let addedLoad = false
 
-    gameStore.assets.forEach((asset) => {
-      if (scene.load.textureManager.exists(asset.key)) return
-      addedLoad = true
-      if (asset.group === 'sprite_animations') {
-        scene.load.spritesheet(asset.key, config.server_endpoint + asset.url, { frameWidth: asset.frameWidth ?? 0, frameHeight: asset.frameHeight ?? 0 })
-      } else {
-        scene.load.image(asset.key, config.server_endpoint + asset.url)
-      }
+    assetManager.getAssetsByGroup('tiles').then((assets) => {
+      assets.forEach((asset) => {
+        if (scene.load.textureManager.exists(asset.key)) return
+        addedLoad = true
+        scene.textures.addBase64(asset.key, asset.data)
+      })
+    })
+
+    assetManager.getAssetsByGroup('objects').then((assets) => {
+      assets.forEach((asset) => {
+        if (scene.load.textureManager.exists(asset.key)) return
+        addedLoad = true
+        scene.textures.addBase64(asset.key, asset.data)
+      })
+    })
+
+    assetManager.getAssetsByGroup('sprites').then((assets) => {
+      assets.forEach((asset) => {
+        if (scene.load.textureManager.exists(asset.key)) return
+        let img = new Image();
+        img.onload = () => {
+          scene.textures.addSpriteSheet(asset.key, img, { frameWidth: asset.frameWidth ?? 0, frameHeight: asset.frameHeight ?? 0 })
+        };
+        img.src = asset.data;
+        addedLoad = true
+      })
     })
 
     if (addedLoad) {
diff --git a/src/screens/Characters.vue b/src/screens/Characters.vue
index f048449..ab99162 100644
--- a/src/screens/Characters.vue
+++ b/src/screens/Characters.vue
@@ -114,13 +114,7 @@ gameStore.connection?.on('character:list', (data: any) => {
 })
 
 onMounted(async () => {
-  /**
-   * Fetch sprite assets from the server
-   * This is done here because phaser needs it in createScene in Game.vue.
-   */
-  await gameStore.fetchSpriteAssets()
-
-  // wait 0.5 sec
+  // wait 0.75 sec
   setTimeout(() => {
     gameStore.connection?.emit('character:list')
     isLoading.value = false
diff --git a/src/screens/Game.vue b/src/screens/Game.vue
index 6432a9e..714bbd7 100644
--- a/src/screens/Game.vue
+++ b/src/screens/Game.vue
@@ -19,11 +19,12 @@
   </div>
 </template>
 
-<script setup lang="ts">
+<script setup lang="ts" async>
 import config from '@/config'
 import 'phaser'
 import { ref, onBeforeUnmount } from 'vue'
 import { Game, Scene } from 'phavuer'
+import { useAssetManager } from '@/utilities/assets'
 import { useGameStore } from '@/stores/gameStore'
 import Menu from '@/components/gui/Menu.vue'
 import ExpBar from '@/components/gui/ExpBar.vue'
@@ -36,6 +37,7 @@ import Effects from '@/components/Effects.vue'
 import { loadAssets } from '@/composables/zoneComposable'
 import Minimap from '@/components/gui/Minimap.vue'
 
+const assetManager = useAssetManager
 const gameStore = useGameStore()
 const isLoaded = ref(false)
 
@@ -120,16 +122,16 @@ const createScene = async (scene: Phaser.Scene) => {
    * Create sprite animations
    * This is done here because phaser forces us to
    */
-  gameStore.assets.forEach((asset) => {
-    if (asset.group !== 'sprite_animations') return
-
-    scene.anims.create({
-      key: asset.key,
-      frameRate: 7,
-      frames: scene.anims.generateFrameNumbers(asset.key, { start: 0, end: asset.frameCount! - 1 }),
-      repeat: -1
-    })
-  })
+  // assetManager.getAssetsByGroup('sprite_animations').then((assets) => {
+  //   assets.forEach((asset) => {
+  //     scene.anims.create({
+  //       key: asset.key,
+  //       frameRate: 7,
+  //       frames: scene.anims.generateFrameNumbers(asset.key, { start: 0, end: asset.frameCount! - 1 }),
+  //       repeat: -1
+  //     })
+  // })
+  //   })
 }
 
 onBeforeUnmount(() => {
diff --git a/src/screens/Loading.vue b/src/screens/Loading.vue
new file mode 100644
index 0000000..c3abe7f
--- /dev/null
+++ b/src/screens/Loading.vue
@@ -0,0 +1,78 @@
+<template>
+  <div class="flex flex-col justify-center items-center h-dvh relative">
+    <div v-if="!isLoaded" class="w-20 h-20 rounded-full border-4 border-solid border-gray-300 border-t-transparent animate-spin"></div>
+    <button v-else @click="continueBtnClick" class="w-20 h-20 rounded-full bg-gray-500 flex items-center justify-center hover:bg-gray-600 transition-colors">
+      <svg xmlns="http://www.w3.org/2000/svg" class="h-10 w-10 text-white" fill="none" viewBox="0 0 24 24" stroke="currentColor">
+        <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7" />
+      </svg>
+    </button>
+    <div v-if="!isLoaded" class="text-center mt-6">
+      <h1 class="text-2xl font-bold">Loading...</h1>
+      <p class="text-gray-400">Please wait while we load the assets.</p>
+    </div>
+  </div>
+</template>
+
+<script setup lang="ts" async>
+import { onMounted, ref } from 'vue'
+import config from '@/config'
+import type { AssetT as ServerAsset } from '@/types'
+import { useAssetManager } from '@/utilities/assets'
+import { useGameStore } from '@/stores/gameStore'
+
+const gameStore = useGameStore();
+const assetManager = useAssetManager;
+const isLoaded = ref(false)
+
+async function getAssets() {
+  return fetch(config.server_endpoint + '/assets/')
+    .then((response) => {
+      if (!response.ok) throw new Error('Failed to fetch assets')
+      console.log(response)
+      return response.json()
+    })
+    .catch((error) => {
+      console.error('Error fetching assets:', error)
+      return false
+    })
+}
+
+async function loadAssetsIntoAssetManager(assets: ServerAsset[]): Promise<void> {
+  for (const asset of assets) {
+    // Check if the asset is already loaded
+    const existingAsset = await assetManager.getAsset(asset.key);
+
+    // Check if the asset needs to be updated
+    if (!existingAsset || new Date(asset.updatedAt) > new Date(existingAsset.updatedAt)) {
+
+      // Check if the asset is already loaded, if so, delete it
+      if (existingAsset) {
+        await assetManager.deleteAsset(asset.key);
+      }
+
+      // Add the asset to the asset manager
+      await assetManager.addAsset(
+        asset.key,
+        asset.url,
+        asset.group,
+        asset.updatedAt,
+        asset.frameCount,
+        asset.frameWidth,
+        asset.frameHeight
+      );
+    }
+  }
+}
+
+function continueBtnClick() {
+  gameStore.isAssetsLoaded = true
+}
+
+onMounted(async () => {
+  const assets = await getAssets()
+  if (assets) {
+    await loadAssetsIntoAssetManager(assets)
+    isLoaded.value = true
+  }
+})
+</script>
\ No newline at end of file
diff --git a/src/screens/ZoneEditor.vue b/src/screens/ZoneEditor.vue
index 239d036..b4b463e 100644
--- a/src/screens/ZoneEditor.vue
+++ b/src/screens/ZoneEditor.vue
@@ -16,7 +16,6 @@ import { Game, Scene } from 'phavuer'
 import { useGameStore } from '@/stores/gameStore'
 import { useZoneEditorStore } from '@/stores/zoneEditorStore'
 import ZoneEditor from '@/components/gameMaster/zoneEditor/ZoneEditor.vue'
-import { loadAssets } from '@/composables/zoneComposable'
 
 const gameStore = useGameStore()
 const zoneEditorStore = useZoneEditorStore()
@@ -93,11 +92,6 @@ const preloadScene = async (scene: Phaser.Scene) => {
   scene.load.image('TELEPORT', '/assets/zone/tp_tile.png')
   scene.load.image('blank_tile', '/assets/zone/blank_tile.png')
   scene.load.image('waypoint', '/assets/waypoint.png')
-
-  /**
-   * Load the assets into the Phaser scene
-   */
-  await loadAssets(scene)
 }
 
 const createScene = async (scene: Phaser.Scene) => {
diff --git a/src/services/authentication.ts b/src/services/authentication.ts
index 1fed2d3..4ccb044 100644
--- a/src/services/authentication.ts
+++ b/src/services/authentication.ts
@@ -1,9 +1,8 @@
 import axios from 'axios'
 import config from '@/config'
-import { useGameStore } from '@/stores/gameStore'
 import { useCookies } from '@vueuse/integrations/useCookies'
 
-export async function register(username: string, password: string, gameStore = useGameStore()) {
+export async function register(username: string, password: string) {
   try {
     const response = await axios.post(`${config.server_endpoint}/register`, { username, password })
     useCookies().set('token', response.data.token as string)
@@ -13,13 +12,13 @@ export async function register(username: string, password: string, gameStore = u
   }
 }
 
-export async function login(username: string, password: string, gameStore = useGameStore()) {
+export async function login(username: string, password: string) {
   try {
     const response = await axios.post(`${config.server_endpoint}/login`, { username, password })
     useCookies().set('token', response.data.token as string, {
       // for whole domain
       // @TODO : #190
-      domain: window.location.hostname.split('.').slice(-2).join('.')
+      // domain: window.location.hostname.split('.').slice(-2).join('.')
     })
     return { success: true, token: response.data.token }
   } catch (error: any) {
diff --git a/src/stores/gameStore.ts b/src/stores/gameStore.ts
index df3ca75..c6737fd 100644
--- a/src/stores/gameStore.ts
+++ b/src/stores/gameStore.ts
@@ -7,9 +7,8 @@ import { useCookies } from '@vueuse/integrations/useCookies'
 export const useGameStore = defineStore('game', {
   state: () => {
     return {
-      loginMessage: null as string | null,
       notifications: [] as Notification[],
-      assets: [] as Asset[],
+      isAssetsLoaded: false,
       token: '' as string | null,
       connection: null as Socket | null,
       user: null as User | null,
@@ -47,54 +46,6 @@ export const useGameStore = defineStore('game', {
     removeNotification(id: string) {
       this.notifications = this.notifications.filter((notification: Notification) => notification.id !== id)
     },
-    setAssets(assets: Asset[]) {
-      this.assets = assets
-    },
-    addAsset(asset: Asset) {
-      this.assets.push(asset)
-    },
-    addAssets(assets: Asset[]) {
-      this.assets = this.assets.concat(assets)
-    },
-    async fetchSpriteAssets() {
-      return fetch(config.server_endpoint + '/assets/sprites')
-        .then((response) => response.json())
-        .then((assets) => {
-          // Only add the sprites that are not already in the store
-          this.addAssets(assets.filter((asset: Asset) => !this.getAssetByKey(asset.key)))
-          return true
-        })
-        .catch((error) => {
-          console.error('Error fetching assets:', error)
-          return false
-        })
-    },
-    async fetchZoneAssets(zoneId: number) {
-      return fetch(config.server_endpoint + '/assets/zone/' + zoneId)
-        .then((response) => response.json())
-        .then((assets) => {
-          // Only add the zones that are not already in the store
-          this.addAssets(assets.filter((asset: Asset) => !this.getAssetByKey(asset.key)))
-          return true
-        })
-        .catch((error) => {
-          console.error('Error fetching assets:', error)
-          return false
-        })
-    },
-    async fetchAllZoneAssets() {
-      return fetch(config.server_endpoint + '/assets/zone')
-        .then((response) => response.json())
-        .then((assets) => {
-          // Only add the zones that are not already in the store
-          this.addAssets(assets.filter((asset: Asset) => !this.getAssetByKey(asset.key)))
-          return true
-        })
-        .catch((error) => {
-          console.error('Error fetching assets:', error)
-          return false
-        })
-    },
     setToken(token: string) {
       this.token = token
     },
@@ -152,15 +103,15 @@ export const useGameStore = defineStore('game', {
       })
     },
     disconnectSocket() {
-      if (this.connection) this.connection.disconnect()
+      this.connection?.disconnect()
 
       useCookies().remove('token', {
         // for whole domain
         // @TODO : #190
-        domain: window.location.hostname.split('.').slice(-2).join('.')
+        // domain: window.location.hostname.split('.').slice(-2).join('.')
       })
 
-      this.assets = []
+      // this.isAssetsLoaded = false
       this.connection = null
       this.token = null
       this.user = null
diff --git a/src/types.ts b/src/types.ts
index a784736..0ffa1d8 100644
--- a/src/types.ts
+++ b/src/types.ts
@@ -4,10 +4,11 @@ export type Notification = {
   message?: string
 }
 
-export type Asset = {
+export type AssetT = {
   key: string
   url: string
   group: 'tiles' | 'objects' | 'sprites' | 'sprite_animations' | 'sound' | 'music' | 'ui' | 'font' | 'other'
+  updatedAt: Date
   frameCount?: number
   frameWidth?: number
   frameHeight?: number
diff --git a/src/utilities/assets.ts b/src/utilities/assets.ts
new file mode 100644
index 0000000..e33db9d
--- /dev/null
+++ b/src/utilities/assets.ts
@@ -0,0 +1,101 @@
+import config from '@/config';
+import Dexie from 'dexie';
+
+interface Asset {
+  key: string;
+  data: Blob;
+  group: string;
+  updatedAt: Date;
+  frameCount?: number;
+  frameWidth?: number;
+  frameHeight?: number;
+}
+
+interface AssetWithUrl extends Omit<Asset, 'data'> {
+  data: string;
+}
+
+class AssetManager extends Dexie {
+  assets!: Dexie.Table<Asset, string>;
+
+  constructor() {
+    super('AssetManager');
+    this.version(1).stores({
+      assets: 'key, group'
+    });
+  }
+
+  async getAssetCount(): Promise<number> {
+    try {
+      const count = await this.assets.count();
+      return count;
+    } catch (error) {
+      console.error('Failed to get asset count:', error);
+      return 0;
+    }
+  }
+
+  async addAsset(
+    key: string,
+    url: string,
+    group: string,
+    updatedAt: Date,
+    frameCount?: number,
+    frameWidth?: number,
+    frameHeight?: number
+  ): Promise<void> {
+    try {
+      const response = await fetch(config.server_endpoint + url);
+      const blob = await response.blob();
+      await this.assets.put({ key, data: blob, group, updatedAt, frameCount, frameWidth, frameHeight });
+    } catch (error) {
+      console.error(`Failed to add asset ${key}:`, error);
+    }
+  }
+
+  async getAsset(key: string): Promise<AssetWithUrl | null> {
+    try {
+      const asset = await this.assets.get(key);
+      if (asset) {
+        return {
+          ...asset,
+          data: URL.createObjectURL(asset.data)
+        };
+      }
+    } catch (error) {
+      console.error(`Failed to retrieve asset ${key}:`, error);
+    }
+    return null;
+  }
+
+  async getAssetsByGroup(group: string): Promise<AssetWithUrl[]> {
+    try {
+      const assets = await this.assets.where('group').equals(group).toArray();
+      return assets.map(asset => ({
+        ...asset,
+        data: URL.createObjectURL(asset.data)
+      }));
+    } catch (error) {
+      console.error(`Failed to retrieve assets for group ${group}:`, error);
+      return [];
+    }
+  }
+
+  async clearAssets(): Promise<void> {
+    try {
+      await this.assets.clear();
+    } catch (error) {
+      console.error('Failed to clear assets:', error);
+    }
+  }
+
+  async deleteAsset(key: string): Promise<void> {
+    try {
+      await this.assets.delete(key);
+    } catch (error) {
+      console.error(`Failed to delete asset ${key}:`, error);
+    }
+  }
+}
+
+export const useAssetManager = new AssetManager();
\ No newline at end of file