diff --git a/ATTRIBUTIONS b/ATTRIBUTIONS
new file mode 100644
index 0000000000000000000000000000000000000000..5f75402b1ceb5f6c7502827137a9e8cbe43aa076
--- /dev/null
+++ b/ATTRIBUTIONS
@@ -0,0 +1,5 @@
+The following music was used for this media project:
+Music: Fantasy Chamber Adventure  by Rafael Krux
+Free download: https://filmmusic.io/song/5424-fantasy-chamber-adventure
+License (CC BY 4.0): https://filmmusic.io/standard-license
+Artist website: https://www.orchestralis.net/
diff --git a/CREDITS.toml b/CREDITS.toml
index d34acc3a9037490f5f3fd9cacb35c8b5392b234c..1234027e9aedaa93f34b998cbdb9134d7ba5286a 100644
--- a/CREDITS.toml
+++ b/CREDITS.toml
@@ -8,4 +8,6 @@ usage = "Header and title font"
 name = "EquipmentPro.ttf"
 author = "Eeve Somepx"
 website = "https://somepx.itch.io/humble-fonts-free"
-usage = "Interface body font"
\ No newline at end of file
+usage = "Interface body font"
+
+[[music]]
\ No newline at end of file
diff --git a/Cargo.lock b/Cargo.lock
index 2d2ed425816ac02d5e84902e15fb3bce4f775449..a200f8180697e8ed5e478009b4a468c0f3c88783 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -367,17 +367,6 @@ dependencies = [
  "syn",
 ]
 
-[[package]]
-name = "bevy_ecs_tilemap"
-version = "0.9.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7ce7f9fa49f364602ac0901d5f181445529b23fb0623e39e791548f8a6e8c2b5"
-dependencies = [
- "bevy",
- "log",
- "regex",
-]
-
 [[package]]
 name = "bevy_ecs_tilemap"
 version = "0.9.0"
@@ -1601,12 +1590,13 @@ version = "0.1.0"
 dependencies = [
  "anyhow",
  "bevy",
- "bevy_ecs_tilemap 0.9.0 (git+https://github.com/StarArawn/bevy_ecs_tilemap?rev=eb20fcaccdd253ea5bf280cac7ffc5a69b674df2)",
+ "bevy_ecs_tilemap",
  "bevy_tweening",
  "fastrand",
  "iyes_loopless",
  "kayak_font",
  "kayak_ui",
+ "kira",
  "ldtk_rust",
  "log",
  "micro_asset_io",
@@ -2320,7 +2310,7 @@ checksum = "db0f957d47035e81cda7636e2c9e17aa8fb8816a29bd1739935893b605ad4a09"
 dependencies = [
  "anyhow",
  "bevy",
- "bevy_ecs_tilemap 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "bevy_ecs_tilemap",
  "serde",
  "serde_json",
 ]
diff --git a/Cargo.toml b/Cargo.toml
index 1ae6fca738a1d73d133d7756e3b707c5adaceb7e..2828020c059ba3467a22039785e3b9086e86972c 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -17,7 +17,7 @@ serde_json = "1.0.87"
 iyes_loopless = "0.9.1"
 
 micro_musicbox = { version = "0.5.0", features = ["mp3"] }
-micro_banimate = "0.2.1"
+micro_banimate = { version = "0.2.1", features = ["ecs_tilemap"] }
 
 kayak_ui = { rev = "220694d12a5aeffe680fdaf2a8e5ac3ed9d81ae7", git = "https://github.com/StarArawn/kayak_ui" }
 kayak_font = { rev = "220694d12a5aeffe680fdaf2a8e5ac3ed9d81ae7", git = "https://github.com/StarArawn/kayak_ui.git" }
@@ -37,6 +37,9 @@ features = [
     "filesystem_watcher"
 ]
 
+[patch.crates-io]
+bevy_ecs_tilemap = { version = "0.9", git = "https://github.com/StarArawn/bevy_ecs_tilemap", rev = "eb20fcaccdd253ea5bf280cac7ffc5a69b674df2" }
+
 [profile.release]
 debug = 0
 opt-level = 3
diff --git a/assets/resources.apack b/assets/resources.apack
index 7db455ac57e9cdf7a2613e753ddcba42dc63d082..d2135afe72f2d406a1bca0936d17c6985064d106 100644
--- a/assets/resources.apack
+++ b/assets/resources.apack
@@ -1,3 +1,3 @@
 version https://git-lfs.github.com/spec/v1
-oid sha256:63fc5d48bdcc9ef1268da7877f399916b45d0463cb3f7a116594220ad5fdfa00
-size 110420
+oid sha256:8d6537a1b832665cd6f32c15a1988fed80b55999aa07cf4b73e8db2ecb02674e
+size 1577712
diff --git a/game_core/Cargo.toml b/game_core/Cargo.toml
index 8fc672658d2982bc0f63bc2eed7ab2c81ef560b2..a3c6e55a67107b0b9dca3c9eee63686ecf5a0b94 100644
--- a/game_core/Cargo.toml
+++ b/game_core/Cargo.toml
@@ -21,6 +21,7 @@ micro_musicbox.workspace = true
 
 micro_asset_io = { path = "../micro_asset_io" }
 num-traits = "0.2.15"
+kira = { version = "0.7", default-features = false, features = ["cpal", "mp3"] }
 
 bevy_ecs_tilemap = { git = "https://github.com/StarArawn/bevy_ecs_tilemap", rev = "eb20fcaccdd253ea5bf280cac7ffc5a69b674df2" }
 bevy_tweening = "0.6.0"
@@ -33,5 +34,6 @@ kayak_font.workspace = true
 
 #remote_events = { git = "https://lab.lcr.gr/microhacks/micro-bevy-remote-events.git", rev = "be0c6b43a73e4c5e7ece20797e3d6f59340147b4"}
 
+
 [target.'cfg(target_arch = "wasm32")'.dependencies]
 web-sys = { version = "0.3.58", features = ["Window"] }
diff --git a/game_core/src/assets/apack_handler.rs b/game_core/src/assets/apack_handler.rs
index 422ad53e118acd33da5c8eeefcc40ef1124ba931..d9b7604c8ea288ccf53ba0c6bdf1f7c00ce119a8 100644
--- a/game_core/src/assets/apack_handler.rs
+++ b/game_core/src/assets/apack_handler.rs
@@ -5,6 +5,7 @@
 /// files. Have a peek for curiosity, but try not to learn from this particular file
 ///
 use std::ffi::OsStr;
+use std::io::Cursor;
 use std::path::PathBuf;
 
 use bevy::asset::Asset;
@@ -13,8 +14,10 @@ use bevy::math::vec2;
 use bevy::prelude::*;
 use bevy::render::texture::{CompressedImageFormats, ImageType};
 use kayak_font::{KayakFont, Sdf};
+use kira::sound::static_sound::{StaticSoundData, StaticSoundSettings};
 use ldtk_rust::Project;
 use micro_asset_io::{APack, APackProcessingComplete};
+use micro_musicbox::prelude::AudioSource;
 use serde::{Deserialize, Serialize};
 
 use crate::assets::asset_types::ldtk_project::LdtkProject;
@@ -96,6 +99,8 @@ pub struct APackManifest {
 	pub fonts: Vec<ManifestFontAsset>,
 	#[serde(default = "Vec::new")]
 	pub ldtk: Vec<ManifestGenericAsset>,
+	#[serde(default = "Vec::new")]
+	pub sounds: Vec<ManifestGenericAsset>,
 }
 
 fn in_world<Param: SystemParam + 'static>(
@@ -132,7 +137,7 @@ pub fn handle_apack_process_events(world: &mut World) {
 	};
 
 	for event in events {
-		let pack = {
+		let mut pack = {
 			let mut state: SystemState<ResMut<Assets<APack>>> = SystemState::new(world);
 			let mut packs = state.get_mut(world);
 			if let Some(pack) = packs.get_mut(&event.0) {
@@ -311,6 +316,28 @@ pub fn handle_apack_process_events(world: &mut World) {
 					},
 				);
 			}
+
+			for sound_entry in &manifest.sounds {
+				let mut asset = pack
+					.get_mut(&format!("./{}", &sound_entry.path))
+					.expect("Missing asset");
+
+				let owned = std::mem::take(asset);
+				let sound = StaticSoundData::from_cursor(
+					Cursor::new(owned),
+					StaticSoundSettings::default(),
+				)
+				.unwrap();
+				let asset = AudioSource { sound };
+
+				in_world::<ParamSet<(ResMut<Assets<AudioSource>>, ResMut<AssetHandles>)>>(
+					world,
+					move |params| {
+						let handle = params.p0().add(asset);
+						params.p1().sounds.insert(sound_entry.name.clone(), handle);
+					},
+				);
+			}
 		}
 
 		log::info!("LOADED AN APACK");
diff --git a/game_core/src/assets/asset_types/ldtk_project.rs b/game_core/src/assets/asset_types/ldtk_project.rs
index 7135d2e88647d72ab89229c8dd2d5d96189d3a01..f3f7aaf1e114203ff7025649e677166ae964db81 100644
--- a/game_core/src/assets/asset_types/ldtk_project.rs
+++ b/game_core/src/assets/asset_types/ldtk_project.rs
@@ -5,7 +5,10 @@ use anyhow::Error;
 use bevy::asset::{AssetEvent, AssetLoader, Assets, BoxedFuture, LoadContext, LoadedAsset};
 use bevy::prelude::{EventReader, Res, ResMut, Resource};
 use bevy::reflect::TypeUuid;
-use ldtk_rust::{Level, Project};
+use ldtk_rust::{Level, Project, TilesetDefinition};
+use serde_json::Value;
+
+use crate::system::utilities::SerdeClone;
 
 #[derive(TypeUuid)]
 #[uuid = "eb97c508-61ea-11ed-abd3-db91dd0a6e8b"]
@@ -61,20 +64,46 @@ impl DerefMut for LevelIndex {
 	}
 }
 
+#[derive(Default)]
+pub struct TileMetadata(pub HashMap<i64, Value>);
+
+#[derive(Resource, Default)]
+pub struct TilesetIndex(pub HashMap<String, TileMetadata>);
+impl Deref for TilesetIndex {
+	type Target = HashMap<String, TileMetadata>;
+	fn deref(&self) -> &Self::Target {
+		&self.0
+	}
+}
+impl DerefMut for TilesetIndex {
+	fn deref_mut(&mut self) -> &mut Self::Target {
+		&mut self.0
+	}
+}
+
 pub fn handle_ldtk_project_events(
 	mut events: EventReader<AssetEvent<LdtkProject>>,
 	assets: Res<Assets<LdtkProject>>,
 	mut level_index: ResMut<LevelIndex>,
+	mut tilset_index: ResMut<TilesetIndex>,
 ) {
 	for event in events.iter() {
 		match event {
 			AssetEvent::Created { handle } | AssetEvent::Modified { handle } => {
 				if let Some(LdtkProject(project)) = assets.get(handle) {
 					for level in &project.levels {
-						level_index.insert(
-							level.identifier.clone(),
-							serde_json::from_value(serde_json::to_value(level).unwrap()).unwrap(),
-						);
+						level_index.insert(level.identifier.clone(), level.serde_clone());
+					}
+
+					for tileset in &project.defs.tilesets {
+						let mut tile_meta = HashMap::new();
+						for custom in &tileset.custom_data {
+							tile_meta.insert(
+								custom.tile_id,
+								serde_json::from_str(&*custom.data).unwrap(),
+							);
+						}
+						tilset_index.insert(tileset.identifier.clone(), TileMetadata(tile_meta));
 					}
 				}
 			}
diff --git a/game_core/src/assets/mod.rs b/game_core/src/assets/mod.rs
index e5443711f0b5305384b596a19dcbb4e488e63d09..33fdbf7251811e5f0c007bd560621207ea1c92d2 100644
--- a/game_core/src/assets/mod.rs
+++ b/game_core/src/assets/mod.rs
@@ -4,7 +4,7 @@ mod loader;
 mod resources;
 mod startup;
 
-pub use asset_types::ldtk_project::{LdtkLoader, LdtkProject, LevelIndex};
+pub use asset_types::ldtk_project::{LdtkLoader, LdtkProject, LevelIndex, TilesetIndex};
 use bevy::app::{App, Plugin};
 use bevy::prelude::AddAsset;
 use iyes_loopless::condition::ConditionSet;
@@ -21,6 +21,7 @@ impl Plugin for AssetsPlugin {
 	fn build(&self, app: &mut App) {
 		app.init_resource::<AssetHandles>()
 			.init_resource::<LevelIndex>()
+			.init_resource::<TilesetIndex>()
 			.add_asset::<LdtkProject>()
 			.add_asset_loader(LdtkLoader)
 			.add_enter_system(AppState::Preload, startup::start_preload_resources)
diff --git a/game_core/src/main.rs b/game_core/src/main.rs
index 92cafa4914079ebd706afc0613edf057818c7fa0..a8a988f7c1af0b495623a483e55028b59d76e517 100644
--- a/game_core/src/main.rs
+++ b/game_core/src/main.rs
@@ -21,6 +21,7 @@ fn main() {
 		.add_plugin(game_core::world::WorldPlugin)
 		.add_plugin(TilemapPlugin)
 		.add_plugin(game_core::graphics::GraphicsPlugin)
+		.add_plugins(micro_banimate::BanimatePluginGroup)
 		.add_plugins(AdventUIPlugins)
 		.run();
 }
diff --git a/game_core/src/states/menu_state.rs b/game_core/src/states/menu_state.rs
index 40ea5502d7384f88e19ad01c27e166200f6e8343..f6a1012c3f3826a07d6557148d3ccdb55c3fb56e 100644
--- a/game_core/src/states/menu_state.rs
+++ b/game_core/src/states/menu_state.rs
@@ -4,6 +4,7 @@ use bevy::prelude::*;
 use bevy_tweening::lens::TextColorLens;
 use bevy_tweening::{Animator, EaseFunction, RepeatCount, RepeatStrategy, Tween};
 use iyes_loopless::state::NextState;
+use micro_musicbox::prelude::{AudioEasing, AudioTween, MusicBox};
 
 use crate::assets::AssetHandles;
 use crate::system::flow::AppState;
@@ -11,7 +12,16 @@ use crate::system::flow::AppState;
 #[derive(Component)]
 pub struct MenuStateEntity;
 
-pub fn spawn_menu_entities(mut commands: Commands, assets: Res<AssetHandles>) {
+pub fn spawn_menu_entities(
+	mut commands: Commands,
+	assets: Res<AssetHandles>,
+	mut musicbox: MusicBox<AssetHandles>,
+) {
+	musicbox.fade_in_music(
+		"bgm",
+		AudioTween::new(Duration::from_secs(2), AudioEasing::Linear),
+	);
+
 	commands.spawn((
 		SpriteBundle {
 			texture: assets.image("menu_background"),
diff --git a/game_core/src/system/utilities.rs b/game_core/src/system/utilities.rs
index 1f4517c03498b76d829bf61502ee19c69819d153..c26cbbfb885883b05e84c1d1d7e85eb9a515f9bd 100644
--- a/game_core/src/system/utilities.rs
+++ b/game_core/src/system/utilities.rs
@@ -2,6 +2,8 @@ use std::ops::{Deref, DerefMut};
 
 use bevy::prelude::UVec2;
 use num_traits::AsPrimitive;
+use serde::de::DeserializeOwned;
+use serde::{Deserialize, Serialize};
 
 #[inline]
 pub fn f32_max(a: f32, b: f32) -> f32 {
@@ -128,3 +130,16 @@ impl Indexer {
 		self.height
 	}
 }
+
+pub trait SerdeClone {
+	fn serde_clone(&self) -> Self;
+}
+
+impl<T> SerdeClone for T
+where
+	T: Serialize + DeserializeOwned,
+{
+	fn serde_clone(&self) -> Self {
+		serde_json::from_value(serde_json::to_value(&self).unwrap()).unwrap()
+	}
+}
diff --git a/game_core/src/ui/components/button.rs b/game_core/src/ui/components/button.rs
index 58bc3a6c75a377e97202c4681fded3fca8199a51..ba2adbb0ef66ecc2252344cccbdb47f1ffb995b9 100644
--- a/game_core/src/ui/components/button.rs
+++ b/game_core/src/ui/components/button.rs
@@ -1,6 +1,7 @@
 use bevy::prelude::*;
 use kayak_ui::prelude::*;
-use kayak_ui::widgets::{NinePatch, NinePatchBundle, TextProps, TextWidgetBundle};
+use kayak_ui::widgets::{ElementBundle, NinePatch, NinePatchBundle, TextProps, TextWidgetBundle};
+use micro_musicbox::prelude::MusicBox;
 use num_traits::AsPrimitive;
 
 use crate::assets::AssetHandles;
@@ -73,17 +74,27 @@ pub fn render_button_widget(
 				      mut params: ParamSet<(
 					Query<&ButtonWidgetProps>,
 					Query<&mut ButtonWidgetState>,
+					MusicBox<AssetHandles>,
 				)>| {
 					let widget_props = match params.p0().get(entity) {
 						Ok(p) => p.clone(),
 						Err(..) => return (event_dispatcher_context, event),
 					};
 
+					let mut should_click = false;
+					let mut should_proing = false;
+
 					if let Ok(mut state) = params.p1().get_mut(state_entity) {
 						match &event.event_type {
-							EventType::Hover(..) | EventType::MouseIn(..) => {
+							EventType::Hover(..) => {
+								if !widget_props.is_disabled {
+									state.is_hovered = true;
+								}
+							}
+							EventType::MouseIn(..) => {
 								if !widget_props.is_disabled {
 									state.is_hovered = true;
+									should_click = true;
 								}
 							}
 							EventType::MouseOut(..) => {
@@ -102,12 +113,21 @@ pub fn render_button_widget(
 								if widget_props.is_disabled {
 									event.prevent_default();
 									event.stop_propagation();
+								} else {
+									should_proing = true;
 								}
 							}
 							_ => {}
 						}
 					}
 
+					if should_click {
+						params.p2().play_sfx("ui_ping");
+					}
+					if should_proing {
+						params.p2().play_sfx("ui_confirm");
+					}
+
 					(event_dispatcher_context, event)
 				},
 			);
@@ -139,17 +159,17 @@ pub fn render_button_widget(
 			let sizing = if state.is_pressed {
 				KStyle {
 					top: px(11.0),
-					right: stretch(1.0),
 					bottom: px(11.0),
-					left: stretch(1.0),
+					right: px(4.0),
+					left: px(4.0),
 					..Default::default()
 				}
 			} else {
 				KStyle {
 					top: px(8.0),
-					right: stretch(1.0),
 					bottom: px(14.0),
-					left: stretch(1.0),
+					right: px(4.0),
+					left: px(4.0),
 					..Default::default()
 				}
 			};
@@ -159,15 +179,23 @@ pub fn render_button_widget(
 				min_height: px(32.0),
 				min_width: px(32.0),
 				height: px(button_height),
-				padding: value(Edge::all(Units::Stretch(0.0))),
 				..Default::default()
 			}
-			.with_style(style)
+			.with_style(style.clone())
+			.with_style(KStyle {
+				padding_bottom: stretch(0.0),
+				padding_top: stretch(0.0),
+				padding_left: stretch(0.0),
+				padding_right: stretch(0.0),
+				..Default::default()
+			})
 			.into();
 
 			let ninepatch_styles = KStyle {
 				layout_type: value(LayoutType::Row),
 				col_between: px(15.0),
+				padding_left: stretch(1.0),
+				padding_right: stretch(1.0),
 				..Default::default()
 			};
 
@@ -178,17 +206,38 @@ pub fn render_button_widget(
 			.with_style(if state.is_pressed {
 				KStyle {
 					top: px(6.0),
-					right: stretch(1.0),
 					bottom: px(16.0),
-					left: stretch(1.0),
+					// right: px(4.0),
+					// left: px(4.0),
 					..Default::default()
 				}
 			} else {
 				KStyle {
 					top: px(3.0),
-					right: stretch(1.0),
 					bottom: px(19.0),
-					left: stretch(1.0),
+					// right: px(4.0),
+					// left: px(4.0),
+					..Default::default()
+				}
+			});
+
+			let icon_wrapper_style = KStyle {
+				..Default::default()
+			}
+			.with_style(if state.is_pressed {
+				KStyle {
+					padding_top: px(11.0),
+					padding_bottom: px(11.0),
+					// padding_right: px(4.0),
+					// padding_left: px(4.0),
+					..Default::default()
+				}
+			} else {
+				KStyle {
+					padding_top: px(8.0),
+					padding_bottom: px(14.0),
+					// padding_right: px(4.0),
+					// padding_left: px(4.0),
 					..Default::default()
 				}
 			});
@@ -201,11 +250,15 @@ pub fn render_button_widget(
 				>
 					{ if !props.left_icon.is_none() {
 						constructor!(
-							<InsetIconWidget
-								styles={sizing.clone()}
-
-								props={InsetIconProps { image: props.left_icon.clone(), size: props.font_size }}
-							/>
+							<ElementBundle styles={icon_wrapper_style.clone()}>
+								<InsetIconWidget
+									styles={sizing.clone()}
+									props={InsetIconProps {
+										image: props.left_icon.clone(),
+										size: props.font_size
+									}}
+								/>
+							</ElementBundle>
 						);
 					}}
 					<TextWidgetBundle
@@ -222,10 +275,15 @@ pub fn render_button_widget(
 
 					{ if !props.right_icon.is_none() {
 						constructor!(
-							<InsetIconWidget
-								styles={sizing.clone()}
-								props={InsetIconProps { image: props.right_icon.clone(), size: props.font_size }}
-							/>
+							<ElementBundle styles={icon_wrapper_style.clone()}>
+								<InsetIconWidget
+									styles={sizing.clone()}
+									props={InsetIconProps {
+										image: props.right_icon.clone(),
+										size: props.font_size
+									}}
+								/>
+							</ElementBundle>
 						);
 					}}
 				</NinePatchBundle>
diff --git a/game_core/src/ui/utilities.rs b/game_core/src/ui/utilities.rs
index 5dfb3276879c7f67cd683eb80c349b089ede87e7..cc2ad0168c48a2cb325dbecd4eae22bb2bc60751 100644
--- a/game_core/src/ui/utilities.rs
+++ b/game_core/src/ui/utilities.rs
@@ -191,10 +191,17 @@ pub mod context {
 			register_widget_with_resource!(
 				widget_context,
 				TownMenuPanelProps,
-				EmptyState,
+				TownMenuPanelState,
 				UITravelInfo,
 				render_town_menu_panel
 			);
+			register_widget_with_resource!(
+				widget_context,
+				TransitPanelProps,
+				EmptyState,
+				UITravelInfo,
+				render_transit_panel
+			);
 		}
 	}
 }
diff --git a/game_core/src/ui/widgets/mod.rs b/game_core/src/ui/widgets/mod.rs
index 7acc2b1c34bb12a4c5fc92d508487f17edd4970a..7f5cc2e249fc533b48e7e53ec36c1e79b6562174 100644
--- a/game_core/src/ui/widgets/mod.rs
+++ b/game_core/src/ui/widgets/mod.rs
@@ -1,3 +1,8 @@
+mod shop_panel;
 mod town_menu;
+mod transit_panel;
 
-pub use town_menu::{render_town_menu_panel, TownMenuPanel, TownMenuPanelProps};
+pub use town_menu::{
+	render_town_menu_panel, TownMenuPanel, TownMenuPanelProps, TownMenuPanelState,
+};
+pub use transit_panel::{render_transit_panel, TransitPanel, TransitPanelProps};
diff --git a/game_core/src/ui/widgets/shop_panel.rs b/game_core/src/ui/widgets/shop_panel.rs
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/game_core/src/ui/widgets/town_menu.rs b/game_core/src/ui/widgets/town_menu.rs
index b7e75c147a79dccb5c4df4ee666e177ca5ca4322..f015185fcbc9e129f35b000bc3aaadf6babf1e7e 100644
--- a/game_core/src/ui/widgets/town_menu.rs
+++ b/game_core/src/ui/widgets/town_menu.rs
@@ -14,54 +14,32 @@ use crate::ui::widgets::*;
 use crate::world::{CurrentResidence, MapQuery, TownPaths};
 use crate::{basic_widget, empty_props, on_button_click};
 
-pub fn transit_button_factory(target: String) -> OnEvent {
-	let target = target.clone();
-	on_button_click!(
-		ParamSet<(
-			Commands,
-			Res<TownPaths>,
-			Query<(Entity, &CurrentResidence), With<Player>>,
-			MapQuery,
-		)>,
-		|mut params: ParamSet<(
-			Commands,
-			Res<TownPaths>,
-			Query<(Entity, &CurrentResidence), With<Player>>,
-			MapQuery,
-		)>| {
-			let target = target.clone();
-			let (entity, current) = {
-				match params.p2().get_single() {
-					Ok((entity, current)) => (entity.clone(), (current.get_location()).clone()),
-					_ => return,
-				}
-			};
-
-			let places = match params.p1().routes.get(&current) {
-				Some(places) => places.clone(),
-				None => return,
-			};
-
-			let bundle = match params.p3().get_active_level() {
-				Some(level) => places.create_route_bundle_for(target, level).unwrap(),
-				None => return,
-			};
+empty_props!(TownMenuPanelProps);
+basic_widget!(TownMenuPanelProps => TownMenuPanel);
 
-			params.p0().entity(entity).insert(bundle);
-		}
-	)
+#[derive(Debug, Ord, PartialOrd, PartialEq, Eq, Copy, Clone, Default)]
+pub enum TownMenuTab {
+	#[default]
+	Travel,
+	Merchant,
+	Tavern,
 }
 
-empty_props!(TownMenuPanelProps);
-basic_widget!(TownMenuPanelProps => TownMenuPanel);
+#[derive(Component, Clone, PartialEq, Default)]
+pub struct TownMenuPanelState {
+	pub tab: TownMenuTab,
+}
 
 pub fn render_town_menu_panel(
 	In((widget_context, entity)): In<(KayakWidgetContext, Entity)>,
 	mut commands: Commands,
+	state_query: Query<&TownMenuPanelState>,
 	ui_data: Res<UITravelInfo>,
 	places: Res<TownPaths>,
 ) -> bool {
 	let parent_id = Some(entity);
+	let state_entity =
+		widget_context.use_state(&mut commands, entity, TownMenuPanelState::default());
 
 	let distance_style = KStyle {
 		position_type: value(KPositionType::SelfDirected),
@@ -89,7 +67,31 @@ pub fn render_town_menu_panel(
 		..Default::default()
 	};
 
-	if let Some(ref place) = ui_data.current_town {
+	let click_tab_travel = on_button_click!(Query<&mut TownMenuPanelState>, |mut q: Query<
+		&mut TownMenuPanelState,
+	>| {
+		if let Ok(mut state) = q.get_mut(state_entity) {
+			state.tab = TownMenuTab::Travel;
+		}
+	});
+	let click_tab_merchant = on_button_click!(Query<&mut TownMenuPanelState>, |mut q: Query<
+		&mut TownMenuPanelState,
+	>| {
+		if let Ok(mut state) = q.get_mut(state_entity) {
+			state.tab = TownMenuTab::Merchant;
+		}
+	});
+	let click_tab_tavern = on_button_click!(Query<&mut TownMenuPanelState>, |mut q: Query<
+		&mut TownMenuPanelState,
+	>| {
+		if let Ok(mut state) = q.get_mut(state_entity) {
+			state.tab = TownMenuTab::Tavern;
+		}
+	});
+
+	if let (Some(ref place), Some(state)) =
+		(&ui_data.current_town, state_query.get(state_entity).ok())
+	{
 		rsx! {
 			<PanelWidget
 				styles={panel_style}
@@ -112,43 +114,69 @@ pub fn render_town_menu_panel(
 
 				<VDividerWidget props={VDividerWidgetProps { height: 4.0, padding: 5.0, color: Color::rgb(0.52, 0.369, 0.18)}} />
 
-				<TextWidgetBundle
-					text={TextProps {
-						content: format!("Set off for:"),
-						size: 32.0,
-						..Default::default()
-					}}
-					styles={KStyle {
-						color: value(Color::BLACK),
-						padding: edge_px(20.0),
-						left: stretch(1.0),
-						right: stretch(1.0),
-						..Default::default()
-					}}
-				/>
+				<ElementBundle styles={KStyle {
+					layout_type: value(LayoutType::Row),
+					padding_top: stretch(1.0),
+					padding_bottom: stretch(1.0),
+					padding_left: stretch(1.0),
+					padding_right: stretch(1.0),
+					height: px(60.0),
+					col_between: px(15.0),
+					..Default::default()
+				}}>
+					<ButtonWidget
+						styles={KStyle {
+							width: px(225.0),
+							padding_left: px(30.0),
+							padding_right: px(20.0),
+							..Default::default()
+						}}
+						props={ButtonWidgetProps {
+							left_icon: IconContent::Atlas(String::from("icons"), 4),
+							..ButtonWidgetProps::text("Travel", 28.0)
+						}}
+						on_event={click_tab_travel}
+					/>
+					<ButtonWidget
+						styles={KStyle {
+							width: px(260.0),
+							padding_left: px(30.0),
+							padding_right: px(20.0),
+							..Default::default()
+						}}
+						props={ButtonWidgetProps {
+							left_icon: IconContent::Atlas(String::from("icons"), 9),
+							..ButtonWidgetProps::text("Merchant", 28.0)
+						}}
+						on_event={click_tab_merchant}
+					/>
+					<ButtonWidget
+						styles={KStyle {
+							width: px(225.0),
+							padding_left: px(30.0),
+							padding_right: px(20.0),
+							..Default::default()
+						}}
+						props={ButtonWidgetProps {
+							left_icon: IconContent::Atlas(String::from("icons"), 11),
+							..ButtonWidgetProps::text("Tavern", 28.0)
+						}}
+						on_event={click_tab_tavern}
+					/>
+				</ElementBundle>
+
+				<VDividerWidget props={VDividerWidgetProps { height: 4.0, padding: 5.0, color: Color::rgb(0.52, 0.369, 0.18)}} />
+
 
 				{
-					for (place, distance) in ui_data.travel_options.iter() {
-						constructor! {
-							<ButtonWidget
-								styles={
-									KStyle {
-										left: stretch(1.0),
-										right: stretch(1.0),
-										width: pct(70.0),
-										min_width: px(300.0),
-										max_width: px(600.0),
-										bottom: px(10.0),
-										..Default::default()
-									}
-								}
-								props={ButtonWidgetProps {
-									left_icon: IconContent::Atlas(String::from("characters"), 0),
-									..ButtonWidgetProps::text(format!("{}: {:.2}KM", &place, distance), 28.0)
-								}}
-								on_event={transit_button_factory(place.clone())}
-							/>
-						}
+					match state.tab {
+						TownMenuTab::Travel => {
+							constructor! {
+								<TransitPanel />
+							}
+						},
+						TownMenuTab::Merchant => {}
+						TownMenuTab::Tavern => {}
 					}
 				}
 			</PanelWidget>
diff --git a/game_core/src/ui/widgets/transit_panel.rs b/game_core/src/ui/widgets/transit_panel.rs
new file mode 100644
index 0000000000000000000000000000000000000000..6e3571e0de00ec944ef16ffffca872e1a3a10271
--- /dev/null
+++ b/game_core/src/ui/widgets/transit_panel.rs
@@ -0,0 +1,113 @@
+use bevy::prelude::*;
+use kayak_ui::prelude::*;
+use kayak_ui::widgets::{
+	ElementBundle, ScrollBoxBundle, ScrollBoxProps, ScrollContextProviderBundle, TextProps,
+	TextWidgetBundle,
+};
+
+use crate::assets::AssetHandles;
+use crate::states::Player;
+use crate::ui::components::*;
+use crate::ui::prelude::*;
+use crate::ui::sync::UITravelInfo;
+use crate::ui::widgets::*;
+use crate::world::{CurrentResidence, MapQuery, TownPaths};
+use crate::{basic_widget, empty_props, on_button_click};
+
+empty_props!(TransitPanelProps);
+basic_widget!(TransitPanelProps => TransitPanel);
+
+pub fn transit_button_factory(target: String) -> OnEvent {
+	let target = target.clone();
+	on_button_click!(
+		ParamSet<(
+			Commands,
+			Res<TownPaths>,
+			Query<(Entity, &CurrentResidence), With<Player>>,
+			MapQuery,
+		)>,
+		|mut params: ParamSet<(
+			Commands,
+			Res<TownPaths>,
+			Query<(Entity, &CurrentResidence), With<Player>>,
+			MapQuery,
+		)>| {
+			let target = target.clone();
+			let (entity, current) = {
+				match params.p2().get_single() {
+					Ok((entity, current)) => (entity.clone(), (current.get_location()).clone()),
+					_ => return,
+				}
+			};
+
+			let places = match params.p1().routes.get(&current) {
+				Some(places) => places.clone(),
+				None => return,
+			};
+
+			let bundle = match params.p3().get_active_level() {
+				Some(level) => places.create_route_bundle_for(target, level).unwrap(),
+				None => return,
+			};
+
+			params.p0().entity(entity).insert(bundle);
+		}
+	)
+}
+
+pub fn render_transit_panel(
+	In((widget_context, entity)): In<(KayakWidgetContext, Entity)>,
+	mut commands: Commands,
+	ui_data: Res<UITravelInfo>,
+) -> bool {
+	let parent_id = Some(entity);
+
+	rsx! {
+		<ElementBundle>
+			<TextWidgetBundle
+				text={TextProps {
+					content: format!("Set off for:"),
+					size: 32.0,
+					..Default::default()
+				}}
+				styles={KStyle {
+					color: value(Color::BLACK),
+					padding: edge_px(20.0),
+					bottom: px(15.0),
+					left: stretch(1.0),
+					right: stretch(1.0),
+					..Default::default()
+				}}
+			/>
+			<ScrollContextProviderBundle>
+				<ScrollBoxBundle>
+					{
+						for (place, distance) in ui_data.travel_options.iter() {
+							constructor! {
+								<ButtonWidget
+									styles={
+										KStyle {
+											left: stretch(1.0),
+											right: stretch(1.0),
+											width: pct(70.0),
+											min_width: px(300.0),
+											max_width: px(600.0),
+											bottom: px(10.0),
+											padding_left: px(20.0),
+											padding_right: px(20.0),
+											..Default::default()
+										}
+									}
+									props={ButtonWidgetProps::text(format!("{}: {:.2}KM", &place, distance), 28.0)}
+									on_event={transit_button_factory(place.clone())}
+								/>
+							}
+						}
+					}
+				</ScrollBoxBundle>
+			</ScrollContextProviderBundle>
+		</ElementBundle>
+	}
+
+	true
+}
diff --git a/game_core/src/world/debug.rs b/game_core/src/world/debug.rs
index 97c9eae4e2f8f66a96dea13b9db91374bba8bb0c..b3d96377f506525f0422d07d982a502645369d96 100644
--- a/game_core/src/world/debug.rs
+++ b/game_core/src/world/debug.rs
@@ -21,7 +21,7 @@ pub fn create_tombstones(
 			commands.spawn((
 				SpriteSheetBundle {
 					transform: Transform::from_translation(point.extend(900.0)),
-					texture_atlas: assets.atlas("characters"),
+					texture_atlas: assets.atlas("icons"),
 					sprite: TextureAtlasSprite::new(1),
 					..Default::default()
 				},
diff --git a/game_core/src/world/spawning.rs b/game_core/src/world/spawning.rs
index 1c94ea2b1b70368c270fd65b974b990b897e6dff..20c7adfedf2fe005e08b1c1f814649e9df3fbfaf 100644
--- a/game_core/src/world/spawning.rs
+++ b/game_core/src/world/spawning.rs
@@ -1,8 +1,10 @@
 use bevy::prelude::*;
 use bevy_ecs_tilemap::prelude::*;
+use micro_banimate::definitions::SimpleAnimationBundle;
 use num_traits::AsPrimitive;
+use serde_json::Value;
 
-use crate::assets::{AssetHandles, LdtkProject, LevelIndex};
+use crate::assets::{AssetHandles, LdtkProject, LevelIndex, TilesetIndex};
 use crate::states::Player;
 use crate::system::camera::ChaseCam;
 use crate::world::towns::{CurrentResidence, TownPaths};
@@ -15,12 +17,17 @@ pub fn spawn_world_data(
 	assets: Res<AssetHandles>,
 	projects: Res<Assets<LdtkProject>>,
 	level_index: Res<LevelIndex>,
+	tileset_index: Res<TilesetIndex>,
 	mut last_spawned_level: Local<String>,
 ) {
 	let mut active_level = match active_level {
 		Some(l) => l,
 		None => return,
 	};
+	let mut tileset = match tileset_index.get(&String::from("Overworld")) {
+		Some(l) => l,
+		None => return,
+	};
 
 	if *last_spawned_level == active_level.map && !active_level.dirty {
 		return;
@@ -62,14 +69,44 @@ pub fn spawn_world_data(
 
 				tile_pos.y = map_tile_height as u32 - tile_pos.y - 1;
 
-				let tile_entity = commands
-					.spawn(TileBundle {
-						position: tile_pos,
-						tilemap_id: TilemapId(map_entity),
-						texture_index: TileTextureIndex(tile.tile.t as u32),
-						..Default::default()
-					})
-					.id();
+				let tile_entity = if let Some(tile_entity) = tileset.0.get(&tile.tile.t) {
+					match tile_entity.get("animations") {
+						Some(Value::Array(frames)) => commands
+							.spawn((
+								TileBundle {
+									position: tile_pos,
+									tilemap_id: TilemapId(map_entity),
+									texture_index: TileTextureIndex(tile.tile.t as u32),
+									..Default::default()
+								},
+								SimpleAnimationBundle::new(
+									frames
+										.iter()
+										.map(|val| val.as_u64().unwrap() as usize)
+										.collect(),
+									0.5,
+								),
+							))
+							.id(),
+						_ => commands
+							.spawn(TileBundle {
+								position: tile_pos,
+								tilemap_id: TilemapId(map_entity),
+								texture_index: TileTextureIndex(tile.tile.t as u32),
+								..Default::default()
+							})
+							.id(),
+					}
+				} else {
+					commands
+						.spawn(TileBundle {
+							position: tile_pos,
+							tilemap_id: TilemapId(map_entity),
+							texture_index: TileTextureIndex(tile.tile.t as u32),
+							..Default::default()
+						})
+						.id()
+				};
 
 				storage.set(&tile_pos, tile_entity);
 			});
@@ -112,10 +149,7 @@ pub fn spawn_world_data(
 		});
 
 		let trade_routes = TownPaths::from(MapQuery::get_entities_of(level));
-		let random_start = trade_routes
-			.routes
-			.values()
-			.nth(fastrand::usize(0..trade_routes.routes.len()));
+		let random_start = trade_routes.routes.get(&String::from("The Royal Lampoon"));
 
 		if let Some(start) = random_start {
 			if let Some(route) = start.routes.values().next() {
@@ -128,7 +162,7 @@ pub fn spawn_world_data(
 							level.px_hei as f32 - grid_to_px(point.tile_y),
 							400.0,
 						),
-						texture_atlas: assets.atlas("characters"),
+						texture_atlas: assets.atlas("icons"),
 						sprite: TextureAtlasSprite {
 							index: 0,
 							..Default::default()
diff --git a/game_core/src/world/utils.rs b/game_core/src/world/utils.rs
index 63abfb2914481d6d1c9d2301cb1f3c8fd2272db5..ccf8f7fa49b62e2b949c4811c701cdb243e9aa51 100644
--- a/game_core/src/world/utils.rs
+++ b/game_core/src/world/utils.rs
@@ -10,7 +10,7 @@ pub fn px_to_grid<T: AsPrimitive<i64>>(t: T) -> i64 {
 }
 
 pub fn grid_to_px<T: AsPrimitive<f32>>(t: T) -> f32 {
-	t.as_() * TILE_SCALE_F32 + (TILE_SCALE_F32 / 2.0)
+	t.as_() * TILE_SCALE_F32 // + (TILE_SCALE_F32 / 2.0)
 }
 
 pub fn entity_to_worldspace(level_height: i64, entity: &EntityInstance) -> (f32, f32) {
diff --git a/raw_assets/.gitignore b/raw_assets/.gitignore
index 6d9884b663e9accb8ff0f70cee0dd3736afd8c43..8443973da8667ad7dcba09700be1afd0247e9d1f 100644
--- a/raw_assets/.gitignore
+++ b/raw_assets/.gitignore
@@ -3,6 +3,7 @@ fonts/
 sprites/
 ldtk/
 ui/
+sound/
 
 !.gitignore
 !manifest.toml
\ No newline at end of file
diff --git a/raw_assets/manifest.toml b/raw_assets/manifest.toml
index c2b8e6e6e862c09a97b9f82f843be0b39b571ba6..41bb26226d291b164b8fae8d2495e3406d78ee2c 100644
--- a/raw_assets/manifest.toml
+++ b/raw_assets/manifest.toml
@@ -4,8 +4,8 @@ name = "overworld"
 tiles = { size = 4, columns = 32, rows = 64 }
 
 [[spritesheets]]
-path = "sprites/characters.png"
-name = "characters"
+path = "sprites/icons.png"
+name = "icons"
 tiles = { size = 8, columns = 16, rows = 16 }
 
 [[images]]
@@ -57,4 +57,20 @@ msdf = "fonts/EquipmentPro.kayak_font"
 
 [[ldtk]]
 path = "ldtk/overworld_maps.ldtk"
-name = "overworld"
\ No newline at end of file
+name = "overworld"
+
+[[sounds]]
+path = "sound/ui_click.mp3"
+name = "ui_click"
+
+[[sounds]]
+path = "sound/ui_ping.mp3"
+name = "ui_ping"
+
+[[sounds]]
+path = "sound/ui_confirm.mp3"
+name = "ui_confirm"
+
+[[sounds]]
+path = "sound/fantasy-chamber-adventure-loop.mp3"
+name = "bgm"
\ No newline at end of file