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

commit7845c04f9d8cd5cc0c1bd3a8118502ab0c45ab27
parent1a03ce252b
authorLucas Galante <[email protected]>
date2026-06-16 12:14
Support configurable background color and opacity for notifications on the Interface page

 src/main.rs            | 227 +++++++++++++++++++++++++++-------------------
 src/pages/input.rs     |  45 ++++++++-
 src/pages/interface.rs | 241 +++++++++++++++++++++++++++++++++++++++++++++----
 src/pages/services.rs  | 108 ----------------------
 4 files changed, 398 insertions(+), 223 deletions(-)

diff --git a/src/main.rs b/src/main.rs
index 40b2147..045e601 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -157,7 +157,6 @@ struct SystemInterface {
     needs_rebuild: bool,
     scroll_y: f32,
     max_scroll_y: f32,
-    opacity_dragging: bool,
     audio_sink_dragging: Option<usize>,
     audio_source_dragging: Option<usize>,
     display_brightness_dragging: bool,
@@ -465,7 +464,6 @@ impl clear_ui::engine::Application for SystemInterface {
             needs_rebuild: true,
             scroll_y: 4000.0,
             max_scroll_y: 0.0,
-            opacity_dragging: false,
             audio_sink_dragging: None,
             audio_source_dragging: None,
             display_brightness_dragging: false,
@@ -718,6 +716,8 @@ fn collect_popover_rects(w: &dyn clear_ui::widget::Element, popovers: &mut Vec<(
         self.app.interface.dropdown_height_spinbox.set_parent(None, &mut self.ui_context);
         self.app.interface.button_corner_radius_spinbox.clear_children(&mut self.ui_context);
         self.app.interface.button_corner_radius_spinbox.set_parent(None, &mut self.ui_context);
+        self.app.interface.notification_opacity_spinbox.clear_children(&mut self.ui_context);
+        self.app.interface.notification_opacity_spinbox.set_parent(None, &mut self.ui_context);
 
         self.app.interface.sans_box.clear_children(&mut self.ui_context); self.app.interface.sans_box.set_parent(None, &mut self.ui_context);
         self.app.interface.serif_box.clear_children(&mut self.ui_context); self.app.interface.serif_box.set_parent(None, &mut self.ui_context);
@@ -734,7 +734,6 @@ fn collect_popover_rects(w: &dyn clear_ui::widget::Element, popovers: &mut Vec<(
         self.app.services.notifications_enable_toggle.clear_children(&mut self.ui_context); self.app.services.notifications_enable_toggle.set_parent(None, &mut self.ui_context);
         self.app.services.notifications_bell_toggle.clear_children(&mut self.ui_context); self.app.services.notifications_bell_toggle.set_parent(None, &mut self.ui_context);
         self.app.services.notifications_duration_spinbox.clear_children(&mut self.ui_context); self.app.services.notifications_duration_spinbox.set_parent(None, &mut self.ui_context);
-        self.app.services.notifications_opacity_slider.clear_children(&mut self.ui_context); self.app.services.notifications_opacity_slider.set_parent(None, &mut self.ui_context);
 
         self.app.input.rate_spinbox.clear_children(&mut self.ui_context); self.app.input.rate_spinbox.set_parent(None, &mut self.ui_context);
         self.app.input.delay_spinbox.clear_children(&mut self.ui_context); self.app.input.delay_spinbox.set_parent(None, &mut self.ui_context);
@@ -751,6 +750,8 @@ fn collect_popover_rects(w: &dyn clear_ui::widget::Element, popovers: &mut Vec<(
         self.app.input.trackpoint_accel_profile_menu.clear_children(&mut self.ui_context); self.app.input.trackpoint_accel_profile_menu.set_parent(None, &mut self.ui_context);
         self.app.input.cursor_theme_menu.clear_children(&mut self.ui_context); self.app.input.cursor_theme_menu.set_parent(None, &mut self.ui_context);
         self.app.input.cursor_size_spinbox.clear_children(&mut self.ui_context); self.app.input.cursor_size_spinbox.set_parent(None, &mut self.ui_context);
+        self.app.input.zoom_in_box.clear_children(&mut self.ui_context); self.app.input.zoom_in_box.set_parent(None, &mut self.ui_context);
+        self.app.input.zoom_out_box.clear_children(&mut self.ui_context); self.app.input.zoom_out_box.set_parent(None, &mut self.ui_context);
 
         for sb in &mut self.app.audio.sink_spinboxes {
             sb.clear_children(&mut self.ui_context);
@@ -831,7 +832,6 @@ fn collect_popover_rects(w: &dyn clear_ui::widget::Element, popovers: &mut Vec<(
                 link_parent_child(&mut self.page_sec_containers[1], &mut self.app.services.notifications_enable_toggle, &mut self.ui_context);
                 link_parent_child(&mut self.page_sec_containers[1], &mut self.app.services.notifications_bell_toggle, &mut self.ui_context);
                 link_parent_child(&mut self.page_sec_containers[1], &mut self.app.services.notifications_duration_spinbox, &mut self.ui_context);
-                link_parent_child(&mut self.page_sec_containers[1], &mut self.app.services.notifications_opacity_slider, &mut self.ui_context);
 
                 link_parent_child(&mut self.page_sec_containers[2], &mut self.app.services.status_separators_toggle, &mut self.ui_context);
                 link_parent_child(&mut self.page_sec_containers[2], &mut self.app.services.status_underline_toggle, &mut self.ui_context);
@@ -891,9 +891,9 @@ fn collect_popover_rects(w: &dyn clear_ui::widget::Element, popovers: &mut Vec<(
                 link_parent_child(&mut self.page_sec_containers[6], &mut self.app.layout.side_panel_border_opacity_spinbox, &mut self.ui_context);
             }
             Page::Interface => {
-                self.page_sec_containers.resize_with(5, clear_ui::widget::Container::new);
+                self.page_sec_containers.resize_with(6, clear_ui::widget::Container::new);
                 
-                for i in 0..5 {
+                for i in 0..6 {
                     link_parent_child(page_root, &mut self.page_sec_containers[i], &mut self.ui_context);
                 }
                 
@@ -948,13 +948,13 @@ fn collect_popover_rects(w: &dyn clear_ui::widget::Element, popovers: &mut Vec<(
                 
                 // (ScrollingList child widgets)
                 link_parent_child(&mut self.page_sec_containers[2], &mut self.app.interface.color_selectors[14], &mut self.ui_context);
-
+ 
                 // (Breadcrumb child widgets)
                 link_parent_child(&mut self.page_sec_containers[2], &mut self.app.interface.color_selectors[15], &mut self.ui_context);
-
+ 
                 // (Popover child widgets)
                 link_parent_child(&mut self.page_sec_containers[2], &mut self.app.interface.color_selectors[16], &mut self.ui_context);
-
+ 
                 // (Spinbox child widgets)
                 link_parent_child(&mut self.page_sec_containers[2], &mut self.app.interface.spinbox_height_spinbox, &mut self.ui_context);
                 
@@ -983,30 +983,35 @@ fn collect_popover_rects(w: &dyn clear_ui::widget::Element, popovers: &mut Vec<(
                 // Section 3: Indicators (parent of Primary Highlight)
                 link_parent_child(&mut self.page_sec_containers[3], &mut self.app.interface.color_selectors[10], &mut self.ui_context);
                 
-                // Section 4: Fonts (parent of System Fonts and Program Fonts)
+                // Section 4: Notification
+                link_parent_child(&mut self.page_sec_containers[4], &mut self.app.interface.color_selectors[17], &mut self.ui_context);
+                link_parent_child(&mut self.page_sec_containers[4], &mut self.app.interface.notification_opacity_spinbox, &mut self.ui_context);
+                
+                // Section 5: Fonts (parent of System Fonts and Program Fonts)
                 // (System Fonts)
-                link_parent_child(&mut self.page_sec_containers[4], &mut self.app.interface.sans_box, &mut self.ui_context);
-                link_parent_child(&mut self.page_sec_containers[4], &mut self.app.interface.serif_box, &mut self.ui_context);
-                link_parent_child(&mut self.page_sec_containers[4], &mut self.app.interface.mono_box, &mut self.ui_context);
+                link_parent_child(&mut self.page_sec_containers[5], &mut self.app.interface.sans_box, &mut self.ui_context);
+                link_parent_child(&mut self.page_sec_containers[5], &mut self.app.interface.serif_box, &mut self.ui_context);
+                link_parent_child(&mut self.page_sec_containers[5], &mut self.app.interface.mono_box, &mut self.ui_context);
                 
                 // (Program Fonts)
-                link_parent_child(&mut self.page_sec_containers[4], &mut self.app.interface.borders_menu, &mut self.ui_context);
-                link_parent_child(&mut self.page_sec_containers[4], &mut self.app.interface.borders_box, &mut self.ui_context);
-                link_parent_child(&mut self.page_sec_containers[4], &mut self.app.interface.status_menu, &mut self.ui_context);
-                link_parent_child(&mut self.page_sec_containers[4], &mut self.app.interface.status_box, &mut self.ui_context);
-                link_parent_child(&mut self.page_sec_containers[4], &mut self.app.interface.fuzzel_menu, &mut self.ui_context);
-                link_parent_child(&mut self.page_sec_containers[4], &mut self.app.interface.fuzzel_box, &mut self.ui_context);
-                link_parent_child(&mut self.page_sec_containers[4], &mut self.app.interface.terminal_menu, &mut self.ui_context);
-                link_parent_child(&mut self.page_sec_containers[4], &mut self.app.interface.terminal_box, &mut self.ui_context);
+                link_parent_child(&mut self.page_sec_containers[5], &mut self.app.interface.borders_menu, &mut self.ui_context);
+                link_parent_child(&mut self.page_sec_containers[5], &mut self.app.interface.borders_box, &mut self.ui_context);
+                link_parent_child(&mut self.page_sec_containers[5], &mut self.app.interface.status_menu, &mut self.ui_context);
+                link_parent_child(&mut self.page_sec_containers[5], &mut self.app.interface.status_box, &mut self.ui_context);
+                link_parent_child(&mut self.page_sec_containers[5], &mut self.app.interface.fuzzel_menu, &mut self.ui_context);
+                link_parent_child(&mut self.page_sec_containers[5], &mut self.app.interface.fuzzel_box, &mut self.ui_context);
+                link_parent_child(&mut self.page_sec_containers[5], &mut self.app.interface.terminal_menu, &mut self.ui_context);
+                link_parent_child(&mut self.page_sec_containers[5], &mut self.app.interface.terminal_box, &mut self.ui_context);
             }
 
             Page::Input => {
-                self.page_sec_containers.resize_with(5, clear_ui::widget::Container::new);
+                self.page_sec_containers.resize_with(6, clear_ui::widget::Container::new);
                 link_parent_child(page_root, &mut self.page_sec_containers[0], &mut self.ui_context);
                 link_parent_child(page_root, &mut self.page_sec_containers[1], &mut self.ui_context);
                 link_parent_child(page_root, &mut self.page_sec_containers[2], &mut self.ui_context);
                 link_parent_child(page_root, &mut self.page_sec_containers[3], &mut self.ui_context);
                 link_parent_child(page_root, &mut self.page_sec_containers[4], &mut self.ui_context);
+                link_parent_child(page_root, &mut self.page_sec_containers[5], &mut self.ui_context);
                 
                 link_parent_child(&mut self.page_sec_containers[0], &mut self.app.input.dwtp_toggle, &mut self.ui_context);
                 link_parent_child(&mut self.page_sec_containers[0], &mut self.app.input.trackpoint_accel_speed_spinbox, &mut self.ui_context);
@@ -1027,6 +1032,9 @@ fn collect_popover_rects(w: &dyn clear_ui::widget::Element, popovers: &mut Vec<(
                 link_parent_child(&mut self.page_sec_containers[4], &mut self.app.input.pointer_friction_spinbox, &mut self.ui_context);
                 link_parent_child(&mut self.page_sec_containers[4], &mut self.app.input.trackpad_toggle, &mut self.ui_context);
                 link_parent_child(&mut self.page_sec_containers[4], &mut self.app.input.trackpad_friction_spinbox, &mut self.ui_context);
+
+                link_parent_child(&mut self.page_sec_containers[5], &mut self.app.input.zoom_in_box, &mut self.ui_context);
+                link_parent_child(&mut self.page_sec_containers[5], &mut self.app.input.zoom_out_box, &mut self.ui_context);
             }
             Page::Audio => {
                 self.page_sec_containers.resize_with(2, clear_ui::widget::Container::new);
@@ -1940,6 +1948,9 @@ fn collect_popover_rects(w: &dyn clear_ui::widget::Element, popovers: &mut Vec<(
             if self.app.interface.button_corner_radius_spinbox.cursor_moved(lx, ly, &mut self.ui_context) {
                 changed = true;
             }
+            if self.app.interface.notification_opacity_spinbox.cursor_moved(lx, ly, &mut self.ui_context) {
+                changed = true;
+            }
             if self.app.interface.sans_box.cursor_moved(lx, ly, &mut self.ui_context) {
                 changed = true;
             }
@@ -2023,6 +2034,12 @@ fn collect_popover_rects(w: &dyn clear_ui::widget::Element, popovers: &mut Vec<(
             if self.app.input.trackpoint_accel_profile_menu.cursor_moved(lx, ly, &mut self.ui_context) {
                 changed = true;
             }
+            if self.app.input.zoom_in_box.cursor_moved(lx, ly, &mut self.ui_context) {
+                changed = true;
+            }
+            if self.app.input.zoom_out_box.cursor_moved(lx, ly, &mut self.ui_context) {
+                changed = true;
+            }
         }
         if self.app.current_page == Page::Audio {
             if let Some(idx) = self.audio_sink_dragging {
@@ -2197,56 +2214,47 @@ fn collect_popover_rects(w: &dyn clear_ui::widget::Element, popovers: &mut Vec<(
 
 
         if self.app.current_page == Page::Services {
-            if self.opacity_dragging {
-                if self.app.services.notifications_opacity_slider.drag_update(lx, ly) {
-                    changed = true;
-                }
+            if self.app.services.search_box.cursor_moved(lx, ly, &mut self.ui_context) {
+                changed = true;
+            }
+            if self.app.services.list_box.cursor_moved(lx, ly, &mut self.ui_context) {
+                changed = true;
+            }
+            let query = if self.app.services.search_box.editing {
+                self.app.services.search_box.edit_buffer.to_lowercase()
             } else {
-                if self.app.services.search_box.cursor_moved(lx, ly, &mut self.ui_context) {
-                    changed = true;
-                }
-                if self.app.services.list_box.cursor_moved(lx, ly, &mut self.ui_context) {
-                    changed = true;
-                }
-                let query = if self.app.services.search_box.editing {
-                    self.app.services.search_box.edit_buffer.to_lowercase()
-                } else {
-                    self.app.services.search_box.text.to_lowercase()
-                };
-                let matching_count = self.app.services.services.iter()
-                    .filter(|s| s.is_system == (self.app.services.active_tab == pages::services::ServiceTab::System))
-                    .filter(|s| s.name.to_lowercase().contains(&query) || s.description.to_lowercase().contains(&query))
-                    .count();
-                for i in 0..matching_count.min(self.app.services.service_items.len()) {
-                    if self.app.services.service_items[i].cursor_moved(lx, ly, &mut self.ui_context) {
-                        changed = true;
-                    }
-                }
-                if self.app.services.notifications_enable_toggle.cursor_moved(lx, ly, &mut self.ui_context) {
-                    changed = true;
-                }
-                if self.app.services.notifications_bell_toggle.cursor_moved(lx, ly, &mut self.ui_context) {
-                    changed = true;
-                }
-                if self.app.services.notifications_duration_spinbox.cursor_moved(lx, ly, &mut self.ui_context) {
-                    changed = true;
-                }
-                if self.app.services.notifications_opacity_slider.cursor_moved(lx, ly, &mut self.ui_context) {
-                    changed = true;
-                }
-                if self.app.services.status_label.cursor_moved(lx, ly, &mut self.ui_context) {
-                    changed = true;
-                }
-                if self.app.services.status_separators_toggle.cursor_moved(lx, ly, &mut self.ui_context) {
-                    changed = true;
-                }
-                if self.app.services.status_underline_toggle.cursor_moved(lx, ly, &mut self.ui_context) {
-                    changed = true;
-                }
-                if self.app.services.status_padding_spinbox.cursor_moved(lx, ly, &mut self.ui_context) {
+                self.app.services.search_box.text.to_lowercase()
+            };
+            let matching_count = self.app.services.services.iter()
+                .filter(|s| s.is_system == (self.app.services.active_tab == pages::services::ServiceTab::System))
+                .filter(|s| s.name.to_lowercase().contains(&query) || s.description.to_lowercase().contains(&query))
+                .count();
+            for i in 0..matching_count.min(self.app.services.service_items.len()) {
+                if self.app.services.service_items[i].cursor_moved(lx, ly, &mut self.ui_context) {
                     changed = true;
                 }
             }
+            if self.app.services.notifications_enable_toggle.cursor_moved(lx, ly, &mut self.ui_context) {
+                changed = true;
+            }
+            if self.app.services.notifications_bell_toggle.cursor_moved(lx, ly, &mut self.ui_context) {
+                changed = true;
+            }
+            if self.app.services.notifications_duration_spinbox.cursor_moved(lx, ly, &mut self.ui_context) {
+                changed = true;
+            }
+            if self.app.services.status_label.cursor_moved(lx, ly, &mut self.ui_context) {
+                changed = true;
+            }
+            if self.app.services.status_separators_toggle.cursor_moved(lx, ly, &mut self.ui_context) {
+                changed = true;
+            }
+            if self.app.services.status_underline_toggle.cursor_moved(lx, ly, &mut self.ui_context) {
+                changed = true;
+            }
+            if self.app.services.status_padding_spinbox.cursor_moved(lx, ly, &mut self.ui_context) {
+                changed = true;
+            }
         }
         if self.app.current_page == Page::Accounts {
             if self.app.accounts.editing_oauth_creds {
@@ -2419,6 +2427,7 @@ fn collect_popover_rects(w: &dyn clear_ui::widget::Element, popovers: &mut Vec<(
                     if self.app.interface.font_selector_height_spinbox.hit_test(lx, ly, &self.ui_context) { clicked_any_focusable = true; }
                     if self.app.interface.dropdown_height_spinbox.hit_test(lx, ly, &self.ui_context) { clicked_any_focusable = true; }
                     if self.app.interface.button_corner_radius_spinbox.hit_test(lx, ly, &self.ui_context) { clicked_any_focusable = true; }
+                    if self.app.interface.notification_opacity_spinbox.hit_test(lx, ly, &self.ui_context) { clicked_any_focusable = true; }
                     let tf = &mut self.app.interface;
                     if tf.sans_box.hit_test(lx, ly, &self.ui_context) { clicked_any_focusable = true; }
                     if tf.serif_box.hit_test(lx, ly, &self.ui_context) { clicked_any_focusable = true; }
@@ -2440,6 +2449,8 @@ fn collect_popover_rects(w: &dyn clear_ui::widget::Element, popovers: &mut Vec<(
                     if self.app.input.trackpad_friction_spinbox.hit_test(lx, ly, &self.ui_context) { clicked_any_focusable = true; }
                     if self.app.input.trackpoint_accel_speed_spinbox.hit_test(lx, ly, &self.ui_context) { clicked_any_focusable = true; }
                     if self.app.input.trackpoint_accel_profile_menu.hit_test(lx, ly, &self.ui_context) { clicked_any_focusable = true; }
+                    if self.app.input.zoom_in_box.hit_test(lx, ly, &self.ui_context) { clicked_any_focusable = true; }
+                    if self.app.input.zoom_out_box.hit_test(lx, ly, &self.ui_context) { clicked_any_focusable = true; }
                 }
 
                 Page::Audio => {
@@ -2475,7 +2486,6 @@ fn collect_popover_rects(w: &dyn clear_ui::widget::Element, popovers: &mut Vec<(
                     if srv.search_box.hit_test(lx, ly, &self.ui_context) { clicked_any_focusable = true; }
                     if srv.list_box.hit_test(lx, ly, &self.ui_context) { clicked_any_focusable = true; }
                     if srv.notifications_duration_spinbox.hit_test(lx, ly, &self.ui_context) { clicked_any_focusable = true; }
-                    if srv.notifications_opacity_slider.hit_test(lx, ly, &self.ui_context) { clicked_any_focusable = true; }
                     if srv.status_label.hit_test(lx, ly, &self.ui_context) { clicked_any_focusable = true; }
                     if srv.status_padding_spinbox.hit_test(lx, ly, &self.ui_context) { clicked_any_focusable = true; }
                 }
@@ -2646,6 +2656,7 @@ fn collect_popover_rects(w: &dyn clear_ui::widget::Element, popovers: &mut Vec<(
                         14 => pages::interface::InterfaceMessage::PickScrollingListBgColor,
                         15 => pages::interface::InterfaceMessage::PickBreadcrumbBgColor,
                         16 => pages::interface::InterfaceMessage::PickPopoverBgColor,
+                        17 => pages::interface::InterfaceMessage::PickNotificationBgColor,
                         _ => pages::interface::InterfaceMessage::PickLowColor,
                     }));
                 }
@@ -2668,6 +2679,7 @@ fn collect_popover_rects(w: &dyn clear_ui::widget::Element, popovers: &mut Vec<(
                         14 => pages::interface::InterfaceMessage::SetScrollingListBgColor(cp.color),
                         15 => pages::interface::InterfaceMessage::SetBreadcrumbBgColor(cp.color),
                         16 => pages::interface::InterfaceMessage::SetPopoverBgColor(cp.color),
+                        17 => pages::interface::InterfaceMessage::SetNotificationBgColor(cp.color),
                         _ => pages::interface::InterfaceMessage::SetLowColor(cp.color),
                     }));
                 }
@@ -2678,6 +2690,12 @@ fn collect_popover_rects(w: &dyn clear_ui::widget::Element, popovers: &mut Vec<(
             if sb.mouse_input(button, state, lx, ly, &mut self.ui_context) && sb.value != old {
                 actions.push(AppAction::Interface(pages::interface::InterfaceMessage::SetMenubarOpacity(sb.value as f32 / 100.0)));
             }
+            let sb = &mut self.app.interface.notification_opacity_spinbox;
+            if !sb.hit_test(lx, ly, &self.ui_context) { sb.unfocus(); }
+            let old = sb.value;
+            if sb.mouse_input(button, state, lx, ly, &mut self.ui_context) && sb.value != old {
+                actions.push(AppAction::Interface(pages::interface::InterfaceMessage::SetNotificationOpacity(sb.value as f32 / 100.0)));
+            }
             let sb = &mut self.app.interface.tab_margin_spinbox_x;
             if !sb.hit_test(lx, ly, &self.ui_context) { sb.unfocus(); }
             let old = sb.value;
@@ -2951,6 +2969,24 @@ fn collect_popover_rects(w: &dyn clear_ui::widget::Element, popovers: &mut Vec<(
             if state == clear_ui::widget::ElementState::Pressed && menu.take_change() {
                 actions.push(AppAction::Input(pages::input::InputMessage::ApplyCursorTheme(menu.selected)));
             }
+
+            let tb = &mut self.app.input.zoom_in_box;
+            if state == clear_ui::widget::ElementState::Pressed && !tb.hit_test(lx, ly, &self.ui_context) { tb.unfocus(); }
+            if tb.mouse_input(button, state, lx, ly, &mut self.ui_context) {
+                self.needs_rebuild = true;
+            }
+            if state == clear_ui::widget::ElementState::Pressed && tb.take_change() {
+                actions.push(AppAction::Input(pages::input::InputMessage::ApplyZoomIn));
+            }
+
+            let tb = &mut self.app.input.zoom_out_box;
+            if state == clear_ui::widget::ElementState::Pressed && !tb.hit_test(lx, ly, &self.ui_context) { tb.unfocus(); }
+            if tb.mouse_input(button, state, lx, ly, &mut self.ui_context) {
+                self.needs_rebuild = true;
+            }
+            if state == clear_ui::widget::ElementState::Pressed && tb.take_change() {
+                actions.push(AppAction::Input(pages::input::InputMessage::ApplyZoomOut));
+            }
         }
         if self.app.current_page == Page::Services {
             let toggle = &mut self.app.services.notifications_enable_toggle;
@@ -2963,24 +2999,6 @@ fn collect_popover_rects(w: &dyn clear_ui::widget::Element, popovers: &mut Vec<(
             if toggle.take_click() {
                 actions.push(AppAction::Services(pages::services::ServicesMessage::ToggleNotificationsBell));
             }
-            let slider = &mut self.app.services.notifications_opacity_slider;
-            if button == clear_ui::widget::MouseButton::Left {
-                if state == clear_ui::widget::ElementState::Pressed {
-                    if slider.hit_test(lx, ly, &self.ui_context) {
-                        slider.drag_begin(lx, ly);
-                        self.opacity_dragging = true;
-                        self.needs_rebuild = true;
-                    }
-                } else if state == clear_ui::widget::ElementState::Released {
-                    if self.opacity_dragging {
-                        self.opacity_dragging = false;
-                        slider.drag_end();
-                        let val = slider.value() as f32 / 100.0;
-                        actions.push(AppAction::Services(pages::services::ServicesMessage::SetNotificationsOpacity(val)));
-                        self.needs_rebuild = true;
-                    }
-                }
-            }
             if state == clear_ui::widget::ElementState::Pressed {
                 let lbl1 = &mut self.app.services.status_label;
                 if !lbl1.hit_test(lx, ly, &self.ui_context) { lbl1.unfocus(); }
@@ -3547,10 +3565,14 @@ fn collect_popover_rects(w: &dyn clear_ui::widget::Element, popovers: &mut Vec<(
                             14 => pages::interface::InterfaceMessage::SetScrollingListBgColor(cp.color),
                             15 => pages::interface::InterfaceMessage::SetBreadcrumbBgColor(cp.color),
                             16 => pages::interface::InterfaceMessage::SetPopoverBgColor(cp.color),
+                            17 => pages::interface::InterfaceMessage::SetNotificationBgColor(cp.color),
                             _ => pages::interface::InterfaceMessage::SetLowColor(cp.color),
                         }));
                     }
                 }
+                if self.app.interface.notification_opacity_spinbox.take_change() {
+                    actions.push(AppAction::Interface(pages::interface::InterfaceMessage::SetNotificationOpacity(self.app.interface.notification_opacity_spinbox.value as f32 / 100.0)));
+                }
                 if self.app.interface.menubar_opacity_spinbox.take_change() {
                     actions.push(AppAction::Interface(pages::interface::InterfaceMessage::SetMenubarOpacity(self.app.interface.menubar_opacity_spinbox.value as f32 / 100.0)));
                 }
@@ -3733,6 +3755,12 @@ fn collect_popover_rects(w: &dyn clear_ui::widget::Element, popovers: &mut Vec<(
                 if self.app.input.cursor_theme_menu.take_change() {
                     actions.push(AppAction::Input(pages::input::InputMessage::ApplyCursorTheme(self.app.input.cursor_theme_menu.selected)));
                 }
+                if self.app.input.zoom_in_box.take_change() {
+                    actions.push(AppAction::Input(pages::input::InputMessage::ApplyZoomIn));
+                }
+                if self.app.input.zoom_out_box.take_change() {
+                    actions.push(AppAction::Input(pages::input::InputMessage::ApplyZoomOut));
+                }
             }
             Page::Services => {
                 if self.app.services.notifications_duration_spinbox.take_change() {
@@ -3747,10 +3775,6 @@ fn collect_popover_rects(w: &dyn clear_ui::widget::Element, popovers: &mut Vec<(
                 if self.app.services.notifications_bell_toggle.take_change() {
                     actions.push(AppAction::Services(pages::services::ServicesMessage::ToggleNotificationsBell));
                 }
-                if self.app.services.notifications_opacity_slider.take_change() {
-                    let val = self.app.services.notifications_opacity_slider.value() as f32 / 100.0;
-                    actions.push(AppAction::Services(pages::services::ServicesMessage::SetNotificationsOpacity(val)));
-                }
                 if self.app.services.status_separators_toggle.take_change() {
                     actions.push(AppAction::Services(pages::services::ServicesMessage::StatusToggleSeparators));
                 }
@@ -4246,6 +4270,7 @@ fn collect_popover_rects(w: &dyn clear_ui::widget::Element, popovers: &mut Vec<(
                             14 => pages::interface::InterfaceMessage::SetScrollingListBgColor(cp.color),
                             15 => pages::interface::InterfaceMessage::SetBreadcrumbBgColor(cp.color),
                             16 => pages::interface::InterfaceMessage::SetPopoverBgColor(cp.color),
+                            17 => pages::interface::InterfaceMessage::SetNotificationBgColor(cp.color),
                             _ => pages::interface::InterfaceMessage::SetLowColor(cp.color),
                         }));
                     }
@@ -4259,6 +4284,16 @@ fn collect_popover_rects(w: &dyn clear_ui::widget::Element, popovers: &mut Vec<(
                 self.needs_rebuild = true;
                 return true;
             }
+            let sb = &mut self.app.interface.notification_opacity_spinbox;
+            let old = sb.value;
+            if sb.keyboard_input(event, &mut self.ui_context) {
+                let new_val = sb.value;
+                if new_val != old {
+                    self.handle_action(&AppAction::Interface(pages::interface::InterfaceMessage::SetNotificationOpacity(new_val as f32 / 100.0)));
+                }
+                self.needs_rebuild = true;
+                return true;
+            }
             let sb = &mut self.app.interface.menubar_opacity_spinbox;
             let old = sb.value;
             if sb.keyboard_input(event, &mut self.ui_context) {
@@ -4656,6 +4691,16 @@ fn collect_popover_rects(w: &dyn clear_ui::widget::Element, popovers: &mut Vec<(
                 self.needs_rebuild = true;
                 return true;
             }
+            if self.app.input.zoom_in_box.keyboard_input(event, &mut self.ui_context) {
+                self.handle_action(&AppAction::Input(pages::input::InputMessage::ApplyZoomIn));
+                self.needs_rebuild = true;
+                return true;
+            }
+            if self.app.input.zoom_out_box.keyboard_input(event, &mut self.ui_context) {
+                self.handle_action(&AppAction::Input(pages::input::InputMessage::ApplyZoomOut));
+                self.needs_rebuild = true;
+                return true;
+            }
         }
         if self.app.current_page == Page::Accounts {
             let mut consumed = false;
diff --git a/src/pages/input.rs b/src/pages/input.rs
index 16855fe..88b5f3a 100644
--- a/src/pages/input.rs
+++ b/src/pages/input.rs
@@ -3,7 +3,7 @@ use std::io::Write;
 
 use crate::app::PageContent;
 use clear_ui::layout::{PageLayoutBuilder, LayoutStrategy};
-use clear_ui::widget::{Spinbox, Toggle, Trackpad, Dropdown, Finger, Element};
+use clear_ui::widget::{Spinbox, Toggle, Trackpad, Dropdown, Finger, Element, TextBox};
 
 const CONFIG_PATH: &str = "/home/lsgalante/.config/cce/config.toml";
 
@@ -70,6 +70,10 @@ pub struct InputState {
     pub cursor_theme_menu: Dropdown,
     pub cursor_size_spinbox: Spinbox,
     pub cursor_themes: Vec<String>,
+
+    // Graph settings
+    pub zoom_in_box: TextBox,
+    pub zoom_out_box: TextBox,
 }
 
 fn scan_cursor_themes() -> Vec<String> {
@@ -149,6 +153,10 @@ impl Default for InputState {
             cursor_theme_menu: Dropdown::new(cursor_themes.clone(), 0).with_label("Cursor Theme"),
             cursor_size_spinbox: Spinbox::new(24, 16, 64, 4).with_label("Cursor Size").with_unit("px"),
             cursor_themes,
+
+            // Graph defaults
+            zoom_in_box: TextBox::new("=".to_string()).with_label("Zoom In"),
+            zoom_out_box: TextBox::new("-".to_string()).with_label("Zoom Out"),
         }
     }
 }
@@ -181,6 +189,9 @@ pub enum InputMessage {
 
     ApplyCursorTheme(usize),
     ApplyCursorSize,
+
+    ApplyZoomIn,
+    ApplyZoomOut,
 }
 
 pub fn read_input_config() -> InputState {
@@ -212,6 +223,9 @@ pub fn read_input_config() -> InputState {
     let cursor_themes = scan_cursor_themes();
     let theme_idx = cursor_themes.iter().position(|t| t == &cursor_theme).unwrap_or(0);
 
+    let zoom_in = parse_string_key(&content, "zoom_in", "=");
+    let zoom_out = parse_string_key(&content, "zoom_out", "-");
+
     InputState {
         tap_to_click: tap,
         repeat_rate: rate,
@@ -256,6 +270,10 @@ pub fn read_input_config() -> InputState {
         cursor_theme_menu: Dropdown::new(cursor_themes.clone(), theme_idx).with_label("Cursor Theme"),
         cursor_size_spinbox: Spinbox::new(cursor_size as i32, 16, 64, 4).with_label("Cursor Size").with_unit("px"),
         cursor_themes,
+
+        // Graph settings
+        zoom_in_box: TextBox::new(zoom_in).with_label("Zoom In"),
+        zoom_out_box: TextBox::new(zoom_out).with_label("Zoom Out"),
     }
 }
 
@@ -340,7 +358,9 @@ fn write_config_value(key: &str, value: &str) {
         .join("\n");
 
     if !found {
-        let section = if key == "tap_to_click" || key == "dwtp"
+        let section = if key == "zoom_in" || key == "zoom_out" {
+            "[graph]"
+        } else if key == "tap_to_click" || key == "dwtp"
                 || key == "trackpoint_accel_speed" || key == "trackpoint_accel_profile"
                 || key == "cursor_theme" || key == "cursor_size" || key == "natural_scroll" {
             "[input]"
@@ -392,7 +412,7 @@ 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, sec_focused: &[bool], layout: &mut dyn LayoutStrategy, ctx: &mut clear_ui::context::UiContext) -> PageContent {
     let mut final_pc = PageContent::new();
     let sec_w = 320.0f32;
-    let mut builder = PageLayoutBuilder::new(layout, cx, cy, cw, ch, sec_w).with_section_count(7);
+    let mut builder = PageLayoutBuilder::new(layout, cx, cy, cw, ch, sec_w).with_section_count(8);
 
     // ── Touchpad ──
     builder.add_section(&mut final_pc, "Touchpad", false, |sec| {
@@ -473,7 +493,16 @@ pub fn view(state: &mut InputState, cx: f32, cy: f32, cw: f32, ch: f32, sec_focu
         sec.spacing(8.0);
     });
 
-    // ── Keybindings ──
+    // ── Graph ──
+    builder.add_section(&mut final_pc, "Graph", sec_focused.get(5).copied().unwrap_or(false), |sec| {
+        sec.widget_full(&mut state.zoom_in_box, 44.0, ctx);
+        sec.spacing(12.0);
+
+        sec.widget_full(&mut state.zoom_out_box, 44.0, ctx);
+        sec.spacing(8.0);
+    });
+
+    // ── Keyboard Bindings ──
     builder.add_section(&mut final_pc, "Keyboard Bindings", false, |sec| {
         for kb in &state.keybinds {
             let binding = if kb.mods.is_empty() {
@@ -583,6 +612,14 @@ pub fn update(state: &mut InputState, msg: InputMessage) {
             write_config_value("cursor_size", &size.to_string());
             send_ipc_command(&format!("input cursor-size {}", size));
         }
+        InputMessage::ApplyZoomIn => {
+            let val = state.zoom_in_box.text.clone();
+            write_config_value("zoom_in", &format!("\"{}\"", val));
+        }
+        InputMessage::ApplyZoomOut => {
+            let val = state.zoom_out_box.text.clone();
+            write_config_value("zoom_out", &format!("\"{}\"", val));
+        }
         InputMessage::Refreshed(new) => {
             let fingers = state.fingers.clone();
             *state = new;
diff --git a/src/pages/interface.rs b/src/pages/interface.rs
index 8a60f22..a3196ad 100644
--- a/src/pages/interface.rs
+++ b/src/pages/interface.rs
@@ -37,6 +37,9 @@ pub struct InterfaceState {
     pub color_selectors: Vec<ColorSelector>,
     pub menubar_opacity: f32,
     pub menubar_opacity_spinbox: Spinbox,
+    pub notification_bg_color: [u8; 3],
+    pub notification_opacity: f32,
+    pub notification_opacity_spinbox: Spinbox,
     pub paginator_tab_margin_x: u16,
     pub paginator_tab_margin_y: u16,
     pub tab_margin_spinbox_x: Spinbox,
@@ -167,6 +170,7 @@ impl Default for InterfaceState {
                 ColorSelector::new([81, 81, 97]).with_label("Background"), // 14: ScrollingList - Background
                 ColorSelector::new([81, 81, 97]).with_label("Background"), // 15: Breadcrumb - Background
                 ColorSelector::new([81, 81, 97]).with_label("Background"), // 16: Popover - Background
+                ColorSelector::new([0x08, 0x08, 0x0c]).with_label("Background"), // 17: Notification - Background
             ],
             paginator_tab_margin_x: 5,
             paginator_tab_margin_y: 10,
@@ -224,25 +228,25 @@ impl Default for InterfaceState {
             paginator: String::new(),
             all_fonts: Vec::new(),
             mono_fonts: Vec::new(),
-            sans_box: TextBox::default(),
-            serif_box: TextBox::default(),
-            mono_box: TextBox::default(),
-            borders_box: TextBox::default(),
-            status_box: TextBox::default(),
-            fuzzel_box: TextBox::default(),
-            terminal_box: TextBox::default(),
-            borders_menu: Dropdown::default(),
-            status_menu: Dropdown::default(),
-            fuzzel_menu: Dropdown::default(),
-            terminal_menu: Dropdown::default(),
-            borders_size_box: Spinbox::new(11, 6, 72, 1),
-            status_size_box: Spinbox::new(11, 6, 72, 1),
+            sans_box: TextBox::new(String::new()).with_label("Sans-Serif"),
+            serif_box: TextBox::new(String::new()).with_label("Serif"),
+            mono_box: TextBox::new(String::new()).with_label("Monospace"),
+            borders_box: TextBox::new(String::new()).with_label("Active Font"),
+            status_box: TextBox::new(String::new()).with_label("Active Font"),
+            fuzzel_box: TextBox::new(String::new()).with_label("Active Font"),
+            terminal_box: TextBox::new(String::new()).with_label("Active Font"),
+            borders_menu: Dropdown::new(Vec::new(), 0),
+            status_menu: Dropdown::new(Vec::new(), 0),
+            fuzzel_menu: Dropdown::new(Vec::new(), 0),
+            terminal_menu: Dropdown::new(Vec::new(), 0),
+            borders_size_box: Spinbox::new(14, 6, 72, 1),
+            status_size_box: Spinbox::new(14, 6, 72, 1),
             fuzzel_size_box: Spinbox::new(14, 6, 72, 1),
-            terminal_size_box: Spinbox::new(12, 6, 72, 1),
+            terminal_size_box: Spinbox::new(14, 6, 72, 1),
             color_selector_font: "monospace".to_string(),
-            color_selector_font_selector: FontSelector::new("monospace".to_string()).with_label("Value"),
+            color_selector_font_selector: FontSelector::new("monospace".to_string()).with_label("Color Label"),
             menubar_font: "Outfit".to_string(),
-            menubar_font_selector: FontSelector::new("Outfit".to_string()).with_label("Font"),
+            menubar_font_selector: FontSelector::new("Outfit".to_string()).with_label("Menu Label"),
             section_label_font: "Outfit".to_string(),
             section_label_font_selector: FontSelector::new("Outfit".to_string()).with_label("Label"),
             nested_section_label_font: "Outfit".to_string(),
@@ -261,6 +265,9 @@ impl Default for InterfaceState {
             graph_gap_width_spinbox: Spinbox::new(35, 0, 100, 1).with_label("Gap Width").with_unit("px"),
             menubar_opacity: 0.9,
             menubar_opacity_spinbox: Spinbox::new(90, 0, 100, 5).with_label("Opacity").with_unit("%"),
+            notification_bg_color: [0x08, 0x08, 0x0c],
+            notification_opacity: 0.9,
+            notification_opacity_spinbox: Spinbox::new(90, 0, 100, 5).with_label("Opacity").with_unit("%"),
         }
     }
 }
@@ -284,6 +291,8 @@ pub enum InterfaceMessage {
     SetScrollingListBgColor([u8; 3]),
     SetBreadcrumbBgColor([u8; 3]),
     SetPopoverBgColor([u8; 3]),
+    SetNotificationBgColor([u8; 3]),
+    SetNotificationOpacity(f32),
     SetTabMarginX(u16),
     SetTabMarginY(u16),
     SetTabPaddingX(u16),
@@ -333,6 +342,7 @@ pub enum InterfaceMessage {
     PickScrollingListBgColor,
     PickBreadcrumbBgColor,
     PickPopoverBgColor,
+    PickNotificationBgColor,
     Refreshed(InterfaceState),
     TypefaceRefreshed(InterfaceState),
     SetSans(String),
@@ -434,6 +444,8 @@ pub fn read_interface_config() -> InterfaceState {
     let graph_gap_opacity = parse_f32_from(&content, "graph_gap_opacity", legacy_opacity);
     let graph_gap_width = parse_u16_from(&content, "graph_gap_width", 35);
     let opacity = parse_transparency_opacity(&content);
+    let notification_bg_color = parse_notifications_color(&content, "bg_color", [0x08, 0x08, 0x0c]);
+    let notification_opacity = parse_notifications_opacity(&content);
     
     InterfaceState {
         low_color: bg,
@@ -471,6 +483,7 @@ pub fn read_interface_config() -> InterfaceState {
             ColorSelector::new(scrollinglist_bg).with_label("Background").with_font_family(&color_selector_font), // 14: ScrollingList - Background
             ColorSelector::new(breadcrumb_bg).with_label("Background").with_font_family(&color_selector_font), // 15: Breadcrumb - Background
             ColorSelector::new(popover_bg).with_label("Background").with_font_family(&color_selector_font), // 16: Popover - Background
+            ColorSelector::new(notification_bg_color).with_label("Background").with_font_family(&color_selector_font), // 17: Notification - Background
         ],
         paginator_tab_margin_x,
         paginator_tab_margin_y,
@@ -565,6 +578,9 @@ pub fn read_interface_config() -> InterfaceState {
         graph_gap_width_spinbox: Spinbox::new(graph_gap_width as i32, 0, 100, 1).with_label("Gap Width").with_unit("px"),
         menubar_opacity: opacity,
         menubar_opacity_spinbox: Spinbox::new((opacity * 100.0).round() as i32, 0, 100, 5).with_label("Opacity").with_unit("%"),
+        notification_bg_color,
+        notification_opacity,
+        notification_opacity_spinbox: Spinbox::new((notification_opacity * 100.0).round() as i32, 0, 100, 5).with_label("Opacity").with_unit("%"),
     }
 }
 
@@ -817,6 +833,17 @@ fn apply_popover_bg_color(rgb: [u8; 3]) {
     clear_ui::color::set_popover_bg_color([r, g, b, 1.0]);
 }
 
+fn apply_notifications_bg_color(rgb: [u8; 3]) {
+    let hex = format!("\"#{:02x}{:02x}{:02x}\"", rgb[0], rgb[1], rgb[2]);
+    write_notifications_config_value("bg_color", &hex);
+    send_ipc_command("reload");
+}
+
+fn apply_notifications_opacity(opacity: f32) {
+    write_notifications_config_value("opacity", &format!("{:.2}", opacity));
+    send_ipc_command("reload");
+}
+
 fn apply_paginator_tab_margin_x(margin: u16) {
     write_config_value("paginator_tab_margin_x", &margin.to_string());
     send_ipc_command(&format!("layout paginator_tab_margin_x {}", margin));
@@ -1351,7 +1378,7 @@ pub async fn fetch_typeface_state() -> InterfaceState {
 pub fn view(state: &mut InterfaceState, cx: f32, cy: f32, cw: f32, ch: f32, sec_focused: &[bool], layout: &mut dyn LayoutStrategy, ctx: &mut clear_ui::context::UiContext) -> PageContent {
     let mut final_pc = PageContent::new();
     let sec_w = 260.0f32;
-    let mut builder = PageLayoutBuilder::new(layout, cx, cy, cw, ch, sec_w).with_section_count(5);
+    let mut builder = PageLayoutBuilder::new(layout, cx, cy, cw, ch, sec_w).with_section_count(6);
 
     // 1. Layout Section
     builder.add_section(&mut final_pc, "Layout", false, |sec| {
@@ -1634,12 +1661,22 @@ pub fn view(state: &mut InterfaceState, cx: f32, cy: f32, cw: f32, ch: f32, sec_
         sec.spacing(8.0);
     });
 
+    // 5. Notification Section
+    builder.add_section(&mut final_pc, "Notification", false, |sec| {
+        sec.spacing(8.0);
+        state.color_selectors[17].color = state.notification_bg_color;
+        sec.widget_full(&mut state.color_selectors[17], 40.0, ctx);
+        sec.spacing(8.0);
+        state.notification_opacity_spinbox.value = (state.notification_opacity * 100.0).round() as i32;
+        sec.widget_full(&mut state.notification_opacity_spinbox, 44.0, ctx);
+        sec.spacing(8.0);
+    });
 
     let widget_h = 26.0;
     const TEXT_DIM: [f32; 4] = [0.53, 0.53, 0.60, 1.0];
 
-    // 5. Fonts Section
-    builder.add_section(&mut final_pc, "Fonts", sec_focused.get(4).copied().unwrap_or(false), |sec| {
+    // 6. Fonts Section
+    builder.add_section(&mut final_pc, "Fonts", sec_focused.get(5).copied().unwrap_or(false), |sec| {
         sec.spacing(8.0);
 
         // System Fonts Section
@@ -1803,6 +1840,15 @@ pub fn update(state: &mut InterfaceState, msg: InterfaceMessage) {
             state.popover_bg_color = rgb;
             apply_popover_bg_color(rgb);
         }
+        InterfaceMessage::SetNotificationBgColor(rgb) => {
+            state.notification_bg_color = rgb;
+            apply_notifications_bg_color(rgb);
+        }
+        InterfaceMessage::SetNotificationOpacity(opacity) => {
+            state.notification_opacity = opacity;
+            state.notification_opacity_spinbox.value = (opacity * 100.0).round() as i32;
+            apply_notifications_opacity(opacity);
+        }
         InterfaceMessage::SetTabMarginX(margin) => {
             state.paginator_tab_margin_x = margin;
             apply_paginator_tab_margin_x(margin);
@@ -1956,7 +2002,7 @@ pub fn update(state: &mut InterfaceState, msg: InterfaceMessage) {
             send_ipc_command("reload");
             status_interface_reload();
         }
-        InterfaceMessage::PickLowColor | InterfaceMessage::PickHighColor | InterfaceMessage::PickDisabledColor | InterfaceMessage::PickSeparatorColor | InterfaceMessage::PickVisualGuides | InterfaceMessage::PickSliderTrackColor | InterfaceMessage::PickPageLowColor | InterfaceMessage::PickColorBordersColor | InterfaceMessage::PickNormalColor | InterfaceMessage::PickPaginatorSidebarColor | InterfaceMessage::PickPrimaryHighlightColor | InterfaceMessage::PickMenubarTabLabelColor | InterfaceMessage::PickToggleEnabledColor | InterfaceMessage::PickToggleDisabledColor | InterfaceMessage::PickScrollingListBgColor | InterfaceMessage::PickBreadcrumbBgColor | InterfaceMessage::PickPopoverBgColor => {}
+        InterfaceMessage::PickLowColor | InterfaceMessage::PickHighColor | InterfaceMessage::PickDisabledColor | InterfaceMessage::PickSeparatorColor | InterfaceMessage::PickVisualGuides | InterfaceMessage::PickSliderTrackColor | InterfaceMessage::PickPageLowColor | InterfaceMessage::PickColorBordersColor | InterfaceMessage::PickNormalColor | InterfaceMessage::PickPaginatorSidebarColor | InterfaceMessage::PickPrimaryHighlightColor | InterfaceMessage::PickMenubarTabLabelColor | InterfaceMessage::PickToggleEnabledColor | InterfaceMessage::PickToggleDisabledColor | InterfaceMessage::PickScrollingListBgColor | InterfaceMessage::PickBreadcrumbBgColor | InterfaceMessage::PickPopoverBgColor | InterfaceMessage::PickNotificationBgColor => {}
         InterfaceMessage::Refreshed(new) => {
             let was_mx_hovered = state.tab_margin_spinbox_x.hovered();
             let was_my_hovered = state.tab_margin_spinbox_y.hovered();
@@ -1979,6 +2025,7 @@ pub fn update(state: &mut InterfaceState, msg: InterfaceMessage) {
             let was_fsh_hovered = state.font_selector_height_spinbox.hovered();
             let was_lm_hovered = state.label_margin_spinbox.hovered();
             let was_mo_hovered = state.menubar_opacity_spinbox.hovered();
+            let was_no_hovered = state.notification_opacity_spinbox.hovered();
             // Preserve typeface fields
             let typeface_loaded = state.typeface_loaded;
             let sans_serif = state.sans_serif.clone();
@@ -2030,6 +2077,7 @@ pub fn update(state: &mut InterfaceState, msg: InterfaceMessage) {
             state.font_selector_height_spinbox.set_hovered(was_fsh_hovered);
             state.label_margin_spinbox.set_hovered(was_lm_hovered);
             state.menubar_opacity_spinbox.set_hovered(was_mo_hovered);
+            state.notification_opacity_spinbox.set_hovered(was_no_hovered);
 
             if typeface_loaded {
                 state.typeface_loaded = typeface_loaded;
@@ -2452,6 +2500,122 @@ fn write_transparency_config_value(key: &str, value: &str) {
     let _ = fs::write(CONFIG_PATH, updated);
 }
 
+fn parse_notifications_color(content: &str, key: &str, default: [u8; 3]) -> [u8; 3] {
+    let mut in_section = false;
+    for line in content.lines() {
+        let trimmed = line.trim();
+        if trimmed == "[notifications]" {
+            in_section = true;
+            continue;
+        }
+        if trimmed.starts_with('[') && in_section {
+            break;
+        }
+        if in_section && trimmed.starts_with(key) {
+            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().trim_start_matches('#');
+                return parse_hex(hex);
+            }
+        }
+    }
+    default
+}
+
+fn parse_notifications_opacity(content: &str) -> f32 {
+    let mut in_section = false;
+    for line in content.lines() {
+        let trimmed = line.trim();
+        if trimmed == "[notifications]" {
+            in_section = true;
+            continue;
+        }
+        if trimmed.starts_with('[') && in_section {
+            break;
+        }
+        if in_section && trimmed.starts_with("opacity") {
+            if let Some(val) = trimmed.split('=').nth(1) {
+                if let Ok(o) = val.trim().parse::<f32>() {
+                    return o.clamp(0.0, 1.0);
+                }
+            }
+        }
+    }
+    0.9 // default to 0.9
+}
+
+fn write_notifications_config_value(key: &str, value: &str) {
+    write_notifications_config_value_path(CONFIG_PATH, key, value);
+}
+
+fn write_notifications_config_value_path(path: &str, key: &str, value: &str) {
+    let content = fs::read_to_string(path).unwrap_or_default();
+    let new_line = format!("{} = {}", key, value);
+
+    let mut found = false;
+    let mut updated_lines = Vec::new();
+    let mut in_section = false;
+
+    for line in content.lines() {
+        let trimmed = line.trim();
+        if trimmed == "[notifications]" {
+            in_section = true;
+            updated_lines.push(line.to_string());
+            continue;
+        }
+        if trimmed.starts_with('[') && in_section {
+            in_section = false;
+        }
+        if in_section && trimmed.starts_with(key) {
+            found = true;
+            updated_lines.push(new_line.clone());
+        } else {
+            updated_lines.push(line.to_string());
+        }
+    }
+
+    let mut updated = updated_lines.join("\n");
+
+    if !found {
+        let mut result = String::new();
+        let has_section = content.lines().any(|l| l.trim() == "[notifications]");
+        if has_section {
+            let mut in_section = false;
+            let mut inserted = false;
+            for line in updated.lines() {
+                if line.trim() == "[notifications]" {
+                    in_section = true;
+                    result.push_str(line);
+                    result.push('\n');
+                    continue;
+                }
+                if line.trim().starts_with('[') && in_section {
+                    if !inserted {
+                        result.push_str(&new_line);
+                        result.push('\n');
+                        inserted = true;
+                    }
+                    in_section = false;
+                }
+                result.push_str(line);
+                result.push('\n');
+            }
+            if !inserted {
+                result.push_str(&new_line);
+                result.push('\n');
+            }
+            updated = result;
+        } else {
+            updated.push_str("\n[notifications]\n");
+            updated.push_str(&new_line);
+            updated.push_str("\n");
+        }
+    }
+    let _ = fs::write(path, updated);
+}
+
+
+
 #[cfg(test)]
 mod tests {
     use super::*;
@@ -3191,6 +3355,43 @@ mod tests {
         // Clean up
         let _ = fs::remove_file(path_str);
     }
+
+    #[test]
+    fn test_read_write_notifications_config() {
+        let dir = std::env::temp_dir();
+        let path = dir.join("test_notifications_config.toml");
+        let path_str = path.to_str().unwrap();
+
+        // 1. Initial configuration
+        let initial_content = "[notifications]\nenable = true\n\n[layout]\ngap = 18\n";
+        fs::write(path_str, initial_content).unwrap();
+
+        // 2. Parse opacity when missing (should return default 0.9)
+        let content = fs::read_to_string(path_str).unwrap();
+        let opacity = parse_notifications_opacity(&content);
+        assert_eq!(opacity, 0.9);
+
+        // 3. Write opacity config
+        write_notifications_config_value_path(path_str, "opacity", "0.85");
+        let updated = fs::read_to_string(path_str).unwrap();
+        assert!(updated.contains("opacity = 0.85"));
+
+        // 4. Parse opacity when present (should return 0.85)
+        let opacity2 = parse_notifications_opacity(&updated);
+        assert_eq!(opacity2, 0.85);
+
+        // 5. Write bg_color config
+        write_notifications_config_value_path(path_str, "bg_color", "\"#112233\"");
+        let updated2 = fs::read_to_string(path_str).unwrap();
+        assert!(updated2.contains("bg_color = \"#112233\""));
+
+        // 6. Parse bg_color when present
+        let bg_color = parse_notifications_color(&updated2, "bg_color", [0, 0, 0]);
+        assert_eq!(bg_color, [17, 34, 51]);
+
+        // Clean up
+        let _ = fs::remove_file(path_str);
+    }
 }
 
 
diff --git a/src/pages/services.rs b/src/pages/services.rs
index 6804308..f255e9b 100644
--- a/src/pages/services.rs
+++ b/src/pages/services.rs
@@ -12,7 +12,6 @@ pub struct NotificationsConfig {
     pub enable: bool,
     pub bell: bool,
     pub duration: i32,
-    pub opacity: f32,
 }
 
 // ── Status Interface Data ──
@@ -64,8 +63,6 @@ pub struct ServicesState {
     pub notifications_bell_toggle: Toggle,
     pub notifications_duration: i32,
     pub notifications_duration_spinbox: Spinbox,
-    pub notifications_opacity: f32,
-    pub notifications_opacity_slider: Slider,
 
     // Status Interface fields
     pub status_loaded: bool,
@@ -98,10 +95,6 @@ impl Default for ServicesState {
             notifications_duration_spinbox: Spinbox::new(5, 1, 60, 1)
                 .with_label("Notification Duration")
                 .with_unit("s"),
-            notifications_opacity: 0.9,
-            notifications_opacity_slider: Slider::new()
-                .with_label("Transparency")
-                .with_value(0.9),
 
             // Status Interface default initialization
             status_loaded: false,
@@ -128,7 +121,6 @@ pub enum ServicesMessage {
     ToggleNotificationsEnable,
     ToggleNotificationsBell,
     SetNotificationsDuration(i32),
-    SetNotificationsOpacity(f32),
     SendTestNotification,
     NotificationsRefreshed(NotificationsConfig),
 
@@ -435,11 +427,6 @@ pub fn view(state: &mut ServicesState, cx: f32, cy: f32, cw: f32, ch: f32, sec_f
         sec2.widget(&mut state.notifications_duration_spinbox, 14.0, sec_w - 28.0, 44.0, ctx);
         sec2.spacing(16.0);
 
-        // Opacity Slider (Transparency, moved here)
-        state.notifications_opacity_slider.set_value(state.notifications_opacity);
-        sec2.widget(&mut state.notifications_opacity_slider, 14.0, sec_w - 28.0, 38.0, ctx);
-        sec2.spacing(16.0);
-
         let btn_h = 32.0;
         let btn_y = sec2.ay();
         let white_color = [1.0, 1.0, 1.0, 1.0];
@@ -562,11 +549,6 @@ pub fn update(state: &mut ServicesState, msg: ServicesMessage) {
             state.notifications_duration = d;
             write_config_value("duration", &state.notifications_duration.to_string());
         }
-        ServicesMessage::SetNotificationsOpacity(o) => {
-            state.notifications_opacity = o;
-            write_transparency_config_value("opacity", &format!("{:.2}", o));
-            send_ipc_command("reload");
-        }
         ServicesMessage::SendTestNotification => {
             send_ipc_command("notify \"cce-client\" \"System notifications are working correctly!\"");
         }
@@ -575,7 +557,6 @@ pub fn update(state: &mut ServicesState, msg: ServicesMessage) {
             state.notifications_enable = new.enable;
             state.notifications_bell = new.bell;
             state.notifications_duration = new.duration;
-            state.notifications_opacity = new.opacity;
         }
         ServicesMessage::StatusRefreshed(new) => {
             let was_status_hovered = state.status_label.hovered();
@@ -631,12 +612,10 @@ pub fn read_notifications_config() -> NotificationsConfig {
     let enable = parse_notifications_enable(&content);
     let bell = parse_notifications_bell(&content);
     let duration = parse_notifications_duration(&content);
-    let opacity = parse_transparency_opacity(&content);
     NotificationsConfig {
         enable,
         bell,
         duration,
-        opacity,
     }
 }
 
@@ -702,28 +681,6 @@ fn parse_notifications_duration(content: &str) -> i32 {
     5 // default to 5 seconds
 }
 
-fn parse_transparency_opacity(content: &str) -> f32 {
-    let mut in_section = false;
-    for line in content.lines() {
-        let trimmed = line.trim();
-        if trimmed == "[transparency]" {
-            in_section = true;
-            continue;
-        }
-        if trimmed.starts_with('[') && in_section {
-            break;
-        }
-        if in_section && trimmed.starts_with("opacity") {
-            if let Some(val) = trimmed.split('=').nth(1) {
-                if let Ok(o) = val.trim().parse::<f32>() {
-                    return o.clamp(0.0, 1.0);
-                }
-            }
-        }
-    }
-    0.9 // default to 0.9
-}
-
 fn send_ipc_command(cmd: &str) {
     if let Ok(mut stream) = std::os::unix::net::UnixStream::connect(get_socket_path()) {
         let _ = stream.write_all(format!("{}\n", cmd).as_bytes());
@@ -801,71 +758,6 @@ fn write_enable_notifications(enabled: bool) {
     send_ipc_command("reload");
 }
 
-fn write_transparency_config_value(key: &str, value: &str) {
-    let content = fs::read_to_string(CONFIG_PATH).unwrap_or_default();
-    let new_line = format!("{} = {}", key, value);
-
-    let mut found = false;
-    let mut updated_lines = Vec::new();
-    let mut in_section = false;
-
-    for line in content.lines() {
-        let trimmed = line.trim();
-        if trimmed == "[transparency]" {
-            in_section = true;
-            updated_lines.push(line.to_string());
-            continue;
-        }
-        if trimmed.starts_with('[') && in_section {
-            in_section = false;
-        }
-        if in_section && trimmed.starts_with(key) {
-            found = true;
-            updated_lines.push(new_line.clone());
-        } else {
-            updated_lines.push(line.to_string());
-        }
-    }
-
-    let mut updated = updated_lines.join("\n");
-
-    if !found {
-        let mut result = String::new();
-        let has_section = content.lines().any(|l| l.trim() == "[transparency]");
-        if has_section {
-            let mut in_section = false;
-            let mut inserted = false;
-            for line in updated.lines() {
-                if line.trim() == "[transparency]" {
-                    in_section = true;
-                    result.push_str(line);
-                    result.push('\n');
-                    continue;
-                }
-                if line.trim().starts_with('[') && in_section {
-                    if !inserted {
-                        result.push_str(&new_line);
-                        result.push('\n');
-                        inserted = true;
-                    }
-                    in_section = false;
-                }
-                result.push_str(line);
-                result.push('\n');
-            }
-            if !inserted {
-                result.push_str(&new_line);
-                result.push('\n');
-            }
-            updated = result;
-        } else {
-            updated.push_str("\n[transparency]\n");
-            updated.push_str(&new_line);
-            updated.push_str("\n");
-        }
-    }
-    let _ = fs::write(CONFIG_PATH, updated);
-}
 
 thread_local! {
     static TEST_CONFIG_PATH: std::cell::RefCell<Option<String>> = std::cell::RefCell::new(None);