git.lucas.co / cce-system-interface
system settings
git clone https://git.lucas.co/cce-system-interface.git

commit3ed99e89c2e900e475e53789fa8642798643f440
parentc018dd96fb
authorLucas Galante <[email protected]>
date2026-05-26 15:20
Update system configuration and interface modules

 src/app.rs              |   4 +
 src/main.rs             | 606 +++++++++++++++++++++++++++++++++++++++++++++---
 src/pages/audio.rs      |   6 +-
 src/pages/colors.rs     | 316 +++++++++++++++++++++++++
 src/pages/input.rs      |   6 +-
 src/pages/layout.rs     | 103 +-------
 src/pages/mod.rs        |  12 +-
 src/pages/network.rs    |  43 ++--
 src/pages/processors.rs |  27 +--
 src/pages/services.rs   |  53 +++--
 src/pages/typeface.rs   | 468 +++++++++++++++++++++++++++++++++----
 11 files changed, 1420 insertions(+), 224 deletions(-)

diff --git a/src/app.rs b/src/app.rs
index 353ea6c..c3e0548 100644
--- a/src/app.rs
+++ b/src/app.rs
@@ -14,6 +14,7 @@ use crate::pages::system_info;
 use crate::pages::backup;
 use crate::pages::typeface;
 use crate::pages::services;
+use crate::pages::colors;
 use crate::pages::Page;
 
 pub struct AppState {
@@ -32,6 +33,7 @@ pub struct AppState {
     pub backup: backup::BackupState,
     pub typeface: typeface::TypefaceState,
     pub services: services::ServicesState,
+    pub colors: colors::ColorsState,
 }
 
 impl Default for AppState {
@@ -52,6 +54,7 @@ impl Default for AppState {
             backup: backup::BackupState::default(),
             typeface: typeface::TypefaceState::default(),
             services: services::ServicesState::default(),
+            colors: colors::ColorsState::default(),
         }
     }
 }
@@ -72,6 +75,7 @@ pub enum AppAction {
     Backup(backup::BackupMessage),
     Typeface(typeface::TypefaceMessage),
     Services(services::ServicesMessage),
+    Colors(colors::ColorsMessage),
 }
 
 pub struct PageContent {
diff --git a/src/main.rs b/src/main.rs
index 6d71f67..3cb4510 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -166,6 +166,7 @@ struct SystemInterface {
     rx_backup_state: std::sync::mpsc::Receiver<pages::backup::BackupState>,
     rx_typeface: std::sync::mpsc::Receiver<pages::typeface::TypefaceState>,
     rx_services: std::sync::mpsc::Receiver<Vec<pages::services::ServiceInfo>>,
+    rx_colors: std::sync::mpsc::Receiver<pages::colors::ColorsState>,
     tx_backup: std::sync::mpsc::Sender<pages::backup::BackupMessage>,
     rx_backup: std::sync::mpsc::Receiver<pages::backup::BackupMessage>,
     tx_color_selector: std::sync::mpsc::Sender<ColorSelectorAction>,
@@ -177,6 +178,8 @@ struct SystemInterface {
     needs_rebuild: bool,
     scroll_y: f32,
     max_scroll_y: f32,
+    page_root_container: clear_ui::widget::Container,
+    page_sec_containers: Vec<clear_ui::widget::Container>,
 }
 
 impl SystemInterface {
@@ -369,6 +372,17 @@ impl SystemInterface {
         let rx_backup_state = spawn_bg(30, || pages::backup::fetch_backup_state());
         let rx_typeface = spawn_bg(30, || pages::typeface::fetch_typeface_state());
         let rx_services = spawn_bg(3, || pages::services::fetch_services());
+        let rx_colors = {
+            let (tx, rx) = std::sync::mpsc::channel::<pages::colors::ColorsState>();
+            tokio::spawn(async move {
+                loop {
+                    let val = tokio::task::spawn_blocking(|| pages::colors::read_colors_config()).await;
+                    if let Ok(val) = val { if tx.send(val).is_err() { break; } }
+                    tokio::time::sleep(std::time::Duration::from_secs(30)).await;
+                }
+            });
+            rx
+        };
         let (tx_backup, rx_backup) = std::sync::mpsc::channel();
 
         let (tx_color_selector, rx_color_selector) = std::sync::mpsc::channel();
@@ -385,12 +399,14 @@ impl SystemInterface {
             scale_factor,
             rx_power, rx_audio, rx_display, rx_network, rx_layout, rx_input, rx_fingers,
             rx_processors, rx_system, rx_status, rx_storage, rx_notifications,
-            rx_backup_state, rx_typeface, rx_services, tx_backup, rx_backup,
+            rx_backup_state, rx_typeface, rx_services, rx_colors, tx_backup, rx_backup,
             tx_color_selector, rx_color_selector,
             width, height,
             needs_rebuild: true,
             scroll_y: 0.0,
             max_scroll_y: 0.0,
+            page_root_container: clear_ui::widget::Container::new(),
+            page_sec_containers: Vec::new(),
         };
         this.rebuild_layout(width as f32, height as f32);
         this
@@ -503,7 +519,7 @@ impl SystemInterface {
 
             // Auto-detect if inside a ScrollBox to apply left alignment by default
             if !left_align && btn.w >= 60.0 {
-                if self.app.current_page == Page::Typeface {
+                if self.app.current_page == Page::Typefaces {
                     let sb = &self.app.typeface.list_box;
                     let (sb_x, sb_y, sb_w, sb_h) = sb.rect();
                     if btn.x >= sb_x - 1.0 && btn.x + btn.w <= sb_x + sb_w + 1.0
@@ -547,6 +563,169 @@ impl SystemInterface {
             page_buttons.push(cb);
         }
 
+        // ── Rebuild Widget Focus Hierarchy ──
+        self.page_root_container.clear_children();
+        self.page_root_container.set_parent(None);
+        self.page_sec_containers.clear();
+
+        // Clear all widgets' hierarchy links
+        self.app.typeface.sans_box.clear_children(); self.app.typeface.sans_box.set_parent(None);
+        self.app.typeface.serif_box.clear_children(); self.app.typeface.serif_box.set_parent(None);
+        self.app.typeface.mono_box.clear_children(); self.app.typeface.mono_box.set_parent(None);
+        self.app.typeface.borders_menu.clear_children(); self.app.typeface.borders_menu.set_parent(None);
+        self.app.typeface.borders_box.clear_children(); self.app.typeface.borders_box.set_parent(None);
+        self.app.typeface.status_menu.clear_children(); self.app.typeface.status_menu.set_parent(None);
+        self.app.typeface.status_box.clear_children(); self.app.typeface.status_box.set_parent(None);
+        self.app.typeface.fuzzel_menu.clear_children(); self.app.typeface.fuzzel_menu.set_parent(None);
+        self.app.typeface.fuzzel_box.clear_children(); self.app.typeface.fuzzel_box.set_parent(None);
+        self.app.typeface.terminal_menu.clear_children(); self.app.typeface.terminal_menu.set_parent(None);
+        self.app.typeface.terminal_box.clear_children(); self.app.typeface.terminal_box.set_parent(None);
+        self.app.typeface.search_box.clear_children(); self.app.typeface.search_box.set_parent(None);
+        self.app.typeface.list_box.scroll_box.clear_children(); self.app.typeface.list_box.scroll_box.set_parent(None);
+
+        self.app.services.search_box.clear_children(); self.app.services.search_box.set_parent(None);
+
+        self.app.services.list_box.scroll_box.clear_children(); self.app.services.list_box.scroll_box.set_parent(None);
+
+        self.app.processors.cpu_list_box.scroll_box.clear_children(); self.app.processors.cpu_list_box.scroll_box.set_parent(None);
+
+        self.app.network.wifi_list_box.scroll_box.clear_children(); self.app.network.wifi_list_box.scroll_box.set_parent(None);
+
+        for sb in &mut self.app.layout.spinboxes {
+            sb.clear_children();
+            sb.set_parent(None);
+        }
+        self.app.layout.cascade_offset_spinbox.clear_children(); self.app.layout.cascade_offset_spinbox.set_parent(None);
+        self.app.layout.edge_gap_spinbox.clear_children(); self.app.layout.edge_gap_spinbox.set_parent(None);
+        self.app.layout.top_gap_spinbox.clear_children(); self.app.layout.top_gap_spinbox.set_parent(None);
+
+        for cs in &mut self.app.colors.color_selectors {
+            cs.clear_children();
+            cs.set_parent(None);
+        }
+
+        self.app.notifications.duration_spinbox.clear_children(); self.app.notifications.duration_spinbox.set_parent(None);
+
+        self.app.input.rate_spinbox.clear_children(); self.app.input.rate_spinbox.set_parent(None);
+        self.app.input.delay_spinbox.clear_children(); self.app.input.delay_spinbox.set_parent(None);
+        self.app.input.scroll_friction_spinbox.clear_children(); self.app.input.scroll_friction_spinbox.set_parent(None);
+        self.app.input.pointer_friction_spinbox.clear_children(); self.app.input.pointer_friction_spinbox.set_parent(None);
+        self.app.input.trackpad_friction_spinbox.clear_children(); self.app.input.trackpad_friction_spinbox.set_parent(None);
+
+        for sb in &mut self.app.audio.sink_spinboxes {
+            sb.clear_children();
+            sb.set_parent(None);
+        }
+        for sb in &mut self.app.audio.source_spinboxes {
+            sb.clear_children();
+            sb.set_parent(None);
+        }
+
+        self.app.display.brightness_spinbox.clear_children(); self.app.display.brightness_spinbox.set_parent(None);
+
+        use clear_ui::widget::focus::link_parent_child;
+        match self.app.current_page {
+            Page::Typefaces => {
+                self.page_sec_containers.resize_with(3, clear_ui::widget::Container::new);
+                
+                link_parent_child(&mut self.page_root_container, &mut self.page_sec_containers[0]);
+                link_parent_child(&mut self.page_root_container, &mut self.page_sec_containers[1]);
+                link_parent_child(&mut self.page_root_container, &mut self.page_sec_containers[2]);
+                
+                link_parent_child(&mut self.page_sec_containers[0], &mut self.app.typeface.sans_box);
+                link_parent_child(&mut self.page_sec_containers[0], &mut self.app.typeface.serif_box);
+                link_parent_child(&mut self.page_sec_containers[0], &mut self.app.typeface.mono_box);
+                
+                link_parent_child(&mut self.page_sec_containers[1], &mut self.app.typeface.borders_menu);
+                link_parent_child(&mut self.page_sec_containers[1], &mut self.app.typeface.borders_box);
+                link_parent_child(&mut self.page_sec_containers[1], &mut self.app.typeface.status_menu);
+                link_parent_child(&mut self.page_sec_containers[1], &mut self.app.typeface.status_box);
+                link_parent_child(&mut self.page_sec_containers[1], &mut self.app.typeface.fuzzel_menu);
+                link_parent_child(&mut self.page_sec_containers[1], &mut self.app.typeface.fuzzel_box);
+                link_parent_child(&mut self.page_sec_containers[1], &mut self.app.typeface.terminal_menu);
+                link_parent_child(&mut self.page_sec_containers[1], &mut self.app.typeface.terminal_box);
+                
+                link_parent_child(&mut self.page_sec_containers[2], &mut self.app.typeface.search_box);
+                link_parent_child(&mut self.page_sec_containers[2], &mut self.app.typeface.list_box.scroll_box);
+            }
+            Page::Services => {
+                link_parent_child(&mut self.page_root_container, &mut self.app.services.search_box);
+                link_parent_child(&mut self.page_root_container, &mut self.app.services.list_box.scroll_box);
+            }
+            Page::Processors => {
+                link_parent_child(&mut self.page_root_container, &mut self.app.processors.cpu_list_box.scroll_box);
+            }
+            Page::Radios => {
+                link_parent_child(&mut self.page_root_container, &mut self.app.network.wifi_list_box.scroll_box);
+            }
+            Page::Layout => {
+                self.page_sec_containers.resize_with(6, clear_ui::widget::Container::new);
+                
+                for i in 0..6 {
+                    link_parent_child(&mut self.page_root_container, &mut self.page_sec_containers[i]);
+                }
+                
+                if !self.app.layout.spinboxes.is_empty() {
+                    link_parent_child(&mut self.page_sec_containers[0], &mut self.app.layout.spinboxes[0]);
+                }
+                if self.app.layout.spinboxes.len() > 1 {
+                    link_parent_child(&mut self.page_sec_containers[1], &mut self.app.layout.spinboxes[1]);
+                }
+                link_parent_child(&mut self.page_sec_containers[1], &mut self.app.layout.cascade_offset_spinbox);
+                link_parent_child(&mut self.page_sec_containers[1], &mut self.app.layout.edge_gap_spinbox);
+                link_parent_child(&mut self.page_sec_containers[1], &mut self.app.layout.top_gap_spinbox);
+                
+                if self.app.layout.spinboxes.len() > 2 {
+                    link_parent_child(&mut self.page_sec_containers[2], &mut self.app.layout.spinboxes[2]);
+                }
+                if self.app.layout.spinboxes.len() > 3 {
+                    link_parent_child(&mut self.page_sec_containers[3], &mut self.app.layout.spinboxes[3]);
+                }
+                if self.app.layout.spinboxes.len() > 4 {
+                    link_parent_child(&mut self.page_sec_containers[4], &mut self.app.layout.spinboxes[4]);
+                }
+                if self.app.layout.spinboxes.len() > 5 {
+                    link_parent_child(&mut self.page_sec_containers[5], &mut self.app.layout.spinboxes[5]);
+                }
+            }
+            Page::Colors => {
+                for cs in &mut self.app.colors.color_selectors {
+                    link_parent_child(&mut self.page_root_container, cs);
+                }
+            }
+            Page::Notifications => {
+                link_parent_child(&mut self.page_root_container, &mut self.app.notifications.duration_spinbox);
+            }
+            Page::Input => {
+                self.page_sec_containers.resize_with(2, clear_ui::widget::Container::new);
+                link_parent_child(&mut self.page_root_container, &mut self.page_sec_containers[0]);
+                link_parent_child(&mut self.page_root_container, &mut self.page_sec_containers[1]);
+                
+                link_parent_child(&mut self.page_sec_containers[0], &mut self.app.input.rate_spinbox);
+                link_parent_child(&mut self.page_sec_containers[0], &mut self.app.input.delay_spinbox);
+                
+                link_parent_child(&mut self.page_sec_containers[1], &mut self.app.input.scroll_friction_spinbox);
+                link_parent_child(&mut self.page_sec_containers[1], &mut self.app.input.pointer_friction_spinbox);
+                link_parent_child(&mut self.page_sec_containers[1], &mut self.app.input.trackpad_friction_spinbox);
+            }
+            Page::Audio => {
+                self.page_sec_containers.resize_with(2, clear_ui::widget::Container::new);
+                link_parent_child(&mut self.page_root_container, &mut self.page_sec_containers[0]);
+                link_parent_child(&mut self.page_root_container, &mut self.page_sec_containers[1]);
+                
+                for sb in &mut self.app.audio.sink_spinboxes {
+                    link_parent_child(&mut self.page_sec_containers[0], sb);
+                }
+                for sb in &mut self.app.audio.source_spinboxes {
+                    link_parent_child(&mut self.page_sec_containers[1], sb);
+                }
+            }
+            Page::Display => {
+                link_parent_child(&mut self.page_root_container, &mut self.app.display.brightness_spinbox);
+            }
+            _ => {}
+        }
+
         self.widgets = widgets;
         self.text_items = text_items;
         self.page_buttons = page_buttons;
@@ -555,21 +734,26 @@ impl SystemInterface {
 
     fn render_page_content(&mut self, cx: f32, cy: f32, cw: f32, ch: f32) -> PageContent {
         use pages::*;
+        let root_focused = clear_ui::widget::focus::is_focused(&self.page_root_container);
+        let sec_focused: Vec<bool> = self.page_sec_containers.iter()
+            .map(|c| clear_ui::widget::focus::is_focused(c))
+            .collect();
         match self.app.current_page {
             Page::Power => power::view(&self.app.power, cx, cy, cw, ch),
-            Page::Audio => audio::view(&mut self.app.audio, cx, cy, cw, ch),
+            Page::Audio => audio::view(&mut self.app.audio, cx, cy, cw, ch, &sec_focused),
             Page::Display => display::view(&mut self.app.display, cx, cy, cw, ch),
-            Page::Radios => network::view(&self.app.network, cx, cy, cw, ch),
-            Page::Layout => layout::view(&mut self.app.layout, cx, cy, cw, ch),
-            Page::Processors => processors::view(&mut self.app.processors, cx, cy, cw, ch),
-            Page::Input => input::view(&mut self.app.input, cx, cy, cw, ch),
+            Page::Radios => network::view(&mut self.app.network, cx, cy, cw, ch, root_focused),
+            Page::Layout => layout::view(&mut self.app.layout, cx, cy, cw, ch, &sec_focused),
+            Page::Processors => processors::view(&mut self.app.processors, cx, cy, cw, ch, root_focused),
+            Page::Input => input::view(&mut self.app.input, cx, cy, cw, ch, &sec_focused),
             Page::System => system_info::view(&self.app.system_info, cx, cy, cw, ch),
             Page::Status => status::view(&mut self.app.status, cx, cy, cw, ch),
             Page::Storage => storage::view(&self.app.storage, cx, cy, cw, ch),
             Page::Notifications => notifications::view(&mut self.app.notifications, cx, cy, cw, ch),
             Page::Backup => backup::view(&self.app.backup, cx, cy, cw, ch),
-            Page::Typeface => typeface::view(&mut self.app.typeface, cx, cy, cw, ch),
-            Page::Services => services::view(&mut self.app.services, cx, cy, cw, ch),
+            Page::Typefaces => typeface::view(&mut self.app.typeface, cx, cy, cw, ch, &sec_focused),
+            Page::Services => services::view(&mut self.app.services, cx, cy, cw, ch, root_focused),
+            Page::Colors => colors::view(&mut self.app.colors, cx, cy, cw, ch),
         }
     }
 
@@ -696,6 +880,10 @@ impl SystemInterface {
             pages::services::update(&mut self.app.services, pages::services::ServicesMessage::Refreshed(s));
             self.needs_rebuild = true;
         }
+        while let Ok(s) = self.rx_colors.try_recv() {
+            colors::update(&mut self.app.colors, pages::colors::ColorsMessage::Refreshed(s));
+            self.needs_rebuild = true;
+        }
         while let Ok(m) = self.rx_backup.try_recv() {
             self.handle_action(&AppAction::Backup(m));
             self.needs_rebuild = true;
@@ -703,10 +891,10 @@ impl SystemInterface {
         while let Ok(action) = self.rx_color_selector.try_recv() {
             match action {
                 ColorSelectorAction::Background(rgb) => {
-                    layout::update(&mut self.app.layout, layout::LayoutMessage::SetBackground(rgb));
+                    colors::update(&mut self.app.colors, pages::colors::ColorsMessage::SetLowColor(rgb));
                 }
                 ColorSelectorAction::Border(rgb) => {
-                    layout::update(&mut self.app.layout, layout::LayoutMessage::SetBorderColor(rgb));
+                    colors::update(&mut self.app.colors, pages::colors::ColorsMessage::SetHighColor(rgb));
                 }
             }
             self.needs_rebuild = true;
@@ -729,6 +917,7 @@ impl SystemInterface {
             AppAction::Notifications(m) => notifications::update(&mut self.app.notifications, m.clone()),
             AppAction::Typeface(m) => typeface::update(&mut self.app.typeface, m.clone()),
             AppAction::Services(m) => services::update(&mut self.app.services, m.clone()),
+            AppAction::Colors(m) => colors::update(&mut self.app.colors, m.clone()),
             AppAction::Backup(m) => match m {
                 pages::backup::BackupMessage::StartBackup => {
                     pages::backup::update(&mut self.app.backup, pages::backup::BackupMessage::StartBackup);
@@ -774,7 +963,9 @@ impl SystemInterface {
             if self.app.layout.top_gap_spinbox.cursor_moved(lx, ly) {
                 changed = true;
             }
-            for cp in &mut self.app.layout.color_selectors {
+        }
+        if self.app.current_page == Page::Colors {
+            for cp in &mut self.app.colors.color_selectors {
                 if cp.cursor_moved(lx, ly) {
                     changed = true;
                 }
@@ -874,7 +1065,7 @@ impl SystemInterface {
                 changed = true;
             }
         }
-        if self.app.current_page == Page::Typeface {
+        if self.app.current_page == Page::Typefaces {
             if self.app.typeface.sans_box.cursor_moved(lx, ly) {
                 changed = true;
             }
@@ -884,15 +1075,27 @@ impl SystemInterface {
             if self.app.typeface.mono_box.cursor_moved(lx, ly) {
                 changed = true;
             }
+            if self.app.typeface.borders_menu.cursor_moved(lx, ly) {
+                changed = true;
+            }
             if self.app.typeface.borders_box.cursor_moved(lx, ly) {
                 changed = true;
             }
+            if self.app.typeface.status_menu.cursor_moved(lx, ly) {
+                changed = true;
+            }
             if self.app.typeface.status_box.cursor_moved(lx, ly) {
                 changed = true;
             }
+            if self.app.typeface.fuzzel_menu.cursor_moved(lx, ly) {
+                changed = true;
+            }
             if self.app.typeface.fuzzel_box.cursor_moved(lx, ly) {
                 changed = true;
             }
+            if self.app.typeface.terminal_menu.cursor_moved(lx, ly) {
+                changed = true;
+            }
             if self.app.typeface.terminal_box.cursor_moved(lx, ly) {
                 changed = true;
             }
@@ -931,6 +1134,7 @@ impl SystemInterface {
                 if px >= w.x && px <= w.x + w.w && py >= w.y && py <= w.y + w.h {
                     if let WidgetKind::PageButton(p) = &w.kind {
                         if self.app.current_page != *p {
+                            clear_ui::widget::focus::clear_focus();
                             self.app.current_page = *p;
                             self.scroll_y = 0.0;
                             self.needs_rebuild = true;
@@ -943,6 +1147,93 @@ impl SystemInterface {
         let lx = self.cursor_x / s;
         let ly = self.cursor_y / s + self.scroll_y;
         let mut actions = Vec::new();
+
+        if state == clear_ui::widget::ElementState::Pressed {
+            let mut clicked_any_focusable = false;
+            match self.app.current_page {
+                Page::Layout => {
+                    for sb in &mut self.app.layout.spinboxes {
+                        if sb.hit_test(lx, ly) { clicked_any_focusable = true; }
+                    }
+                    if self.app.layout.cascade_offset_spinbox.hit_test(lx, ly) { clicked_any_focusable = true; }
+                    if self.app.layout.edge_gap_spinbox.hit_test(lx, ly) { clicked_any_focusable = true; }
+                    if self.app.layout.top_gap_spinbox.hit_test(lx, ly) { clicked_any_focusable = true; }
+                }
+                Page::Colors => {
+                    for cp in &mut self.app.colors.color_selectors {
+                        if cp.hit_test(lx, ly) { clicked_any_focusable = true; }
+                    }
+                }
+                Page::Input => {
+                    if self.app.input.rate_spinbox.hit_test(lx, ly) { clicked_any_focusable = true; }
+                    if self.app.input.delay_spinbox.hit_test(lx, ly) { clicked_any_focusable = true; }
+                    if self.app.input.scroll_friction_spinbox.hit_test(lx, ly) { clicked_any_focusable = true; }
+                    if self.app.input.pointer_friction_spinbox.hit_test(lx, ly) { clicked_any_focusable = true; }
+                    if self.app.input.trackpad_friction_spinbox.hit_test(lx, ly) { clicked_any_focusable = true; }
+                }
+                Page::Notifications => {
+                    if self.app.notifications.duration_spinbox.hit_test(lx, ly) { clicked_any_focusable = true; }
+                }
+                Page::Audio => {
+                    for sb in &mut self.app.audio.sink_spinboxes {
+                        if sb.hit_test(lx, ly) { clicked_any_focusable = true; }
+                    }
+                    for sb in &mut self.app.audio.source_spinboxes {
+                        if sb.hit_test(lx, ly) { clicked_any_focusable = true; }
+                    }
+                }
+                Page::Display => {
+                    if self.app.display.brightness_spinbox.hit_test(lx, ly) { clicked_any_focusable = true; }
+                    if self.app.display.night_light_label.hit_test(lx, ly) { clicked_any_focusable = true; }
+                    for out in &mut self.app.display.outputs {
+                        if out.name_label.hit_test(lx, ly) { clicked_any_focusable = true; }
+                        if out.resolution_label.hit_test(lx, ly) { clicked_any_focusable = true; }
+                        if let Some(ref mut scale_lbl) = out.scale_label {
+                            if scale_lbl.hit_test(lx, ly) { clicked_any_focusable = true; }
+                        }
+                    }
+                }
+                Page::Status => {
+                    if self.app.status.status_label.hit_test(lx, ly) { clicked_any_focusable = true; }
+                    if self.app.status.size_label.hit_test(lx, ly) { clicked_any_focusable = true; }
+                }
+                Page::Typefaces => {
+                    let tf = &mut self.app.typeface;
+                    if tf.sans_box.hit_test(lx, ly) { clicked_any_focusable = true; }
+                    if tf.serif_box.hit_test(lx, ly) { clicked_any_focusable = true; }
+                    if tf.mono_box.hit_test(lx, ly) { clicked_any_focusable = true; }
+                    if tf.borders_menu.hit_test(lx, ly) { clicked_any_focusable = true; }
+                    if tf.borders_box.hit_test(lx, ly) { clicked_any_focusable = true; }
+                    if tf.status_menu.hit_test(lx, ly) { clicked_any_focusable = true; }
+                    if tf.status_box.hit_test(lx, ly) { clicked_any_focusable = true; }
+                    if tf.fuzzel_menu.hit_test(lx, ly) { clicked_any_focusable = true; }
+                    if tf.fuzzel_box.hit_test(lx, ly) { clicked_any_focusable = true; }
+                    if tf.terminal_menu.hit_test(lx, ly) { clicked_any_focusable = true; }
+                    if tf.terminal_box.hit_test(lx, ly) { clicked_any_focusable = true; }
+                    if tf.search_box.hit_test(lx, ly) { clicked_any_focusable = true; }
+                    if tf.list_box.hit_test(lx, ly) { clicked_any_focusable = true; }
+                }
+                Page::Services => {
+                    let srv = &mut self.app.services;
+                    if srv.search_box.hit_test(lx, ly) { clicked_any_focusable = true; }
+                    if srv.list_box.hit_test(lx, ly) { clicked_any_focusable = true; }
+                }
+                Page::Processors => {
+                    let proc = &mut self.app.processors;
+                    if proc.cpu_list_box.hit_test(lx, ly) { clicked_any_focusable = true; }
+                }
+                Page::Radios => {
+                    let net = &mut self.app.network;
+                    if net.wifi_list_box.hit_test(lx, ly) { clicked_any_focusable = true; }
+                }
+                _ => {}
+            }
+
+            if !clicked_any_focusable {
+                clear_ui::widget::focus::clear_focus();
+            }
+        }
+
         if state == clear_ui::widget::ElementState::Pressed && self.app.current_page == Page::Layout {
             for (i, sb) in self.app.layout.spinboxes.iter_mut().enumerate() {
                 if !sb.hit_test(lx, ly) { sb.unfocus(); }
@@ -980,20 +1271,24 @@ impl SystemInterface {
                     pages::layout::LayoutMessage::SetTopGap(sb.value as u16)
                 ));
             }
-            for (i, cp) in self.app.layout.color_selectors.iter_mut().enumerate() {
+        }
+        if state == clear_ui::widget::ElementState::Pressed && self.app.current_page == Page::Colors {
+            for (i, cp) in self.app.colors.color_selectors.iter_mut().enumerate() {
                 let old = cp.color;
                 if !cp.hit_test(lx, ly) { cp.unfocus(); }
                 cp.mouse_input(button, state, lx, ly);
                 if cp.take_click() {
-                    actions.push(AppAction::Layout(match i {
-                        0 => pages::layout::LayoutMessage::PickBackgroundColor,
-                        _ => pages::layout::LayoutMessage::PickBorderColor,
+                    actions.push(AppAction::Colors(match i {
+                        0 => pages::colors::ColorsMessage::PickLowColor,
+                        1 => pages::colors::ColorsMessage::PickHighColor,
+                        _ => pages::colors::ColorsMessage::PickDisabledColor,
                     }));
                 }
                 if cp.color != old {
-                    actions.push(AppAction::Layout(match i {
-                        0 => pages::layout::LayoutMessage::SetBackground(cp.color),
-                        _ => pages::layout::LayoutMessage::SetBorderColor(cp.color),
+                    actions.push(AppAction::Colors(match i {
+                        0 => pages::colors::ColorsMessage::SetLowColor(cp.color),
+                        1 => pages::colors::ColorsMessage::SetHighColor(cp.color),
+                        _ => pages::colors::ColorsMessage::SetDisabledColor(cp.color),
                     }));
                 }
             }
@@ -1125,7 +1420,7 @@ impl SystemInterface {
             if !lbl2.hit_test(lx, ly) { lbl2.unfocus(); }
             lbl2.mouse_input(button, state, lx, ly);
         }
-        if state == clear_ui::widget::ElementState::Pressed && self.app.current_page == Page::Typeface {
+        if state == clear_ui::widget::ElementState::Pressed && self.app.current_page == Page::Typefaces {
             let tb = &mut self.app.typeface.sans_box;
             if !tb.hit_test(lx, ly) { tb.unfocus(); }
             if tb.mouse_input(button, state, lx, ly) {
@@ -1153,6 +1448,15 @@ impl SystemInterface {
                 actions.push(AppAction::Typeface(pages::typeface::TypefaceMessage::SetMono(tb.text.clone())));
             }
 
+            let menu = &mut self.app.typeface.borders_menu;
+            if !menu.hit_test(lx, ly) { menu.unfocus(); }
+            if menu.mouse_input(button, state, lx, ly) {
+                self.needs_rebuild = true;
+            }
+            if menu.take_change() {
+                actions.push(AppAction::Typeface(pages::typeface::TypefaceMessage::SetBordersMenu(menu.selected)));
+            }
+
             let tb = &mut self.app.typeface.borders_box;
             if !tb.hit_test(lx, ly) { tb.unfocus(); }
             if tb.mouse_input(button, state, lx, ly) {
@@ -1162,6 +1466,15 @@ impl SystemInterface {
                 actions.push(AppAction::Typeface(pages::typeface::TypefaceMessage::SetBorders(tb.text.clone())));
             }
 
+            let menu = &mut self.app.typeface.status_menu;
+            if !menu.hit_test(lx, ly) { menu.unfocus(); }
+            if menu.mouse_input(button, state, lx, ly) {
+                self.needs_rebuild = true;
+            }
+            if menu.take_change() {
+                actions.push(AppAction::Typeface(pages::typeface::TypefaceMessage::SetStatusMenu(menu.selected)));
+            }
+
             let tb = &mut self.app.typeface.status_box;
             if !tb.hit_test(lx, ly) { tb.unfocus(); }
             if tb.mouse_input(button, state, lx, ly) {
@@ -1171,6 +1484,15 @@ impl SystemInterface {
                 actions.push(AppAction::Typeface(pages::typeface::TypefaceMessage::SetStatus(tb.text.clone())));
             }
 
+            let menu = &mut self.app.typeface.fuzzel_menu;
+            if !menu.hit_test(lx, ly) { menu.unfocus(); }
+            if menu.mouse_input(button, state, lx, ly) {
+                self.needs_rebuild = true;
+            }
+            if menu.take_change() {
+                actions.push(AppAction::Typeface(pages::typeface::TypefaceMessage::SetFuzzelMenu(menu.selected)));
+            }
+
             let tb = &mut self.app.typeface.fuzzel_box;
             if !tb.hit_test(lx, ly) { tb.unfocus(); }
             if tb.mouse_input(button, state, lx, ly) {
@@ -1180,6 +1502,15 @@ impl SystemInterface {
                 actions.push(AppAction::Typeface(pages::typeface::TypefaceMessage::SetFuzzel(tb.text.clone())));
             }
 
+            let menu = &mut self.app.typeface.terminal_menu;
+            if !menu.hit_test(lx, ly) { menu.unfocus(); }
+            if menu.mouse_input(button, state, lx, ly) {
+                self.needs_rebuild = true;
+            }
+            if menu.take_change() {
+                actions.push(AppAction::Typeface(pages::typeface::TypefaceMessage::SetTerminalMenu(menu.selected)));
+            }
+
             let tb = &mut self.app.typeface.terminal_box;
             if !tb.hit_test(lx, ly) { tb.unfocus(); }
             if tb.mouse_input(button, state, lx, ly) {
@@ -1197,6 +1528,11 @@ impl SystemInterface {
             if tb.take_change() {
                 actions.push(AppAction::Typeface(pages::typeface::TypefaceMessage::SetSearch(tb.text.clone())));
             }
+
+            let tf = &mut self.app.typeface;
+            if tf.list_box.mouse_input(button, state, lx, ly) {
+                self.needs_rebuild = true;
+            }
         }
         if state == clear_ui::widget::ElementState::Pressed && self.app.current_page == Page::Services {
             let tb = &mut self.app.services.search_box;
@@ -1204,6 +1540,22 @@ impl SystemInterface {
             if tb.mouse_input(button, state, lx, ly) {
                 self.needs_rebuild = true;
             }
+            let srv = &mut self.app.services;
+            if srv.list_box.mouse_input(button, state, lx, ly) {
+                self.needs_rebuild = true;
+            }
+        }
+        if state == clear_ui::widget::ElementState::Pressed && self.app.current_page == Page::Processors {
+            let proc = &mut self.app.processors;
+            if proc.cpu_list_box.mouse_input(button, state, lx, ly) {
+                self.needs_rebuild = true;
+            }
+        }
+        if state == clear_ui::widget::ElementState::Pressed && self.app.current_page == Page::Radios {
+            let net = &mut self.app.network;
+            if net.wifi_list_box.mouse_input(button, state, lx, ly) {
+                self.needs_rebuild = true;
+            }
         }
         for a in &actions {
             self.handle_action(a);
@@ -1222,7 +1574,7 @@ impl SystemInterface {
             let lx = self.cursor_x / s;
             let ly = self.cursor_y / s + self.scroll_y;
             
-            if self.app.current_page == Page::Typeface {
+            if self.app.current_page == Page::Typefaces {
                 let tf = &mut self.app.typeface;
                 if tf.list_box.mouse_wheel(delta, lx, ly) {
                     self.needs_rebuild = true;
@@ -1243,6 +1595,13 @@ impl SystemInterface {
                     return true;
                 }
             }
+            if self.app.current_page == Page::Radios {
+                let net = &mut self.app.network;
+                if net.wifi_list_box.mouse_wheel(delta, lx, ly) {
+                    self.needs_rebuild = true;
+                    return true;
+                }
+            }
 
             let scroll_speed = 24.0;
             let dy = match delta {
@@ -1259,7 +1618,47 @@ impl SystemInterface {
         false
     }
 
+    fn get_page_root_widget(&mut self) -> Option<*mut (dyn clear_ui::widget::Widget + 'static)> {
+        match self.app.current_page {
+            Page::Typefaces | Page::Services | Page::Processors | Page::Radios |
+            Page::Layout | Page::Colors | Page::Notifications | Page::Input |
+            Page::Audio | Page::Display => {
+                let ptr = &mut self.page_root_container as &mut dyn clear_ui::widget::Widget as *mut dyn clear_ui::widget::Widget;
+                let static_ptr = unsafe {
+                    std::mem::transmute::<*mut dyn clear_ui::widget::Widget, *mut (dyn clear_ui::widget::Widget + 'static)>(ptr)
+                };
+                Some(static_ptr)
+            }
+            _ => None,
+        }
+    }
+
     fn handle_key_input(&mut self, event: &clear_ui::widget::KeyEvent) -> bool {
+        if event.state == clear_ui::widget::ElementState::Pressed && !event.repeat {
+            let is_nav_key = match (&event.logical_key, event.ctrl) {
+                (clear_ui::widget::Key::Character(c), true) if c == "j" || c == "J" || c == "k" || c == "K" || c == "u" || c == "U" || c == "i" || c == "I" => true,
+                _ => false,
+            };
+            if is_nav_key {
+                if clear_ui::widget::focus::has_focus() {
+                    if clear_ui::widget::focus::navigate_focus(&event.logical_key, event.ctrl) {
+                        self.needs_rebuild = true;
+                        return true;
+                    }
+                } else {
+                    if let Some(root_ptr) = self.get_page_root_widget() {
+                        unsafe {
+                            let root_ref = &mut *root_ptr;
+                            clear_ui::widget::focus::set_focused(root_ref);
+                            root_ref.focus();
+                            self.needs_rebuild = true;
+                            return true;
+                        }
+                    }
+                }
+            }
+        }
+
         if self.app.current_page == Page::Layout {
             let mut changed = false;
             let mut actions = Vec::new();
@@ -1307,13 +1706,25 @@ impl SystemInterface {
                 }
                 changed = true;
             }
-            for (i, cp) in self.app.layout.color_selectors.iter_mut().enumerate() {
+            for a in &actions {
+                self.handle_action(a);
+            }
+            if changed {
+                self.needs_rebuild = true;
+                return true;
+            }
+        }
+        if self.app.current_page == Page::Colors {
+            let mut changed = false;
+            let mut actions = Vec::new();
+            for (i, cp) in self.app.colors.color_selectors.iter_mut().enumerate() {
                 let old = cp.color;
                 if cp.keyboard_input(event) {
                     if cp.color != old {
-                        actions.push(AppAction::Layout(match i {
-                            0 => pages::layout::LayoutMessage::SetBackground(cp.color),
-                            _ => pages::layout::LayoutMessage::SetBorderColor(cp.color),
+                        actions.push(AppAction::Colors(match i {
+                            0 => pages::colors::ColorsMessage::SetLowColor(cp.color),
+                            1 => pages::colors::ColorsMessage::SetHighColor(cp.color),
+                            _ => pages::colors::ColorsMessage::SetDisabledColor(cp.color),
                         }));
                     }
                     changed = true;
@@ -1408,11 +1819,114 @@ impl SystemInterface {
                 return true;
             }
         }
-        if self.app.current_page == Page::Typeface {
+        if self.app.current_page == Page::Typefaces {
+            if event.state == clear_ui::widget::ElementState::Pressed {
+                let is_down = match (&event.logical_key, event.ctrl) {
+                    (clear_ui::widget::Key::Character(c), true) if c == "n" || c == "N" => true,
+                    (clear_ui::widget::Key::Named(clear_ui::widget::NamedKey::ArrowDown), false) => true,
+                    _ => false,
+                };
+                let is_up = match (&event.logical_key, event.ctrl) {
+                    (clear_ui::widget::Key::Character(c), true) if c == "p" || c == "P" => true,
+                    (clear_ui::widget::Key::Named(clear_ui::widget::NamedKey::ArrowUp), false) => true,
+                    _ => false,
+                };
+                if is_down {
+                    if !clear_ui::widget::focus::is_focused(&self.app.typeface.list_box.scroll_box) {
+                        return false;
+                    }
+                    let next_idx_font_scroll = {
+                        let tf = &self.app.typeface;
+                        let query = tf.search_box.text.to_lowercase();
+                        let matching_fonts: Vec<&String> = tf.all_fonts.iter()
+                            .filter(|font| font.to_lowercase().contains(&query))
+                            .collect();
+                        if !matching_fonts.is_empty() {
+                            let current_idx = tf.selected_font.as_ref()
+                                .and_then(|f| matching_fonts.iter().position(|&x| x == f));
+                            let next_idx = match current_idx {
+                                Some(idx) => (idx + 1).min(matching_fonts.len() - 1),
+                                None => 0,
+                            };
+                            let font = matching_fonts[next_idx].clone();
+                            
+                            // Compute scroll
+                            let btn_h = 24.0;
+                            let btn_gap = 4.0;
+                            let item_height_full = btn_h + btn_gap;
+                            let item_y = next_idx as f32 * item_height_full;
+                            let list_box_h = 320.0;
+                            
+                            let mut scroll_y = tf.list_box.scroll_y();
+                            if item_y < scroll_y {
+                                scroll_y = item_y;
+                            } else if item_y + btn_h > scroll_y + list_box_h {
+                                scroll_y = item_y + btn_h - list_box_h;
+                            }
+                            Some((font, scroll_y))
+                        } else {
+                            None
+                        }
+                    };
+
+                    if let Some((font, scroll_y)) = next_idx_font_scroll {
+                        self.app.typeface.list_box.set_scroll_y(scroll_y);
+                        self.handle_action(&AppAction::Typeface(pages::typeface::TypefaceMessage::SelectFont(font)));
+                        self.needs_rebuild = true;
+                        return true;
+                    }
+                } else if is_up {
+                    if !clear_ui::widget::focus::is_focused(&self.app.typeface.list_box.scroll_box) {
+                        return false;
+                    }
+                    let next_idx_font_scroll = {
+                        let tf = &self.app.typeface;
+                        let query = tf.search_box.text.to_lowercase();
+                        let matching_fonts: Vec<&String> = tf.all_fonts.iter()
+                            .filter(|font| font.to_lowercase().contains(&query))
+                            .collect();
+                        if !matching_fonts.is_empty() {
+                            let current_idx = tf.selected_font.as_ref()
+                                .and_then(|f| matching_fonts.iter().position(|&x| x == f));
+                            let next_idx = match current_idx {
+                                Some(idx) => idx.saturating_sub(1),
+                                None => 0,
+                            };
+                            let font = matching_fonts[next_idx].clone();
+                            
+                            // Compute scroll
+                            let btn_h = 24.0;
+                            let btn_gap = 4.0;
+                            let item_height_full = btn_h + btn_gap;
+                            let item_y = next_idx as f32 * item_height_full;
+                            let list_box_h = 320.0;
+                            
+                            let mut scroll_y = tf.list_box.scroll_y();
+                            if item_y < scroll_y {
+                                scroll_y = item_y;
+                            } else if item_y + btn_h > scroll_y + list_box_h {
+                                scroll_y = item_y + btn_h - list_box_h;
+                            }
+                            Some((font, scroll_y))
+                        } else {
+                            None
+                        }
+                    };
+
+                    if let Some((font, scroll_y)) = next_idx_font_scroll {
+                        self.app.typeface.list_box.set_scroll_y(scroll_y);
+                        self.handle_action(&AppAction::Typeface(pages::typeface::TypefaceMessage::SelectFont(font)));
+                        self.needs_rebuild = true;
+                        return true;
+                    }
+                }
+            }
+
             let mut actions = Vec::new();
             let mut consumed = false;
             
-            let tb = &mut self.app.typeface.sans_box;
+            let tf = &mut self.app.typeface;
+            let tb = &mut tf.sans_box;
             if tb.keyboard_input(event) {
                 if tb.take_change() {
                     actions.push(AppAction::Typeface(pages::typeface::TypefaceMessage::SetSans(tb.text.clone())));
@@ -1485,13 +1999,32 @@ impl SystemInterface {
             }
         }
         if self.app.current_page == Page::Services {
-            let tb = &mut self.app.services.search_box;
+            let srv = &mut self.app.services;
+            if srv.list_box.keyboard_input(event) {
+                self.needs_rebuild = true;
+                return true;
+            }
+            let tb = &mut srv.search_box;
             if tb.keyboard_input(event) {
                 tb.take_change();
                 self.needs_rebuild = true;
                 return true;
             }
         }
+        if self.app.current_page == Page::Processors {
+            let proc = &mut self.app.processors;
+            if proc.cpu_list_box.keyboard_input(event) {
+                self.needs_rebuild = true;
+                return true;
+            }
+        }
+        if self.app.current_page == Page::Radios {
+            let net = &mut self.app.network;
+            if net.wifi_list_box.keyboard_input(event) {
+                self.needs_rebuild = true;
+                return true;
+            }
+        }
         false
     }
 
@@ -1570,6 +2103,7 @@ struct App {
     initial_page: Page,
     exit: bool,
     redraw: bool,
+    ctrl_pressed: bool,
 }
 
 
@@ -1804,9 +2338,11 @@ impl KeyboardHandler for App {
         _qh: &QueueHandle<Self>,
         _keyboard: &wl_keyboard::WlKeyboard,
         _serial: u32,
-        _modifiers: smithay_client_toolkit::seat::keyboard::Modifiers,
+        modifiers: smithay_client_toolkit::seat::keyboard::Modifiers,
         _layout: u32,
-    ) {}
+    ) {
+        self.ctrl_pressed = modifiers.ctrl;
+    }
 }
 
 impl App {
@@ -1823,6 +2359,12 @@ impl App {
             xkeysym::Keysym::Tab => Key::Named(NamedKey::Tab),
             xkeysym::Keysym::Delete => Key::Named(NamedKey::Delete),
             xkeysym::Keysym::space => Key::Named(NamedKey::Space),
+            xkeysym::Keysym::j | xkeysym::Keysym::J => Key::Character("j".to_string()),
+            xkeysym::Keysym::k | xkeysym::Keysym::K => Key::Character("k".to_string()),
+            xkeysym::Keysym::u | xkeysym::Keysym::U => Key::Character("u".to_string()),
+            xkeysym::Keysym::i | xkeysym::Keysym::I => Key::Character("i".to_string()),
+            xkeysym::Keysym::n | xkeysym::Keysym::N => Key::Character("n".to_string()),
+            xkeysym::Keysym::p | xkeysym::Keysym::P => Key::Character("p".to_string()),
             _ => {
                 if let Some(ref text) = event.utf8 {
                     Key::Character(text.clone())
@@ -1837,6 +2379,7 @@ impl App {
             logical_key,
             text: event.utf8.clone(),
             repeat: false,
+            ctrl: self.ctrl_pressed,
         };
 
         if let Some(st) = &mut self.state {
@@ -1949,6 +2492,7 @@ fn main() {
         initial_page,
         exit: false,
         redraw: true,
+        ctrl_pressed: false,
     };
 
     // Perform a roundtrip to populate output_state with active output scales
diff --git a/src/pages/audio.rs b/src/pages/audio.rs
index 7b32a46..c53dde2 100644
--- a/src/pages/audio.rs
+++ b/src/pages/audio.rs
@@ -207,7 +207,7 @@ const FILL_BAR: [f32; 4] = [0.30, 0.50, 0.32, 1.0];
 const WHITE: [f32; 4] = [1.0, 1.0, 1.0, 1.0];
 const RED: [f32; 4] = [1.0, 0.33, 0.33, 1.0];
 
-pub fn view(state: &mut AudioState, cx: f32, cy: f32, cw: f32, _ch: f32) -> PageContent {
+pub fn view(state: &mut AudioState, cx: f32, cy: f32, cw: f32, _ch: f32, sec_focused: &[bool]) -> PageContent {
     let mut pc = PageContent::new();
     let mut y = cy + 12.0;
 
@@ -266,7 +266,7 @@ pub fn view(state: &mut AudioState, cx: f32, cy: f32, cw: f32, _ch: f32) -> Page
         }
     }
 
-    y = sec.finish(&mut pc);
+    y = sec.finish_focused(&mut pc, sec_focused.get(0).copied().unwrap_or(false));
 
     // ── Input section ──
     let mut sec = Section::new(&mut pc, cx, y, cw, "Input");
@@ -323,7 +323,7 @@ pub fn view(state: &mut AudioState, cx: f32, cy: f32, cw: f32, _ch: f32) -> Page
         }
     }
 
-    sec.finish(&mut pc);
+    sec.finish_focused(&mut pc, sec_focused.get(1).copied().unwrap_or(false));
 
     pc
 }
diff --git a/src/pages/colors.rs b/src/pages/colors.rs
new file mode 100644
index 0000000..c1d41a5
--- /dev/null
+++ b/src/pages/colors.rs
@@ -0,0 +1,316 @@
+use std::fs;
+use std::io::Write;
+use crate::app::PageContent;
+use clear_ui::layout::Section;
+use clear_ui::widget::ColorSelector;
+
+const CONFIG_PATH: &str = "/home/lsgalante/.config/clearwm/config.toml";
+const CLEARWM_SOCK: &str = "/tmp/clearwm.sock";
+
+#[derive(Debug, Clone)]
+pub struct ColorsState {
+    pub low_color: [u8; 3],
+    pub high_color: [u8; 3],
+    pub disabled_color: [u8; 3],
+    pub color_selectors: Vec<ColorSelector>,
+    pub preset_colors: Vec<(&'static str, [u8; 3])>,
+}
+
+impl Default for ColorsState {
+    fn default() -> Self {
+        Self {
+            low_color: [0x0a, 0x1a, 0x0e],
+            high_color: [0x3e, 0x3e, 0x3e],
+            disabled_color: [0x55, 0x55, 0x55],
+            color_selectors: vec![
+                ColorSelector::new([0x0a, 0x1a, 0x0e]).with_label("Low Color"),
+                ColorSelector::new([0x3e, 0x3e, 0x3e]).with_label("High Color"),
+                ColorSelector::new([0x55, 0x55, 0x55]).with_label("Disabled"),
+            ],
+            preset_colors: preset_colors(),
+        }
+    }
+}
+
+#[derive(Debug, Clone)]
+pub enum ColorsMessage {
+    SetLowColor([u8; 3]),
+    SetHighColor([u8; 3]),
+    SetDisabledColor([u8; 3]),
+    PickLowColor,
+    PickHighColor,
+    PickDisabledColor,
+    Refreshed(ColorsState),
+}
+
+fn preset_colors() -> Vec<(&'static str, [u8; 3])> {
+    vec![
+        ("Black",       [0x00, 0x00, 0x00]),
+        ("Dark Gray",   [0x1a, 0x1a, 0x2e]),
+        ("Slate",       [0x2d, 0x2d, 0x3d]),
+        ("Dark Forest", [0x0a, 0x1a, 0x0e]),
+        ("Forest",      [0x1a, 0x2a, 0x1c]),
+        ("Dark Teal",   [0x0a, 0x1a, 0x1e]),
+        ("Navy",        [0x0a, 0x0f, 0x2e]),
+        ("Dark Wine",   [0x1e, 0x0a, 0x14]),
+        ("Dark Brown",  [0x1e, 0x16, 0x0e]),
+        ("Charcoal",    [0x22, 0x22, 0x22]),
+        ("Midnight",    [0x10, 0x10, 0x20]),
+        ("Deep Sea",    [0x06, 0x14, 0x1e]),
+    ]
+}
+
+pub fn read_colors_config() -> ColorsState {
+    let content = fs::read_to_string(CONFIG_PATH).unwrap_or_default();
+    let has_low = content.lines().any(|l| l.trim().starts_with("low_color"));
+    let bg = if has_low {
+        parse_color_from_key(&content, "low_color", [0x0a, 0x1a, 0x0e])
+    } else {
+        parse_color_from_key(&content, "background_color", [0x0a, 0x1a, 0x0e])
+    };
+    
+    let has_high = content.lines().any(|l| l.trim().starts_with("high_color"));
+    let border = if has_high {
+        parse_color_from_key(&content, "high_color", [0x3e, 0x3e, 0x3e])
+    } else {
+        parse_color_from_key(&content, "border_color", [0x3e, 0x3e, 0x3e])
+    };
+    
+    let disabled = parse_color_from_key(&content, "disabled_color", [0x55, 0x55, 0x55]);
+    
+    ColorsState {
+        low_color: bg,
+        high_color: border,
+        disabled_color: disabled,
+        color_selectors: vec![
+            ColorSelector::new(bg).with_label("Low Color"),
+            ColorSelector::new(border).with_label("High Color"),
+            ColorSelector::new(disabled).with_label("Disabled"),
+        ],
+        preset_colors: preset_colors(),
+    }
+}
+
+fn parse_color_from_key(content: &str, key: &str, default: [u8; 3]) -> [u8; 3] {
+    for line in content.lines() {
+        let trimmed = line.trim();
+        if let Some(rest) = trimmed.strip_prefix(key) {
+            let rest = rest.trim_start_matches(|c: char| c == ' ' || c == '=' || c == '"');
+            let hex = rest.trim_end_matches('"').trim();
+            return parse_hex(hex);
+        }
+    }
+    default
+}
+
+fn parse_hex(s: &str) -> [u8; 3] {
+    let s = s.trim_start_matches('#');
+    if s.len() >= 6 {
+        let r = u8::from_str_radix(&s[0..2], 16).unwrap_or(0x0a);
+        let g = u8::from_str_radix(&s[2..4], 16).unwrap_or(0x1a);
+        let b = u8::from_str_radix(&s[4..6], 16).unwrap_or(0x0e);
+        [r, g, b]
+    } else { [0x0a, 0x1a, 0x0e] }
+}
+
+fn write_config_value(key: &str, value: &str) -> bool {
+    write_config_value_path(CONFIG_PATH, key, value)
+}
+
+fn write_config_value_path(path: &str, key: &str, value: &str) -> bool {
+    let content = fs::read_to_string(path).unwrap_or_default();
+    let old_key = match key {
+        "low_color" => "background_color",
+        "high_color" => "border_color",
+        _ => "",
+    };
+    let new_line = format!("{} = {}", key, value);
+    let mut found = false;
+    let updated: String = content.lines()
+        .map(|line| {
+            let trimmed = line.trim();
+            if trimmed.starts_with(key) {
+                found = true;
+                new_line.clone()
+            } else if !old_key.is_empty() && trimmed.starts_with(old_key) {
+                found = true;
+                new_line.clone()
+            } else {
+                line.to_string()
+            }
+        }).collect::<Vec<_>>().join("\n");
+    if !found {
+        let mut result = String::new();
+        let mut in_layout = false;
+        let mut inserted = false;
+        for line in updated.lines() {
+            if line.trim() == "[layout]" { in_layout = true; }
+            else if line.trim().starts_with('[') && in_layout {
+                if !inserted { result.push_str(&new_line); result.push('\n'); inserted = true; }
+                in_layout = false;
+            }
+            result.push_str(line); result.push('\n');
+        }
+        if in_layout && !inserted { result.push_str(&new_line); result.push('\n'); }
+        fs::write(path, result).is_ok()
+    } else { fs::write(path, updated).is_ok() }
+}
+
+fn send_ipc_command(cmd: &str) {
+    if let Ok(mut stream) = std::os::unix::net::UnixStream::connect(CLEARWM_SOCK) {
+        let _ = stream.write_all(format!("{}\n", cmd).as_bytes());
+    }
+}
+
+fn apply_background(rgb: [u8; 3]) {
+    let _ = std::process::Command::new("pkill").args(["-x", "swaybg"]).status();
+    std::thread::sleep(std::time::Duration::from_millis(100));
+    let hex = format!("{:02x}{:02x}{:02x}", rgb[0], rgb[1], rgb[2]);
+    let _ = std::process::Command::new("swaybg").arg("-c").arg(&hex).spawn();
+    write_config_value("low_color", &format!("\"#{}\"", hex));
+    send_ipc_command(&format!("layout low_color #{}", hex));
+}
+
+fn apply_border_color(rgb: [u8; 3]) {
+    let hex = format!("\"#{:02x}{:02x}{:02x}\"", rgb[0], rgb[1], rgb[2]);
+    write_config_value("high_color", &hex);
+    send_ipc_command(&format!("layout high_color #{:02x}{:02x}{:02x}", rgb[0], rgb[1], rgb[2]));
+}
+
+fn apply_disabled_color(rgb: [u8; 3]) {
+    let hex = format!("\"#{:02x}{:02x}{:02x}\"", rgb[0], rgb[1], rgb[2]);
+    write_config_value("disabled_color", &hex);
+    send_ipc_command(&format!("layout disabled_color #{:02x}{:02x}{:02x}", rgb[0], rgb[1], rgb[2]));
+}
+
+pub fn view(state: &mut ColorsState, cx: f32, cy: f32, cw: f32, _ch: f32) -> PageContent {
+    let mut pc = PageContent::new();
+    let mut y = cy + 12.0;
+
+    // 1. Layout Section
+    let mut sec = Section::new(&mut pc, cx, y, cw, "Layout");
+    sec.spacing(8.0);
+    state.color_selectors[0].color = state.low_color;
+    sec.widget(&mut pc, &mut state.color_selectors[0], 12.0, 220.0, 22.0);
+    sec.spacing(8.0);
+    state.color_selectors[1].color = state.high_color;
+    sec.widget(&mut pc, &mut state.color_selectors[1], 12.0, 220.0, 22.0);
+    sec.spacing(8.0);
+    y = sec.finish(&mut pc);
+
+    // 2. Status Section
+    let mut sec = Section::new(&mut pc, cx, y, cw, "Status");
+    sec.spacing(8.0);
+    state.color_selectors[2].color = state.disabled_color;
+    sec.widget(&mut pc, &mut state.color_selectors[2], 12.0, 220.0, 22.0);
+    sec.spacing(8.0);
+    y = sec.finish(&mut pc);
+
+    // 3. Preset Background Colors Grid
+    let mut sec = Section::new(&mut pc, cx, y, cw, "Preset Backgrounds");
+    let cols = 4;
+    let gap = 8.0;
+    let btn_w = (cw - 24.0 - (gap * (cols - 1) as f32)) / cols as f32;
+    let btn_h = 28.0;
+
+    for (i, (name, rgb)) in state.preset_colors.iter().enumerate() {
+        let col = i % cols;
+        let row = i / cols;
+        let bx = cx + 12.0 + col as f32 * (btn_w + gap);
+        let by = sec.ay() + row as f32 * (btn_h + gap);
+
+        let r = rgb[0] as f32 / 255.0;
+        let g = rgb[1] as f32 / 255.0;
+        let b = rgb[2] as f32 / 255.0;
+        let luminance = 0.299 * r + 0.587 * g + 0.114 * b;
+        let text_color = if luminance > 0.5 { [0.08, 0.08, 0.12, 1.0] } else { [0.90, 0.90, 0.95, 1.0] };
+
+        pc.button(
+            name,
+            bx,
+            by,
+            btn_w,
+            btn_h,
+            [r, g, b, 0.8],
+            [r, g, b, 1.0],
+            text_color,
+            crate::app::AppAction::Colors(ColorsMessage::SetLowColor(*rgb)),
+        );
+    }
+
+    let rows = (state.preset_colors.len() + cols - 1) / cols;
+    sec.content_y += rows as f32 * (btn_h + gap) + 4.0;
+    sec.finish(&mut pc);
+
+    pc
+}
+
+pub fn update(state: &mut ColorsState, msg: ColorsMessage) {
+    match msg {
+        ColorsMessage::SetLowColor(rgb) => {
+            state.low_color = rgb;
+            apply_background(rgb);
+        }
+        ColorsMessage::SetHighColor(rgb) => {
+            state.high_color = rgb;
+            apply_border_color(rgb);
+        }
+        ColorsMessage::SetDisabledColor(rgb) => {
+            state.disabled_color = rgb;
+            apply_disabled_color(rgb);
+        }
+        ColorsMessage::PickLowColor | ColorsMessage::PickHighColor | ColorsMessage::PickDisabledColor => {}
+        ColorsMessage::Refreshed(new) => {
+            *state = new;
+        }
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+
+    #[test]
+    fn test_parse_hex() {
+        assert_eq!(parse_hex("#ffffff"), [255, 255, 255]);
+        assert_eq!(parse_hex("000000"), [0, 0, 0]);
+        assert_eq!(parse_hex("#123456"), [18, 52, 86]);
+        assert_eq!(parse_hex("invalid"), [0x0a, 0x1a, 0x0e]);
+    }
+
+    #[test]
+    fn test_parse_color_from_key() {
+        let content = "\n[layout]\nlow_color = \"#112233\"\nhigh_color = \"#445566\"\ndisabled_color = \"#778899\"\n";
+        assert_eq!(parse_color_from_key(content, "low_color", [0, 0, 0]), [17, 34, 51]);
+        assert_eq!(parse_color_from_key(content, "high_color", [0, 0, 0]), [68, 85, 102]);
+        assert_eq!(parse_color_from_key(content, "disabled_color", [0, 0, 0]), [119, 136, 153]);
+        assert_eq!(parse_color_from_key(content, "non_existent", [1, 2, 3]), [1, 2, 3]);
+    }
+
+    #[test]
+    fn test_write_config_value_path() {
+        let dir = std::env::temp_dir();
+        let path = dir.join("test_config.toml");
+        let path_str = path.to_str().unwrap();
+
+        // 1. Write initial file content with [layout] and other keys
+        let initial_content = "[layout]\ngap = 18\nborder_color = \"#374673\"\n\n[output]\nscale = 2\n";
+        fs::write(path_str, initial_content).unwrap();
+
+        // 2. Write disabled_color which does not exist yet (key not found case)
+        assert!(write_config_value_path(path_str, "disabled_color", "\"#555555\""));
+        let updated = fs::read_to_string(path_str).unwrap();
+        assert!(updated.contains("disabled_color = \"#555555\""));
+        // Check it was inserted before [output]
+        assert!(updated.find("disabled_color = \"#555555\"").unwrap() < updated.find("[output]").unwrap());
+
+        // 3. Update disabled_color (key found case)
+        assert!(write_config_value_path(path_str, "disabled_color", "\"#666666\""));
+        let updated2 = fs::read_to_string(path_str).unwrap();
+        assert!(updated2.contains("disabled_color = \"#666666\""));
+        assert!(!updated2.contains("disabled_color = \"#555555\""));
+
+        // Clean up
+        let _ = fs::remove_file(path_str);
+    }
+}
diff --git a/src/pages/input.rs b/src/pages/input.rs
index f007262..247658c 100644
--- a/src/pages/input.rs
+++ b/src/pages/input.rs
@@ -247,7 +247,7 @@ fn apply_repeat_config(rate: u16, delay: u16) {
 const TEXT_FG: [f32; 4] = [0.83, 0.83, 0.83, 1.0];
 const TEXT_DIM: [f32; 4] = [0.53, 0.53, 0.60, 1.0];
 
-pub fn view(state: &mut InputState, cx: f32, cy: f32, cw: f32, _ch: f32) -> PageContent {
+pub fn view(state: &mut InputState, cx: f32, cy: f32, cw: f32, _ch: f32, sec_focused: &[bool]) -> PageContent {
     let mut pc = PageContent::new();
     let mut y = cy + 12.0;
 
@@ -304,7 +304,7 @@ pub fn view(state: &mut InputState, cx: f32, cy: f32, cw: f32, _ch: f32) -> Page
 
     sec.widget(&mut pc, &mut state.delay_spinbox, 14.0, 200.0, 26.0);
     sec.spacing(8.0);
-    y = sec.finish(&mut pc);
+    y = sec.finish_focused(&mut pc, sec_focused.get(0).copied().unwrap_or(false));
 
     // ── Inertial Input ──
     let mut sec = Section::new(&mut pc, cx, y, cw, "Inertial Input");
@@ -330,7 +330,7 @@ pub fn view(state: &mut InputState, cx: f32, cy: f32, cw: f32, _ch: f32) -> Page
     sec.widget(&mut pc, &mut state.trackpad_friction_spinbox, 14.0, 200.0, 26.0);
     sec.spacing(8.0);
 
-    y = sec.finish(&mut pc);
+    y = sec.finish_focused(&mut pc, sec_focused.get(1).copied().unwrap_or(false));
 
     // ── Keybindings ──
     let mut sec = Section::new(&mut pc, cx, y, cw, "Keyboard Bindings");
diff --git a/src/pages/layout.rs b/src/pages/layout.rs
index cfd0d2a..c0da47e 100644
--- a/src/pages/layout.rs
+++ b/src/pages/layout.rs
@@ -3,7 +3,7 @@ use std::io::Write;
 
 use crate::app::PageContent;
 use clear_ui::layout::Section;
-use clear_ui::widget::{ColorSelector, Spinbox};
+use clear_ui::widget::Spinbox;
 
 const CONFIG_PATH: &str = "/home/lsgalante/.config/clearwm/config.toml";
 const CLEARWM_SOCK: &str = "/tmp/clearwm.sock";
@@ -53,8 +53,6 @@ fn make_spinboxes(fs: u16, ca: u16, g: u16, v: u16, h: u16, fl: u16) -> Vec<Spin
 
 #[derive(Debug, Clone)]
 pub struct LayoutState {
-    pub background_color: [u8; 3],
-    pub border_color: [u8; 3],
     pub fullscreen_border_width: u16,
     pub cascade_border_width: u16,
     pub grid_border_width: u16,
@@ -64,19 +62,15 @@ pub struct LayoutState {
     pub cascade_offset: u16,
     pub edge_gap: u16,
     pub top_gap: u16,
-    pub color_options: Vec<(&'static str, [u8; 3])>,
     pub spinboxes: Vec<Spinbox>,
     pub cascade_offset_spinbox: Spinbox,
     pub edge_gap_spinbox: Spinbox,
     pub top_gap_spinbox: Spinbox,
-    pub color_selectors: Vec<ColorSelector>,
 }
 
 impl Default for LayoutState {
     fn default() -> Self {
         Self {
-            background_color: [0x0a, 0x1a, 0x0e],
-            border_color: [0x3e, 0x3e, 0x3e],
             fullscreen_border_width: 0,
             cascade_border_width: 6,
             grid_border_width: 6,
@@ -86,25 +80,16 @@ impl Default for LayoutState {
             cascade_offset: 20,
             edge_gap: 48,
             top_gap: 48,
-            color_options: preset_colors(),
             spinboxes: make_spinboxes(0, 6, 6, 6, 6, 6),
             cascade_offset_spinbox: Spinbox::new(20, 0, 200, 1),
             edge_gap_spinbox: Spinbox::new(48, 0, 200, 1),
             top_gap_spinbox: Spinbox::new(48, 0, 200, 1),
-            color_selectors: vec![
-                ColorSelector::new([0x0a, 0x1a, 0x0e]).with_label("Desktop Background"),
-                ColorSelector::new([0x3e, 0x3e, 0x3e]).with_label("Border Color"),
-            ],
         }
     }
 }
 
 #[derive(Debug, Clone)]
 pub enum LayoutMessage {
-    SetBackground([u8; 3]),
-    SetBorderColor([u8; 3]),
-    PickBackgroundColor,
-    PickBorderColor,
     SetWidth(WidthParam, u16),
     SetCascadeOffset(u16),
     SetEdgeGap(u16),
@@ -112,23 +97,6 @@ pub enum LayoutMessage {
     Refreshed(LayoutState),
 }
 
-fn preset_colors() -> Vec<(&'static str, [u8; 3])> {
-    vec![
-        ("Black",       [0x00, 0x00, 0x00]),
-        ("Dark Gray",   [0x1a, 0x1a, 0x2e]),
-        ("Slate",       [0x2d, 0x2d, 0x3d]),
-        ("Dark Forest", [0x0a, 0x1a, 0x0e]),
-        ("Forest",      [0x1a, 0x2a, 0x1c]),
-        ("Dark Teal",   [0x0a, 0x1a, 0x1e]),
-        ("Navy",        [0x0a, 0x0f, 0x2e]),
-        ("Dark Wine",   [0x1e, 0x0a, 0x14]),
-        ("Dark Brown",  [0x1e, 0x16, 0x0e]),
-        ("Charcoal",    [0x22, 0x22, 0x22]),
-        ("Midnight",    [0x10, 0x10, 0x20]),
-        ("Deep Sea",    [0x06, 0x14, 0x1e]),
-    ]
-}
-
 pub fn read_layout_config() -> LayoutState {
     let content = fs::read_to_string(CONFIG_PATH).unwrap_or_default();
     let fs = parse_u16_from(&content, "fullscreen_border_width", 0);
@@ -141,8 +109,6 @@ pub fn read_layout_config() -> LayoutState {
     let gl = parse_u16_from(&content, "gap_left", 48);
     let gt = parse_u16_from(&content, "gap_top", 48);
     LayoutState {
-        background_color: parse_color_from_key(&content, "background_color", [0x0a, 0x1a, 0x0e]),
-        border_color: parse_color_from_key(&content, "border_color", [0x3e, 0x3e, 0x3e]),
         fullscreen_border_width: fs,
         cascade_border_width: ca,
         grid_border_width: g,
@@ -152,32 +118,13 @@ pub fn read_layout_config() -> LayoutState {
         cascade_offset: co,
         edge_gap: gl,
         top_gap: gt,
-        color_options: preset_colors(),
         spinboxes: make_spinboxes(fs, ca, g, v, h, fl),
         cascade_offset_spinbox: Spinbox::new(co as i32, 0, 200, 1),
         edge_gap_spinbox: Spinbox::new(gl as i32, 0, 200, 1),
         top_gap_spinbox: Spinbox::new(gt as i32, 0, 200, 1),
-        color_selectors: vec![
-            ColorSelector::new(parse_color_from_key(&content, "background_color", [0x0a, 0x1a, 0x0e]))
-                .with_label("Desktop Background"),
-            ColorSelector::new(parse_color_from_key(&content, "border_color", [0x3e, 0x3e, 0x3e]))
-                .with_label("Border Color"),
-        ],
     }
 }
 
-fn parse_color_from_key(content: &str, key: &str, default: [u8; 3]) -> [u8; 3] {
-    for line in content.lines() {
-        let trimmed = line.trim();
-        if let Some(rest) = trimmed.strip_prefix(key) {
-            let rest = rest.trim_start_matches(|c: char| c == ' ' || c == '=' || c == '"');
-            let hex = rest.trim_end_matches('"').trim();
-            return parse_hex(hex);
-        }
-    }
-    default
-}
-
 fn parse_u16_from(content: &str, key: &str, default: u16) -> u16 {
     for line in content.lines() {
         let trimmed = line.trim();
@@ -189,16 +136,6 @@ fn parse_u16_from(content: &str, key: &str, default: u16) -> u16 {
     default
 }
 
-fn parse_hex(s: &str) -> [u8; 3] {
-    let s = s.trim_start_matches('#');
-    if s.len() >= 6 {
-        let r = u8::from_str_radix(&s[0..2], 16).unwrap_or(0x0a);
-        let g = u8::from_str_radix(&s[2..4], 16).unwrap_or(0x1a);
-        let b = u8::from_str_radix(&s[4..6], 16).unwrap_or(0x0e);
-        [r, g, b]
-    } else { [0x0a, 0x1a, 0x0e] }
-}
-
 fn write_config_value(key: &str, value: &str) -> bool {
     let content = fs::read_to_string(CONFIG_PATH).unwrap_or_default();
     let new_line = format!("{} = {}", key, value);
@@ -231,21 +168,6 @@ fn send_ipc_command(cmd: &str) {
     }
 }
 
-fn apply_background(rgb: [u8; 3]) {
-    let _ = std::process::Command::new("pkill").args(["-x", "swaybg"]).status();
-    std::thread::sleep(std::time::Duration::from_millis(100));
-    let hex = format!("{:02x}{:02x}{:02x}", rgb[0], rgb[1], rgb[2]);
-    let _ = std::process::Command::new("swaybg").arg("-c").arg(&hex).spawn();
-    write_config_value("background_color", &format!("\"#{}\"", hex));
-    send_ipc_command(&format!("layout background_color #{}", hex));
-}
-
-fn apply_border_color(rgb: [u8; 3]) {
-    let hex = format!("\"#{:02x}{:02x}{:02x}\"", rgb[0], rgb[1], rgb[2]);
-    write_config_value("border_color", &hex);
-    send_ipc_command(&format!("layout border_color #{:02x}{:02x}{:02x}", rgb[0], rgb[1], rgb[2]));
-}
-
 fn apply_all_widths(s: &LayoutState) {
     let w = |k: &str, v: u16| { write_config_value(k, &v.to_string()); send_ipc_command(&format!("layout {} {}", k, v)); };
     w("fullscreen_border_width", s.fullscreen_border_width);
@@ -261,20 +183,10 @@ fn apply_all_widths(s: &LayoutState) {
     w("gap_top", s.top_gap);
 }
 
-pub fn view(state: &mut LayoutState, cx: f32, cy: f32, cw: f32, _ch: f32) -> PageContent {
+pub fn view(state: &mut LayoutState, cx: f32, cy: f32, cw: f32, _ch: f32, sec_focused: &[bool]) -> PageContent {
     let mut pc = PageContent::new();
     let mut y = cy + 12.0;
 
-    let mut sec = Section::new(&mut pc, cx, y, cw, "Desktop Background");
-    state.color_selectors[0].color = state.background_color;
-    sec.widget(&mut pc, &mut state.color_selectors[0], 12.0, 220.0, 22.0);
-    y = sec.finish(&mut pc);
-
-    let mut sec = Section::new(&mut pc, cx, y, cw, "Border Color");
-    state.color_selectors[1].color = state.border_color;
-    sec.widget(&mut pc, &mut state.color_selectors[1], 12.0, 220.0, 22.0);
-    y = sec.finish(&mut pc);
-
     for (i, param) in WidthParam::ALL.iter().enumerate() {
         let mut sec = Section::new(&mut pc, cx, y, cw, param.label());
         sec.spacing(8.0);
@@ -292,7 +204,7 @@ pub fn view(state: &mut LayoutState, cx: f32, cy: f32, cw: f32, _ch: f32) -> Pag
             sec.widget(&mut pc, &mut state.top_gap_spinbox, 14.0, 200.0, 26.0);
         }
         sec.spacing(8.0);
-        y = sec.finish(&mut pc);
+        y = sec.finish_focused(&mut pc, sec_focused.get(i).copied().unwrap_or(false));
     }
 
     pc
@@ -325,15 +237,6 @@ fn param_idx(p: WidthParam) -> usize {
 
 pub fn update(state: &mut LayoutState, msg: LayoutMessage) {
     match msg {
-        LayoutMessage::SetBackground(rgb) => {
-            state.background_color = rgb;
-            apply_background(rgb);
-        }
-        LayoutMessage::SetBorderColor(rgb) => {
-            state.border_color = rgb;
-            apply_border_color(rgb);
-        }
-        LayoutMessage::PickBackgroundColor | LayoutMessage::PickBorderColor => {}
         LayoutMessage::SetWidth(p, v) => set_width(state, p, v),
         LayoutMessage::SetCascadeOffset(v) => {
             let val = v.min(200);
diff --git a/src/pages/mod.rs b/src/pages/mod.rs
index 80e3046..56878b4 100644
--- a/src/pages/mod.rs
+++ b/src/pages/mod.rs
@@ -13,6 +13,7 @@ pub mod notifications;
 pub mod backup;
 pub mod typeface;
 pub mod services;
+pub mod colors;
 
 #[derive(Debug, Clone, Copy, PartialEq, Eq)]
 pub enum Page {
@@ -29,13 +30,15 @@ pub enum Page {
     Status,
     Notifications,
     Backup,
-    Typeface,
+    Typefaces,
+    Colors,
 }
 
 impl Page {
-    pub const ALL: [Page; 14] = [
+    pub const ALL: [Page; 15] = [
         Page::Audio,
         Page::Backup,
+        Page::Colors,
         Page::Display,
         Page::Input,
         Page::Layout,
@@ -47,7 +50,7 @@ impl Page {
         Page::Status,
         Page::Storage,
         Page::System,
-        Page::Typeface,
+        Page::Typefaces,
     ];
 
     pub fn label(self) -> &'static str {
@@ -65,7 +68,8 @@ impl Page {
             Page::Status => "Status",
             Page::Notifications => "Notifications",
             Page::Backup => "Backup",
-            Page::Typeface => "Typeface",
+            Page::Typefaces => "Typefaces",
+            Page::Colors => "Colors",
         }
     }
 
diff --git a/src/pages/network.rs b/src/pages/network.rs
index 9e1dcdd..a55db88 100644
--- a/src/pages/network.rs
+++ b/src/pages/network.rs
@@ -1,5 +1,6 @@
 use crate::app::{AppAction, PageContent};
 use clear_ui::layout::Section;
+use clear_ui::widget::{Widget, TextLabel, ScrollBox, ScrollingList};
 
 #[derive(Debug, Clone)]
 pub struct WifiNetwork {
@@ -29,6 +30,7 @@ pub struct NetworkState {
     pub bt_enabled: bool,
     pub bt_devices: Vec<BluetoothDevice>,
     pub bt_scanning: bool,
+    pub wifi_list_box: ScrollingList,
 }
 
 #[derive(Debug, Clone)]
@@ -90,6 +92,7 @@ pub async fn fetch_network_state() -> NetworkState {
         wifi_enabled, connected_ssid, signal_strength: signal,
         ip_address, device, available,
         bt_enabled, bt_devices, bt_scanning: false,
+        wifi_list_box: ScrollingList::new(26.0, 4.0),
     }
 }
 
@@ -205,7 +208,7 @@ const NET_BTN: [f32; 4] = [0.13, 0.20, 0.27, 1.0];
 const ACT_BTN: [f32; 4] = [0.16, 0.29, 0.18, 1.0];
 const WHITE: [f32; 4] = [1.0, 1.0, 1.0, 1.0];
 
-pub fn view(state: &NetworkState, cx: f32, cy: f32, cw: f32, _ch: f32) -> PageContent {
+pub fn view(state: &mut NetworkState, cx: f32, cy: f32, cw: f32, _ch: f32, root_focused: bool) -> PageContent {
     let mut pc = PageContent::new();
     let mut y = cy + 12.0;
 
@@ -235,21 +238,31 @@ pub fn view(state: &NetworkState, cx: f32, cy: f32, cw: f32, _ch: f32) -> PageCo
         }
 
         if state.wifi_enabled && !state.available.is_empty() {
-            for net in &state.available {
-                let prefix = if net.in_use { ">" } else { " " };
-                let label = format!("{}  {}  ({}%)", prefix, net.ssid, net.signal);
-                let active = net.in_use;
-                let yt = sec.ay();
-                pc.button(&label, sec.ax(14.0), yt, cw - 28.0, 26.0,
-                    if active { ACT_BTN } else { NET_BTN }, BTN_HOVER,
-                    if active { ACCENT } else { TEXT_FG },
-                    AppAction::Radios(NetworkMessage::ConnectWifi(net.ssid.clone())));
-                sec.content_y += 30.0;
+            let list_box_x = cx + 12.0;
+            let list_box_y = sec.ay();
+            let list_box_w = cw - 24.0;
+            let list_box_h = 160.0;
+
+            clear_ui::layout::render_widget(&mut pc, &mut state.wifi_list_box, list_box_x, list_box_y, list_box_w, list_box_h);
+
+            state.wifi_list_box.update_bounds(state.available.len(), list_box_y, list_box_h);
+
+            for (idx, net) in state.available.iter().enumerate() {
+                if let Some(draw_y) = state.wifi_list_box.get_item_draw_y(idx, 4.0) {
+                    let prefix = if net.in_use { ">" } else { " " };
+                    let label = format!("{}  {}  ({}%)", prefix, net.ssid, net.signal);
+                    let active = net.in_use;
+                    pc.button(&label, list_box_x + 4.0, draw_y, list_box_w - 24.0, 26.0,
+                        if active { ACT_BTN } else { NET_BTN }, BTN_HOVER,
+                        if active { ACCENT } else { TEXT_FG },
+                        AppAction::Radios(NetworkMessage::ConnectWifi(net.ssid.clone())));
+                }
             }
+            sec.content_y += list_box_h + 8.0;
         }
     }
 
-    y = sec.finish(&mut pc);
+    y = sec.finish_focused(&mut pc, root_focused);
 
     // ── Bluetooth ──
     let mut sec = Section::new(&mut pc, cx, y, cw, "Bluetooth");
@@ -297,7 +310,11 @@ pub fn view(state: &NetworkState, cx: f32, cy: f32, cw: f32, _ch: f32) -> PageCo
 
 pub fn update(state: &mut NetworkState, msg: NetworkMessage) {
     match msg {
-        NetworkMessage::Refreshed(new) => { *state = new; }
+        NetworkMessage::Refreshed(new) => {
+            let old_scroll = state.wifi_list_box.scroll_y();
+            *state = new;
+            state.wifi_list_box.set_scroll_y(old_scroll);
+        }
         NetworkMessage::ToggleWifi => {
             state.wifi_enabled = !state.wifi_enabled;
             wifi_toggle(state.wifi_enabled);
diff --git a/src/pages/processors.rs b/src/pages/processors.rs
index 2c7f855..a225900 100644
--- a/src/pages/processors.rs
+++ b/src/pages/processors.rs
@@ -1,6 +1,6 @@
 use crate::app::PageContent;
 use clear_ui::layout::Section;
-use clear_ui::widget::{Label, ScrollBox};
+use clear_ui::widget::{Label, ScrollingList};
 
 #[derive(Debug, Clone)]
 pub struct ProcessorsState {
@@ -12,7 +12,7 @@ pub struct ProcessorsState {
     pub cpu_label: Label,
     pub gpu_labels: Vec<Label>,
     pub processes: Vec<(String, String, String)>, // (pid, cpu, comm)
-    pub cpu_list_box: ScrollBox,
+    pub cpu_list_box: ScrollingList,
 }
 
 impl Default for ProcessorsState {
@@ -26,7 +26,7 @@ impl Default for ProcessorsState {
             cpu_label: Label::new("CPU Info"),
             gpu_labels: Vec::new(),
             processes: Vec::new(),
-            cpu_list_box: ScrollBox::new(),
+            cpu_list_box: ScrollingList::new(24.0, 2.0),
         }
     }
 }
@@ -197,14 +197,14 @@ pub async fn fetch_processors_state() -> ProcessorsState {
         cpu_label: Label::new(&cpu_label_text).with_font_size(12.0).with_color([212, 212, 212]),
         gpu_labels,
         processes,
-        cpu_list_box: ScrollBox::new(),
+        cpu_list_box: ScrollingList::new(24.0, 2.0),
     }
 }
 
 const TEXT_FG: [f32; 4] = [0.83, 0.83, 0.83, 1.0];
 const TEXT_DIM: [f32; 4] = [0.53, 0.53, 0.60, 1.0];
 
-pub fn view(state: &mut ProcessorsState, cx: f32, cy: f32, cw: f32, _ch: f32) -> PageContent {
+pub fn view(state: &mut ProcessorsState, cx: f32, cy: f32, cw: f32, _ch: f32, root_focused: bool) -> PageContent {
     let mut pc = PageContent::new();
     let mut y = cy + 12.0;
 
@@ -238,18 +238,13 @@ pub fn view(state: &mut ProcessorsState, cx: f32, cy: f32, cw: f32, _ch: f32) ->
 
         let row_h = 24.0;
         let row_gap = 2.0;
-        let item_height_full = row_h + row_gap;
-        let content_h = state.processes.len() as f32 * item_height_full;
-        
-        // Update ScrollBox bounds for the scrollable viewport (which starts below the header)
-        state.cpu_list_box.update_bounds(content_h, list_box_y + header_h, list_box_h - header_h - 6.0);
+        // Update ScrollingList bounds for the scrollable viewport (which starts below the header)
+        state.cpu_list_box.update_bounds(state.processes.len(), list_box_y + header_h, list_box_h - header_h - 6.0);
 
         // Visible process rows rendering (virtualized/clipped)
         
         for (idx, (pid, cpu, comm)) in state.processes.iter().enumerate() {
-            let virtual_y = idx as f32 * item_height_full + 4.0;
-            
-            if let Some(draw_y) = state.cpu_list_box.get_item_draw_y(virtual_y, row_h) {
+            if let Some(draw_y) = state.cpu_list_box.get_item_draw_y(idx, 4.0) {
                 // Standard row action button (transparent background, highlights on hover)
                 pc.button(
                     "",
@@ -275,7 +270,7 @@ pub fn view(state: &mut ProcessorsState, cx: f32, cy: f32, cw: f32, _ch: f32) ->
 
         sec.content_y += list_box_h;
     }
-    y = sec.finish(&mut pc);
+    y = sec.finish_focused(&mut pc, root_focused);
 
     // ── GPU Section ──
     let mut sec_gpu = Section::new(&mut pc, cx, y, cw, "GPU");
@@ -304,9 +299,9 @@ pub fn update(state: &mut ProcessorsState, msg: ProcessorsMessage) {
             state.cpu_label = new.cpu_label;
             state.gpu_labels = new.gpu_labels;
             state.processes = new.processes;
-            let old_scroll = state.cpu_list_box.scroll_y;
+            let old_scroll = state.cpu_list_box.scroll_y();
             state.cpu_list_box = new.cpu_list_box;
-            state.cpu_list_box.scroll_y = old_scroll;
+            state.cpu_list_box.set_scroll_y(old_scroll);
         }
         ProcessorsMessage::None => {}
     }
diff --git a/src/pages/services.rs b/src/pages/services.rs
index 91f2f7a..e7fdb0a 100644
--- a/src/pages/services.rs
+++ b/src/pages/services.rs
@@ -1,6 +1,6 @@
 use crate::app::PageContent;
 use clear_ui::layout::Section;
-use clear_ui::widget::{Widget, TextLabel, ScrollBox};
+use clear_ui::widget::{Widget, TextLabel, ScrollBox, ScrollingList};
 use clear_ui::widget::{ElementState, KeyEvent, MouseButton, Key, NamedKey};
 
 // ── TextBox Widget ──
@@ -16,6 +16,8 @@ pub struct TextBox {
     label: Option<String>,
     row_x: f32,
     row_w: f32,
+    pub parent: Option<*mut (dyn Widget + 'static)>,
+    pub children: Vec<*mut (dyn Widget + 'static)>,
 }
 
 impl TextBox {
@@ -30,6 +32,8 @@ impl TextBox {
             label: None,
             row_x: 0.0,
             row_w: 0.0,
+            parent: None,
+            children: Vec::new(),
         }
     }
 
@@ -89,7 +93,15 @@ impl Widget for TextBox {
     fn mouse_input(&mut self, button: MouseButton, state: ElementState, px: f32, py: f32) -> bool {
         if button != MouseButton::Left { return false; }
         if state != ElementState::Pressed { return false; }
-        if !self.hit_test(px, py) { return false; }
+        let (x, y, w, h) = self.rect();
+        let (hy, hh) = if self.label.is_some() {
+            (y - 18.0, h + 18.0)
+        } else {
+            (y, h)
+        };
+        if !(px >= x && px <= x + w && py >= hy && py <= hy + hh) {
+            return false;
+        }
         self.focus();
         true
     }
@@ -98,6 +110,7 @@ impl Widget for TextBox {
         if !self.editing {
             self.editing = true;
             self.edit_buffer = self.text.clone();
+            clear_ui::widget::focus::set_focused(self);
         }
     }
 
@@ -202,8 +215,23 @@ impl Widget for TextBox {
         });
         labels
     }
+
+    fn parent(&self) -> Option<*mut (dyn Widget + 'static)> { self.parent }
+    fn set_parent(&mut self, parent: Option<*mut (dyn Widget + 'static)>) { self.parent = parent; }
+    fn children(&self) -> Vec<*mut (dyn Widget + 'static)> { self.children.clone() }
+    fn add_child(&mut self, child: *mut (dyn Widget + 'static)) { self.children.push(child); }
+    fn clear_children(&mut self) { self.children.clear(); }
+}
+
+impl Drop for TextBox {
+    fn drop(&mut self) {
+        clear_ui::widget::focus::clear_if_matches(self);
+    }
 }
 
+unsafe impl Send for TextBox {}
+unsafe impl Sync for TextBox {}
+
 // ── Service Types and Page State ──
 
 #[derive(Debug, Clone)]
@@ -233,7 +261,7 @@ pub struct ServicesState {
     pub services: Vec<ServiceInfo>,
     pub active_tab: ServiceTab,
     pub search_box: TextBox,
-    pub list_box: ScrollBox,
+    pub list_box: ScrollingList,
 }
 
 impl Default for ServicesState {
@@ -243,7 +271,7 @@ impl Default for ServicesState {
             services: Vec::new(),
             active_tab: ServiceTab::System,
             search_box: TextBox::new(String::new()).with_label("Filter Services"),
-            list_box: ScrollBox::new(),
+            list_box: ScrollingList::new(36.0, 6.0),
         }
     }
 }
@@ -337,7 +365,7 @@ fn service_action(name: &str, action: &str, is_system: bool) {
 
 const TEXT_DIM: [f32; 4] = [0.53, 0.53, 0.60, 1.0];
 
-pub fn view(state: &mut ServicesState, cx: f32, cy: f32, cw: f32, _ch: f32) -> PageContent {
+pub fn view(state: &mut ServicesState, cx: f32, cy: f32, cw: f32, _ch: f32, root_focused: bool) -> PageContent {
     let mut pc = PageContent::new();
     let y = cy + 12.0;
 
@@ -415,16 +443,13 @@ pub fn view(state: &mut ServicesState, cx: f32, cy: f32, cw: f32, _ch: f32) -> P
             .filter(|s| s.name.to_lowercase().contains(&query) || s.description.to_lowercase().contains(&query))
             .collect();
 
-        let item_h = 36.0;
-        let item_gap = 6.0;
-        let item_height_full = item_h + item_gap;
-        let content_h = filtered_services.len() as f32 * item_height_full;
+        // Update ScrollingList bounds
+        state.list_box.update_bounds(filtered_services.len(), list_box_y, list_box_h);
 
-        state.list_box.update_bounds(content_h, list_box_y, list_box_h);
+        let item_h = state.list_box.item_height;
 
         for (idx, service) in filtered_services.iter().enumerate() {
-            let virtual_y = idx as f32 * item_height_full + 4.0;
-            if let Some(draw_y) = state.list_box.get_item_draw_y(virtual_y, item_h) {
+            if let Some(draw_y) = state.list_box.get_item_draw_y(idx, 4.0) {
                 // Item background
                 let bg_color = [0.08, 0.08, 0.12, 0.2];
                 pc.rect(bg_color, list_box_x + 4.0, draw_y, list_box_w - 24.0, item_h);
@@ -514,7 +539,7 @@ pub fn view(state: &mut ServicesState, cx: f32, cy: f32, cw: f32, _ch: f32) -> P
         sec.content_y += list_box_h;
     }
 
-    sec.finish(&mut pc);
+    sec.finish_focused(&mut pc, root_focused);
     pc
 }
 
@@ -526,7 +551,7 @@ pub fn update(state: &mut ServicesState, msg: ServicesMessage) {
         }
         ServicesMessage::SetTab(tab) => {
             state.active_tab = tab;
-            state.list_box.scroll_y = 0.0;
+            state.list_box.set_scroll_y(0.0);
         }
         ServicesMessage::Start(name, is_system) => {
             if let Some(srv) = state.services.iter_mut().find(|s| s.name == name && s.is_system == is_system) {
diff --git a/src/pages/typeface.rs b/src/pages/typeface.rs
index 0696163..9bdab1c 100644
--- a/src/pages/typeface.rs
+++ b/src/pages/typeface.rs
@@ -1,7 +1,7 @@
 use std::fs;
 use crate::app::PageContent;
 use clear_ui::layout::Section;
-use clear_ui::widget::{Widget, TextLabel, ScrollBox};
+use clear_ui::widget::{Widget, TextLabel, ScrollBox, ScrollingList, Dropdown};
 use clear_ui::widget::{ElementState, KeyEvent, MouseButton, Key, NamedKey};
 
 const FONTS_CONF_PATH: &str = "/home/lsgalante/.config/fontconfig/fonts.conf";
@@ -19,6 +19,9 @@ pub struct TextBox {
     label: Option<String>,
     row_x: f32,
     row_w: f32,
+    pub disabled: bool,
+    pub parent: Option<*mut (dyn Widget + 'static)>,
+    pub children: Vec<*mut (dyn Widget + 'static)>,
 }
 
 impl TextBox {
@@ -33,6 +36,9 @@ impl TextBox {
             label: None,
             row_x: 0.0,
             row_w: 0.0,
+            disabled: false,
+            parent: None,
+            children: Vec::new(),
         }
     }
 
@@ -84,23 +90,39 @@ impl Widget for TextBox {
     fn top_room(&self) -> f32 { if self.label.is_some() { 18.0 } else { 0.0 } }
 
     fn cursor_moved(&mut self, px: f32, py: f32) -> bool {
+        if self.disabled {
+            let was = self.hovered;
+            self.hovered = false;
+            return was;
+        }
         let was = self.hovered;
         self.hovered = self.hit_test(px, py);
         was != self.hovered
     }
 
     fn mouse_input(&mut self, button: MouseButton, state: ElementState, px: f32, py: f32) -> bool {
+        if self.disabled { return false; }
         if button != MouseButton::Left { return false; }
         if state != ElementState::Pressed { return false; }
-        if !self.hit_test(px, py) { return false; }
+        let (x, y, w, h) = self.rect();
+        let (hy, hh) = if self.label.is_some() {
+            (y - 18.0, h + 18.0)
+        } else {
+            (y, h)
+        };
+        if !(px >= x && px <= x + w && py >= hy && py <= hy + hh) {
+            return false;
+        }
         self.focus();
         true
     }
 
     fn focus(&mut self) {
+        if self.disabled { return; }
         if !self.editing {
             self.editing = true;
             self.edit_buffer = self.text.clone();
+            clear_ui::widget::focus::set_focused(self);
         }
     }
 
@@ -115,6 +137,7 @@ impl Widget for TextBox {
     }
 
     fn keyboard_input(&mut self, event: &KeyEvent) -> bool {
+        if self.disabled { return false; }
         if !self.editing { return false; }
         if event.state != ElementState::Pressed { return false; }
         match &event.logical_key {
@@ -153,6 +176,11 @@ impl Widget for TextBox {
 
     fn extra_quads(&self) -> Vec<(f32, f32, f32, f32, [f32; 4])> {
         let mut quads = Vec::new();
+        if self.disabled {
+            quads.push((self.x, self.y, self.w, self.h, [0.12, 0.12, 0.16, 1.0])); // border
+            quads.push((self.x + 1.0, self.y + 1.0, self.w - 2.0, self.h - 2.0, [0.06, 0.06, 0.08, 1.0])); // bg
+            return quads;
+        }
         if self.hovered {
             let (hy, hh) = if self.label.is_some() {
                 (self.y - 18.0, self.h + 18.0)
@@ -201,12 +229,27 @@ impl Widget for TextBox {
             x: self.x + 8.0,
             y: self.y + (self.h - 12.0) / 2.0,
             font_size: 13.0,
-            color: if self.editing { [0xee, 0xee, 0xf5] } else { [0xcc, 0xcc, 0xd4] },
+            color: if self.disabled { [0x53, 0x53, 0x5a] } else if self.editing { [0xee, 0xee, 0xf5] } else { [0xcc, 0xcc, 0xd4] },
         });
         labels
     }
+
+    fn parent(&self) -> Option<*mut (dyn Widget + 'static)> { self.parent }
+    fn set_parent(&mut self, parent: Option<*mut (dyn Widget + 'static)>) { self.parent = parent; }
+    fn children(&self) -> Vec<*mut (dyn Widget + 'static)> { self.children.clone() }
+    fn add_child(&mut self, child: *mut (dyn Widget + 'static)) { self.children.push(child); }
+    fn clear_children(&mut self) { self.children.clear(); }
+}
+
+impl Drop for TextBox {
+    fn drop(&mut self) {
+        clear_ui::widget::focus::clear_if_matches(self);
+    }
 }
 
+unsafe impl Send for TextBox {}
+unsafe impl Sync for TextBox {}
+
 // ── TypefaceState and TypefaceMessage ──
 
 #[derive(Debug, Clone)]
@@ -230,7 +273,11 @@ pub struct TypefaceState {
     pub terminal_box: TextBox,
     pub search_box: TextBox,
     pub selected_font: Option<String>,
-    pub list_box: ScrollBox,
+    pub list_box: ScrollingList,
+    pub borders_menu: Dropdown,
+    pub status_menu: Dropdown,
+    pub fuzzel_menu: Dropdown,
+    pub terminal_menu: Dropdown,
 }
 
 impl Default for TypefaceState {
@@ -255,7 +302,11 @@ impl Default for TypefaceState {
             terminal_box: TextBox::default(),
             search_box: TextBox::default(),
             selected_font: None,
-            list_box: ScrollBox::new(),
+            list_box: ScrollingList::new(24.0, 4.0),
+            borders_menu: Dropdown::default(),
+            status_menu: Dropdown::default(),
+            fuzzel_menu: Dropdown::default(),
+            terminal_menu: Dropdown::default(),
         }
     }
 }
@@ -272,6 +323,11 @@ pub enum TypefaceMessage {
     SetTerminal(String),
     SetSearch(String),
     SelectFont(String),
+    CopyFontName(String),
+    SetBordersMenu(usize),
+    SetStatusMenu(usize),
+    SetFuzzelMenu(usize),
+    SetTerminalMenu(usize),
 }
 
 fn parse_font_for_alias(content: &str, alias: &str) -> Option<String> {
@@ -445,40 +501,82 @@ pub async fn fetch_typeface_state() -> TypefaceState {
     let mono_fonts = parse_families(mono_output);
     
     let selected_font = all_fonts.first().cloned();
+
+    let determine_dropdown_index = |font: &str, sans: &str, serif: &str, mono: &str| -> usize {
+        if font == sans {
+            0
+        } else if font == serif {
+            1
+        } else if font == mono {
+            2
+        } else {
+            3
+        }
+    };
+
+    let borders_idx = determine_dropdown_index(&borders, &sans, &serif, &mono);
+    let status_idx = determine_dropdown_index(&status, &sans, &serif, &mono);
+    let fuzzel_idx = determine_dropdown_index(&fuzzel_font, &sans, &serif, &mono);
+    let terminal_idx = determine_dropdown_index(&term, &sans, &serif, &mono);
+
+    let menu_options = vec![
+        "Sans-Serif".to_string(),
+        "Serif".to_string(),
+        "Monospace".to_string(),
+        "Other".to_string(),
+    ];
+
+    let mut borders_box = TextBox::new(borders.clone()).with_label("Window Borders");
+    borders_box.disabled = borders_idx != 3;
+
+    let mut status_box = TextBox::new(status.clone()).with_label("Status Interface");
+    status_box.disabled = status_idx != 3;
+
+    let mut fuzzel_box = TextBox::new(fuzzel_font.clone()).with_label("Fuzzel");
+    fuzzel_box.disabled = fuzzel_idx != 3;
+
+    let mut terminal_box = TextBox::new(term.clone()).with_label("Terminal");
+    terminal_box.disabled = terminal_idx != 3;
+
     TypefaceState {
         loaded: true,
         sans_serif: sans.clone(),
         serif: serif.clone(),
         monospace: mono.clone(),
-        window_borders: borders.clone(),
-        status_interface: status.clone(),
-        fuzzel: fuzzel_font.clone(),
-        terminal: term.clone(),
+        window_borders: borders,
+        status_interface: status,
+        fuzzel: fuzzel_font,
+        terminal: term,
         all_fonts,
         mono_fonts,
         sans_box: TextBox::new(sans).with_label("Sans-Serif"),
         serif_box: TextBox::new(serif).with_label("Serif"),
         mono_box: TextBox::new(mono).with_label("Monospace"),
-        borders_box: TextBox::new(borders).with_label("Window Borders"),
-        status_box: TextBox::new(status).with_label("Status Interface"),
-        fuzzel_box: TextBox::new(fuzzel_font).with_label("Fuzzel"),
-        terminal_box: TextBox::new(term).with_label("Terminal"),
+        borders_box,
+        status_box,
+        fuzzel_box,
+        terminal_box,
         search_box: TextBox::new(String::new()).with_label("Filter Fonts"),
         selected_font,
-        list_box: ScrollBox::new(),
+        list_box: ScrollingList::new(24.0, 4.0),
+        borders_menu: Dropdown::new(menu_options.clone(), borders_idx),
+        status_menu: Dropdown::new(menu_options.clone(), status_idx),
+        fuzzel_menu: Dropdown::new(menu_options.clone(), fuzzel_idx),
+        terminal_menu: Dropdown::new(menu_options.clone(), terminal_idx),
     }
 }
 
 const TEXT_DIM: [f32; 4] = [0.53, 0.53, 0.60, 1.0];
 
-pub fn view(state: &mut TypefaceState, cx: f32, cy: f32, cw: f32, _ch: f32) -> PageContent {
+pub fn view(state: &mut TypefaceState, cx: f32, cy: f32, cw: f32, _ch: f32, sec_focused: &[bool]) -> PageContent {
     let mut pc = PageContent::new();
     let mut y = cy + 12.0;
 
     let widget_w = cw - 24.0;
     let widget_h = 26.0;
 
-    let mut sec = Section::new(&mut pc, cx, y, cw, "Typeface Settings");
+    // ── System Typefaces Section ──
+    let mut sec = Section::new(&mut pc, cx, y, cw, "System Typefaces");
     sec.spacing(8.0);
 
     if !state.loaded {
@@ -495,25 +593,60 @@ pub fn view(state: &mut TypefaceState, cx: f32, cy: f32, cw: f32, _ch: f32) -> P
 
         // Monospace
         sec.widget(&mut pc, &mut state.mono_box, 12.0, widget_w, widget_h);
-        sec.spacing(12.0);
+        sec.spacing(8.0);
+    }
+    let sys_focused = sec_focused.get(0).copied().unwrap_or(false);
+    y = sec.finish_focused(&mut pc, sys_focused);
+
+    // ── Program Typefaces Section ──
+    let mut sec = Section::new(&mut pc, cx, y, cw, "Program Typefaces");
+    sec.spacing(8.0);
+
+    if !state.loaded {
+        sec.text(&mut pc, "Loading typefaces...", 12.0, 0.0, 12.0, TEXT_DIM);
+        sec.spacing(18.0);
+    } else {
+        let dropdown_w = 120.0;
+        let textbox_w = widget_w - dropdown_w - 12.0;
 
         // Window Borders
-        sec.widget(&mut pc, &mut state.borders_box, 12.0, widget_w, widget_h);
-        sec.spacing(12.0);
+        let start_y = sec.ay();
+        let top_room = state.borders_box.top_room();
+        state.borders_menu.set_row_rect(cx + 8.0, cw - 16.0);
+        clear_ui::layout::render_widget(&mut pc, &mut state.borders_menu, cx + 12.0, start_y + top_room, dropdown_w, widget_h);
+        state.borders_box.set_row_rect(cx + 8.0, cw - 16.0);
+        clear_ui::layout::render_widget(&mut pc, &mut state.borders_box, cx + 12.0 + dropdown_w + 12.0, start_y + top_room, textbox_w, widget_h);
+        sec.spacing(widget_h + top_room + 12.0);
 
         // Status Interface
-        sec.widget(&mut pc, &mut state.status_box, 12.0, widget_w, widget_h);
-        sec.spacing(12.0);
+        let start_y = sec.ay();
+        let top_room = state.status_box.top_room();
+        state.status_menu.set_row_rect(cx + 8.0, cw - 16.0);
+        clear_ui::layout::render_widget(&mut pc, &mut state.status_menu, cx + 12.0, start_y + top_room, dropdown_w, widget_h);
+        state.status_box.set_row_rect(cx + 8.0, cw - 16.0);
+        clear_ui::layout::render_widget(&mut pc, &mut state.status_box, cx + 12.0 + dropdown_w + 12.0, start_y + top_room, textbox_w, widget_h);
+        sec.spacing(widget_h + top_room + 12.0);
 
         // Fuzzel
-        sec.widget(&mut pc, &mut state.fuzzel_box, 12.0, widget_w, widget_h);
-        sec.spacing(12.0);
+        let start_y = sec.ay();
+        let top_room = state.fuzzel_box.top_room();
+        state.fuzzel_menu.set_row_rect(cx + 8.0, cw - 16.0);
+        clear_ui::layout::render_widget(&mut pc, &mut state.fuzzel_menu, cx + 12.0, start_y + top_room, dropdown_w, widget_h);
+        state.fuzzel_box.set_row_rect(cx + 8.0, cw - 16.0);
+        clear_ui::layout::render_widget(&mut pc, &mut state.fuzzel_box, cx + 12.0 + dropdown_w + 12.0, start_y + top_room, textbox_w, widget_h);
+        sec.spacing(widget_h + top_room + 12.0);
 
         // Terminal
-        sec.widget(&mut pc, &mut state.terminal_box, 12.0, widget_w, widget_h);
-        sec.spacing(8.0);
+        let start_y = sec.ay();
+        let top_room = state.terminal_box.top_room();
+        state.terminal_menu.set_row_rect(cx + 8.0, cw - 16.0);
+        clear_ui::layout::render_widget(&mut pc, &mut state.terminal_menu, cx + 12.0, start_y + top_room, dropdown_w, widget_h);
+        state.terminal_box.set_row_rect(cx + 8.0, cw - 16.0);
+        clear_ui::layout::render_widget(&mut pc, &mut state.terminal_box, cx + 12.0 + dropdown_w + 12.0, start_y + top_room, textbox_w, widget_h);
+        sec.spacing(widget_h + top_room + 8.0);
     }
-    y = sec.finish(&mut pc);
+    let prog_focused = sec_focused.get(1).copied().unwrap_or(false);
+    y = sec.finish_focused(&mut pc, prog_focused);
 
     // ── Typefaces Section (List & Preview) ──
     let mut sec = Section::new(&mut pc, cx, y, cw, "Typefaces");
@@ -551,7 +684,7 @@ pub fn view(state: &mut TypefaceState, cx: f32, cy: f32, cw: f32, _ch: f32) -> P
         let list_box_y = left_y;
         let list_box_h = 320.0;
         
-        // Render the standardized ScrollBox widget
+        // Render the standardized ScrollingList widget
         clear_ui::layout::render_widget(&mut pc, &mut state.list_box, left_x, list_box_y, left_w, list_box_h);
 
         let query = state.search_box.text.to_lowercase();
@@ -560,22 +693,17 @@ pub fn view(state: &mut TypefaceState, cx: f32, cy: f32, cw: f32, _ch: f32) -> P
             .collect();
 
         let btn_h = 24.0;
-        let btn_gap = 4.0;
         let inner_x = left_x + 4.0;
         let inner_w = left_w - 16.0; // leave room for scrollbar
-        let item_height_full = btn_h + btn_gap;
-        let content_h = matching_fonts.len() as f32 * item_height_full;
         
-        // Update ScrollBox bounds to clamp and render correctly
-        state.list_box.update_bounds(content_h, list_box_y, list_box_h);
+        // Update ScrollingList bounds to clamp and render correctly
+        state.list_box.update_bounds(matching_fonts.len(), list_box_y, list_box_h);
 
         // Render visible buttons inside scroll region
         
         for (idx, font_name) in matching_fonts.iter().enumerate() {
-            let virtual_y = idx as f32 * item_height_full;
-            
             // Only render buttons that are completely within the visible area
-            if let Some(draw_y) = state.list_box.get_item_draw_y(virtual_y, btn_h) {
+            if let Some(draw_y) = state.list_box.get_item_draw_y(idx, 0.0) {
                 let is_selected = state.selected_font.as_ref() == Some(*font_name);
                 
                 let (bg, hover_bg, text_color) = if is_selected {
@@ -588,13 +716,31 @@ pub fn view(state: &mut TypefaceState, cx: f32, cy: f32, cw: f32, _ch: f32) -> P
                     font_name,
                     inner_x,
                     draw_y,
-                    inner_w,
+                    inner_w - 44.0,
                     btn_h,
                     bg,
                     hover_bg,
                     text_color,
                     crate::app::AppAction::Typeface(TypefaceMessage::SelectFont((*font_name).clone())),
                 );
+
+                let (copy_bg, copy_hover_bg, copy_text_color) = if is_selected {
+                    ([0.20, 0.40, 0.65, 0.2], [0.30, 0.52, 0.78, 0.5], [0.90, 0.90, 0.95, 1.0])
+                } else {
+                    ([0.0, 0.0, 0.0, 0.0], [0.20, 0.20, 0.25, 0.25], [0.70, 0.70, 0.75, 1.0])
+                };
+
+                pc.button(
+                    "📋",
+                    inner_x + inner_w - 40.0,
+                    draw_y,
+                    40.0,
+                    btn_h,
+                    copy_bg,
+                    copy_hover_bg,
+                    copy_text_color,
+                    crate::app::AppAction::Typeface(TypefaceMessage::CopyFontName((*font_name).clone())),
+                );
             }
         }
         
@@ -607,6 +753,33 @@ pub fn view(state: &mut TypefaceState, cx: f32, cy: f32, cw: f32, _ch: f32) -> P
         // 2. Render Right Column (Live Preview)
         let mut right_y = start_y;
         
+        // Render Info Box
+        let info_h = 96.0;
+        let info_bg = [0.12, 0.18, 0.28, 0.3]; // Sleek translucent blue-ish background
+        let info_border = [0.25, 0.40, 0.60, 0.5]; // Soft blue border
+        
+        pc.rect(info_bg, right_x, right_y, right_w, info_h);
+        pc.rect(info_border, right_x, right_y, right_w, 1.0);
+        pc.rect(info_border, right_x, right_y + info_h - 1.0, right_w, 1.0);
+        pc.rect(info_border, right_x, right_y, 1.0, info_h);
+        pc.rect(info_border, right_x + right_w - 1.0, right_y, 1.0, info_h);
+        
+        let text_padding_x = 16.0;
+        let mut text_y = right_y + 12.0;
+        
+        pc.text("Font Directories & Installation", right_x + text_padding_x, text_y, 12.0, [0.35, 0.65, 0.90, 1.0]);
+        text_y += 20.0;
+        
+        pc.text("• Active Directory: ~/Dropbox/Fonts", right_x + text_padding_x, text_y, 11.0, [0.80, 0.80, 0.85, 1.0]);
+        text_y += 16.0;
+        
+        pc.text("• Place TTF/OTF files there to install new fonts.", right_x + text_padding_x, text_y, 11.0, [0.80, 0.80, 0.85, 1.0]);
+        text_y += 16.0;
+
+        pc.text("• Changes will be cached automatically by fontconfig.", right_x + text_padding_x, text_y, 11.0, [0.55, 0.55, 0.60, 1.0]);
+        
+        right_y += info_h + 12.0;
+        
         if let Some(ref font_name) = state.selected_font {
             let card_h = 240.0;
             pc.rect([0.10, 0.10, 0.14, 0.3], right_x, right_y, right_w, card_h);
@@ -665,7 +838,7 @@ pub fn view(state: &mut TypefaceState, cx: f32, cy: f32, cw: f32, _ch: f32) -> P
             text_y += 32.0;
 
             pc.text_with_font(
-                "Pack my box with five dozen liquor jugs.",
+                "The five boxing wizards jump quickly.",
                 right_x + text_padding_x,
                 text_y,
                 24.0,
@@ -681,7 +854,45 @@ pub fn view(state: &mut TypefaceState, cx: f32, cy: f32, cw: f32, _ch: f32) -> P
 
         sec.content_y = left_y.max(right_y);
     }
-    sec.finish(&mut pc);
+    let list_focused = sec_focused.get(2).copied().unwrap_or(false);
+    sec.finish_focused(&mut pc, list_focused);
+
+    // Filter out base text items covered by any open popover to prevent showing through
+    let mut popovers = Vec::new();
+    if state.borders_menu.open {
+        let (x, y, w, h) = state.borders_menu.rect();
+        popovers.push((x, y + h, w, state.borders_menu.options.len() as f32 * 24.0));
+    }
+    if state.status_menu.open {
+        let (x, y, w, h) = state.status_menu.rect();
+        popovers.push((x, y + h, w, state.status_menu.options.len() as f32 * 24.0));
+    }
+    if state.fuzzel_menu.open {
+        let (x, y, w, h) = state.fuzzel_menu.rect();
+        popovers.push((x, y + h, w, state.fuzzel_menu.options.len() as f32 * 24.0));
+    }
+    if state.terminal_menu.open {
+        let (x, y, w, h) = state.terminal_menu.rect();
+        popovers.push((x, y + h, w, state.terminal_menu.options.len() as f32 * 24.0));
+    }
+
+    if !popovers.is_empty() {
+        pc.texts.retain(|(_, _, tx, ty, _, _)| {
+            for &(px, py, pw, ph) in &popovers {
+                if *tx >= px && *tx <= px + pw && *ty >= py && *ty <= py + ph {
+                    return false;
+                }
+            }
+            true
+        });
+    }
+
+    // Render dropdown popovers on top of all other widgets
+    state.borders_menu.render_popover(&mut pc);
+    state.status_menu.render_popover(&mut pc);
+    state.fuzzel_menu.render_popover(&mut pc);
+    state.terminal_menu.render_popover(&mut pc);
+
     pc
 }
 
@@ -709,29 +920,49 @@ pub fn update(state: &mut TypefaceState, msg: TypefaceMessage) {
             if !state.borders_box.editing {
                 state.window_borders = new.window_borders.clone();
                 state.borders_box = new.borders_box;
+                state.borders_menu = new.borders_menu;
             }
             if !state.status_box.editing {
                 state.status_interface = new.status_interface.clone();
                 state.status_box = new.status_box;
+                state.status_menu = new.status_menu;
             }
             if !state.fuzzel_box.editing {
                 state.fuzzel = new.fuzzel.clone();
                 state.fuzzel_box = new.fuzzel_box;
+                state.fuzzel_menu = new.fuzzel_menu;
             }
             if !state.terminal_box.editing {
                 state.terminal = new.terminal.clone();
                 state.terminal_box = new.terminal_box;
+                state.terminal_menu = new.terminal_menu;
             }
             if !state.search_box.editing {
                 state.search_box = new.search_box;
             }
-            let old_scroll = state.list_box.scroll_y;
+            let old_scroll = state.list_box.scroll_y();
             state.list_box = new.list_box;
-            state.list_box.scroll_y = old_scroll;
+            state.list_box.set_scroll_y(old_scroll);
         }
         TypefaceMessage::SetSans(sans) => {
             state.sans_serif = sans.clone();
             state.sans_box.text = sans;
+            if state.borders_menu.selected == 0 {
+                state.window_borders = state.sans_serif.clone();
+                state.borders_box.text = state.sans_serif.clone();
+            }
+            if state.status_menu.selected == 0 {
+                state.status_interface = state.sans_serif.clone();
+                state.status_box.text = state.sans_serif.clone();
+            }
+            if state.fuzzel_menu.selected == 0 {
+                state.fuzzel = state.sans_serif.clone();
+                state.fuzzel_box.text = state.sans_serif.clone();
+            }
+            if state.terminal_menu.selected == 0 {
+                state.terminal = state.sans_serif.clone();
+                state.terminal_box.text = state.sans_serif.clone();
+            }
             save_preferred_fonts(
                 &state.sans_serif,
                 &state.serif,
@@ -745,6 +976,22 @@ pub fn update(state: &mut TypefaceState, msg: TypefaceMessage) {
         TypefaceMessage::SetSerif(serif) => {
             state.serif = serif.clone();
             state.serif_box.text = serif;
+            if state.borders_menu.selected == 1 {
+                state.window_borders = state.serif.clone();
+                state.borders_box.text = state.serif.clone();
+            }
+            if state.status_menu.selected == 1 {
+                state.status_interface = state.serif.clone();
+                state.status_box.text = state.serif.clone();
+            }
+            if state.fuzzel_menu.selected == 1 {
+                state.fuzzel = state.serif.clone();
+                state.fuzzel_box.text = state.serif.clone();
+            }
+            if state.terminal_menu.selected == 1 {
+                state.terminal = state.serif.clone();
+                state.terminal_box.text = state.serif.clone();
+            }
             save_preferred_fonts(
                 &state.sans_serif,
                 &state.serif,
@@ -758,6 +1005,22 @@ pub fn update(state: &mut TypefaceState, msg: TypefaceMessage) {
         TypefaceMessage::SetMono(mono) => {
             state.monospace = mono.clone();
             state.mono_box.text = mono;
+            if state.borders_menu.selected == 2 {
+                state.window_borders = state.monospace.clone();
+                state.borders_box.text = state.monospace.clone();
+            }
+            if state.status_menu.selected == 2 {
+                state.status_interface = state.monospace.clone();
+                state.status_box.text = state.monospace.clone();
+            }
+            if state.fuzzel_menu.selected == 2 {
+                state.fuzzel = state.monospace.clone();
+                state.fuzzel_box.text = state.monospace.clone();
+            }
+            if state.terminal_menu.selected == 2 {
+                state.terminal = state.monospace.clone();
+                state.terminal_box.text = state.monospace.clone();
+            }
             save_preferred_fonts(
                 &state.sans_serif,
                 &state.serif,
@@ -826,6 +1089,131 @@ pub fn update(state: &mut TypefaceState, msg: TypefaceMessage) {
         TypefaceMessage::SelectFont(font) => {
             state.selected_font = Some(font);
         }
+        TypefaceMessage::CopyFontName(font) => {
+            use std::io::Write;
+            std::thread::spawn({
+                let text = font.clone();
+                move || {
+                    let mut copied = false;
+                    if let Ok(mut child) = std::process::Command::new("wl-copy")
+                        .stdin(std::process::Stdio::piped())
+                        .spawn()
+                    {
+                        if let Some(mut stdin) = child.stdin.take() {
+                            if stdin.write_all(text.as_bytes()).is_ok() {
+                                copied = true;
+                            }
+                        }
+                        let _ = child.wait();
+                    }
+                    if !copied {
+                        if let Ok(mut child) = std::process::Command::new("xclip")
+                            .arg("-selection")
+                            .arg("clipboard")
+                            .stdin(std::process::Stdio::piped())
+                            .spawn()
+                        {
+                            if let Some(mut stdin) = child.stdin.take() {
+                                let _ = stdin.write_all(text.as_bytes());
+                            }
+                            let _ = child.wait();
+                        }
+                    }
+                }
+            });
+        }
+        TypefaceMessage::SetBordersMenu(idx) => {
+            state.borders_menu.selected = idx;
+            state.borders_box.disabled = idx != 3;
+            if idx == 0 {
+                state.window_borders = state.sans_serif.clone();
+                state.borders_box.text = state.sans_serif.clone();
+            } else if idx == 1 {
+                state.window_borders = state.serif.clone();
+                state.borders_box.text = state.serif.clone();
+            } else if idx == 2 {
+                state.window_borders = state.monospace.clone();
+                state.borders_box.text = state.monospace.clone();
+            }
+            save_preferred_fonts(
+                &state.sans_serif,
+                &state.serif,
+                &state.monospace,
+                &state.window_borders,
+                &state.status_interface,
+                &state.fuzzel,
+                &state.terminal,
+            );
+        }
+        TypefaceMessage::SetStatusMenu(idx) => {
+            state.status_menu.selected = idx;
+            state.status_box.disabled = idx != 3;
+            if idx == 0 {
+                state.status_interface = state.sans_serif.clone();
+                state.status_box.text = state.sans_serif.clone();
+            } else if idx == 1 {
+                state.status_interface = state.serif.clone();
+                state.status_box.text = state.serif.clone();
+            } else if idx == 2 {
+                state.status_interface = state.monospace.clone();
+                state.status_box.text = state.monospace.clone();
+            }
+            save_preferred_fonts(
+                &state.sans_serif,
+                &state.serif,
+                &state.monospace,
+                &state.window_borders,
+                &state.status_interface,
+                &state.fuzzel,
+                &state.terminal,
+            );
+        }
+        TypefaceMessage::SetFuzzelMenu(idx) => {
+            state.fuzzel_menu.selected = idx;
+            state.fuzzel_box.disabled = idx != 3;
+            if idx == 0 {
+                state.fuzzel = state.sans_serif.clone();
+                state.fuzzel_box.text = state.sans_serif.clone();
+            } else if idx == 1 {
+                state.fuzzel = state.serif.clone();
+                state.fuzzel_box.text = state.serif.clone();
+            } else if idx == 2 {
+                state.fuzzel = state.monospace.clone();
+                state.fuzzel_box.text = state.monospace.clone();
+            }
+            save_preferred_fonts(
+                &state.sans_serif,
+                &state.serif,
+                &state.monospace,
+                &state.window_borders,
+                &state.status_interface,
+                &state.fuzzel,
+                &state.terminal,
+            );
+        }
+        TypefaceMessage::SetTerminalMenu(idx) => {
+            state.terminal_menu.selected = idx;
+            state.terminal_box.disabled = idx != 3;
+            if idx == 0 {
+                state.terminal = state.sans_serif.clone();
+                state.terminal_box.text = state.sans_serif.clone();
+            } else if idx == 1 {
+                state.terminal = state.serif.clone();
+                state.terminal_box.text = state.serif.clone();
+            } else if idx == 2 {
+                state.terminal = state.monospace.clone();
+                state.terminal_box.text = state.monospace.clone();
+            }
+            save_preferred_fonts(
+                &state.sans_serif,
+                &state.serif,
+                &state.monospace,
+                &state.window_borders,
+                &state.status_interface,
+                &state.fuzzel,
+                &state.terminal,
+            );
+        }
     }
 }