diff --git a/Cargo.lock b/Cargo.lock
index 1ce3e1b..020fd37 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -1359,6 +1359,15 @@ dependencies = [
  "serde",
 ]
 
+[[package]]
+name = "egui_flex"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0d2598b8bd01a7bfdaede32b297e5ba671640d4220f6c353025042a82cefee6e"
+dependencies = [
+ "egui",
+]
+
 [[package]]
 name = "egui_glow"
 version = "0.31.1"
@@ -6238,6 +6247,7 @@ dependencies = [
  "egui-phosphor",
  "egui_demo_lib",
  "egui_extras",
+ "egui_flex",
  "futures",
  "getrandom 0.3.2",
  "image",
diff --git a/crates/client/Cargo.toml b/crates/client/Cargo.toml
index 83dd903..a6f3b3d 100644
--- a/crates/client/Cargo.toml
+++ b/crates/client/Cargo.toml
@@ -21,6 +21,7 @@ walkers = { version = "0.36", git = "https://github.com/c0repwn3r/walkers", bran
 getrandom = { version = "0.3", features = ["wasm_js"] }
 lru = "0.13"
 futures = "0.3"
+egui_flex = "0.3"
 
 [target.'cfg(target_arch = "wasm32")'.dependencies]
 tracing-web = "0.1"
diff --git a/crates/client/src/app.rs b/crates/client/src/app.rs
index 6620573..35220bb 100644
--- a/crates/client/src/app.rs
+++ b/crates/client/src/app.rs
@@ -14,15 +14,17 @@ use serde::{Deserialize, Serialize};
 use std::collections::HashMap;
 use std::ops::{Deref, DerefMut};
 use walkers::{HttpTiles, MapMemory, Position, Tiles};
+use crate::ui::{Breakpoint, CtxExt};
 
 #[derive(Serialize, Deserialize)]
 pub struct App {
     pub(crate) frame_time_history: History<f32>,
-    add_layer_open: bool,
-    selected_layer: Option<LayerId>,
-    map_memory: MapMemory,
-    layer_manager: LayerManager,
-    position: Position,
+    pub add_layer_open: bool,
+    pub selected_layer: Option<LayerId>,
+    pub map_memory: MapMemory,
+    pub layer_manager: LayerManager,
+    pub position: Position,
+    pub menu_open: bool
 }
 
 impl App {
@@ -57,6 +59,7 @@ impl App {
             map_memory: MapMemory::default(),
             layer_manager,
             position: Position::new(35.227085, -80.843124),
+            menu_open: false
         }
     }
 }
@@ -72,13 +75,19 @@ impl eframe::App for App {
         }
 
         top_bar(ctx);
-        footer(ctx, &self);
-        left_bar(
-            ctx,
-            &mut self.add_layer_open,
-            &mut self.layer_manager,
-            &mut self.selected_layer,
-        );
+
+        let show_main_menu_in_footer = ctx.breakpoint() < Breakpoint::Xl4;
+
+        footer(ctx, self, show_main_menu_in_footer);
+
+        if !show_main_menu_in_footer {
+            left_bar(
+                ctx,
+                &mut self.add_layer_open,
+                &mut self.layer_manager,
+                &mut self.selected_layer,
+            );
+        }
         //right_bar(ctx);
 
         egui::CentralPanel::default().show(ctx, |ui| {
diff --git a/crates/client/src/ui/mod.rs b/crates/client/src/ui/mod.rs
index f35230f..83ee64b 100644
--- a/crates/client/src/ui/mod.rs
+++ b/crates/client/src/ui/mod.rs
@@ -1,6 +1,7 @@
 use egui::emath::GuiRounding;
-use egui::{emath, CursorIcon, Id, InnerResponse, LayerId, Order, Sense, Ui, UiBuilder};
+use egui::{emath, CursorIcon, Id, InnerResponse, LayerId, Order, Sense, Ui, UiBuilder, Context};
 use std::any::Any;
+use std::cmp::Ordering;
 
 pub mod shell;
 pub mod tokens;
@@ -48,6 +49,11 @@ pub trait UiExt {
         }
         response
     }
+
+    fn breakpoint(&self) -> Breakpoint {
+        let ui = self.ui();
+        ui.ctx().breakpoint()
+    }
 }
 
 impl UiExt for egui::Ui {
@@ -61,3 +67,93 @@ impl UiExt for egui::Ui {
         self
     }
 }
+
+pub trait CtxExt {
+    fn ctx(&self) -> &egui::Context;
+    fn breakpoint(&self) -> Breakpoint {
+        let ctx = self.ctx();
+        Breakpoint::current_breakpoint(ctx.input(|i| i.screen_rect().width()))
+    }
+}
+impl CtxExt for egui::Context {
+    #[inline]
+    fn ctx(&self) -> &Context { self }
+}
+
+
+#[derive(PartialEq, Eq)]
+pub enum Breakpoint {
+    Xs3,
+    Xs2,
+    Xs,
+    Sm,
+    Md,
+    Lg,
+    Xl,
+    Xl2,
+    Xl3,
+    Xl4,
+    Xl5,
+    Xl6,
+    Xl7,
+}
+impl Breakpoint {
+    pub const fn minimum_width(&self) -> f32 {
+        match self {
+            Breakpoint::Xs3 => 256.0,
+            Breakpoint::Xs2 => 288.0,
+            Breakpoint::Xs => 320.0,
+            Breakpoint::Sm => 384.0,
+            Breakpoint::Md => 448.0,
+            Breakpoint::Lg => 512.0,
+            Breakpoint::Xl => 576.0,
+            Breakpoint::Xl2 => 672.0,
+            Breakpoint::Xl3 => 768.0,
+            Breakpoint::Xl4 => 896.0,
+            Breakpoint::Xl5 => 1024.0,
+            Breakpoint::Xl6 => 1152.0,
+            Breakpoint::Xl7 => 1280.0,
+        }
+    }
+    pub const fn current_breakpoint(width: f32) -> Self {
+        return if width > Breakpoint::Xl7.minimum_width() {
+            Breakpoint::Xl7
+        } else if width > Breakpoint::Xl6.minimum_width() {
+            Breakpoint::Xl6
+        } else if width > Breakpoint::Xl5.minimum_width() {
+            Breakpoint::Xl5
+        } else if width > Breakpoint::Xl4.minimum_width() {
+            Breakpoint::Xl4
+        } else if width > Breakpoint::Xl3.minimum_width() {
+            Breakpoint::Xl3
+        } else if width > Breakpoint::Xl2.minimum_width() {
+            Breakpoint::Xl2
+        } else if width > Breakpoint::Xl.minimum_width() {
+            Breakpoint::Xl
+        } else if width > Breakpoint::Lg.minimum_width() {
+            Breakpoint::Lg
+        } else if width > Breakpoint::Md.minimum_width() {
+            Breakpoint::Md
+        } else if width > Breakpoint::Sm.minimum_width() {
+            Breakpoint::Sm
+        } else if width > Breakpoint::Xs.minimum_width() {
+            Breakpoint::Xs
+        } else if width > Breakpoint::Xs2.minimum_width() {
+            Breakpoint::Xs2
+        } else {
+            Breakpoint::Xs3
+        };
+    }
+}
+
+impl PartialOrd<Self> for Breakpoint {
+    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
+        self.minimum_width().partial_cmp(&other.minimum_width())
+    }
+}
+
+impl Ord for Breakpoint {
+    fn cmp(&self, other: &Self) -> Ordering {
+        self.minimum_width().partial_cmp(&other.minimum_width()).unwrap()
+    }
+}
\ No newline at end of file
diff --git a/crates/client/src/ui/shell/footer.rs b/crates/client/src/ui/shell/footer.rs
index 19d2d19..af0267d 100644
--- a/crates/client/src/ui/shell/footer.rs
+++ b/crates/client/src/ui/shell/footer.rs
@@ -1,6 +1,10 @@
 use crate::app::App;
 use eframe::emath::Align;
-use egui::{Hyperlink, Layout, Ui};
+use egui::{Color32, FontFamily, Hyperlink, Layout, RichText, Ui};
+use crate::map::sources::LayerId;
+use crate::map::tiles::LayerManager;
+use crate::ui::shell::inner_footer::inner_footer;
+use crate::ui::shell::main_menu::main_menu;
 
 fn version_and_changelog(ui: &mut Ui) {
     ui.add(
@@ -36,19 +40,49 @@ fn e3team_credits(ui: &mut Ui) {
     );
 }
 
-pub fn footer(ctx: &egui::Context, app: &App) {
+pub fn footer(
+    ctx: &egui::Context,
+    app: &mut App,
+    show_main_menu_here: bool
+) {
     egui::TopBottomPanel::bottom("footer")
         .resizable(false)
         .show(ctx, |ui| {
-            ui.style_mut().visuals.hyperlink_color = ui.visuals().weak_text_color();
 
-            ui.horizontal(|ui| {
-                version_and_changelog(ui);
-                frame_time_warning(ui, app);
+            if show_main_menu_here {
+                if app.menu_open {
+                    ui.with_layout(Layout::top_down_justified(Align::Center), |ui| {
+                        if ui.button(
+                            RichText::new(egui_phosphor::regular::ARROW_DOWN)
+                                .family(FontFamily::Name("icon-only".into()))
+                        ).clicked() {
+                            app.menu_open = false;
+                        }
+                        main_menu(
+                            ui,
+                            &mut app.add_layer_open,
+                            &mut app.layer_manager,
+                            &mut app.selected_layer
+                        );
+                        inner_footer(ui, app);
+                    });
+
+                } else {
+                    ui.with_layout(Layout::top_down_justified(Align::Center), |ui| {
+                        let previous_visuals = ui.visuals().widgets.inactive.clone();
+                        ui.visuals_mut().widgets.inactive.weak_bg_fill = Color32::TRANSPARENT;
+                        if ui.button(
+                            RichText::new(egui_phosphor::regular::ARROW_UP)
+                                .family(FontFamily::Name("icon-only".into()))
+                        ).clicked() {
+                            app.menu_open = true;
+                        }
+                        ui.visuals_mut().widgets.inactive = previous_visuals;
+                    });
+                }
+            } else {
+                inner_footer(ui, app);
+            }
 
-                ui.with_layout(Layout::right_to_left(Align::Center), |ui| {
-                    e3team_credits(ui);
-                });
-            });
         });
 }
diff --git a/crates/client/src/ui/shell/inner_footer.rs b/crates/client/src/ui/shell/inner_footer.rs
new file mode 100644
index 0000000..025a482
--- /dev/null
+++ b/crates/client/src/ui/shell/inner_footer.rs
@@ -0,0 +1,50 @@
+use crate::app::App;
+use eframe::emath::Align;
+use egui::{Direction, FontFamily, Hyperlink, Layout, RichText, Ui};
+
+fn version_and_changelog(ui: &mut Ui) {
+    ui.add(
+        Hyperlink::from_label_and_url(
+            format!("v{}", env!("CARGO_PKG_VERSION")),
+            "https://weather.ax/changelog",
+        )
+        .open_in_new_tab(true),
+    );
+}
+
+fn frame_time_warning(ui: &mut Ui, app: &App) {
+    if let Some(frame_time) = app.frame_time_history.average() {
+        ui.separator();
+
+        let ms = frame_time * 1e3;
+        let visuals = ui.visuals();
+        let color = if ms < 15.0 {
+            visuals.weak_text_color()
+        } else {
+            visuals.warn_fg_color
+        };
+        let text = format!("{ms:.1} ms");
+        ui.label(egui::RichText::new(text).monospace().color(color))
+            .on_hover_text("CPU time used by wxbox client each frame, lower is better");
+    }
+}
+
+fn e3team_credits(ui: &mut Ui) {
+    ui.add(
+        Hyperlink::from_label_and_url("built with <3 by e3team", "https://e3t.cc")
+            .open_in_new_tab(true),
+    );
+}
+
+pub fn inner_footer(ui: &mut egui::Ui, app: &App) {
+    ui.style_mut().visuals.hyperlink_color = ui.visuals().weak_text_color();
+
+    ui.horizontal(|ui| {
+        version_and_changelog(ui);
+        frame_time_warning(ui, app);
+
+        ui.with_layout(Layout::right_to_left(Align::Center), |ui| {
+            e3team_credits(ui);
+        });
+    });
+}
diff --git a/crates/client/src/ui/shell/left_bar.rs b/crates/client/src/ui/shell/left_bar.rs
index 3eec890..89212d0 100644
--- a/crates/client/src/ui/shell/left_bar.rs
+++ b/crates/client/src/ui/shell/left_bar.rs
@@ -14,6 +14,7 @@ use egui::{
 use std::collections::HashMap;
 use std::sync::Arc;
 use tracing::debug;
+use crate::ui::shell::main_menu::main_menu;
 
 fn outer_rect(f: &Prepared) -> Rect {
     let content_rect = f.content_ui.min_rect();
@@ -32,420 +33,9 @@ pub fn left_bar(
     frame.inner_margin.left = 0;
     frame.inner_margin.right = 0;
 
-    let mut needs_any_update = false;
-
     egui::SidePanel::left("left_panel")
         .frame(frame)
         .show(ctx, |ui| {
-            pane_header(
-                ui,
-                "Layers",
-                Some(egui_phosphor::regular::STACK),
-                false,
-                |ui| {
-                    ui.style_mut().visuals.widgets.inactive.weak_bg_fill = Color32::TRANSPARENT;
-                    if ui
-                        .button(RichText::new(egui_phosphor::regular::PLUS).size(12.0))
-                        .clicked()
-                    {
-                        *add_open = true;
-                    }
-                },
-            );
-
-            let lm_registry = &layer_manager.registered_sources;
-            let layers_to_draw = layer_manager.active_layers.len();
-
-            let mut layers_to_remove = vec![];
-
-            for i in 0..layer_manager.active_layers.len() {
-                let (retain, update_layers) = draw_layer(
-                    ui,
-                    i,
-                    lm_registry,
-                    &mut layer_manager.active_layers,
-                    selected_layer,
-                );
-
-                if !retain {
-                    layers_to_remove.push(layer_manager.active_layers[i].layer_id);
-                }
-                needs_any_update = needs_any_update || update_layers;
-            }
-
-            for layer_to_remove in &layers_to_remove {
-                if *selected_layer == Some(*layer_to_remove) {
-                    *selected_layer = None;
-                }
-            }
-
-            layer_manager
-                .active_layers
-                .retain(|layer| !layers_to_remove.contains(&layer.layer_id));
+            main_menu(ui, add_open, layer_manager, selected_layer);
         });
-
-    if *add_open {
-        let mut frame = Frame::popup(&ctx.style()).corner_radius(CornerRadius::ZERO);
-        frame.inner_margin.left = 0;
-        frame.inner_margin.right = 0;
-        egui::Modal::new(Id::new("add_source_modal"))
-            .frame(frame)
-            .show(ctx, |ui| {
-                pane_header(
-                    ui,
-                    "Add layer",
-                    Some(egui_phosphor::regular::STACK_PLUS),
-                    false,
-                    |ui| {
-                        ui.style_mut().visuals.widgets.inactive.weak_bg_fill = Color32::TRANSPARENT;
-                        if ui
-                            .button(RichText::new(egui_phosphor::regular::X).size(12.0))
-                            .clicked()
-                        {
-                            *add_open = false;
-                        }
-                    },
-                );
-
-                for (id, registered_layer) in &layer_manager.registered_sources {
-                    // ghost button
-
-                    let mut frame = Frame::new();
-
-                    frame.inner_margin.top = 4;
-                    frame.inner_margin.left = 8;
-                    frame.inner_margin.right = 8;
-                    frame.inner_margin.bottom = 4;
-
-                    let mut frame = frame.begin(ui);
-
-                    {
-                        frame.content_ui.horizontal(|ui| {
-                            let mut frame = Frame::new().fill(
-                                DESIGN_TOKENS
-                                    .get_color(
-                                        "colors.base.4",
-                                        if ui.style().visuals.dark_mode {
-                                            Theme::Dark
-                                        } else {
-                                            Theme::Light
-                                        },
-                                    )
-                                    .unwrap(),
-                            );
-
-                            frame.inner_margin.top = 4;
-                            frame.inner_margin.left = 8;
-                            frame.inner_margin.right = 8;
-                            frame.inner_margin.bottom = 4;
-
-                            frame.corner_radius = CornerRadius::from(4);
-
-                            frame.show(ui, |ui| {
-                                ui.label(
-                                    RichText::new(registered_layer.type_hint.icon()).size(24.0),
-                                );
-                            });
-
-                            ui.vertical(|ui| {
-                                ui.label(&registered_layer.display_name);
-                                ui.horizontal(|ui| {
-                                    let type_hint = registered_layer.type_hint.name();
-                                    let icon = registered_layer.type_hint.icon();
-
-                                    let type_lbl = format!("{icon} {type_hint}");
-                                    ui.allocate_ui_with_layout(
-                                        vec2(90.0, 20.0),
-                                        Layout::left_to_right(Align::Center),
-                                        |ui| {
-                                            ui.label(RichText::new(type_lbl).weak());
-                                            ui.add_space(ui.available_width());
-                                        },
-                                    );
-
-                                    ui.allocate_ui_with_layout(
-                                        vec2(120.0, 20.0),
-                                        Layout::left_to_right(Align::Center),
-                                        |ui| {
-                                            ui.label(
-                                                RichText::new(format!(
-                                                    "{} {}",
-                                                    egui_phosphor::regular::NAVIGATION_ARROW,
-                                                    &registered_layer.location
-                                                ))
-                                                .weak(),
-                                            );
-                                            ui.add_space(ui.available_width());
-                                        },
-                                    );
-
-                                    ui.allocate_ui_with_layout(
-                                        vec2(100.0, 20.0),
-                                        Layout::left_to_right(Align::Center),
-                                        |ui| {
-                                            ui.label(
-                                                RichText::new(format!(
-                                                    "{} {}",
-                                                    egui_phosphor::regular::IDENTIFICATION_BADGE,
-                                                    &registered_layer.source
-                                                ))
-                                                .weak(),
-                                            );
-                                            ui.add_space(ui.available_width());
-                                        },
-                                    );
-                                });
-                            });
-
-                            ui.add_space(ui.available_width());
-                        });
-                    }
-                    let response = ui.allocate_rect(outer_rect(&frame), Sense::click_and_drag());
-                    if response.hovered() {
-                        frame.frame.fill = DESIGN_TOKENS
-                            .get_color(
-                                "colors.base.7",
-                                if ui.style().visuals.dark_mode {
-                                    Theme::Dark
-                                } else {
-                                    Theme::Light
-                                },
-                            )
-                            .unwrap();
-                        ui.ctx()
-                            .output_mut(|u| u.cursor_icon = CursorIcon::PointingHand);
-                    }
-                    if response.clicked() {
-                        layer_manager.active_layers.push(ActiveLayer {
-                            source_id: *id,
-                            layer_id: LayerId::new(),
-                            visible: true,
-                        });
-                        debug!("{:?}", layer_manager);
-                        *add_open = false;
-                    }
-                    frame.paint(ui);
-                }
-            });
-    }
-}
-
-fn draw_layer(
-    ui: &mut Ui,
-    you: usize,
-    registry: &HashMap<SourceId, LayerSource>,
-    active_layers: &mut Vec<ActiveLayer>,
-    selected_layer: &mut Option<LayerId>,
-) -> (bool, bool) {
-    let mut retain = true;
-    let mut needs_layers_update = false;
-    let layer = active_layers[you];
-
-    let source = registry.get(&layer.source_id).unwrap();
-
-    let r = ui.scope_builder(
-        UiBuilder::new()
-            .id_salt(layer.layer_id)
-            .sense(Sense::click_and_drag()),
-        |ui| {
-            let title = source.short_name.as_str();
-            let icon = source.type_hint.icon();
-
-            let mut any_children_hovered = false;
-
-            let mut frame = Frame::new();
-            //frame.fill = ui.style().visuals.hyperlink_color;
-            frame.inner_margin.left = 8;
-            frame.inner_margin.right = 8;
-            frame.inner_margin.top = 2;
-            frame.inner_margin.bottom = 2;
-
-            let mut frame = frame.begin(ui);
-            {
-                frame.content_ui.horizontal(|ui| {
-                    ui.add(
-                        Label::new(
-                            RichText::new(egui_phosphor::regular::DOTS_SIX_VERTICAL)
-                                .size(12.0)
-                                .family(FontFamily::Name("icon-only".into()))
-                                .weak(),
-                        )
-                        .sense(Sense::empty())
-                        .selectable(false),
-                    );
-
-                    ui.add_space(8.0);
-
-                    let mut name_label = RichText::new(format!("{icon} {title}"));
-                    if !layer.visible {
-                        name_label = name_label.weak();
-                    }
-
-                    ui.add(
-                        Label::new(name_label)
-                            .sense(Sense::empty())
-                            .selectable(false),
-                    );
-
-                    ui.with_layout(Layout::right_to_left(Align::Center), |ui| {
-                        ui.style_mut().visuals.widgets.inactive.weak_bg_fill = Color32::TRANSPARENT;
-
-                        let mut visibility_label = RichText::new(if layer.visible {
-                            egui_phosphor::regular::EYE
-                        } else {
-                            egui_phosphor::regular::EYE_SLASH
-                        })
-                        .size(12.0)
-                        .family(FontFamily::Name("icon-only".into()));
-
-                        if !layer.visible {
-                            visibility_label = visibility_label.weak();
-                        }
-
-                        let visibility_btn = ui.button(visibility_label);
-
-                        any_children_hovered = any_children_hovered || visibility_btn.hovered();
-                        if visibility_btn.clicked() {
-                            active_layers[you].visible = !active_layers[you].visible;
-                        }
-
-                        let mut delete_label = RichText::new(egui_phosphor::regular::TRASH)
-                            .size(12.0)
-                            .family(FontFamily::Name("icon-only".into()));
-
-                        if !layer.visible {
-                            delete_label = delete_label.weak();
-                        }
-
-                        let delete_button = ui.button(delete_label);
-                        any_children_hovered = any_children_hovered || delete_button.hovered();
-
-                        if delete_button.clicked() {
-                            retain = false;
-                            needs_layers_update = true;
-                        }
-                    });
-                });
-            }
-            frame.allocate_space(ui);
-
-            let response = ui.response();
-
-            if response.hovered() && !any_children_hovered {
-                frame.frame.fill = ui.visuals().widgets.active.weak_bg_fill;
-                ui.ctx().output_mut(|u| u.cursor_icon = CursorIcon::Grab);
-            }
-
-            if response.clicked() {
-                *selected_layer = Some(layer.layer_id);
-            }
-
-            if let Some(sel_layer) = selected_layer {
-                if *sel_layer == layer.layer_id {
-                    if !layer.visible {
-                        frame.frame.fill = ui.visuals().widgets.inactive.bg_fill;
-                    } else {
-                        frame.frame.fill = ui.visuals().widgets.hovered.bg_fill;
-                    }
-                }
-            }
-
-            frame.paint(ui);
-        },
-    );
-
-    let response = r.response;
-
-    let is_layer_being_dragged = egui::DragAndDrop::has_payload_of_type::<LayerId>(ui.ctx());
-    if is_layer_being_dragged {
-        let layer_being_dragged: Arc<LayerId> = egui::DragAndDrop::payload(ui.ctx()).unwrap();
-        if *layer_being_dragged != layer.layer_id && response.contains_pointer() {
-            let rect = response.rect;
-            if let Some(pointer) = ui.input(|i| i.pointer.interact_pos()) {
-                let stroke = ui.style().visuals.widgets.active.fg_stroke;
-
-                let above = pointer.y < rect.center().y;
-                if above {
-                    // above us
-                    ui.painter().hline(rect.x_range(), rect.top(), stroke);
-                } else {
-                    // below us
-                    ui.painter().hline(rect.x_range(), rect.bottom(), stroke);
-                }
-
-                if let Some(payload) = response.dnd_release_payload() {
-                    // released this frame
-                    let layer_id: LayerId = *payload;
-                    // remove it from the array
-
-                    let pos = active_layers
-                        .iter()
-                        .position(|u| u.layer_id == layer_id)
-                        .unwrap();
-                    let layer = active_layers.remove(pos);
-
-                    // add it back at the appropriate index
-                    let mut add_idx = you;
-                    if !above {
-                        add_idx += 1;
-                    }
-                    if add_idx > active_layers.len() {
-                        active_layers.push(layer);
-                    } else {
-                        active_layers.insert(add_idx, layer);
-                    }
-
-                    needs_layers_update = true;
-                }
-            }
-        }
-    }
-
-    if response.dragged() {
-        egui::DragAndDrop::set_payload(ui.ctx(), layer.layer_id);
-        *selected_layer = Some(layer.layer_id);
-        let layer_id = egui::LayerId::new(Order::Tooltip, layer.layer_id.into());
-        ui.ctx().style_mut(|style| {
-            style.visuals.window_fill = DESIGN_TOKENS
-                .get_color(
-                    "colors.accent.3",
-                    if ui.style().visuals.dark_mode {
-                        Theme::Dark
-                    } else {
-                        Theme::Light
-                    },
-                )
-                .unwrap();
-            style.visuals.widgets.noninteractive.fg_stroke.color = DESIGN_TOKENS
-                .get_color(
-                    "colors.accent.12",
-                    if ui.style().visuals.dark_mode {
-                        Theme::Dark
-                    } else {
-                        Theme::Light
-                    },
-                )
-                .unwrap();
-        });
-        egui::show_tooltip(ui.ctx(), layer_id, egui::Id::new("drag_detail"), |ui| {
-            ui.horizontal(|ui| {
-                ui.label(RichText::new(source.type_hint.icon()));
-                ui.label(RichText::new(&source.short_name));
-            })
-        });
-        // reset style
-        DESIGN_TOKENS.set_color(ui.ctx());
-    }
-
-    if response.drag_started() {}
-
-    // handle potential droppage
-    let Some(dragged_layer_id) =
-        egui::DragAndDrop::payload(ui.ctx()).map(|payload: Arc<LayerId>| (*payload))
-    else {
-        return (retain, needs_layers_update); // nothing is being dragged
-    };
-    ui.ctx().set_cursor_icon(CursorIcon::Grabbing);
-
-    (retain, needs_layers_update)
 }
diff --git a/crates/client/src/ui/shell/main_menu.rs b/crates/client/src/ui/shell/main_menu.rs
new file mode 100644
index 0000000..a56a9e3
--- /dev/null
+++ b/crates/client/src/ui/shell/main_menu.rs
@@ -0,0 +1,446 @@
+use crate::map::sources::{ActiveLayer, LayerId, LayerSource};
+use crate::map::tiles::{LayerManager, SourceId};
+use crate::ui::shell::bars::pane_header;
+use crate::ui::tokens::DESIGN_TOKENS;
+use crate::ui::UiExt;
+use eframe::emath::Align;
+use egui::epaint::Marginf;
+use egui::frame::Prepared;
+use egui::{
+    vec2, Button, Color32, CornerRadius, CursorIcon, FontFamily, Frame, Id, InnerResponse, Label,
+    Layout, Margin, Order, Rect, Response, RichText, ScrollArea, Sense, Separator, Theme, Ui,
+    UiBuilder, Visuals,
+};
+use std::collections::HashMap;
+use std::sync::Arc;
+use tracing::debug;
+
+fn outer_rect(f: &Prepared) -> Rect {
+    let content_rect = f.content_ui.min_rect();
+
+    content_rect + f.frame.inner_margin + Marginf::from(f.frame.stroke.width) + f.frame.outer_margin
+}
+
+pub fn main_menu(
+    ui: &mut egui::Ui,
+    add_open: &mut bool,
+    layer_manager: &mut LayerManager,
+    selected_layer: &mut Option<LayerId>,
+) {
+    let mut frame = Frame::side_top_panel(&ui.ctx().style());
+    frame.inner_margin.top = 4;
+    frame.inner_margin.left = 0;
+    frame.inner_margin.right = 0;
+
+    let mut needs_any_update = false;
+            pane_header(
+                ui,
+                "Layers",
+                Some(egui_phosphor::regular::STACK),
+                false,
+                |ui| {
+                    ui.style_mut().visuals.widgets.inactive.weak_bg_fill = Color32::TRANSPARENT;
+                    if ui
+                        .button(RichText::new(egui_phosphor::regular::PLUS).size(12.0))
+                        .clicked()
+                    {
+                        *add_open = true;
+                    }
+                },
+            );
+
+            let lm_registry = &layer_manager.registered_sources;
+            let layers_to_draw = layer_manager.active_layers.len();
+
+            let mut layers_to_remove = vec![];
+
+            for i in 0..layer_manager.active_layers.len() {
+                let (retain, update_layers) = draw_layer(
+                    ui,
+                    i,
+                    lm_registry,
+                    &mut layer_manager.active_layers,
+                    selected_layer,
+                );
+
+                if !retain {
+                    layers_to_remove.push(layer_manager.active_layers[i].layer_id);
+                }
+                needs_any_update = needs_any_update || update_layers;
+            }
+
+            for layer_to_remove in &layers_to_remove {
+                if *selected_layer == Some(*layer_to_remove) {
+                    *selected_layer = None;
+                }
+            }
+
+            layer_manager
+                .active_layers
+                .retain(|layer| !layers_to_remove.contains(&layer.layer_id));
+
+    if *add_open {
+        let mut frame = Frame::popup(&ui.ctx().style()).corner_radius(CornerRadius::ZERO);
+        frame.inner_margin.left = 0;
+        frame.inner_margin.right = 0;
+        egui::Modal::new(Id::new("add_source_modal"))
+            .frame(frame)
+            .show(ui.ctx(), |ui| {
+                pane_header(
+                    ui,
+                    "Add layer",
+                    Some(egui_phosphor::regular::STACK_PLUS),
+                    false,
+                    |ui| {
+                        ui.style_mut().visuals.widgets.inactive.weak_bg_fill = Color32::TRANSPARENT;
+                        if ui
+                            .button(RichText::new(egui_phosphor::regular::X).size(12.0))
+                            .clicked()
+                        {
+                            *add_open = false;
+                        }
+                    },
+                );
+
+                for (id, registered_layer) in &layer_manager.registered_sources {
+                    // ghost button
+
+                    let mut frame = Frame::new();
+
+                    frame.inner_margin.top = 4;
+                    frame.inner_margin.left = 8;
+                    frame.inner_margin.right = 8;
+                    frame.inner_margin.bottom = 4;
+
+                    let mut frame = frame.begin(ui);
+
+                    {
+                        frame.content_ui.horizontal(|ui| {
+                            let mut frame = Frame::new().fill(
+                                DESIGN_TOKENS
+                                    .get_color(
+                                        "colors.base.4",
+                                        if ui.style().visuals.dark_mode {
+                                            Theme::Dark
+                                        } else {
+                                            Theme::Light
+                                        },
+                                    )
+                                    .unwrap(),
+                            );
+
+                            frame.inner_margin.top = 4;
+                            frame.inner_margin.left = 8;
+                            frame.inner_margin.right = 8;
+                            frame.inner_margin.bottom = 4;
+
+                            frame.corner_radius = CornerRadius::from(4);
+
+                            frame.show(ui, |ui| {
+                                ui.label(
+                                    RichText::new(registered_layer.type_hint.icon()).size(24.0),
+                                );
+                            });
+
+                            ui.vertical(|ui| {
+                                ui.label(&registered_layer.display_name);
+                                ui.horizontal(|ui| {
+                                    let type_hint = registered_layer.type_hint.name();
+                                    let icon = registered_layer.type_hint.icon();
+
+                                    let type_lbl = format!("{icon} {type_hint}");
+                                    ui.allocate_ui_with_layout(
+                                        vec2(90.0, 20.0),
+                                        Layout::left_to_right(Align::Center),
+                                        |ui| {
+                                            ui.label(RichText::new(type_lbl).weak());
+                                            ui.add_space(ui.available_width());
+                                        },
+                                    );
+
+                                    ui.allocate_ui_with_layout(
+                                        vec2(120.0, 20.0),
+                                        Layout::left_to_right(Align::Center),
+                                        |ui| {
+                                            ui.label(
+                                                RichText::new(format!(
+                                                    "{} {}",
+                                                    egui_phosphor::regular::NAVIGATION_ARROW,
+                                                    &registered_layer.location
+                                                ))
+                                                    .weak(),
+                                            );
+                                            ui.add_space(ui.available_width());
+                                        },
+                                    );
+
+                                    ui.allocate_ui_with_layout(
+                                        vec2(100.0, 20.0),
+                                        Layout::left_to_right(Align::Center),
+                                        |ui| {
+                                            ui.label(
+                                                RichText::new(format!(
+                                                    "{} {}",
+                                                    egui_phosphor::regular::IDENTIFICATION_BADGE,
+                                                    &registered_layer.source
+                                                ))
+                                                    .weak(),
+                                            );
+                                            ui.add_space(ui.available_width());
+                                        },
+                                    );
+                                });
+                            });
+
+                            ui.add_space(ui.available_width());
+                        });
+                    }
+                    let response = ui.allocate_rect(outer_rect(&frame), Sense::click_and_drag());
+                    if response.hovered() {
+                        frame.frame.fill = DESIGN_TOKENS
+                            .get_color(
+                                "colors.base.7",
+                                if ui.style().visuals.dark_mode {
+                                    Theme::Dark
+                                } else {
+                                    Theme::Light
+                                },
+                            )
+                            .unwrap();
+                        ui.ctx()
+                            .output_mut(|u| u.cursor_icon = CursorIcon::PointingHand);
+                    }
+                    if response.clicked() {
+                        layer_manager.active_layers.push(ActiveLayer {
+                            source_id: *id,
+                            layer_id: LayerId::new(),
+                            visible: true,
+                        });
+                        debug!("{:?}", layer_manager);
+                        *add_open = false;
+                    }
+                    frame.paint(ui);
+                }
+            });
+    }
+}
+
+fn draw_layer(
+    ui: &mut Ui,
+    you: usize,
+    registry: &HashMap<SourceId, LayerSource>,
+    active_layers: &mut Vec<ActiveLayer>,
+    selected_layer: &mut Option<LayerId>,
+) -> (bool, bool) {
+    let mut retain = true;
+    let mut needs_layers_update = false;
+    let layer = active_layers[you];
+
+    let source = registry.get(&layer.source_id).unwrap();
+
+    let r = ui.scope_builder(
+        UiBuilder::new()
+            .id_salt(layer.layer_id)
+            .sense(Sense::click_and_drag()),
+        |ui| {
+            let title = source.short_name.as_str();
+            let icon = source.type_hint.icon();
+
+            let mut any_children_hovered = false;
+
+            let mut frame = Frame::new();
+            //frame.fill = ui.style().visuals.hyperlink_color;
+            frame.inner_margin.left = 8;
+            frame.inner_margin.right = 8;
+            frame.inner_margin.top = 2;
+            frame.inner_margin.bottom = 2;
+
+            let mut frame = frame.begin(ui);
+            {
+                frame.content_ui.horizontal(|ui| {
+                    ui.add(
+                        Label::new(
+                            RichText::new(egui_phosphor::regular::DOTS_SIX_VERTICAL)
+                                .size(12.0)
+                                .family(FontFamily::Name("icon-only".into()))
+                                .weak(),
+                        )
+                            .sense(Sense::empty())
+                            .selectable(false),
+                    );
+
+                    ui.add_space(8.0);
+
+                    let mut name_label = RichText::new(format!("{icon} {title}"));
+                    if !layer.visible {
+                        name_label = name_label.weak();
+                    }
+
+                    ui.add(
+                        Label::new(name_label)
+                            .sense(Sense::empty())
+                            .selectable(false),
+                    );
+
+                    ui.with_layout(Layout::right_to_left(Align::Center), |ui| {
+                        ui.style_mut().visuals.widgets.inactive.weak_bg_fill = Color32::TRANSPARENT;
+
+                        let mut visibility_label = RichText::new(if layer.visible {
+                            egui_phosphor::regular::EYE
+                        } else {
+                            egui_phosphor::regular::EYE_SLASH
+                        })
+                            .size(12.0)
+                            .family(FontFamily::Name("icon-only".into()));
+
+                        if !layer.visible {
+                            visibility_label = visibility_label.weak();
+                        }
+
+                        let visibility_btn = ui.button(visibility_label);
+
+                        any_children_hovered = any_children_hovered || visibility_btn.hovered();
+                        if visibility_btn.clicked() {
+                            active_layers[you].visible = !active_layers[you].visible;
+                        }
+
+                        let mut delete_label = RichText::new(egui_phosphor::regular::TRASH)
+                            .size(12.0)
+                            .family(FontFamily::Name("icon-only".into()));
+
+                        if !layer.visible {
+                            delete_label = delete_label.weak();
+                        }
+
+                        let delete_button = ui.button(delete_label);
+                        any_children_hovered = any_children_hovered || delete_button.hovered();
+
+                        if delete_button.clicked() {
+                            retain = false;
+                            needs_layers_update = true;
+                        }
+                    });
+                });
+            }
+            frame.allocate_space(ui);
+
+            let response = ui.response();
+
+            if response.hovered() && !any_children_hovered {
+                frame.frame.fill = ui.visuals().widgets.active.weak_bg_fill;
+                ui.ctx().output_mut(|u| u.cursor_icon = CursorIcon::Grab);
+            }
+
+            if response.clicked() {
+                *selected_layer = Some(layer.layer_id);
+            }
+
+            if let Some(sel_layer) = selected_layer {
+                if *sel_layer == layer.layer_id {
+                    if !layer.visible {
+                        frame.frame.fill = ui.visuals().widgets.inactive.bg_fill;
+                    } else {
+                        frame.frame.fill = ui.visuals().widgets.hovered.bg_fill;
+                    }
+                }
+            }
+
+            frame.paint(ui);
+        },
+    );
+
+    let response = r.response;
+
+    let is_layer_being_dragged = egui::DragAndDrop::has_payload_of_type::<LayerId>(ui.ctx());
+    if is_layer_being_dragged {
+        let layer_being_dragged: Arc<LayerId> = egui::DragAndDrop::payload(ui.ctx()).unwrap();
+        if *layer_being_dragged != layer.layer_id && response.contains_pointer() {
+            let rect = response.rect;
+            if let Some(pointer) = ui.input(|i| i.pointer.interact_pos()) {
+                let stroke = ui.style().visuals.widgets.active.fg_stroke;
+
+                let above = pointer.y < rect.center().y;
+                if above {
+                    // above us
+                    ui.painter().hline(rect.x_range(), rect.top(), stroke);
+                } else {
+                    // below us
+                    ui.painter().hline(rect.x_range(), rect.bottom(), stroke);
+                }
+
+                if let Some(payload) = response.dnd_release_payload() {
+                    // released this frame
+                    let layer_id: LayerId = *payload;
+                    // remove it from the array
+
+                    let pos = active_layers
+                        .iter()
+                        .position(|u| u.layer_id == layer_id)
+                        .unwrap();
+                    let layer = active_layers.remove(pos);
+
+                    // add it back at the appropriate index
+                    let mut add_idx = you;
+                    if !above {
+                        add_idx += 1;
+                    }
+                    if add_idx > active_layers.len() {
+                        active_layers.push(layer);
+                    } else {
+                        active_layers.insert(add_idx, layer);
+                    }
+
+                    needs_layers_update = true;
+                }
+            }
+        }
+    }
+
+    if response.dragged() {
+        egui::DragAndDrop::set_payload(ui.ctx(), layer.layer_id);
+        *selected_layer = Some(layer.layer_id);
+        let layer_id = egui::LayerId::new(Order::Tooltip, layer.layer_id.into());
+        ui.ctx().style_mut(|style| {
+            style.visuals.window_fill = DESIGN_TOKENS
+                .get_color(
+                    "colors.accent.3",
+                    if ui.style().visuals.dark_mode {
+                        Theme::Dark
+                    } else {
+                        Theme::Light
+                    },
+                )
+                .unwrap();
+            style.visuals.widgets.noninteractive.fg_stroke.color = DESIGN_TOKENS
+                .get_color(
+                    "colors.accent.12",
+                    if ui.style().visuals.dark_mode {
+                        Theme::Dark
+                    } else {
+                        Theme::Light
+                    },
+                )
+                .unwrap();
+        });
+        egui::show_tooltip(ui.ctx(), layer_id, egui::Id::new("drag_detail"), |ui| {
+            ui.horizontal(|ui| {
+                ui.label(RichText::new(source.type_hint.icon()));
+                ui.label(RichText::new(&source.short_name));
+            })
+        });
+        // reset style
+        DESIGN_TOKENS.set_color(ui.ctx());
+    }
+
+    if response.drag_started() {}
+
+    // handle potential droppage
+    let Some(dragged_layer_id) =
+        egui::DragAndDrop::payload(ui.ctx()).map(|payload: Arc<LayerId>| (*payload))
+    else {
+        return (retain, needs_layers_update); // nothing is being dragged
+    };
+    ui.ctx().set_cursor_icon(CursorIcon::Grabbing);
+
+    (retain, needs_layers_update)
+}
diff --git a/crates/client/src/ui/shell/mod.rs b/crates/client/src/ui/shell/mod.rs
index 7de822b..33a90d3 100644
--- a/crates/client/src/ui/shell/mod.rs
+++ b/crates/client/src/ui/shell/mod.rs
@@ -3,3 +3,5 @@ pub mod footer;
 pub mod left_bar;
 pub mod right_bar;
 pub mod top_bar;
+mod main_menu;
+pub mod inner_footer;
\ No newline at end of file