From a6bed95de36ed41354a8794d824c29af27c19ffa Mon Sep 17 00:00:00 2001
From: asimon-1 <40246417+asimon-1@users.noreply.github.com>
Date: Sat, 9 Apr 2022 18:10:44 -0400
Subject: [PATCH] Tabbed Web Menu (#333)

* Web menu refactor

* Fix some menu items

* Fixes for quick_menu, general clippy fixes

* Revert small testing change

* Add quick menu SVG

* Fix defaults saving/loading

* Log the last URL from the web menu

Co-authored-by: jugeeya <jugeeya@live.com>
---
 src/common/menu.rs                  |  44 +-
 src/common/mod.rs                   | 276 ++++----
 src/lib.rs                          |  35 +-
 src/static/css/training_modpack.css | 466 ++++++-------
 src/static/img/quick_menu.svg       |  29 +
 src/static/js/training_modpack.js   | 399 ++++++------
 src/templates/menu.html             | 160 ++---
 training_mod_consts/src/lib.rs      | 974 ++++++++++++----------------
 training_mod_tui/src/lib.rs         | 163 ++---
 training_mod_tui/src/list.rs        |  53 +-
 training_mod_tui/src/main.rs        |   4 +-
 11 files changed, 1137 insertions(+), 1466 deletions(-)
 create mode 100644 src/static/img/quick_menu.svg

diff --git a/src/common/menu.rs b/src/common/menu.rs
index ee8f787..bc074e6 100644
--- a/src/common/menu.rs
+++ b/src/common/menu.rs
@@ -66,10 +66,10 @@ pub unsafe fn write_menu() {
 
 const MENU_CONF_PATH: &str = "sd:/TrainingModpack/training_modpack_menu.conf";
 
-pub fn set_menu_from_url(orig_last_url: &str) {
-    let last_url = &orig_last_url.replace("&save_defaults=1", "");
+pub fn set_menu_from_url(last_url: &str) {
     unsafe {
-        MENU = get_menu_from_url(MENU, last_url);
+        MENU = get_menu_from_url(MENU, last_url, false);
+        DEFAULTS_MENU = get_menu_from_url(MENU, last_url, true);
 
         if MENU.quick_menu == OnOff::Off {
             if is_emulator() {
@@ -83,17 +83,6 @@ pub fn set_menu_from_url(orig_last_url: &str) {
         }
     }
 
-    if last_url.len() != orig_last_url.len() {
-        // Save as default
-        unsafe {
-            DEFAULT_MENU = MENU;
-            write_menu();
-        }
-        let menu_defaults_conf_path = "sd:/TrainingModpack/training_modpack_menu_defaults.conf";
-        std::fs::write(menu_defaults_conf_path, last_url)
-            .expect("Failed to write default menu conf file");
-    }
-
     std::fs::write(MENU_CONF_PATH, last_url).expect("Failed to write menu conf file");
     unsafe {
         EVENT_QUEUE.push(Event::menu_open(last_url.to_string()));
@@ -115,19 +104,22 @@ pub fn spawn_menu() {
 
     if !quick_menu {
         let fname = "training_menu.html";
-        let params = unsafe { MENU.to_url_params() };
-        let page_response = Webpage::new()
-            .background(Background::BlurredScreenshot)
-            .htdocs_dir("training_modpack")
-            .boot_display(BootDisplay::BlurredScreenshot)
-            .boot_icon(true)
-            .start_page(&format!("{}{}", fname, params))
-            .open()
-            .unwrap();
+        unsafe {
+            let params = MENU.to_url_params(false);
+            let default_params = DEFAULTS_MENU.to_url_params(true);
+            let page_response = Webpage::new()
+                .background(Background::BlurredScreenshot)
+                .htdocs_dir("training_modpack")
+                .boot_display(BootDisplay::BlurredScreenshot)
+                .boot_icon(true)
+                .start_page(&format!("{}?{}&{}", fname, params, default_params))
+                .open()
+                .unwrap();
 
-        let orig_last_url = page_response.get_last_url().unwrap();
-
-        set_menu_from_url(orig_last_url);
+            let last_url = page_response.get_last_url().unwrap();
+            println!("Received URL from web menu: {}", last_url);
+            set_menu_from_url(last_url);
+        }
     } else {
         unsafe {
             QUICK_MENU_ACTIVE = true;
diff --git a/src/common/mod.rs b/src/common/mod.rs
index 1673bc5..be0c7ea 100644
--- a/src/common/mod.rs
+++ b/src/common/mod.rs
@@ -1,138 +1,138 @@
-pub mod consts;
-pub mod events;
-pub mod menu;
-pub mod raygun_printer;
-pub mod release;
-
-use crate::common::consts::*;
-use smash::app::{self, lua_bind::*};
-use smash::lib::lua_const::*;
-
-pub use crate::common::consts::MENU;
-pub static mut DEFAULT_MENU: TrainingModpackMenu = crate::common::consts::DEFAULT_MENU;
-pub static mut BASE_MENU: TrainingModpackMenu = unsafe { DEFAULT_MENU };
-pub static mut FIGHTER_MANAGER_ADDR: usize = 0;
-pub static mut STAGE_MANAGER_ADDR: usize = 0;
-
-#[cfg(not(feature = "outside_training_mode"))]
-extern "C" {
-    #[link_name = "\u{1}_ZN3app9smashball16is_training_modeEv"]
-    pub fn is_training_mode() -> bool;
-}
-
-#[cfg(feature = "outside_training_mode")]
-pub fn is_training_mode() -> bool {
-    return true;
-}
-
-pub fn get_category(module_accessor: &mut app::BattleObjectModuleAccessor) -> i32 {
-    (module_accessor.info >> 28) as u8 as i32
-}
-
-pub fn is_emulator() -> bool {
-    unsafe { skyline::hooks::getRegionAddress(skyline::hooks::Region::Text) as u64 == 0x8004000 }
-}
-
-pub fn get_module_accessor(fighter_id: FighterId) -> *mut app::BattleObjectModuleAccessor {
-    let entry_id_int = fighter_id as i32;
-    let entry_id = app::FighterEntryID(entry_id_int);
-    unsafe {
-        let mgr = *(FIGHTER_MANAGER_ADDR as *mut *mut app::FighterManager);
-        let fighter_entry =
-            FighterManager::get_fighter_entry(mgr, entry_id) as *mut app::FighterEntry;
-        let current_fighter_id = FighterEntry::current_fighter_id(fighter_entry);
-        app::sv_battle_object::module_accessor(current_fighter_id as u32)
-    }
-}
-
-pub fn is_fighter(module_accessor: &mut app::BattleObjectModuleAccessor) -> bool {
-    get_category(module_accessor) == BATTLE_OBJECT_CATEGORY_FIGHTER
-}
-
-pub fn is_operation_cpu(module_accessor: &mut app::BattleObjectModuleAccessor) -> bool {
-    unsafe {
-        if !is_fighter(module_accessor) {
-            return false;
-        }
-
-        let entry_id_int =
-            WorkModule::get_int(module_accessor, *FIGHTER_INSTANCE_WORK_ID_INT_ENTRY_ID) as i32;
-
-        if entry_id_int == 0 {
-            return false;
-        }
-
-        let entry_id = app::FighterEntryID(entry_id_int);
-        let mgr = *(FIGHTER_MANAGER_ADDR as *mut *mut app::FighterManager);
-        let fighter_information =
-            FighterManager::get_fighter_information(mgr, entry_id) as *mut app::FighterInformation;
-
-        FighterInformation::is_operation_cpu(fighter_information)
-    }
-}
-
-pub fn is_grounded(module_accessor: &mut app::BattleObjectModuleAccessor) -> bool {
-    let situation_kind = unsafe { StatusModule::situation_kind(module_accessor) as i32 };
-
-    situation_kind == SITUATION_KIND_GROUND
-}
-
-pub fn is_airborne(module_accessor: &mut app::BattleObjectModuleAccessor) -> bool {
-    let situation_kind = unsafe { StatusModule::situation_kind(module_accessor) as i32 };
-
-    situation_kind == SITUATION_KIND_AIR
-}
-
-pub fn is_idle(module_accessor: &mut app::BattleObjectModuleAccessor) -> bool {
-    let status_kind = unsafe { StatusModule::status_kind(module_accessor) };
-
-    status_kind == FIGHTER_STATUS_KIND_WAIT
-}
-
-pub fn is_in_hitstun(module_accessor: &mut app::BattleObjectModuleAccessor) -> bool {
-    let status_kind = unsafe { StatusModule::status_kind(module_accessor) };
-
-    (*FIGHTER_STATUS_KIND_DAMAGE..*FIGHTER_STATUS_KIND_DAMAGE_FALL).contains(&status_kind)
-}
-pub fn is_in_footstool(module_accessor: &mut app::BattleObjectModuleAccessor) -> bool {
-    let status_kind = unsafe { StatusModule::status_kind(module_accessor) };
-
-    (*FIGHTER_STATUS_KIND_TREAD_DAMAGE..=*FIGHTER_STATUS_KIND_TREAD_FALL).contains(&status_kind)
-}
-
-pub fn is_shielding(module_accessor: *mut app::BattleObjectModuleAccessor) -> bool {
-    let status_kind = unsafe { StatusModule::status_kind(module_accessor) as i32 };
-
-    (*FIGHTER_STATUS_KIND_GUARD_ON..=*FIGHTER_STATUS_KIND_GUARD_DAMAGE).contains(&status_kind)
-}
-
-pub fn is_in_shieldstun(module_accessor: &mut app::BattleObjectModuleAccessor) -> bool {
-    let status_kind = unsafe { StatusModule::status_kind(module_accessor) };
-    let prev_status = unsafe { StatusModule::prev_status_kind(module_accessor, 0) };
-
-    // If we are taking shield damage or we are droping shield from taking shield damage we are in hitstun
-    status_kind == FIGHTER_STATUS_KIND_GUARD_DAMAGE
-        || (prev_status == FIGHTER_STATUS_KIND_GUARD_DAMAGE
-            && status_kind == FIGHTER_STATUS_KIND_GUARD_OFF)
-}
-
-pub unsafe fn is_dead(module_accessor: &mut app::BattleObjectModuleAccessor) -> bool {
-    let fighter_kind = app::utility::get_kind(module_accessor);
-    let fighter_is_ptrainer = [
-        *FIGHTER_KIND_PZENIGAME,
-        *FIGHTER_KIND_PFUSHIGISOU,
-        *FIGHTER_KIND_PLIZARDON,
-    ]
-    .contains(&fighter_kind);
-    let status_kind = StatusModule::status_kind(module_accessor) as i32;
-    let prev_status_kind = StatusModule::prev_status_kind(module_accessor, 0);
-    // Pokemon trainer enters FIGHTER_STATUS_KIND_WAIT for one frame during their respawn animation
-    // And the previous status is FIGHTER_STATUS_NONE
-    if fighter_is_ptrainer {
-        [*FIGHTER_STATUS_KIND_DEAD, *FIGHTER_STATUS_KIND_STANDBY].contains(&status_kind)
-            || (status_kind == FIGHTER_STATUS_KIND_WAIT
-                && prev_status_kind == FIGHTER_STATUS_KIND_NONE)
-    } else {
-        [*FIGHTER_STATUS_KIND_DEAD, *FIGHTER_STATUS_KIND_STANDBY].contains(&status_kind)
-    }
-}
+pub mod consts;
+pub mod events;
+pub mod menu;
+pub mod raygun_printer;
+pub mod release;
+
+use crate::common::consts::*;
+use smash::app::{self, lua_bind::*};
+use smash::lib::lua_const::*;
+
+pub use crate::common::consts::MENU;
+pub static mut DEFAULTS_MENU: TrainingModpackMenu = crate::common::consts::DEFAULTS_MENU;
+pub static mut BASE_MENU: TrainingModpackMenu = unsafe { DEFAULTS_MENU };
+pub static mut FIGHTER_MANAGER_ADDR: usize = 0;
+pub static mut STAGE_MANAGER_ADDR: usize = 0;
+
+#[cfg(not(feature = "outside_training_mode"))]
+extern "C" {
+    #[link_name = "\u{1}_ZN3app9smashball16is_training_modeEv"]
+    pub fn is_training_mode() -> bool;
+}
+
+#[cfg(feature = "outside_training_mode")]
+pub fn is_training_mode() -> bool {
+    return true;
+}
+
+pub fn get_category(module_accessor: &mut app::BattleObjectModuleAccessor) -> i32 {
+    (module_accessor.info >> 28) as u8 as i32
+}
+
+pub fn is_emulator() -> bool {
+    unsafe { skyline::hooks::getRegionAddress(skyline::hooks::Region::Text) as u64 == 0x8004000 }
+}
+
+pub fn get_module_accessor(fighter_id: FighterId) -> *mut app::BattleObjectModuleAccessor {
+    let entry_id_int = fighter_id as i32;
+    let entry_id = app::FighterEntryID(entry_id_int);
+    unsafe {
+        let mgr = *(FIGHTER_MANAGER_ADDR as *mut *mut app::FighterManager);
+        let fighter_entry =
+            FighterManager::get_fighter_entry(mgr, entry_id) as *mut app::FighterEntry;
+        let current_fighter_id = FighterEntry::current_fighter_id(fighter_entry);
+        app::sv_battle_object::module_accessor(current_fighter_id as u32)
+    }
+}
+
+pub fn is_fighter(module_accessor: &mut app::BattleObjectModuleAccessor) -> bool {
+    get_category(module_accessor) == BATTLE_OBJECT_CATEGORY_FIGHTER
+}
+
+pub fn is_operation_cpu(module_accessor: &mut app::BattleObjectModuleAccessor) -> bool {
+    unsafe {
+        if !is_fighter(module_accessor) {
+            return false;
+        }
+
+        let entry_id_int =
+            WorkModule::get_int(module_accessor, *FIGHTER_INSTANCE_WORK_ID_INT_ENTRY_ID) as i32;
+
+        if entry_id_int == 0 {
+            return false;
+        }
+
+        let entry_id = app::FighterEntryID(entry_id_int);
+        let mgr = *(FIGHTER_MANAGER_ADDR as *mut *mut app::FighterManager);
+        let fighter_information =
+            FighterManager::get_fighter_information(mgr, entry_id) as *mut app::FighterInformation;
+
+        FighterInformation::is_operation_cpu(fighter_information)
+    }
+}
+
+pub fn is_grounded(module_accessor: &mut app::BattleObjectModuleAccessor) -> bool {
+    let situation_kind = unsafe { StatusModule::situation_kind(module_accessor) as i32 };
+
+    situation_kind == SITUATION_KIND_GROUND
+}
+
+pub fn is_airborne(module_accessor: &mut app::BattleObjectModuleAccessor) -> bool {
+    let situation_kind = unsafe { StatusModule::situation_kind(module_accessor) as i32 };
+
+    situation_kind == SITUATION_KIND_AIR
+}
+
+pub fn is_idle(module_accessor: &mut app::BattleObjectModuleAccessor) -> bool {
+    let status_kind = unsafe { StatusModule::status_kind(module_accessor) };
+
+    status_kind == FIGHTER_STATUS_KIND_WAIT
+}
+
+pub fn is_in_hitstun(module_accessor: &mut app::BattleObjectModuleAccessor) -> bool {
+    let status_kind = unsafe { StatusModule::status_kind(module_accessor) };
+
+    (*FIGHTER_STATUS_KIND_DAMAGE..*FIGHTER_STATUS_KIND_DAMAGE_FALL).contains(&status_kind)
+}
+pub fn is_in_footstool(module_accessor: &mut app::BattleObjectModuleAccessor) -> bool {
+    let status_kind = unsafe { StatusModule::status_kind(module_accessor) };
+
+    (*FIGHTER_STATUS_KIND_TREAD_DAMAGE..=*FIGHTER_STATUS_KIND_TREAD_FALL).contains(&status_kind)
+}
+
+pub fn is_shielding(module_accessor: *mut app::BattleObjectModuleAccessor) -> bool {
+    let status_kind = unsafe { StatusModule::status_kind(module_accessor) as i32 };
+
+    (*FIGHTER_STATUS_KIND_GUARD_ON..=*FIGHTER_STATUS_KIND_GUARD_DAMAGE).contains(&status_kind)
+}
+
+pub fn is_in_shieldstun(module_accessor: &mut app::BattleObjectModuleAccessor) -> bool {
+    let status_kind = unsafe { StatusModule::status_kind(module_accessor) };
+    let prev_status = unsafe { StatusModule::prev_status_kind(module_accessor, 0) };
+
+    // If we are taking shield damage or we are droping shield from taking shield damage we are in hitstun
+    status_kind == FIGHTER_STATUS_KIND_GUARD_DAMAGE
+        || (prev_status == FIGHTER_STATUS_KIND_GUARD_DAMAGE
+            && status_kind == FIGHTER_STATUS_KIND_GUARD_OFF)
+}
+
+pub unsafe fn is_dead(module_accessor: &mut app::BattleObjectModuleAccessor) -> bool {
+    let fighter_kind = app::utility::get_kind(module_accessor);
+    let fighter_is_ptrainer = [
+        *FIGHTER_KIND_PZENIGAME,
+        *FIGHTER_KIND_PFUSHIGISOU,
+        *FIGHTER_KIND_PLIZARDON,
+    ]
+    .contains(&fighter_kind);
+    let status_kind = StatusModule::status_kind(module_accessor) as i32;
+    let prev_status_kind = StatusModule::prev_status_kind(module_accessor, 0);
+    // Pokemon trainer enters FIGHTER_STATUS_KIND_WAIT for one frame during their respawn animation
+    // And the previous status is FIGHTER_STATUS_NONE
+    if fighter_is_ptrainer {
+        [*FIGHTER_STATUS_KIND_DEAD, *FIGHTER_STATUS_KIND_STANDBY].contains(&status_kind)
+            || (status_kind == FIGHTER_STATUS_KIND_WAIT
+                && prev_status_kind == FIGHTER_STATUS_KIND_NONE)
+    } else {
+        [*FIGHTER_STATUS_KIND_DEAD, *FIGHTER_STATUS_KIND_STANDBY].contains(&status_kind)
+    }
+}
diff --git a/src/lib.rs b/src/lib.rs
index d110a66..9fa5a17 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -71,7 +71,7 @@ pub fn render_text_to_screen(s: &str) {
 pub fn main() {
     macro_rules! log {
         ($($arg:tt)*) => {
-            print!("{}{}", "[Training Modpack] ".green(), format!($($arg)*));
+            println!("{}{}", "[Training Modpack] ".green(), format!($($arg)*));
         };
     }
 
@@ -105,10 +105,8 @@ pub fn main() {
         if menu_conf.starts_with(b"http://localhost") {
             log!("Previous menu found, loading from training_modpack_menu.conf");
             unsafe {
-                MENU = get_menu_from_url(MENU, std::str::from_utf8(&menu_conf).unwrap());
-                if is_emulator() {
-                    MENU.quick_menu = OnOff::On;
-                }
+                MENU = get_menu_from_url(MENU, std::str::from_utf8(&menu_conf).unwrap(), false);
+                DEFAULTS_MENU = get_menu_from_url(DEFAULTS_MENU, std::str::from_utf8(&menu_conf).unwrap(), true);
             }
         } else {
             log!("Previous menu found but is invalid.");
@@ -116,32 +114,10 @@ pub fn main() {
     } else {
         log!("No previous menu file found.");
     }
-
-    let menu_defaults_conf_path = "sd:/TrainingModpack/training_modpack_menu_defaults.conf";
-    log!("Checking for previous menu defaults in training_modpack_menu_defaults.conf...");
-    if fs::metadata(menu_defaults_conf_path).is_ok() {
-        let menu_defaults_conf = fs::read(menu_defaults_conf_path).unwrap();
-        if menu_defaults_conf.starts_with(b"http://localhost") {
-            log!("Menu defaults found, loading from training_modpack_menu_defaults.conf");
-            unsafe {
-                DEFAULT_MENU = get_menu_from_url(
-                    DEFAULT_MENU,
-                    std::str::from_utf8(&menu_defaults_conf).unwrap(),
-                );
-                if is_emulator() {
-                    DEFAULT_MENU.quick_menu = OnOff::On;
-                }
-                crate::menu::write_menu();
-            }
-        } else {
-            log!("Previous menu defaults found but are invalid.");
-        }
-    } else {
-        log!("No previous menu defaults found.");
-    }
-
+    
     if is_emulator() {
         unsafe {
+            DEFAULTS_MENU.quick_menu = OnOff::On;
             MENU.quick_menu = OnOff::On;
         }
     }
@@ -195,6 +171,7 @@ pub fn main() {
                         // Leave menu.
                         menu::QUICK_MENU_ACTIVE = false;
                         crate::menu::set_menu_from_url(url.as_str());
+                        println!("URL: {}", url.as_str());
                     }
                 });
                 button_presses.zl.read_press().then(|| {
diff --git a/src/static/css/training_modpack.css b/src/static/css/training_modpack.css
index aa57bb5..46286ae 100644
--- a/src/static/css/training_modpack.css
+++ b/src/static/css/training_modpack.css
@@ -20,337 +20,261 @@
     }
 }
 
-.answer-border-outer {
-    margin-top: 5px;
-}
-
-.button-icon {
-    height: 53px;
-    margin-left: 4px;
-    width: 53px;
-}
-
-.button-icon-wrapper {
-    align-items: center;
-    background: #000000;
+.tab-list-container {
+    overflow: hidden;
+    background-color: #555;
     display: flex;
-    height: 58px;
-    margin-left: -1px;
-    width: 80px;
+    justify-content: flex-start;
+    width: 100%;
+    align-items: center;
 }
 
-.button-msg-wrapper {
-    width: 259px;
+.tab-list-container p {
+    color: #fff;
+    width: 130px;
+    height: fit-content;
+    margin: 0px 10px 0px 10px;
+    padding: 0px;
 }
 
-.checkbox-display {
-    margin: 10px 70px;
-}
 
-.checkbox-display::after {
-    /* Displayed Checkbox (unchecked) */
-    color: white;
-    content: "\E14C";
-}
-
-.defaults-checkbox-container {
-    /* Save Defaults Container */
+.tab-list {
+    overflow: hidden;
     display: flex;
-    flex-direction: column;
-    justify-content: center;
-    margin-top: 10px;
-    position: fixed;
-    right: 50px;
+    justify-content: flex-start;
+    width: 100%;
 }
 
-.flex-button {
-    align-items: center;
-    display: inline-flex;
-    flex: 1;
-    justify-content: center;
-    text-align: center;
-    vertical-align: middle;
+.tab-list button {
+    background-color: inherit;
+    float: left;
+    border: none;
+    outline: none;
+    cursor: pointer;
+    padding: 14px 16px;
+    color: #fff;
+    margin: 5px 0px 0px 8px;
+    border-radius: 8px 8px 0px 0px;
+    font-size: large;
 }
 
-.footer {
-    align-items: center;
-    background: #000000;
+.tab-list button:hover {
+    background: #797979;
+}
+
+.tab-list button.active {
+    color: #000;
+    background: #ccc;
+}
+
+.tab-content {
+    width: 100%;
     display: flex;
-    height: 73px;
-    justify-content: center;
-    position: fixed;
-    z-index: 10;
+    flex-wrap: wrap;
+    justify-content: space-evenly;
 }
 
-.header {
-    align-items: center;
-    background: #000000;
-    border-bottom: 7px solid #000000;
+body {
+    background: none;
+    font-family: "nintendo_ext_003", "nintendo_udsgr_std_003";
+    margin: 0;
+}
+
+/* Overwrite padding from keyword stuff. */
+.l-main-content {
+    padding: 0px 0px 0px;
+}
+
+/* Handle alignment of items in the header */
+.l-header {
     display: flex;
-    height: 65px;
-}
-
-.header-decoration {
-    align-items: center;
-    background: #a80114;
-    display: inline-flex;
-    height: 65px;
-    padding-left: 21px;
-    width: 101px;
-}
-
-.header-desc {
-    color: white;
+    justify-content: space-between;
+    position: relative;
+    width: 100%;
+    height: 80px;
+    z-index: 100;
+    background: #000;
+    box-shadow: 0px 1px 1px #000;
 }
 
 .header-title {
     color: #f46264;
     font-size: 26px;
     line-height: 2.5;
+    margin: 0px;
+    padding: 0px;
+    word-break: normal;
 }
 
-.is-appear {
-    opacity: 1;
+.header-label {
+    display: flex;
+    flex-direction: column;
+    justify-content: center;
+    align-items: end;
+    margin-right: 15px;
 }
 
-.is-focused .question-border::before {
-    background: #000000;
-    box-shadow: none;
+.header-label > p {
+    color: #fff;
+    margin: 0;
+    padding: 0;
 }
 
-.is-focused .question-message {
-    color: #FFFFFF;
-    text-shadow: 2px 0 0 #000, -2px 0 0 #000, 0 2px 0 #000, 0 -2px 0 #000, 1px 1px #000, -1px -1px 0 #000, 1px -1px 0 #000, -1px 1px 0 #000;
+.return-icon-container {
+    width: 101px;
+    height: 65px;
+    padding-left: 21px;
+    background: #a80114;
+    border-radius: 0px 0px 15px 0px;
 }
 
-.is-focused .question.scuffle-thema {
-    background: #df1624;
+.return-icon {
+    width: 58px;
+    height: 58px;
+    padding-left: 7px;
+    filter: drop-shadow(3px 5px 2px rgba(0, 0, 0, 0.8));
 }
 
-.is-focused .scuffle-thema {
-    background: #df1624;
+/* Center Icons */
+.question::before {
+    width: 70px;
 }
 
-.is-hidden {
-    display: none;
-}
-
-.is-opened .question {
-    bottom: 11px;
-}
-
-.is-opened .question-border::before {
-    background: #000000;
-    bottom: 5px;
-}
-
-.is-opened .question-outer {
-    height: 86px;
-    width: 100%;
-}
-
-.keyword-button {
-    background: #d9e4ea;
-    border: solid 4px #2e3c45;
-    box-shadow: 1px 1px 6px rgba(24, 24, 24, 0.6);
-    box-sizing: border-box;
-    height: 66px;
-    justify-content: flex-start;
-    position: relative;
-    z-index: 0;
-}
-
-.keyword-button-outer {
-    border: 5px solid transparent;
-    will-change: transform;
-}
-
-.keyword-message {
-    color: #2b3940;
-    font-size: 22px;
-    padding: 0px 5px;
-}
-
-.l-footer {
+/* Footer */
+.footer {
+    position: fixed;
     bottom: 0px;
     left: 0px;
-    width: 100%;
-}
-
-.l-grid {
+    background: #000;
     display: flex;
-    flex-wrap: wrap;
-}
-
-.l-header {
-    box-shadow: 0px 1px 1px #000000;
-    display: flex;
-    left: 0px;
-    position: absolute;
-    top: 0px;
+    justify-content: center;
+    align-items: center;
+    height: 73px;
     width: 100%;
+    color: #fff;
     z-index: 100;
 }
 
-.l-header-title {
-    align-items: center;
+/* Save Defaults Container */
+.defaults-checkbox-container {
+    position: fixed;
+    right: 50px;
+    margin-top: 10px;
     display: flex;
-    height: 100%;
     justify-content: center;
-    left: 0px;
-    position: absolute;
-    top: 0px;
-    width: 1280px;
-}
-
-.l-item {
-    margin: 0px 13px;
-}
-
-.l-main-content {
-    /* Overwrite padding from keyword stuff. */
-    padding: 0px 0px 0px;
-}
-
-.l-qa {
-    /* Column size */
-    flex-basis: 33%;
-}
-
-.l-qa:last-child .qa {
-    /* Overwrite margin on the last child to avoid overlap with footer */
-    margin-bottom: 75px;
-}
-
-.l-qa:last-child .qa.is-opened {
-    margin-bottom: 0px;
-}
-
-.qa {
-    display: block;
-    will-change: scroll-position;
-}
-
-.is-focused {
-    background: linear-gradient(90deg, rgb(255, 109, 0) 0%, rgb(255, 255, 0) 65%, rgb(255, 109, 0) 70%);
-    will-change: animation;
-    animation: background-slide 650ms linear infinite normal;
-}
-
-.question {
-    align-items: center;
-    background: #d9e4ea;
-    bottom: 11px;
-    display: flex;
-    left: 11px;
-    padding: 0px 30px 0px 94px;
-    position: absolute;
-    right: 28px;
-    top: 11px;
-}
-
-.question-border::before {
-    background: #2e3c45;
-    bottom: 6px;
-    box-shadow: 3px 3px 3px rgba(24, 24, 24, 0.5);
-    content: '';
-    left: 6px;
-    position: absolute;
-    right: 6px;
-    top: 6px;
-}
-
-.question-icon {
-    bottom: 0px;
-    height: 60px;
-    left: 2px;
-    position: absolute;
-    top: 2px;
-    transition: opacity 0.2s ease;
-    width: 60px;
-}
-
-.question-message {
-    color: #2b3940;
-    font-size: 23px;
-    width: 100%;
-    z-index: 0;
-}
-
-.question-message span {
-    display: block;
-    letter-spacing: normal;
-}
-
-.question-outer {
-    height: 86px;
-    position: relative;
-}
-
-.question::before {
-    width: 70px;
-    background: #000000;
-    bottom: 0px;
-    content: '';
-    left: 0px;
-    position: absolute;
-    top: 0px;
-}
-
-.ret-icon {
-    display: inline-block;
-    height: 58px;
-    transition: opacity 0.2s ease;
-    width: 58px;
-}
-
-.ret-icon-wrapper {
-    margin-left: -4px;
-    position: relative;
-    will-change: transform;
+    flex-direction: column;
 }
 
+/* Checkbox element (hidden) */
 #saveDefaults {
-    /* Checkbox element (hidden) */
-    left: -100vw;
     position: absolute;
+    left: -100vw;
 }
 
+.checkbox-display {
+    margin: 10px 70px;
+}
+
+/* Displayed Checkbox (unchecked) */
+.checkbox-display::after {
+    content: "\E14C";
+    color: white;
+}
+
+/* Displayed Checkbox (checked) */
 #saveDefaults:checked~.checkbox-display::after {
-    /* Displayed Checkbox (checked) */
     content: "\E14B";
 }
 
-a {
-    text-decoration: none;
+.menu-item {
+    /* Container div for menu link and menu list */
+    width: 30%;
+    margin: 6px;
 }
 
-body {
-    background: none;
-    width: 1280px;
-}
-
-body, div, th, td, p, ul, ol, dl, dt, dd, img, h1, h2, h3, h4, h5, h6, footer, header, nav, p, section, span, figure {
-    margin: 0px;
-    overflow-wrap: break-word;
+.menu-button, .menu-item > div button {
+    /* Item styling */
+    background-color: #d9e4ea;
+    border: solid black;
+    border-width: 3px 20px 3px 3px;
     padding: 0px;
-    word-break: normal;
-    font-family: "nintendo_ext_003", "nintendo_udsgr_std_003";
+    box-shadow: 3px 3px 3px #18181880;
+    display: flex;
+    height: 60px;
+    width: 100%;
+    align-items: center;
+    align-content: center;
+    user-select: none;
 }
 
-img, svg {
-    opacity: 0;
+.menu-item > div button {
+    z-index: 1;
+    width: 22%;
+    margin: 4px 17px;
 }
 
-img.question-icon:not(.toggle) {
-    /* Fade icons slightly */
-    opacity: 0.75;
+.modal p {
+    font-size: 18px !important;
 }
 
-span {
-    letter-spacing: 0.01px;
+.menu-icon {
+    background-color: black;
+    flex-basis: auto;
+    width: 60px;
+    height: 48px;
+    border: solid black;
+    border-right-width: 5px;
+}
+
+.menu-icon img {
+    width: 100%;
+    height: 100%;
+}
+
+.menu-item p {
+    font-size: 23px;
+    color: #2b3940;
+    width: 100%;
+    margin: 10px 0px 0px 20px;
+}
+
+.hide {
+    display: none !important;
+}
+
+.menu-item > div {
+    display: flex;
+    flex-wrap: wrap;
+    position: fixed;
+    justify-content: flex-start;
+    align-content: flex-start;
+    top: 80px;
+    bottom: 73px;
+    left: 0px;
+    right: 0px;
+    margin-top: 0px;
+    background-color: rgba(100, 100, 100, 0.9);
+}
+
+:focus {
+    background: rgb(255,70,2);
+    background: linear-gradient(45deg, rgba(255,70,2,1) 20%, rgba(255,215,0,1) 46%, rgba(255,215,0,1) 54%, rgba(255,70,2,1) 80%); 
+    background-size: 500% 100%;
+    will-change: animation;
+    animation: translate-anim 5s infinite linear;
+}
+
+:focus > p {
+    color: #fff;
+    text-shadow: -1px -1px 1px #000, 1px -1px 1px #000, -1px 1px 1px #000, 1px 1px 1px #000;
+}
+
+@keyframes translate-anim {
+	0% {
+		background-position: 0% 0%;
+	}
+	100% {
+		background-position: 100% 0%;
+	}
 }
 
-ul, ol {
-    list-style-type: none;
-}
\ No newline at end of file
diff --git a/src/static/img/quick_menu.svg b/src/static/img/quick_menu.svg
new file mode 100644
index 0000000..14cfdc8
--- /dev/null
+++ b/src/static/img/quick_menu.svg
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+   width="80"
+   height="80"
+   viewBox="0 0 21.166666 21.166667"
+   version="1.1"
+   id="svg5"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:svg="http://www.w3.org/2000/svg">
+  <defs
+     id="defs2" />
+  <g
+     id="layer1">
+    <path
+       style="fill:none;stroke:#ffffff;stroke-width:2.11667;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+       d="M 1.8552457,17.110952 8.469829,10.496369 1.8552457,3.8817854"
+       id="path1904" />
+    <path
+       style="fill:none;stroke:#ffffff;stroke-width:2.11667;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+       d="M 7.22203,17.110952 13.836612,10.49637 7.22203,3.8817855"
+       id="path1904-3" />
+    <path
+       style="fill:none;stroke:#ffffff;stroke-width:2.11667;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+       d="M 12.588812,17.110952 19.203396,10.496369 12.588812,3.8817854"
+       id="path1904-2" />
+  </g>
+</svg>
diff --git a/src/static/js/training_modpack.js b/src/static/js/training_modpack.js
index 8cc210f..8776bff 100644
--- a/src/static/js/training_modpack.js
+++ b/src/static/js/training_modpack.js
@@ -1,22 +1,23 @@
 var isNx = (typeof window.nx !== 'undefined');
-var prevQuestionMsg = null;
-var prevFocusedElm = null;
+var defaults_prefix = "__";
 
 if (isNx) {
-    window.nx.footer.setAssign('B', '', goBackHook, {se: ''});
-    window.nx.footer.setAssign('X', '', resetSubmenu, {se: ''});
+    window.nx.footer.setAssign('B', '', close_or_exit, {se: ''});
+    window.nx.footer.setAssign('X', '', resetCurrentSubmenu, {se: ''});
     window.nx.footer.setAssign('L', '', resetAllSubmenus, {se: ''});
-    window.nx.footer.setAssign('R', '', toggleSaveDefaults, {se: ''});
+    window.nx.footer.setAssign('R', '', saveDefaults, {se: ''});
+    window.nx.footer.setAssign('ZR', '', cycleNextTab, {se: ''});
+    window.nx.footer.setAssign('ZL', '', cyclePrevTab, {se: ''});
 } else {
-    document.getElementById("body").addEventListener('keypress', (event) => {
+    document.addEventListener('keypress', (event) => {
         switch (event.key) {
             case "b":
                 console.log("b");
-                goBackHook();
+                close_or_exit();
                 break;
             case "x":
                 console.log("x");
-                resetSubmenu();
+                resetCurrentSubmenu();
                 break;
             case "l":
                 console.log("l");
@@ -24,20 +25,87 @@ if (isNx) {
                 break;
             case "r":
                 console.log("r");
-                toggleSaveDefaults();
+                saveDefaults();
+                break;
+            case "p":
+                console.log("p");
+                cycleNextTab();
+                break;
+            case "o":
+                console.log("o");
+                cyclePrevTab();
                 break;
         }
     });
 }
 
-window.onload = setSettings;
+window.onload = onLoad;
+var settings = new Map();
+var lastFocusedItem = document.querySelector(".menu-item > button");
 
-function isTextNode(node) {
-    return node.nodeType == Node.TEXT_NODE
+function onLoad() {
+    // Activate the first tab
+    openTab(document.querySelector("button.tab-button"));
+
+    // Extract URL params and set appropriate settings
+    setSettingsFromURL();
+    setSubmenusFromSettings();
+}
+
+function openTab(e) {
+    var tab_id = e.id.replace("button", "tab");
+    var selected_tab = document.getElementById(tab_id);
+
+
+    // Hide all content for all tabs
+    closeAllItems();
+    tabcontent = document.getElementsByClassName("tab-content");
+    Array.from(tabcontent).forEach(element => {
+        element.classList.add("hide");
+    });
+
+
+    // Get all elements with class="tablinks" and remove the class "active"
+    tablinks = document.getElementsByClassName("tab-button");
+    Array.from(tablinks).forEach(element => {
+        element.classList.remove("active");
+    });
+
+    // Show the current tab, and add an "active" class to the button that opened the tab
+    e.classList.add("active");
+    selected_tab.classList.remove("hide");
+    selected_tab.querySelector("button").focus();
+}
+
+function openItem(e) {
+    playSound("SeWebMenuListOpen");
+    var modal = e.parentElement.querySelector(".modal");
+    modal.classList.toggle("hide");
+    modal.querySelector("button").focus();
+    lastFocusedItem = e;
+}
+
+function closeAllItems() {
+    var modals = document.querySelectorAll(".modal");
+    Array.from(modals).forEach(element => {
+        element.classList.add("hide");
+    });
+    lastFocusedItem.focus();
+}
+
+function toggleOption(e) {
+    playSound("SeSelectCheck");
+    if (e.parentElement.classList.contains("single-option")) {
+        selectSingleOption(e);
+    } else {
+        var img = e.querySelector("img");
+        img.classList.toggle("hide");
+    }
 }
 
 function closestClass(elem, class_) {
-    // Returns the closest anscestor (including self) with the given class
+    // Returns the closest ancestor (including self) with the given class
+    // TODO: Consider removing
     if (!elem) {
         // Reached the end of the DOM
         return null
@@ -49,46 +117,6 @@ function closestClass(elem, class_) {
         return closestClass(elem.parentElement, class_);
     }
 }
-
-function getElementByXpath(path) {
-    return document.evaluate(path, document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue;
-}
-
-function focusQA(e) {
-    playSound("SeSelectUncheck");
-    prevFocusedElm = e;
-    e.classList.add("is-focused");
-}
-
-function defocusQA(e) {
-    if (prevFocusedElm) {
-        prevFocusedElm.classList.remove('is-focused');
-
-    }
-    if (prevQuestionMsg) {
-        prevQuestionMsg.remove();
-        prevQuestionMsg = null;
-    }
-}
-
-function toggleAnswer(e) {
-    playSound("SeToggleBtnOn");
-    e.classList.toggle("is-opened");
-
-    // Toggle visibility of child answers
-    [].forEach.call(e.childNodes, function (child) {
-        if (!isTextNode(child) && child.classList.contains("answer-border-outer")) {
-            child.classList.toggle("is-hidden");
-        }
-    });
-
-    // Toggle visibility of sibling answers
-    var sibling = e.nextElementSibling;
-    if (sibling.classList.contains("answer-border-outer")) {
-        sibling.classList.toggle("is-hidden");
-    }
-}
-
 function playSound(label) {
     // Valid labels:
     // SeToggleBtnFocus
@@ -129,164 +157,120 @@ function playSound(label) {
     }
 }
 
-function goBackHook() {
+function exit() {
+    playSound("SeFooterDecideBack");
+    setSettingsFromMenu();
+    var url = buildURLFromSettings();
+
+    if (isNx) {
+        window.location.href = url;
+    } else {
+        console.log(url);
+    }
+}
+
+function close_or_exit() {
     // If any submenus are open, close them
     // Otherwise if all submenus are closed, exit the menu and return to the game
 
-    if (document.querySelectorAll(".qa.is-opened").length == 0) {
-        // If all submenus are closed, exit and return through localhost
-        playSound("SeFooterDecideBack");
-        var url = "http://localhost/";
-        
-        var settings = new Map();
-        
-        // Collect settings for toggles
-        
-        [].forEach.call(document.querySelectorAll("ul.l-grid"), function (toggle) {
-            var section = toggle.id;
-            var val = "";
-            
-            [].forEach.call(toggle.childNodes, function (child) {
-                if (!isTextNode(child) && child.querySelectorAll(".is-appear").length) {
-                    val += child.getAttribute("val") + ",";
-                };
-            });
-            
-            settings.set(section,val);
-        });
-        
-        // Collect settings for OnOffs
-        [].forEach.call(document.querySelectorAll("div.onoff"), function (onoff) {
-            var section = onoff.id;
-            var val = "";
-            if (onoff.querySelectorAll(".is-appear").length) {
-                val = "1";
-            } else {
-                val = "0";
-            }
-            settings.set(section,val);
-        });
-
-        url += "?";
-        settings.forEach((val, section) => { url += section + "=" + val + "&" } );
-
-        if (document.getElementById("saveDefaults").checked) {
-            url += "save_defaults=1";
-        } else {
-            url = url.slice(0, -1);
-        }
-
-        if (isNx) {
-            window.location.href = url;
-        } else {
-            console.log(url);
-        }
-    } else {
+    if (document.querySelector(".modal:not(.hide)")) {
         // Close any open submenus
-        [].forEach.call(document.querySelectorAll(".qa.is-opened"), function (submenu) { toggleAnswer(submenu); });
+        console.log("Closing Items");
+        closeAllItems();
+    } else {
+        // If all submenus are closed, exit and return through localhost
+        console.log("Exiting");
+        exit();
     }
 }
 
-function clickToggle(e) {
-    playSound("SeCheckboxOn");
-    var toggleOptions = e.querySelector(".toggle");
-    if (e.querySelector(".is-single-option")) { // Single-option submenu
-        // Deselect all submenu options
-        closestClass(e, "l-qa").querySelector(".toggle").classList.remove("is-appear");
-        closestClass(e, "l-qa").querySelector(".toggle").classList.add("is-hidden");
-        // Then set the current one as the active setting
-        toggleOptions.classList.add("is-appear");
-        toggleOptions.classList.remove("is-hidden");
-    } else { // Multi-option submenu
-        toggleOptions.classList.toggle("is-appear");
-        toggleOptions.classList.toggle("is-hidden");
-    }
-}
-
-function getParams(url) {
+function setSettingsFromURL() {
     var regex = /[?&]([^=#]+)=([^&#]*)/g,
-        params = {},
         match;
-    while (match = regex.exec(url)) {
-        params[match[1]] = match[2];
+    while (match = regex.exec(document.URL)) {
+        settings.set(match[1], match[2]);
     }
-    return params;
 }
 
-function setSettings() {
-    // Get settings from the URL GET parameters
-    const settings = getParams(document.URL);
-
-    // Set Toggles
-    [].forEach.call(document.querySelectorAll("ul.l-grid"), function (toggle) {
-        var section = toggle.id;
-        var section_setting = decodeURIComponent(settings[section]);
-
-        [].forEach.call(toggle.querySelectorAll("li"), function (child) {
-            var e = child.querySelector("img.toggle");
-            if (section_setting.split(",").includes(child.getAttribute("val"))) {
-                e.classList.add("is-appear");
-                e.classList.remove("is-hidden");
-            } else {
-                e.classList.remove("is-appear");
-                e.classList.add("is-hidden");
-            };
-        });
+function setSettingsFromMenu() {
+    var section;
+    var mask;
+    [].forEach.call(document.querySelectorAll(".menu-item"), function (menuItem) {
+        section = menuItem.id;
+        mask = getMaskFromSubmenu(menuItem);
+        settings.set(section, mask);
     });
+}
 
-    // Set OnOffs
-    [].forEach.call(document.querySelectorAll("div.onoff"), function (onOff) {
-        var section = onOff.id;
-        var section_setting = decodeURIComponent(settings[section]);
-        var e = onOff.querySelector("img.toggle");
-        if (section_setting == "1") {
-            e.classList.add("is-appear");
-            e.classList.remove("is-hidden");
+function buildURLFromSettings() {
+    var url = "http://localhost/";
+    url += "?";
+    settings.forEach((val, key) => { url += key + "=" + String(val) + "&" } );
+    return url
+}
+
+function selectSingleOption(e) {
+    // Deselect all options in the submenu
+    parent = closestClass(e, "single-option");
+    siblings = parent.querySelectorAll(".menu-icon img");
+    [].forEach.call(siblings, function (sibling) {
+        sibling.classList.add("hide");
+    });
+    e.querySelector(".menu-icon img").classList.remove("hide");
+}
+
+function setSubmenusFromSettings() {
+    [].forEach.call(document.querySelectorAll(".menu-item"), function (menuItem) {
+        var section = menuItem.id;
+        var section_mask = decodeURIComponent(settings.get(section));
+        setSubmenuByMask(menuItem, section_mask)
+    });
+}
+
+function setSubmenuByMask(menuItem, mask) {
+    [].forEach.call(menuItem.querySelectorAll(".modal .menu-icon img"), function (toggle) {
+        if (isInBitmask(toggle.dataset.val, mask)) {
+            toggle.classList.remove("hide");
         } else {
-            e.classList.remove("is-appear");
-            e.classList.add("is-hidden");
-        };
-    });
-}
-
-function resetSubmenu() {
-    // Resets any open or focused submenus to the default values
-    playSound("SeToggleBtnOff");
-    [].forEach.call(document.querySelectorAll("[default*='is-appear']"), function (item) {
-        if (isSubmenuFocused(item)) {
-            item.classList.add("is-appear");
-            item.classList.remove("is-hidden");
+            toggle.classList.add("hide");
         }
     });
 
-    [].forEach.call(document.querySelectorAll("[default*='is-hidden']"), function (item) {
-        if (isSubmenuFocused(item)) {
-            item.classList.remove("is-appear");
-            item.classList.add("is-hidden");
-        }
-    });
+    // If no setting for a Single Option is set, select the first one
+    var isSingleOption = menuItem.querySelectorAll(".modal.single-option").length != 0;
+    var isAllDeselected = menuItem.querySelectorAll(".modal .menu-icon img:not(.hide)").length == 0;
+    if (isSingleOption & isAllDeselected) {
+        selectSingleOption(menuItem.querySelector(".modal button"));
+    }
 }
 
-function isSubmenuFocused(elem) {
-    // Return true if the element is in a submenu which is either focused or opened
-    return (
-        closestClass(elem, "l-qa").querySelectorAll(".is-opened, .is-focused").length
-        || closestClass(elem, "is-focused")
-    )
+function getMaskFromSubmenu(menuItem) {
+    var val = 0;
+    [].forEach.call(menuItem.querySelectorAll(".modal img:not(.hide)"), function (toggle) {
+            val += parseInt(toggle.dataset.val);
+    });
+    return val
+}
+
+function resetCurrentSubmenu() {
+    var focus = document.querySelector(".menu-item .modal:not(.hide)");
+    if (!focus) {
+        focus = document.querySelector(":focus");
+    }
+    var menuItem = closestClass(focus, "menu-item");
+
+    var key = defaults_prefix + menuItem.id;
+    var section_mask = decodeURIComponent(settings.get(key));
+    setSubmenuByMask(menuItem, section_mask);
 }
 
 function resetAllSubmenus() {
     // Resets all submenus to the default values
     if (confirm("Are you sure that you want to reset all menu settings to the default?")) {
-        playSound("SeToggleBtnOff");
-        [].forEach.call(document.querySelectorAll("[default*='is-appear']"), function (item) {
-            item.classList.add("is-appear");
-            item.classList.remove("is-hidden");
-        });
-
-        [].forEach.call(document.querySelectorAll("[default*='is-hidden']"), function (item) {
-            item.classList.remove("is-appear");
-            item.classList.add("is-hidden");
+        [].forEach.call(document.querySelectorAll(".menu-item"), function (menuItem) {
+            var key = defaults_prefix + menuItem.id;
+            var mask = decodeURIComponent(settings.get(key));
+            setSubmenuByMask(menuItem, mask)
         });
     }
 }
@@ -296,9 +280,42 @@ function setHelpText(text) {
     document.getElementById("help-text").innerText = text;
 }
 
-function toggleSaveDefaults() {
-    // Change the status of the Save Defaults checkbox
-    playSound("SeCheckboxOn");
-    var saveDefaultsCheckbox = document.getElementById("saveDefaults");
-    saveDefaultsCheckbox.checked = !saveDefaultsCheckbox.checked;
+function saveDefaults() {
+    if (confirm("Are you sure that you want to change the default menu settings to the current selections?")) {
+        var key;
+        var mask;
+        [].forEach.call(document.querySelectorAll(".menu-item"), function (menuItem) {
+            key = defaults_prefix + menuItem.id;
+            mask = getMaskFromSubmenu(menuItem);
+            settings.set(key, mask);
+        });
+    }
+}
+
+function isInBitmask(val, mask) {
+    // Return true if the value is in the bitmask
+    return (mask & val) != 0
+}
+
+function cycleNextTab() {
+    // Cycle to the next tab
+    var activeTab = document.querySelector(".tab-button.active");
+    var nextTab = activeTab.nextElementSibling;
+    if (!nextTab) {
+        // On the last tab - set the next tab as the first tab in the list
+        nextTab = document.querySelector(".tab-button");
+    }
+    openTab(nextTab);
+}
+
+function cyclePrevTab() {
+    // Cycle to the previous tab
+    var activeTab = document.querySelector(".tab-button.active");
+    var prevTab = activeTab.previousElementSibling;
+    if (!prevTab) {
+        // On the first tab - set the next tab as the last tab in the list
+        tabs = document.querySelectorAll(".tab-button");
+        prevTab = tabs[tabs.length - 1];
+    }
+    openTab(prevTab);
 }
\ No newline at end of file
diff --git a/src/templates/menu.html b/src/templates/menu.html
index 37b52e5..4041aa8 100644
--- a/src/templates/menu.html
+++ b/src/templates/menu.html
@@ -1,117 +1,59 @@
 <!DOCTYPE html>
 <html lang="en">
 
-    <head>
-        <meta charset="UTF-8">
-        <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
-        <title>Document</title>
-        <link rel="stylesheet" href="./css/training_modpack.css" />
-    </head>
+<head>
+    <meta charset="UTF-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
+    <title>Modpack Menu</title>
+    <link rel="stylesheet" href="./css/training_modpack.css" />
+</head>
 
-    <body id="body">
-        <script defer src="./js/training_modpack.js"></script>
-        <div class="l-header">
-            <div class="l-header-title">
-                <div class="header-title"><span>Ultimate Training Modpack Menu</span></div>
-            </div>
-            <div class="header" style="flex-grow: 1;">
-                <a id="ret-button" tabindex="-1" class="header-decoration" href="javascript:goBackHook();">
-                    <div class="ret-icon-wrapper">
-                        <img class="ret-icon is-appear" src="./img/m_retnormal.svg">
-                    </div>
-                </a>
-            </div>
-            <div class="header" style="flex-direction: column; justify-content: center; align-items: end; padding-right: 10px;">
-                <p class="header-desc">Reset Current Menu: &#xE0A2;</p>
-                <p class="header-desc">Reset All Menus: &#xE0A4;</p>
-            </div>
+<body>
+    <script defer src="./js/training_modpack.js"></script>
+    <div class="l-header">
+        <a id="ret-button" tabindex="-1" class="return-icon-container" href="javascript:goBackHook();">
+                <img class="return-icon" src="./img/m_retnormal.svg">
+        </a>
+        <p class="header-title">Ultimate Training Modpack Menu</p>
+        <div class="header-label">
+            <p class="header-desc">Reset Current Menu: &#xE0A2;</p>
+            <p class="header-desc">Reset All Menus: &#xE0A4;</p>
+            <p class="header-desc">Save Defaults: &#xE0A5;</p>
         </div>
-        <br>
-        <br>
-        <br>
-        <br>
-
-        <div class="l-grid">
-
-            <!--
-    Script the part below via templating. Overall structure is perhaps
-    [
-        l-qa qa [id=qa-{{menuName}} tabindex="{{index}}"] {
-            // make question for {{menuName}}
-            // make answer with l-grid : l-item list for options
-        },
-        ...
-    ]
-
-
-    Remember to set make max keyword size greater than 23!
-    -->
-            {{#sub_menus}}
-                <div class="l-qa">
-                    {{^onoffselector}}
-                        <a id="qa-{{id}}" class="qa" tabindex="{{index}}" href="javascript:void(0);" onfocus="focusQA(this);setHelpText({{help_text}})" onblur="defocusQA(this)" onclick="toggleAnswer(this)">
-                            <div class="question-outer">
-                                <div class="question-border">
-                                    <div id="question-{{id}}" class="question scuffle-thema">
-                                        <img class="question-icon" src="./img/{{id}}.svg" />
-                                        <p class="question-message">
-                                            <span>{{title}}</span>
-                                        </p>
-                                    </div>
-                                </div>
-                            </div>
-                        </a>
-                        <div id="answer-border-{{id}}" class="answer-border-outer is-hidden">
-                            <div class="l-main">
-                                <ul class="l-grid" id="{{id}}">
-                                    {{#toggles}}
-                                        <li class="l-item" val="{{value}}">
-                                            <div class="keyword-button-outer">
-                                                <a tabindex="{{index}}" class="flex-button keyword-button scuffle-thema" href="javascript:void(0)" onclick="clickToggle(this);">
-                                                    <div class="button-icon-wrapper">
-                                                        <img class="button-icon toggle {{checked}} {{#is_single_option}}is-single-option{{/is_single_option}}" src="./img/check.svg" default="{{default}}">
-                                                    </div>
-                                                    <div class="button-msg-wrapper">
-                                                        <div class="keyword-message">
-                                                            {{title}}
-                                                        </div>
-                                                    </div>
-                                                </a>
-                                            </div>
-                                        </li>
-                                    {{/toggles}}
-                                </ul>
-                            </div>
-                        </div>
-                    {{/onoffselector}}
-                    {{#onoffselector}}
-                        <a id="qa-{{id}}" class="qa" tabindex="{{index}}" href="javascript:void(0);" onfocus="focusQA(this);setHelpText({{help_text}})" onblur="defocusQA(this)" onclick="clickToggle(this)">
-                            <div class="question-outer">
-                                <div class="question-border">
-                                    <div id="question-{{id}}" class="question scuffle-thema">
-                                        <div id="{{id}}" class="onoff">
-                                            <img class="question-icon" style="z-index: 1;" src="./img/{{id}}.svg" />
-                                            <div><img class="question-icon toggle {{checked}}" style="z-index: 2;" src="./img/check.svg" default="{{default}}"/></div>
-                                            <p class="question-message">
-                                                <span>{{title}}</span>
-                                            </p>
-                                        </div>
-                                    </div>
-                                </div>
-                            </div>
-                        </a>
-                    {{/onoffselector}}
+    </div>
+    <div class="tab-list-container">
+        <p>Prev Tab: &#xE0A6;</p>
+        <div class="tab-list">
+            {{#tabs}}
+            <button class="tab-button active" id="{{tab_id}}_button" onclick="openTab(this)" tabindex="-1">{{tab_title}}</button>
+            {{/tabs}}
+        </div>
+        <p>Next Tab: &#xE0A7;</p>
+    </div>
+    <div class="tab-content-container">
+        {{#tabs}}
+        <div id="{{tab_id}}_tab" class="tab-content">
+            {{#tab_submenus}}
+            <div class="menu-item" id="{{submenu_id}}">
+                <button class="menu-button" onfocus="setHelpText('{{help_text}}')" onclick="openItem(this)">
+                    <div class="menu-icon"><img src="./img/{{submenu_id}}.svg" /></div>
+                    <p>{{submenu_title}}</p>
+                </button>
+                <div class="hide modal{{#is_single_option}} single-option{{/is_single_option}}">
+                    {{#toggles}}
+                    <button class="menu-button" onclick="toggleOption(this)">
+                        <div class="menu-icon"><img class="hide" src="./img/check.svg" data-val="{{toggle_value}}"/></div>
+                        <p>{{toggle_title}}</p>
+                    </button>
+                    {{/toggles}}
                 </div>
-            {{/sub_menus}}
-        </div>
-        <footer id="footer" class="footer l-footer">
-            <p id="help-text" class="header-desc"></p>
-            <div class="defaults-checkbox-container">
-                <label class="header-desc" for="saveDefaults">Save defaults: &#xE0A5;</label>
-                <input type="checkbox" id="saveDefaults">
-                <div class="checkbox-display"></div>
             </div>
-        </footer>
-    </body>
-
+            {{/tab_submenus}}
+        </div>
+        {{/tabs}}
+    </div>
+    <footer id="footer" class="footer">
+        <p id="help-text" class="header-desc"></p>
+    </footer>
+</body>
 </html>
diff --git a/training_mod_consts/src/lib.rs b/training_mod_consts/src/lib.rs
index 7972bb6..1f0798b 100644
--- a/training_mod_consts/src/lib.rs
+++ b/training_mod_consts/src/lib.rs
@@ -5,13 +5,18 @@ extern crate bitflags;
 extern crate num_derive;
 
 use core::f64::consts::PI;
+use std::collections::HashMap;
 #[cfg(feature = "smash")]
 use smash::lib::lua_const::*;
+use strum::IntoEnumIterator;
 use strum_macros::EnumIter;
 use serde::{Serialize, Deserialize};
 use ramhorns::Content;
-use strum::IntoEnumIterator;
-use std::ops::BitOr;
+
+pub trait ToggleTrait {
+    fn to_toggle_strs() -> Vec<&'static str>;
+    fn to_toggle_vals() -> Vec<usize>;
+}
 
 // bitflag helper function macro
 macro_rules! extra_bitflag_impls {
@@ -56,22 +61,21 @@ macro_rules! extra_bitflag_impls {
                     }
                 }
             }
-
-            pub fn to_toggle_strs() -> Vec<&'static str> {
+        }
+        impl ToggleTrait for $e {
+            fn to_toggle_strs() -> Vec<&'static str> {
                 let all_options = <$e>::all().to_vec();
                 all_options.iter().map(|i| i.as_str().unwrap_or("")).collect()
             }
 
-            pub fn to_toggle_vals() -> Vec<usize> {
+            fn to_toggle_vals() -> Vec<usize> {
                 let all_options = <$e>::all().to_vec();
                 all_options.iter().map(|i| i.bits() as usize).collect()
             }
-            pub fn to_url_param(&self) -> String {
-                self.to_vec()
-                    .into_iter()
-                    .map(|field| field.bits().to_string())
-                    .collect::<Vec<_>>()
-                    .join(",")
+        }
+        impl ToUrlParam for $e {
+            fn to_url_param(&self) -> String {
+                self.bits().to_string()
             }
         }
     }
@@ -258,10 +262,10 @@ extra_bitflag_impls! {MissTechFlags}
 #[repr(i32)]
 #[derive(Debug, Clone, Copy, PartialEq, FromPrimitive, EnumIter, Serialize, Deserialize)]
 pub enum Shield {
-    None = 0,
-    Infinite = 1,
-    Hold = 2,
-    Constant = 3,
+    None = 0x0,
+    Infinite = 0x1,
+    Hold = 0x2,
+    Constant = 0x4,
 }
 
 impl Shield {
@@ -279,13 +283,23 @@ impl Shield {
     }
 }
 
+impl ToggleTrait for Shield {
+    fn to_toggle_strs() -> Vec<&'static str> {
+        Shield::iter().map(|i| i.as_str().unwrap_or("")).collect()
+    }
+
+    fn to_toggle_vals() -> Vec<usize> {
+        Shield::iter().map(|i| i as usize).collect()
+    }
+}
+
 // Save State Mirroring
 #[repr(i32)]
 #[derive(Debug, Clone, Copy, PartialEq, FromPrimitive, EnumIter, Serialize, Deserialize)]
 pub enum SaveStateMirroring {
-    None = 0,
-    Alternate = 1,
-    Random = 2,
+    None = 0x0,
+    Alternate = 0x1,
+    Random = 0x2,
 }
 
 impl SaveStateMirroring {
@@ -302,6 +316,16 @@ impl SaveStateMirroring {
     }
 }
 
+impl ToggleTrait for SaveStateMirroring {
+    fn to_toggle_strs() -> Vec<&'static str> {
+        SaveStateMirroring::iter().map(|i| i.as_str().unwrap_or("")).collect()
+    }
+
+    fn to_toggle_vals() -> Vec<usize> {
+        SaveStateMirroring::iter().map(|i| i as usize).collect()
+    }
+}
+
 // Defensive States
 bitflags! {
     #[derive(Serialize, Deserialize)]
@@ -357,6 +381,15 @@ impl OnOff {
     }
 }
 
+impl ToggleTrait for OnOff {
+    fn to_toggle_strs() -> Vec<&'static str> {
+        vec!["Off", "On"]
+    }
+    fn to_toggle_vals() -> Vec<usize> {
+        vec![0, 1]
+    }
+}
+
 bitflags! {
     #[derive(Serialize, Deserialize)]
     pub struct Action : u32 {
@@ -861,6 +894,16 @@ impl SdiStrength {
     }
 }
 
+impl ToggleTrait for SdiStrength {
+    fn to_toggle_strs() -> Vec<&'static str> {
+        SdiStrength::iter().map(|i| i.as_str().unwrap_or("")).collect()
+    }
+
+    fn to_toggle_vals() -> Vec<usize> {
+        SdiStrength::iter().map(|i| i as usize).collect()
+    }
+}
+
 // For input delay
 trait ToUrlParam {
     fn to_url_param(&self) -> String;
@@ -887,9 +930,14 @@ macro_rules! url_params {
             $(pub $field_name: $field_type,)*
         }
         impl $e {
-            pub fn to_url_params(&self) -> String {
-                let mut s = "?".to_string();
+            pub fn to_url_params(&self, defaults: bool) -> String {
+                let mut s = "".to_string();
+                let defaults_str = match defaults {
+                    true => &"__",  // Prefix field name with "__" if it is for the default menu
+                    false => &"",
+                };
                 $(
+                    s.push_str(defaults_str);
                     s.push_str(stringify!($field_name));
                     s.push_str(&"=");
                     s.push_str(&self.$field_name.to_url_param());
@@ -1004,35 +1052,10 @@ pub enum FighterId {
     CPU = 1,
 }
 
-#[derive(Content, Clone)]
-pub struct Slider {
-    pub min: usize,
-    pub max: usize,
-    pub index: usize,
-    pub value: usize,
-}
-
-#[derive(Content, Clone)]
-pub struct Toggle<'a> {
-    pub title: &'a str,
-    pub checked: &'a str,
-    pub index: usize,
-    pub value: usize,
-    pub default: &'a str,
-}
-
-#[derive(Content, Clone)]
-pub struct OnOffSelector<'a> {
-    pub title: &'a str,
-    pub checked: &'a str,
-    pub default: &'a str,
-}
-
 #[derive(Clone)]
 pub enum SubMenuType {
     TOGGLE,
     SLIDER,
-    ONOFF,
 }
 
 impl SubMenuType {
@@ -1040,67 +1063,12 @@ impl SubMenuType {
         match s {
             "toggle" => SubMenuType::TOGGLE,
             "slider" => SubMenuType::SLIDER,
-            "onoff" => SubMenuType::ONOFF,
             _ => panic!("Unexpected SubMenuType!")
         }
     }
 }
 
-#[derive(Content, Clone)]
-pub struct SubMenu<'a> {
-    pub title: &'a str,
-    pub id: &'a str,
-    pub _type: &'a str,
-    pub toggles: Vec<Toggle<'a>>,
-    pub sliders: Vec<Slider>,
-    pub onoffselector: Vec<OnOffSelector<'a>>,
-    pub index: usize,
-    pub check_against: usize,
-    pub is_single_option: Option<bool>,
-    pub help_text: &'a str,
-}
-
-impl<'a> SubMenu<'a> {
-    pub fn max_idx(&self) -> usize {
-        self.toggles
-            .iter()
-            .max_by(|t1, t2| t1.index.cmp(&t2.index))
-            .map(|t| t.index)
-            .unwrap_or(self.index)
-    }
-
-    pub fn add_toggle(&mut self, title: &'a str, checked: bool, value: usize, default: bool) {
-        self.toggles.push(Toggle {
-            title,
-            checked: if checked { "is-appear" } else { "is-hidden" },
-            index: self.max_idx() + 1,
-            value,
-            default: if default { "is-appear" } else { "is-hidden" },
-        });
-    }
-
-    pub fn add_slider(&mut self, min: usize, max: usize, value: usize) {
-        self.sliders.push(Slider {
-            min,
-            max,
-            index: self.max_idx() + 1,
-            value,
-        });
-    }
-
-    pub fn add_onoffselector(&mut self, title: &'a str, checked: bool, default: bool) {
-        // TODO: Is there a more elegant way to do this?
-        // The HTML only supports a single onoffselector but the SubMenu stores it as a Vec
-        self.onoffselector.push(OnOffSelector {
-            title,
-            checked: if checked { "is-appear" } else { "is-hidden" },
-            default: if default { "is-appear" } else { "is-hidden" },
-        });
-    }
-}
-
-
-pub static DEFAULT_MENU: TrainingModpackMenu = TrainingModpackMenu {
+pub static DEFAULTS_MENU: TrainingModpackMenu = TrainingModpackMenu {
     hitbox_vis: OnOff::On,
     stage_hazards: OnOff::Off,
     di_state: Direction::empty(),
@@ -1137,452 +1105,106 @@ pub static DEFAULT_MENU: TrainingModpackMenu = TrainingModpackMenu {
     quick_menu: OnOff::Off,
 };
 
-pub static mut MENU: TrainingModpackMenu = DEFAULT_MENU;
+pub static mut MENU: TrainingModpackMenu = DEFAULTS_MENU;
 
 #[derive(Content, Clone)]
-pub struct Menu<'a> {
-    pub sub_menus: Vec<SubMenu<'a>>,
+pub struct Slider {
+    pub min: usize,
+    pub max: usize,
+    pub index: usize,
+    pub value: usize,
 }
 
-impl<'a> Menu<'a> {
-    pub fn max_idx(&self) -> usize {
-        self.sub_menus
-            .iter()
-            .max_by(|x, y| x.max_idx().cmp(&y.max_idx()))
-            .map(|sub_menu| sub_menu.max_idx())
-            .unwrap_or(0)
-    }
-
-    pub fn add_sub_menu(
-        &mut self,
-        title: &'a str,
-        id: &'a str,
-        _type: &'a str,
-        check_against: usize,
-        toggles: Vec<(&'a str, usize)>,
-        sliders: Vec<(usize, usize, usize)>,
-        defaults: usize,
-        help_text: &'a str,
-    ) {
-        let mut sub_menu = SubMenu {
-            title,
-            id,
-            _type,
-            toggles: Vec::new(),
-            sliders: Vec::new(),
-            onoffselector: Vec::new(),
-            index: self.max_idx() + 1,
-            check_against,
-            is_single_option: Some(true),
-            help_text,
-        };
-
-        for toggle in toggles {
-            sub_menu.add_toggle(
-                toggle.0,
-                (check_against & toggle.1) != 0,
-                toggle.1,
-                (defaults & toggle.1) != 0,
-            )
-        }
-
-        for slider in sliders {
-            sub_menu.add_slider(slider.0, slider.1, slider.2);
-        }
-
-        self.sub_menus.push(sub_menu);
-    }
-
-    pub fn add_sub_menu_sep(
-        &mut self,
-        title: &'a str,
-        id: &'a str,
-        _type: &'a str,
-        check_against: usize,
-        strs: Vec<&'a str>,
-        vals: Vec<usize>,
-        defaults: usize,
-        help_text: &'a str,
-    ) {
-        let mut sub_menu = SubMenu {
-            title,
-            id,
-            _type,
-            toggles: Vec::new(),
-            sliders: Vec::new(),
-            onoffselector: Vec::new(),
-            index: self.max_idx() + 1,
-            check_against,
-            is_single_option: None,
-            help_text,
-        };
-
-        for i in 0..strs.len() {
-            sub_menu.add_toggle(
-                strs[i],
-                (check_against & vals[i]) != 0,
-                vals[i],
-                (defaults & vals[i]) != 0,
-            )
-        }
-
-        // TODO: add sliders?
-
-        self.sub_menus.push(sub_menu);
-    }
-
-    pub fn add_sub_menu_onoff(
-        &mut self,
-        title: &'a str,
-        id: &'a str,
-        _type: &'a str,
-        check_against: usize,
-        checked: bool,
-        default: usize,
-        help_text: &'a str,
-    ) {
-        let mut sub_menu = SubMenu {
-            title,
-            id,
-            _type,
-            toggles: Vec::new(),
-            sliders: Vec::new(),
-            onoffselector: Vec::new(),
-            index: self.max_idx() + 1,
-            check_against,
-            is_single_option: None,
-            help_text,
-        };
-
-        sub_menu.add_onoffselector(title, checked, (default & OnOff::On as usize) != 0);
-        self.sub_menus.push(sub_menu);
-    }
+#[derive(Content, Clone)]
+pub struct Toggle<'a> {
+    pub toggle_value: usize,
+    pub toggle_title: &'a str,
+    pub checked: bool,
 }
 
-macro_rules! add_bitflag_submenu {
-    ($menu:ident, $title:literal, $id:ident, $e:ty, $help_text:literal) => {
-        paste::paste!{
-            let [<$id _strs>] = <$e>::to_toggle_strs();
-            let [<$id _vals>] = <$e>::to_toggle_vals();
-
-            $menu.add_sub_menu_sep(
-                $title,
-                stringify!($id),
-                "toggle",
-                MENU.$id.bits() as usize,
-                [<$id _strs>],
-                [<$id _vals>],
-                DEFAULT_MENU.$id.bits() as usize,
-                stringify!($help_text),
-            );
-        }
-    }
+#[derive(Content, Clone)]
+pub struct SubMenu<'a> {
+    pub submenu_title: &'a str,
+    pub submenu_id: &'a str,
+    pub help_text: &'a str,
+    pub is_single_option: bool,
+    pub toggles: Vec<Toggle<'a>>,
+    pub _type: &'a str,
 }
 
-macro_rules! add_single_option_submenu {
-    ($menu:ident, $title:literal, $id:ident, $e:ty, $help_text:literal) => {
-        paste::paste!{
-            let mut [<$id _toggles>] = Vec::new();
-            for val in [<$e>]::iter() {
-                [<$id _toggles>].push((val.as_str().unwrap_or(""), val as usize));
+impl<'a> SubMenu<'a> {
+    pub fn add_toggle(
+        &mut self,
+        toggle_value: usize,
+        toggle_title: &'a str
+    ) {
+        self.toggles.push(
+            Toggle {
+                toggle_value: toggle_value,
+                toggle_title: toggle_title,
+                checked: false
             }
-
-            $menu.add_sub_menu(
-                $title,
-                stringify!($id),
-                "toggle",
-                MENU.$id as usize,
-                [<$id _toggles>],
-                [].to_vec(),
-                DEFAULT_MENU.$id as usize,
-                stringify!($help_text),
-            );
-        }
+        );
+    }
+    pub fn new_with_toggles<T:ToggleTrait>(
+        submenu_title: &'a str,
+        submenu_id: &'a str,
+        help_text: &'a str,
+        is_single_option: bool,
+    ) -> SubMenu<'a> {
+            let mut instance = SubMenu {
+                submenu_title: submenu_title,
+                submenu_id: submenu_id,
+                help_text: help_text,
+                is_single_option: is_single_option,
+                toggles: Vec::new(),
+                _type: "toggle"
+            };
+    
+            let values = T::to_toggle_vals();
+            let titles = T::to_toggle_strs();
+            for i in 0..values.len() {
+                instance.add_toggle(
+                    values[i],
+                    titles[i],
+                );
+            }
+            instance
     }
 }
 
-macro_rules! add_onoff_submenu {
-    ($menu:ident, $title:literal, $id:ident, $help_text:literal) => {
-        paste::paste! {
-            $menu.add_sub_menu_onoff(
-                $title,
-                stringify!($id),
-                "onoff",
-                MENU.$id as usize,
-                (MENU.$id as usize & OnOff::On as usize) != 0,
-                DEFAULT_MENU.$id as usize,
-                stringify!($help_text),
-            );
-        }
-    };
+#[derive(Content)]
+pub struct Tab<'a> {
+    pub tab_id: &'a str,
+    pub tab_title: &'a str,
+    pub tab_submenus: Vec<SubMenu<'a>>,
 }
 
-pub unsafe fn get_menu() -> Menu<'static> {
-    let mut overall_menu = Menu {
-        sub_menus: Vec::new(),
-    };
-
-    // Toggle/bitflag menus
-    add_bitflag_submenu!(
-        overall_menu,
-        "Mash Toggles",
-        mash_state,
-        Action,
-        "Mash Toggles: Actions to be performed as soon as possible"
-    );
-    add_bitflag_submenu!(
-        overall_menu,
-        "Followup Toggles",
-        follow_up,
-        Action,
-        "Followup Toggles: Actions to be performed after the Mash option"
-    );
-    add_bitflag_submenu!(
-        overall_menu,
-        "Attack Angle",
-        attack_angle,
-        AttackAngle,
-        "Attack Angle: For attacks that can be angled, such as some forward tilts"
-    );
-
-    add_bitflag_submenu!(
-        overall_menu,
-        "Ledge Options",
-        ledge_state,
-        LedgeOption,
-        "Ledge Options: Actions to be taken when on the ledge"
-    );
-    add_bitflag_submenu!(
-        overall_menu,
-        "Ledge Delay",
-        ledge_delay,
-        LongDelay,
-        "Ledge Delay: How many frames to delay the ledge option"
-    );
-    add_bitflag_submenu!(
-        overall_menu,
-        "Tech Options",
-        tech_state,
-        TechFlags,
-        "Tech Options: Actions to take when slammed into a hard surface"
-    );
-    add_bitflag_submenu!(
-        overall_menu,
-        "Miss Tech Options",
-        miss_tech_state,
-        MissTechFlags,
-        "Miss Tech Options: Actions to take after missing a tech"
-    );
-    add_bitflag_submenu!(
-        overall_menu,
-        "Defensive Options",
-        defensive_state,
-        Defensive,
-        "Defensive Options: Actions to take after a ledge option, tech option, or miss tech option"
-    );
-
-    add_bitflag_submenu!(
-        overall_menu,
-        "Aerial Delay",
-        aerial_delay,
-        Delay,
-        "Aerial Delay: How long to delay a Mash aerial attack"
-    );
-    add_bitflag_submenu!(
-        overall_menu,
-        "OoS Offset",
-        oos_offset,
-        Delay,
-        "OoS Offset: How many times the CPU shield can be hit before performing a Mash option"
-    );
-    add_bitflag_submenu!(
-        overall_menu,
-        "Reaction Time",
-        reaction_time,
-        Delay,
-        "Reaction Time: How many frames to delay before performing an option out of shield"
-    );
-
-    add_bitflag_submenu!(
-        overall_menu,
-        "Fast Fall",
-        fast_fall,
-        BoolFlag,
-        "Fast Fall: Should the CPU fastfall during a jump"
-    );
-    add_bitflag_submenu!(
-        overall_menu,
-        "Fast Fall Delay",
-        fast_fall_delay,
-        Delay,
-        "Fast Fall Delay: How many frames the CPU should delay their fastfall"
-    );
-    add_bitflag_submenu!(
-        overall_menu,
-        "Falling Aerials",
-        falling_aerials,
-        BoolFlag,
-        "Falling Aerials: Should aerials be performed when rising or when falling"
-    );
-    add_bitflag_submenu!(
-        overall_menu,
-        "Full Hop",
-        full_hop,
-        BoolFlag,
-        "Full Hop: Should the CPU perform a full hop or a short hop"
-    );
-
-    add_bitflag_submenu!(
-        overall_menu,
-        "Shield Tilt",
-        shield_tilt,
-        Direction,
-        "Shield Tilt: Direction to tilt the shield"
-    );
-    add_bitflag_submenu!(
-        overall_menu,
-        "DI Direction",
-        di_state,
-        Direction,
-        "DI Direction: Direction to angle the directional influence during hitlag"
-    );
-    add_bitflag_submenu!(
-        overall_menu,
-        "SDI Direction",
-        sdi_state,
-        Direction,
-        "SDI Direction: Direction to angle the smash directional influence during hitlag"
-    );
-    add_bitflag_submenu!(
-        overall_menu,
-        "Airdodge Direction",
-        air_dodge_dir,
-        Direction,
-        "Airdodge Direction: Direction to angle airdodges"
-    );
-
-    add_single_option_submenu!(
-        overall_menu,
-        "SDI Strength",
-        sdi_strength,
-        SdiStrength,
-        "SDI Strength: Relative strength of the smash directional influence inputs"
-    );
-    add_single_option_submenu!(
-        overall_menu,
-        "Shield Toggles",
-        shield_state,
-        Shield,
-        "Shield Toggles: CPU Shield Behavior"
-    );
-    add_single_option_submenu!(
-        overall_menu,
-        "Mirroring",
-        save_state_mirroring,
-        SaveStateMirroring,
-        "Mirroring: Flips save states in the left-right direction across the stage center"
-    );
-    add_bitflag_submenu!(
-        overall_menu,
-        "Throw Options",
-        throw_state,
-        ThrowOption,
-        "Throw Options: Throw to be performed when a grab is landed"
-    );
-    add_bitflag_submenu!(
-        overall_menu,
-        "Throw Delay",
-        throw_delay,
-        MedDelay,
-        "Throw Delay: How many frames to delay the throw option"
-    );
-    add_bitflag_submenu!(
-        overall_menu,
-        "Pummel Delay",
-        pummel_delay,
-        MedDelay,
-        "Pummel Delay: How many frames after a grab to wait before starting to pummel"
-    );
-    add_bitflag_submenu!(
-        overall_menu,
-        "Buff Options",
-        buff_state,
-        BuffOption,
-        "Buff Options: Buff(s) to be applied to respective character when loading save states"
-    );
-
-    // Slider menus
-    overall_menu.add_sub_menu(
-        "Input Delay",
-        "input_delay",
-        // unnecessary for slider?
-        "toggle",
-        0,
-        [
-            ("0", 0),
-            ("1", 1),
-            ("2", 2),
-            ("3", 3),
-            ("4", 4),
-            ("5", 5),
-            ("6", 6),
-            ("7", 7),
-            ("8", 8),
-            ("9", 9),
-            ("10", 10),
-        ]
-            .to_vec(),
-        [].to_vec(), //(0, 10, MENU.input_delay as usize)
-        DEFAULT_MENU.input_delay as usize,
-        stringify!("Input Delay: Frames to delay player inputs by"),
-    );
-
-    add_onoff_submenu!(
-        overall_menu,
-        "Save States",
-        save_state_enable,
-        "Save States: Enable save states! Save a state with Grab+Down Taunt, load it with Grab+Up Taunt."
-    );
-    add_onoff_submenu!(
-        overall_menu,
-        "Save Damage",
-        save_damage,
-        "Save Damage: Should save states retain player/CPU damage"
-    );
-    add_onoff_submenu!(
-        overall_menu,
-        "Hitbox Visualization",
-        hitbox_vis,
-        "Hitbox Visualization: Should hitboxes be displayed, hiding other visual effects"
-    );
-    add_onoff_submenu!(
-        overall_menu,
-        "Stage Hazards",
-        stage_hazards,
-        "Stage Hazards: Should stage hazards be present"
-    );
-    add_onoff_submenu!(
-        overall_menu,
-        "Frame Advantage",
-        frame_advantage,
-        "Frame Advantage: Display the time difference between when the player is actionable and the CPU is actionable");
-    add_onoff_submenu!(
-        overall_menu,
-        "Mash In Neutral",
-        mash_in_neutral,
-        "Mash In Neutral: Should Mash options be performed repeatedly or only when the CPU is hit"
-    );
-    add_onoff_submenu!(
-        overall_menu,
-        "Quick Menu",
-        quick_menu,
-        "Quick Menu: Whether to use Quick Menu or Web Menu"
-    );
-
-    overall_menu
+impl<'a> Tab<'a> {
+    pub fn add_submenu_with_toggles<T: ToggleTrait>(
+        &mut self,
+        submenu_title: &'a str,
+        submenu_id: &'a str,
+        help_text: &'a str,
+        is_single_option: bool,
+    ) {
+        self.tab_submenus.push(
+            SubMenu::new_with_toggles::<T>(
+                submenu_title,
+                submenu_id,
+                help_text,
+                is_single_option,
+            )
+        );
+    }
 }
 
-pub fn get_menu_from_url(mut menu: TrainingModpackMenu, s: &str) -> TrainingModpackMenu {
+#[derive(Content)]
+pub struct UiMenu<'a> {
+    pub tabs: Vec<Tab<'a>>,
+}
+
+pub fn get_menu_from_url(mut menu: TrainingModpackMenu, s: &str, defaults: bool) -> TrainingModpackMenu {
     let base_url_len = "http://localhost/?".len();
     let total_len = s.len();
 
@@ -1594,21 +1216,283 @@ pub fn get_menu_from_url(mut menu: TrainingModpackMenu, s: &str) -> TrainingModp
 
     for toggle_values in ss.split('&') {
         let toggle_value_split = toggle_values.split('=').collect::<Vec<&str>>();
-        let toggle = toggle_value_split[0];
-        if toggle.is_empty() {
-            continue;
-        }
-
-        let toggle_vals = toggle_value_split[1];
-
-        let bitwise_or = <u32 as BitOr<u32>>::bitor;
-        let bits = toggle_vals
-            .split(',')
-            .filter(|val| !val.is_empty())
-            .map(|val| val.parse().unwrap())
-            .fold(0, bitwise_or);
+        let mut toggle = toggle_value_split[0];
+        if toggle.is_empty() | (
+            // Default menu settings begin with the prefix "__"
+            // So if skip toggles without the prefix if defaults is true
+            // And skip toggles with the prefix if defaults is false
+            defaults ^ toggle.starts_with("__")
+        ) { continue }
+        toggle = toggle.strip_prefix("__").unwrap_or(toggle);
 
+        let bits: u32 = toggle_value_split[1].parse().unwrap_or(0);
         menu.set(toggle, bits);
     }
     menu
-}
\ No newline at end of file
+}
+
+
+
+pub unsafe fn get_menu() -> UiMenu<'static> {
+    let mut overall_menu = UiMenu {
+        tabs: Vec::new(),
+    };
+
+    let mut mash_tab = Tab {
+        tab_id: "mash",
+        tab_title: "Mash Settings",
+        tab_submenus: Vec::new(),
+    };
+    mash_tab.add_submenu_with_toggles::<Action>(
+        "Mash Toggles",
+        "mash_state",
+        "Mash Toggles: Actions to be performed as soon as possible",
+        false,
+    );
+    mash_tab.add_submenu_with_toggles::<Action>(
+        "Followup Toggles",
+        "follow_up",
+        "Followup Toggles: Actions to be performed after the Mash option",
+        false,
+    );
+    mash_tab.add_submenu_with_toggles::<AttackAngle>(
+        "Attack Angle",
+        "attack_angle",
+        "Attack Angle: For attacks that can be angled, such as some forward tilts",
+        false,
+    );
+    mash_tab.add_submenu_with_toggles::<ThrowOption>(
+        "Throw Options",
+        "throw_state",
+        "Throw Options: Throw to be performed when a grab is landed",
+        false,
+    );
+    mash_tab.add_submenu_with_toggles::<MedDelay>(
+        "Throw Delay",
+        "throw_delay",
+        "Throw Delay: How many frames to delay the throw option",
+        false,
+    );
+    mash_tab.add_submenu_with_toggles::<MedDelay>(
+        "Pummel Delay",
+        "pummel_delay",
+        "Pummel Delay: How many frames after a grab to wait before starting to pummel",
+        false,
+    );
+    mash_tab.add_submenu_with_toggles::<BoolFlag>(
+        "Falling Aerials",
+        "falling_aerials",
+        "Falling Aerials: Should aerials be performed when rising or when falling",
+        false, // TODO: Should this be a single option submenu?
+    );
+    mash_tab.add_submenu_with_toggles::<BoolFlag>(
+        "Full Hop",
+        "full_hop",
+        "Full Hop: Should the CPU perform a full hop or a short hop",
+        false,
+    );
+    mash_tab.add_submenu_with_toggles::<Delay>(
+        "Aerial Delay",
+        "aerial_delay",
+        "Aerial Delay: How long to delay a Mash aerial attack",
+        false,
+    );
+    mash_tab.add_submenu_with_toggles::<BoolFlag>(
+        "Fast Fall",
+        "fast_fall",
+        "Fast Fall: Should the CPU fastfall during a jump",
+        false,
+    );
+    mash_tab.add_submenu_with_toggles::<Delay>(
+        "Fast Fall Delay",
+        "fast_fall_delay",
+        "Fast Fall Delay: How many frames the CPU should delay their fastfall",
+        false,
+    );
+    mash_tab.add_submenu_with_toggles::<Delay>(
+        "OoS Offset",
+        "oos_offset",
+        "OoS Offset: How many times the CPU shield can be hit before performing a Mash option",
+        false,
+    );
+    mash_tab.add_submenu_with_toggles::<Delay>(
+        "Reaction Time",
+        "reaction_time",
+        "Reaction Time: How many frames to delay before performing a mash option",
+        false,
+    );
+    mash_tab.add_submenu_with_toggles::<OnOff>(
+        "Mash in Neutral",
+        "mash_in_neutral",
+        "Mash In Neutral: Should Mash options be performed repeatedly or only when the CPU is hit",
+        true,
+    );
+    overall_menu.tabs.push(mash_tab);
+
+
+    let mut defensive_tab = Tab {
+        tab_id: "defensive",
+        tab_title: "Defensive Settings",
+        tab_submenus: Vec::new(),
+    };
+    defensive_tab.add_submenu_with_toggles::<Direction>(
+        "Airdodge Direction",
+        "air_dodge_dir",
+        "Airdodge Direction: Direction to angle airdodges",
+        false,
+    );
+    defensive_tab.add_submenu_with_toggles::<Direction>(
+        "DI Direction",
+        "di_state",
+        "DI Direction: Direction to angle the directional influence during hitlag",
+        false,
+    );
+    defensive_tab.add_submenu_with_toggles::<Direction>(
+        "SDI Direction",
+        "sdi_state",
+        "SDI Direction: Direction to angle the smash directional influence during hitlag",
+        false,
+    );
+    defensive_tab.add_submenu_with_toggles::<SdiStrength>(
+        "SDI Strength",
+        "sdi_strength",
+        "SDI Strength: Relative strength of the smash directional influence inputs",
+        true,
+    );
+    defensive_tab.add_submenu_with_toggles::<LedgeOption>(
+        "Ledge Options",
+        "ledge_state",
+        "Ledge Options: Actions to be taken when on the ledge",
+        false,
+    );
+    defensive_tab.add_submenu_with_toggles::<LongDelay>(
+        "Ledge Delay",
+        "ledge_delay",
+        "Ledge Delay: How many frames to delay the ledge option",
+        false,
+    );
+    defensive_tab.add_submenu_with_toggles::<TechFlags>(
+        "Tech Options",
+        "tech_state",
+        "Tech Options: Actions to take when slammed into a hard surface",
+        false,
+    );
+    defensive_tab.add_submenu_with_toggles::<MissTechFlags>(
+        "Mistech Options",
+        "miss_tech_state",
+        "Mistech Options: Actions to take after missing a tech",
+        false,
+    );
+    defensive_tab.add_submenu_with_toggles::<Shield>(
+        "Shield Toggles",
+        "shield_state",
+        "Shield Toggles: CPU Shield Behavior",
+        true,
+    );
+    defensive_tab.add_submenu_with_toggles::<Direction>(
+        "Shield Tilt",
+        "shield_tilt",
+        "Shield Tilt: Direction to tilt the shield",
+        false, // TODO: Should this be true?
+    );
+    defensive_tab.add_submenu_with_toggles::<Defensive>(
+        "Defensive Toggles",
+        "defensive_state",
+        "Defensive Options: Actions to take after a ledge option, tech option, or mistech option",
+        false,
+    );
+    defensive_tab.add_submenu_with_toggles::<BuffOption>(
+        "Buff Options",
+        "buff_state",
+        "Buff Options: Buff(s) to be applied to respective character when loading save states",
+        false,
+    );
+    overall_menu.tabs.push(defensive_tab);
+
+    let mut misc_tab = Tab {
+        tab_id: "misc",
+        tab_title: "Misc Settings",
+        tab_submenus: Vec::new(),
+    };
+    misc_tab.add_submenu_with_toggles::<SaveStateMirroring>(
+        "Mirroring",
+        "save_state_mirroring",
+        "Mirroring: Flips save states in the left-right direction across the stage center",
+        true,
+    );
+    misc_tab.add_submenu_with_toggles::<OnOff>(
+        "Save Damage",
+        "save_damage",
+        "Save Damage: Should save states retain player/CPU damage",
+        true,
+    );
+    misc_tab.add_submenu_with_toggles::<OnOff>(
+        "Enable Save States",
+        "save_state_enable",
+        "Save States: Enable save states! Save a state with Grab+Down Taunt, load it with Grab+Up Taunt.",
+        true,
+    );
+    misc_tab.add_submenu_with_toggles::<OnOff>(
+        "Frame Advantage",
+        "frame_advantage",
+        "Frame Advantage: Display the time difference between when the player is actionable and the CPU is actionable",
+        true,
+    );
+    misc_tab.add_submenu_with_toggles::<OnOff>(
+        "Hitbox Visualization",
+        "hitbox_vis",
+        "Hitbox Visualization: Should hitboxes be displayed, hiding other visual effects",
+        true,
+    );
+    misc_tab.add_submenu_with_toggles::<Delay>(
+        "Input Delay",
+        "input_delay",
+        "Input Delay: Frames to delay player inputs by",
+        true,
+    );
+    misc_tab.add_submenu_with_toggles::<OnOff>(
+        "Stage Hazards",
+        "stage_hazards",
+        "Stage Hazards: Should stage hazards be present",
+        true
+    );
+    misc_tab.add_submenu_with_toggles::<OnOff>(
+        "Quick Menu",
+        "quick_menu",
+        "Quick Menu: Should use quick or web menu",
+        true
+    );
+    overall_menu.tabs.push(misc_tab);
+
+    let non_ui_menu = MENU;
+    let url_params = non_ui_menu.to_url_params(false);
+    let toggle_values_all = url_params.split("&");
+    let mut sub_menu_id_to_vals : HashMap<&str, Vec<u32>> = HashMap::new();
+    for toggle_values in toggle_values_all {
+        let toggle_value_split = toggle_values.split('=').collect::<Vec<&str>>();
+        let mut sub_menu_id = toggle_value_split[0];
+        if sub_menu_id.is_empty() { continue }
+        sub_menu_id = sub_menu_id.strip_prefix("__").unwrap_or(sub_menu_id);
+
+        let bits: u32 = toggle_value_split[1].parse().unwrap_or(0);
+        if sub_menu_id_to_vals.contains_key(sub_menu_id) {
+            sub_menu_id_to_vals.get_mut(sub_menu_id).unwrap().push(bits);
+        } else {
+            sub_menu_id_to_vals.insert(sub_menu_id, vec![bits]);
+        }
+    }
+    overall_menu.tabs.iter_mut()
+        .for_each(|tab| {
+            tab.tab_submenus.iter_mut().for_each(|sub_menu| {
+                let sub_menu_id = sub_menu.submenu_id;
+                sub_menu.toggles.iter_mut().for_each(|toggle| {
+                    if sub_menu_id_to_vals.contains_key(sub_menu_id) &&
+                        sub_menu_id_to_vals[sub_menu_id].contains(&(toggle.toggle_value as u32)) {
+                        toggle.checked = true
+                    }
+                })
+            })
+        });
+
+    overall_menu
+}
diff --git a/training_mod_tui/src/lib.rs b/training_mod_tui/src/lib.rs
index 2ac8da3..c19f0ff 100644
--- a/training_mod_tui/src/lib.rs
+++ b/training_mod_tui/src/lib.rs
@@ -1,4 +1,4 @@
-use training_mod_consts::{OnOffSelector, Slider, SubMenu, SubMenuType, Toggle};
+use training_mod_consts::{Slider, SubMenu, SubMenuType, Toggle, UiMenu};
 use tui::{
     backend::{Backend},
     layout::{Constraint, Corner, Direction, Layout},
@@ -22,81 +22,26 @@ pub struct App<'a> {
     pub tabs: StatefulList<&'a str>,
     pub menu_items: HashMap<&'a str, MultiStatefulList<SubMenu<'a>>>,
     pub selected_sub_menu_toggles: MultiStatefulList<Toggle<'a>>,
-    pub selected_sub_menu_onoff_selectors: MultiStatefulList<OnOffSelector<'a>>,
     pub selected_sub_menu_sliders: MultiStatefulList<Slider>,
     pub outer_list: bool
 }
 
 impl<'a> App<'a> {
-    pub fn new(menu: training_mod_consts::Menu<'a>) -> App<'a> {
-        let tab_specifiers = vec![
-            ("Mash Settings", vec![
-                "Mash Toggles",
-                "Followup Toggles",
-                "Attack Angle",
-                "Ledge Options",
-                "Ledge Delay",
-                "Tech Options",
-                "Miss Tech Options",
-                "Defensive Options",
-                "Aerial Delay",
-                "OoS Offset",
-                "Reaction Time",
-            ]),
-            ("Defensive Settings", vec![
-                "Fast Fall",
-                "Fast Fall Delay",
-                "Falling Aerials",
-                "Full Hop",
-                "Shield Tilt",
-                "DI Direction",
-                "SDI Direction",
-                "Airdodge Direction",
-                "SDI Strength",
-                "Shield Toggles",
-                "Mirroring",
-                "Throw Options",
-                "Throw Delay",
-                "Pummel Delay",
-                "Buff Options",
-            ]),
-            ("Other Settings", vec![
-                "Input Delay",
-                "Save States",
-                "Save Damage",
-                "Hitbox Visualization",
-                "Stage Hazards",
-                "Frame Advantage",
-                "Mash In Neutral",
-                "Quick Menu"
-            ])
-        ];
-        let mut tabs: std::collections::HashMap<&str, Vec<SubMenu>> = std::collections::HashMap::new();
-        tabs.insert("Mash Settings", vec![]);
-        tabs.insert("Defensive Settings", vec![]);
-        tabs.insert("Other Settings", vec![]);
-
-        for sub_menu in menu.sub_menus.iter() {
-            for tab_spec in tab_specifiers.iter() {
-                if tab_spec.1.contains(&sub_menu.title) {
-                    tabs.get_mut(tab_spec.0).unwrap().push(sub_menu.clone());
-                }
-            }
-        };
+    pub fn new(menu: UiMenu<'a>) -> App<'a> {
         let num_lists = 3;
 
         let mut menu_items_stateful = HashMap::new();
-        tabs.keys().for_each(|k| {
+        menu.tabs.iter().for_each(|tab| {
             menu_items_stateful.insert(
-                k.clone(),
-                MultiStatefulList::with_items(tabs.get(k).unwrap().clone(), num_lists)
+                tab.tab_title,
+                MultiStatefulList::with_items(tab.tab_submenus.clone(), num_lists)
             );
         });
+
         let mut app = App {
-            tabs: StatefulList::with_items(tab_specifiers.iter().map(|(tab_title, _)| *tab_title).collect()),
+            tabs: StatefulList::with_items(menu.tabs.iter().map(|tab| tab.tab_title).collect()),
             menu_items: menu_items_stateful,
             selected_sub_menu_toggles: MultiStatefulList::with_items(vec![], 0),
-            selected_sub_menu_onoff_selectors: MultiStatefulList::with_items(vec![], 0),
             selected_sub_menu_sliders: MultiStatefulList::with_items(vec![], 0),
             outer_list: true
         };
@@ -109,8 +54,7 @@ impl<'a> App<'a> {
         let selected_sub_menu = &self.menu_items.get(self.tab_selected()).unwrap().lists[list_section].items.get(list_idx).unwrap();
 
         let toggles = selected_sub_menu.toggles.clone();
-        let sliders = selected_sub_menu.sliders.clone();
-        let onoffs = selected_sub_menu.onoffselector.clone();
+        // let sliders = selected_sub_menu.sliders.clone();
         match SubMenuType::from_str(self.sub_menu_selected()._type) {
             SubMenuType::TOGGLE => {
                 self.selected_sub_menu_toggles = MultiStatefulList::with_items(
@@ -118,14 +62,9 @@ impl<'a> App<'a> {
                     if selected_sub_menu.toggles.len() >= 3 { 3 } else { selected_sub_menu.toggles.len()} )
             },
             SubMenuType::SLIDER => {
-                self.selected_sub_menu_sliders = MultiStatefulList::with_items(
-                    sliders,
-                    if selected_sub_menu.sliders.len() >= 3 { 3 } else { selected_sub_menu.sliders.len()} )
-            },
-            SubMenuType::ONOFF => {
-                self.selected_sub_menu_onoff_selectors = MultiStatefulList::with_items(
-                    onoffs,
-                    if selected_sub_menu.onoffselector.len() >= 3 { 3 } else { selected_sub_menu.onoffselector.len()} )
+                // self.selected_sub_menu_sliders = MultiStatefulList::with_items(
+                //     sliders,
+                //     if selected_sub_menu.sliders.len() >= 3 { 3 } else { selected_sub_menu.sliders.len()} )
             },
         };
     }
@@ -136,14 +75,13 @@ impl<'a> App<'a> {
 
     fn sub_menu_selected(&self) -> &SubMenu {
         let (list_section, list_idx) = self.menu_items.get(self.tab_selected()).unwrap().idx_to_list_idx(self.menu_items.get(self.tab_selected()).unwrap().state);
-        &self.menu_items.get(self.tab_selected()).unwrap().lists[list_section].items.get(list_idx).unwrap()
+        self.menu_items.get(self.tab_selected()).unwrap().lists[list_section].items.get(list_idx).unwrap()
     }
 
     pub fn sub_menu_next(&mut self) {
         match SubMenuType::from_str(self.sub_menu_selected()._type) {
             SubMenuType::TOGGLE => self.selected_sub_menu_toggles.next(),
             SubMenuType::SLIDER => self.selected_sub_menu_sliders.next(),
-            SubMenuType::ONOFF => self.selected_sub_menu_onoff_selectors.next(),
         }
     }
 
@@ -151,7 +89,6 @@ impl<'a> App<'a> {
         match SubMenuType::from_str(self.sub_menu_selected()._type) {
             SubMenuType::TOGGLE => self.selected_sub_menu_toggles.next_list(),
             SubMenuType::SLIDER => self.selected_sub_menu_sliders.next_list(),
-            SubMenuType::ONOFF => self.selected_sub_menu_onoff_selectors.next_list(),
         }
     }
 
@@ -159,7 +96,6 @@ impl<'a> App<'a> {
         match SubMenuType::from_str(self.sub_menu_selected()._type) {
             SubMenuType::TOGGLE => self.selected_sub_menu_toggles.previous(),
             SubMenuType::SLIDER => self.selected_sub_menu_sliders.previous(),
-            SubMenuType::ONOFF => self.selected_sub_menu_onoff_selectors.previous(),
         }
     }
 
@@ -167,30 +103,22 @@ impl<'a> App<'a> {
         match SubMenuType::from_str(self.sub_menu_selected()._type) {
             SubMenuType::TOGGLE => self.selected_sub_menu_toggles.previous_list(),
             SubMenuType::SLIDER => self.selected_sub_menu_sliders.previous_list(),
-            SubMenuType::ONOFF => self.selected_sub_menu_onoff_selectors.previous_list(),
         }
     }
 
-    pub fn sub_menu_strs_and_states(&mut self) -> (&str, &str, Vec<(Vec<(&str, &str)>, ListState)>) {
-        (self.sub_menu_selected().title, self.sub_menu_selected().help_text,
+    pub fn sub_menu_strs_and_states(&mut self) -> (&str, &str, Vec<(Vec<(bool, &str)>, ListState)>) {
+        (self.sub_menu_selected().submenu_title, self.sub_menu_selected().help_text,
          match SubMenuType::from_str(self.sub_menu_selected()._type) {
             SubMenuType::TOGGLE => {
                 self.selected_sub_menu_toggles.lists.iter().map(|toggle_list| {
                     (toggle_list.items.iter().map(
-                        |toggle| (toggle.checked, toggle.title)
+                        |toggle| (toggle.checked, toggle.toggle_title)
                     ).collect(), toggle_list.state.clone())
                 }).collect()
             },
             SubMenuType::SLIDER => {
                 vec![(vec![], ListState::default())]
             },
-            SubMenuType::ONOFF => {
-                self.selected_sub_menu_onoff_selectors.lists.iter().map(|onoff_list| {
-                    (onoff_list.items.iter().map(
-                        |onoff| (onoff.checked, onoff.title)
-                    ).collect(), onoff_list.state.clone())
-                }).collect()
-            },
         })
     }
 
@@ -208,7 +136,7 @@ impl<'a> App<'a> {
                 .items.get_mut(list_idx).unwrap();
             match SubMenuType::from_str(selected_sub_menu._type) {
                 SubMenuType::TOGGLE => {
-                    let is_single_option = selected_sub_menu.is_single_option.is_some();
+                    let is_single_option = selected_sub_menu.is_single_option;
                     let state = self.selected_sub_menu_toggles.state;
                     self.selected_sub_menu_toggles.lists.iter_mut()
                         .map(|list| (list.state.selected(), &mut list.items))
@@ -216,42 +144,31 @@ impl<'a> App<'a> {
                             .enumerate()
                             .for_each(|(i, o)|
                             if state.is_some() && i == state.unwrap() {
-                               if o.checked != "is-appear" {
-                                   o.checked = "is-appear";
+                               if !o.checked {
+                                   o.checked = true;
                                } else {
-                                   o.checked = "is-hidden";
+                                   o.checked = false;
                                }
                             } else if is_single_option {
-                                o.checked = "is-hidden";
+                                o.checked = false;
                             }
                         ));
                     selected_sub_menu.toggles.iter_mut()
                         .enumerate()
                         .for_each(|(i, o)| {
                             if i == state  {
-                                if o.checked != "is-appear" {
-                                    o.checked = "is-appear";
+                                if !o.checked {
+                                    o.checked = true;
                                 } else {
-                                    o.checked = "is-hidden";
+                                    o.checked = false;
                                 }
                             } else if is_single_option {
-                                o.checked = "is-hidden";
+                                o.checked = false;
                             }
                         });
                 },
-                SubMenuType::ONOFF => {
-                    let onoff = self.selected_sub_menu_onoff_selectors.selected_list_item();
-                    if onoff.checked != "is-appear" {
-                        onoff.checked = "is-appear";
-                    } else {
-                        onoff.checked = "is-hidden";
-                    }
-                    selected_sub_menu.onoffselector.iter_mut()
-                        .filter(|o| o.title == onoff.title)
-                        .for_each(|o| o.checked = onoff.checked);
-                },
                 SubMenuType::SLIDER => {
-                    // self.selected_sub_menu_sliders.selected_list_item().checked = "is-appear";
+                    // self.selected_sub_menu_sliders.selected_list_item().checked = true;
                 }
             }
         }
@@ -352,17 +269,16 @@ pub fn ui<B: Backend>(f: &mut Frame<B>, app: &mut App) -> String {
     if app.outer_list {
         let tab_selected = app.tab_selected();
         let mut item_help = None;
-        for list_section in 0..app.menu_items.get(tab_selected).unwrap().lists.len() {
-            let stateful_list = &app.menu_items.get(tab_selected).unwrap().lists[list_section];
+        for (list_section, stateful_list) in app.menu_items.get(tab_selected).unwrap().lists.iter().enumerate() {
             let items: Vec<ListItem> = stateful_list
                 .items
                 .iter()
                 .map(|i| {
                     let lines = vec![Spans::from(
                         if stateful_list.state.selected().is_some() {
-                            i.title.to_owned()
+                            i.submenu_title.to_owned()
                         } else {
-                            "   ".to_owned() + i.title
+                            "   ".to_owned() + i.submenu_title
                         })];
                     ListItem::new(lines).style(Style::default().fg(Color::White))
                 })
@@ -389,7 +305,7 @@ pub fn ui<B: Backend>(f: &mut Frame<B>, app: &mut App) -> String {
 
         // TODO: Add Save Defaults
         let help_paragraph = Paragraph::new(
-            item_help.unwrap_or("").replace("\"", "") +
+            item_help.unwrap_or("").replace('\"', "") +
             "\nA: Enter sub-menu | B: Exit menu | ZL/ZR: Next tab"
         ).style(Style::default().fg(Color::Cyan));
         f.render_widget(help_paragraph, vertical_chunks[2]);
@@ -401,7 +317,7 @@ pub fn ui<B: Backend>(f: &mut Frame<B>, app: &mut App) -> String {
             let values_items: Vec<ListItem> = sub_menu_str.iter().map(|s| {
                 ListItem::new(
                     vec![
-                        Spans::from((if s.0 == "is-appear" { "X " } else { "  " }).to_owned() + s.1)
+                        Spans::from((if s.0 { "X " } else { "  " }).to_owned() + s.1)
                     ]
                 )
             }).collect();
@@ -419,7 +335,7 @@ pub fn ui<B: Backend>(f: &mut Frame<B>, app: &mut App) -> String {
         }
 
         let help_paragraph = Paragraph::new(
-            help_text.replace("\"", "") +
+            help_text.replace('\"', "") +
                 "\nA: Select toggle | B: Exit submenu"
         ).style(Style::default().fg(Color::Cyan));
         f.render_widget(help_paragraph, vertical_chunks[2]);
@@ -433,22 +349,17 @@ pub fn ui<B: Backend>(f: &mut Frame<B>, app: &mut App) -> String {
     for key in app.menu_items.keys() {
         for list in &app.menu_items.get(key).unwrap().lists {
             for sub_menu in &list.items {
-                let mut val = String::new();
-                sub_menu.toggles.iter()
-                    .filter(|t| t.checked == "is-appear")
-                    .for_each(|t| val.push_str(format!("{},", t.value).as_str()));
+                let val : usize = sub_menu.toggles.iter()
+                    .filter(|t| t.checked)
+                    .map(|t| t.toggle_value)
+                    .sum();
 
-                sub_menu.onoffselector.iter()
-                    .for_each(|o| {
-                        val.push_str(
-                            format!("{}", if o.checked == "is-appear" { 1 } else { 0 }).as_str())
-                    });
-                settings.insert(sub_menu.id, val);
+                settings.insert(sub_menu.submenu_id, val);
             }
         }
     }
 
-    url.push_str("?");
+    url.push('?');
     settings.iter()
         .for_each(|(section, val)| url.push_str(format!("{}={}&", section, val).as_str()));
     url
diff --git a/training_mod_tui/src/list.rs b/training_mod_tui/src/list.rs
index a8cd63b..2e0d337 100644
--- a/training_mod_tui/src/list.rs
+++ b/training_mod_tui/src/list.rs
@@ -49,14 +49,14 @@ impl<T: Clone> MultiStatefulList<T> {
                 state.select(Some(0));
             }
             StatefulList {
-                state: state,
+                state,
                 items: items[list_section_min_idx..list_section_max_idx].to_vec(),
             }
         }).collect();
         let total_len = items.len();
         MultiStatefulList {
-            lists: lists,
-            total_len: total_len,
+            lists,
+            total_len,
             state: 0
         }
     }
@@ -68,12 +68,11 @@ impl<T: Clone> MultiStatefulList<T> {
         if list_section != next_list_section {
             self.lists[list_section].unselect();
         }
-        let state;
-        if self.state + 1 >= self.total_len {
-            state = (0, 0);
+        let state= if self.state + 1 >= self.total_len {
+            (0, 0)
         } else {
-            state = (next_list_section, next_list_idx);
-        }
+            (next_list_section, next_list_idx)
+        };
 
         self.lists[state.0].state.select(Some(state.1));
         self.state = self.list_idx_to_idx(state);
@@ -84,13 +83,12 @@ impl<T: Clone> MultiStatefulList<T> {
         let (last_list_section, last_list_idx) = (self.lists.len() - 1, self.lists[self.lists.len() - 1].items.len() - 1);
 
         self.lists[list_section].unselect();
-        let state;
-        if self.state == 0 {
-            state = (last_list_section, last_list_idx);
+        let state= if self.state == 0 {
+            (last_list_section, last_list_idx)
         } else {
             let (prev_list_section, prev_list_idx) = self.idx_to_list_idx(self.state - 1);
-            state = (prev_list_section, prev_list_idx);
-        }
+            (prev_list_section, prev_list_idx)
+        };
 
         self.lists[state.0].state.select(Some(state.1));
         self.state = self.list_idx_to_idx(state);
@@ -99,12 +97,11 @@ impl<T: Clone> MultiStatefulList<T> {
     pub fn next_list(&mut self) {
         let (list_section, list_idx) = self.idx_to_list_idx(self.state);
         let next_list_section = (list_section + 1) % self.lists.len();
-        let next_list_idx;
-        if list_idx > self.lists[next_list_section].items.len() - 1 {
-            next_list_idx = self.lists[next_list_section].items.len() - 1;
+        let next_list_idx = if list_idx > self.lists[next_list_section].items.len() - 1 {
+            self.lists[next_list_section].items.len() - 1
         } else {
-            next_list_idx = list_idx;
-        }
+            list_idx
+        };
 
         if list_section != next_list_section {
             self.lists[list_section].unselect();
@@ -117,19 +114,17 @@ impl<T: Clone> MultiStatefulList<T> {
 
     pub fn previous_list(&mut self) {
         let (list_section, list_idx) = self.idx_to_list_idx(self.state);
-        let prev_list_section;
-        if list_section == 0 {
-            prev_list_section = self.lists.len() - 1;
+        let prev_list_section = if list_section == 0 {
+            self.lists.len() - 1
         } else {
-            prev_list_section = list_section - 1;
-        }
+            list_section - 1
+        };
 
-        let prev_list_idx;
-        if list_idx > self.lists[prev_list_section].items.len() - 1 {
-            prev_list_idx = self.lists[prev_list_section].items.len() - 1;
+        let prev_list_idx= if list_idx > self.lists[prev_list_section].items.len() - 1 {
+            self.lists[prev_list_section].items.len() - 1
         } else {
-            prev_list_idx = list_idx;
-        }
+            list_idx
+        };
 
         if list_section != prev_list_section {
             self.lists[list_section].unselect();
@@ -152,7 +147,7 @@ impl<T> StatefulList<T> {
         // Enforce state as first of list
         state.select(Some(0));
         StatefulList {
-            state: state,
+            state,
             items,
         }
     }
diff --git a/training_mod_tui/src/main.rs b/training_mod_tui/src/main.rs
index d8353e2..9e4424e 100644
--- a/training_mod_tui/src/main.rs
+++ b/training_mod_tui/src/main.rs
@@ -32,10 +32,10 @@ fn main() -> Result<(), Box<dyn Error>> {
         let mut url = String::new();
         let frame_res = terminal.draw(|f| url = training_mod_tui::ui(f, &mut app))?;
 
-        for (i, cell) in frame_res.buffer.content().into_iter().enumerate() {
+        for (i, cell) in frame_res.buffer.content().iter().enumerate() {
             print!("{}", cell.symbol);
             if i % frame_res.area.width as usize == frame_res.area.width as usize - 1 {
-                print!("\n");
+                println!();
             }
         }
         println!();