git.lucas.co / cce-designer
graphic design tool
git clone https://git.lucas.co/cce-designer.git

commitfbe41d87fe134a2c7808b3c435e8125fbaf9a6b3
parent1d75d50b70
authorLucas Galante <[email protected]>
date2026-06-05 20:13
feat: integrate glassmorphism backdrop blur, panel resizing, Action::Save, and various clear-ui widgets

 Makefile        |   14 +
 project.json    |  116 +++
 src/main.rs     | 2453 +++++++++++++++++++++++++++++++++++++++++++------------
 src/shader.wgsl |   30 +-
 4 files changed, 2075 insertions(+), 538 deletions(-)

diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..803f9e3
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,14 @@
+.PHONY: build install run clean
+
+build:
+	cargo build --release
+
+install: build
+	mkdir -p ~/.local/bin
+	install -m 755 target/release/clear-design-interface ~/.local/bin/clear-design-interface
+
+run:
+	cargo run
+
+clean:
+	cargo clean
diff --git a/project.json b/project.json
new file mode 100644
index 0000000..3e53fbe
--- /dev/null
+++ b/project.json
@@ -0,0 +1,116 @@
+{
+  "name": "Default Project",
+  "root": {
+    "name": "root",
+    "type": "node",
+    "children": [
+      {
+        "name": "Camera 1",
+        "type": "camera",
+        "children": [],
+        "params": [
+          {
+            "name": "Position",
+            "label": "",
+            "type": "float3",
+            "default": "2.50:1.80:2.50",
+            "options": [],
+            "min": -10.0,
+            "max": 10.0,
+            "step": null
+          },
+          {
+            "name": "Rotation",
+            "label": "",
+            "type": "float3",
+            "default": "24.81:-80.35:0.00",
+            "options": [],
+            "min": -180.0,
+            "max": 180.0,
+            "step": null
+          },
+          {
+            "name": "Pivot",
+            "label": "",
+            "type": "float3",
+            "default": "0.00:0.00:0.00",
+            "options": [],
+            "min": -10.0,
+            "max": 10.0,
+            "step": null
+          }
+        ],
+        "position": [
+          1.0,
+          1.0
+        ]
+      },
+      {
+        "name": "Sphere 1",
+        "type": "sphere",
+        "children": [],
+        "params": [
+          {
+            "name": "Radius",
+            "label": "",
+            "type": "slider",
+            "default": "0.5",
+            "options": [],
+            "min": null,
+            "max": null,
+            "step": null
+          }
+        ],
+        "position": [
+          4.0,
+          2.0
+        ]
+      },
+      {
+        "name": "Transform 1",
+        "type": "transform",
+        "children": [],
+        "params": [
+          {
+            "name": "Input",
+            "label": "",
+            "type": "text",
+            "default": "Sphere 1",
+            "options": [],
+            "min": null,
+            "max": null,
+            "step": null
+          },
+          {
+            "name": "Translation",
+            "label": "",
+            "type": "float3",
+            "default": "1.00:0.50:0.00",
+            "options": [],
+            "min": -10.0,
+            "max": 10.0,
+            "step": null
+          }
+        ],
+        "position": [
+          4.0,
+          3.0
+        ]
+      }
+    ],
+    "params": [],
+    "position": [
+      0.0,
+      0.0
+    ]
+  },
+  "view_state": {
+    "active_camera": "Camera 1",
+    "pan": [
+      0.0,
+      122.9747
+    ],
+    "current_path": [],
+    "selected_node": null
+  }
+}
\ No newline at end of file
diff --git a/src/main.rs b/src/main.rs
index b2460d1..f22fb58 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -79,7 +79,7 @@ enum WindowEvent {
 
 use wgpu::util::DeviceExt;
 
-use clear_ui::widget::{Breadcrumb, Canvas, MenuBar, ParametersBg, Splitter, Spreadsheet, StatusBar, TextLabel, ViewportBg, Widget, GraphNode, Graph};
+use clear_ui::widget::{Breadcrumb, Canvas, MenuBar, Plate, ParametersBg, Splitter, Spreadsheet, StatusBar, TextLabel, ViewportBg, Widget, GraphNode, Graph, Paginator, Button, Checkbox, Slider, Spinbox, ScrollingList, Label, ColorSelector};
 use clear_ui::colors;
 
 use glyphon::{
@@ -94,20 +94,23 @@ const CONTENT_IDX: usize = 1;
 const SPLITTER1_IDX: usize = 2;
 const VIEWPORT_IDX: usize = 3;
 const SPLITTER2_IDX: usize = 4;
-const PARAM_IDX: usize = 5;
-const CANVAS_IDX: usize = 6;
-const LEFT_MENUBAR_IDX: usize = 7;
-const RIGHT_MENUBAR_IDX: usize = 8;
-const PARAM_MENUBAR_IDX: usize = 9;
-const STATUS_IDX: usize = 10;
-const BREADCRUMB_IDX: usize = 11;
-const NODE_PALETTE_IDX: usize = 12;
-const SPREADSHEET_IDX: usize = 13;
-const SPREADSHEET_MENUBAR_IDX: usize = 14;
+const PARAM_PLATE_IDX: usize = 5;
+const PARAM_IDX: usize = 6;
+const CANVAS_IDX: usize = 7;
+const LEFT_MENUBAR_IDX: usize = 8;
+const RIGHT_MENUBAR_IDX: usize = 9;
+const PARAM_MENUBAR_IDX: usize = 10;
+const STATUS_IDX: usize = 11;
+const BREADCRUMB_IDX: usize = 12;
+const NODE_PALETTE_IDX: usize = 13;
+const SPREADSHEET_IDX: usize = 14;
+const SPREADSHEET_MENUBAR_IDX: usize = 15;
+const NETWORK_PANEL_IDX: usize = 16;
+const PAGINATOR_IDX: usize = 17;
 
 
 const HEADER_H: f32 = 26.0;
-const STATUS_H: f32 = 28.0;
+const STATUS_H: f32 = 0.0;
 const MENUBAR_H: f32 = 26.0;
 const SPLITTER_W: f32 = 6.0;
 
@@ -495,6 +498,7 @@ enum Action {
     ToggleCameraPivot,
     ToggleCircularPane,
     DetachCircularWindow,
+    Save,
 }
 
 struct KeyBind {
@@ -524,12 +528,14 @@ fn default_keybinds() -> Vec<KeyBind> {
         KeyBind { ctrl: true, shift: false, alt: false, super_: false, key: Key::Character(",".into()), action: Action::ToggleConfigure },
         KeyBind { ctrl: false, shift: false, alt: false, super_: false, key: Key::Character("`".into()), action: Action::ToggleSpreadsheet },
         KeyBind { ctrl: true, shift: false, alt: false, super_: false, key: Key::Character("d".into()), action: Action::ToggleCircularPane },
+        KeyBind { ctrl: true, shift: false, alt: false, super_: false, key: Key::Character("s".into()), action: Action::Save },
     ]
 }
 
 use clear_ui::engine::{
-    Vertex, quad_vertices_with_clip as quad_vertices, widget_vertices,
+    Vertex, widget_vertices,
     quad_vertices_clipped, circle_vertices, circle_border_vertices, arc_background_vertices,
+    extra_quad_vertices, extra_quad_vertices_clipped,
 };
 
 #[repr(C)]
@@ -567,6 +573,24 @@ fn make_text_buffer(font_system: &mut FontSystem, text: &str, size: f32) -> Buff
     buffer
 }
 
+fn make_text_buffer_with_font(font_system: &mut FontSystem, text: &str, size: f32, font: Option<&str>) -> Buffer {
+    let metrics = Metrics::new(size, size * 1.4);
+    let mut buffer = Buffer::new(font_system, metrics);
+    let mut attrs = Attrs::new();
+    if let Some(font_name) = font {
+        let family = match font_name {
+            "monospace" => glyphon::Family::Monospace,
+            "sans-serif" => glyphon::Family::SansSerif,
+            "serif" => glyphon::Family::Serif,
+            _ => glyphon::Family::Name(font_name),
+        };
+        attrs = attrs.family(family);
+    }
+    buffer.set_text(font_system, text, attrs, glyphon::Shaping::Advanced);
+    buffer.shape_until_scroll(font_system, true);
+    buffer
+}
+
 fn get_next_visible_pane(
     current_pane: usize,
     show_network: bool,
@@ -601,6 +625,14 @@ fn get_next_visible_pane(
     visible_panes[next_pos]
 }
 
+#[derive(Clone, Copy, Debug, PartialEq)]
+struct ResizeDirection {
+    left: bool,
+    right: bool,
+    top: bool,
+    bottom: bool,
+}
+
 struct State {
     surface: wgpu::Surface<'static>,
     device: wgpu::Device,
@@ -628,6 +660,11 @@ struct State {
     vertex_count_grid: u32,
     depth_texture: wgpu::Texture,
     depth_texture_view: wgpu::TextureView,
+    backdrop_texture: wgpu::Texture,
+    backdrop_texture_view: wgpu::TextureView,
+    backdrop_sampler: wgpu::Sampler,
+    backdrop_bind_group_layout: wgpu::BindGroupLayout,
+    backdrop_bind_group: wgpu::BindGroup,
     rotation_y: f32,
     rotation_x: f32,
     is_rotating_viewport: bool,
@@ -747,9 +784,6 @@ struct State {
     last_config_read: Instant,
     circular_network_pane: bool,
     circular_network_layout: clear_ui::layout::CircularPaneLayout,
-    is_dragging_network_circle: bool,
-    circle_drag_ox: f32,
-    circle_drag_oy: f32,
     is_detached_network: bool,
     detached_circular_network: bool,
     last_project_mod_time: Option<std::time::SystemTime>,
@@ -762,10 +796,157 @@ struct State {
     uniform_background: bool,
     network_opacity: f32,
     last_design_mod_time: Option<std::time::SystemTime>,
+    floating_network_layout: (f32, f32, f32, f32),
+    is_resizing_network: Option<ResizeDirection>,
+    drag_start_rect: (f32, f32, f32, f32),
+    drag_start_mouse: (f32, f32),
+    floating_param_width: f32,
+    is_resizing_param: bool,
+    drag_start_param_w: f32,
+    floating_spreadsheet_height: f32,
+    is_resizing_spreadsheet: bool,
+    drag_start_spreadsheet_h: f32,
+    paginator_page_widgets: Vec<Vec<Box<dyn Widget>>>,
+    last_paginator_menubar: Option<usize>,
+    loaded_project_path: Option<std::path::PathBuf>,
+    last_saved_root_json: String,
+    recent_files: Vec<std::path::PathBuf>,
+    recent_files_list: ScrollingList,
+    recent_files_buttons: Vec<Button>,
 }
 
 impl State {
-    fn save_settings(&self) {
+    fn has_unsaved_changes(&self) -> bool {
+        if let Ok(current_json) = serde_json::to_string(&self.fs_root) {
+            current_json != self.last_saved_root_json
+        } else {
+            false
+        }
+    }
+
+    fn update_window_title(&self) {
+        let base_title = if self.is_detached_network {
+            "Network Pane"
+        } else {
+            "Clear Design Interface"
+        };
+        
+        let mut title = base_title.to_string();
+        
+        if let Some(path) = &self.loaded_project_path {
+            if let Some(filename) = path.file_name().and_then(|n| n.to_str()) {
+                title.push_str(" - ");
+                title.push_str(filename);
+            }
+        }
+        
+        if self.has_unsaved_changes() {
+            title.push_str("*");
+        }
+        
+        self.window.set_title(&title);
+    }
+
+    fn get_recent_files_path() -> Option<std::path::PathBuf> {
+        std::env::var("HOME").ok().map(|h| {
+            let mut path = std::path::PathBuf::from(h);
+            path.push(".config");
+            path.push("clear-design-interface");
+            path.push("recent_files.json");
+            path
+        })
+    }
+
+    fn load_recent_files() -> Vec<std::path::PathBuf> {
+        if let Some(path) = Self::get_recent_files_path() {
+            if let Ok(file) = std::fs::File::open(path) {
+                if let Ok(list) = serde_json::from_reader::<_, Vec<String>>(file) {
+                    return list.into_iter().map(std::path::PathBuf::from).collect();
+                }
+            }
+        }
+        Vec::new()
+    }
+
+    fn save_recent_files(files: &[std::path::PathBuf]) {
+        if let Some(path) = Self::get_recent_files_path() {
+            if let Some(parent) = path.parent() {
+                let _ = std::fs::create_dir_all(parent);
+            }
+            if let Ok(file) = std::fs::File::create(path) {
+                let list: Vec<String> = files.iter().map(|p| p.to_string_lossy().to_string()).collect();
+                let _ = serde_json::to_writer_pretty(file, &list);
+            }
+        }
+    }
+
+    fn add_recent_file(&mut self, path: std::path::PathBuf) {
+        let abs_path = std::fs::canonicalize(&path).unwrap_or(path);
+        self.recent_files.retain(|p| p != &abs_path);
+        self.recent_files.insert(0, abs_path);
+        self.recent_files.truncate(10);
+        Self::save_recent_files(&self.recent_files);
+        self.rebuild_recent_buttons();
+        self.update_paginator();
+    }
+
+    fn rebuild_recent_buttons(&mut self) {
+        self.recent_files_buttons.clear();
+        for file in &self.recent_files {
+            let label = file.file_name()
+                .map(|n| n.to_string_lossy().to_string())
+                .unwrap_or_else(|| file.to_string_lossy().to_string());
+            let btn = Button::new_list_row(0.0, 0.0, 0.0, 0.0).with_label(&label);
+            self.recent_files_buttons.push(btn);
+        }
+    }
+
+    fn update_recent_files_layout(&mut self) {
+        let active_menubar = self.focused_pane;
+        let menu_names = self.widgets[active_menubar].menu_names();
+        let file_page_idx = match menu_names.iter().position(|name| name == "File") {
+            Some(idx) => idx,
+            None => return,
+        };
+
+        if file_page_idx >= self.paginator_page_widgets.len() {
+            return;
+        }
+
+        let page = &mut self.paginator_page_widgets[file_page_idx];
+        let menu_items = self.widgets[active_menubar].menu_items_list();
+        if file_page_idx >= menu_items.len() {
+            return;
+        }
+        let num_base_items = menu_items[file_page_idx].len();
+        
+        let list_idx = num_base_items + 1;
+        if list_idx >= page.len() {
+            return;
+        }
+
+        let (list_x, list_y, list_w, list_h) = page[list_idx].rect();
+        if list_h <= 0.0 {
+            return;
+        }
+
+        let count = self.recent_files.len();
+        page[list_idx].update_bounds(count, list_y, list_h);
+
+        let btn_h = 22.0;
+        let inner_x = list_x + 4.0;
+        let inner_w = list_w - 16.0;
+
+        for (idx, btn) in self.recent_files_buttons.iter_mut().enumerate() {
+            if let Some(draw_y) = page[list_idx].get_item_draw_y(idx, 0.0) {
+                btn.set_rect(inner_x, draw_y, inner_w, btn_h);
+            } else {
+                btn.set_rect(-9999.0, -9999.0, 0.0, 0.0);
+            }
+        }
+    }
+
+    fn save_settings(&mut self) {
         let settings = DesignSettings {
             grid_snap_enabled: self.grid_snap_enabled,
             network_grid_enabled: self.network_grid_visible,
@@ -790,6 +971,243 @@ impl State {
             network_opacity: self.network_opacity,
         };
         settings.save();
+        self.last_design_mod_time = {
+            let design_path = DesignSettings::file_path();
+            std::fs::metadata(&design_path).and_then(|m| m.modified()).ok()
+        };
+    }
+
+    fn sync_settings_from_paginator(&mut self) {
+        let active_menubar = self.focused_pane;
+        let page_idx = if active_menubar == LEFT_MENUBAR_IDX {
+            3
+        } else if active_menubar == RIGHT_MENUBAR_IDX {
+            4
+        } else {
+            return;
+        };
+
+        if page_idx >= self.paginator_page_widgets.len() {
+            return;
+        }
+
+        let mut left_values = None;
+        let mut right_values = None;
+
+        {
+            let widgets = &self.paginator_page_widgets[page_idx];
+            if widgets.is_empty() {
+                return;
+            }
+
+            if active_menubar == LEFT_MENUBAR_IDX {
+                if widgets.len() >= 11 {
+                    let snap = widgets[0].value();
+                    let vis = widgets[1].value();
+                    let gx = widgets[2].value();
+                    let gy = widgets[3].value();
+                    let srh = widgets[4].value();
+                    let scw = widgets[5].value();
+                    let uni = widgets[6].value();
+                    let op = widgets[7].value();
+                    let node_col = widgets[8].color_u8();
+                    let cell_col = widgets[9].color_u8();
+                    let gap_col = widgets[10].color_u8();
+                    left_values = Some((snap, vis, gx, gy, srh, scw, uni, op, node_col, cell_col, gap_col));
+                }
+            } else if active_menubar == RIGHT_MENUBAR_IDX {
+                if widgets.len() >= 13 {
+                    let sg = widgets[0].value();
+                    let sc = widgets[1].value();
+                    let so = widgets[2].value();
+                    let cp = widgets[3].value();
+                    let gt = widgets[4].value();
+                    let os = widgets[5].value();
+                    let cps = widgets[6].value();
+                    let bgr = widgets[7].value();
+                    let bgg = widgets[8].value();
+                    let bgb = widgets[9].value();
+                    let gcr = widgets[10].value();
+                    let gcg = widgets[11].value();
+                    let gcb = widgets[12].value();
+                    right_values = Some((sg, sc, so, cp, gt, os, cps, bgr, bgg, bgb, gcr, gcg, gcb));
+                }
+            }
+        }
+
+        let mut changed = false;
+
+        if let Some((snap, vis, gx, gy, srh, scw, uni, op, node_col, cell_col, gap_col)) = left_values {
+            // LEFT pane Settings
+            // 0: Snap to Grid (Checkbox)
+            let snap = snap == 1;
+            if self.grid_snap_enabled != snap {
+                self.grid_snap_enabled = snap;
+                changed = true;
+            }
+            // 1: Grid Visible (Checkbox)
+            let vis = vis == 1;
+            if self.network_grid_visible != vis {
+                self.network_grid_visible = vis;
+                changed = true;
+            }
+            // 2: Grid X (Spinbox)
+            let gx = gx as f32;
+            if self.grid_size_x != gx {
+                self.grid_size_x = gx;
+                changed = true;
+            }
+            // 3: Grid Y (Spinbox)
+            let gy = gy as f32;
+            if self.grid_size_y != gy {
+                self.grid_size_y = gy;
+                changed = true;
+            }
+            // 4: Skipped Row H (Spinbox)
+            let srh = srh as f32;
+            if self.skipped_row_h != srh {
+                self.skipped_row_h = srh;
+                changed = true;
+            }
+            // 5: Skipped Col W (Spinbox)
+            let scw = scw as f32;
+            if self.skipped_col_w != scw {
+                self.skipped_col_w = scw;
+                changed = true;
+            }
+            // 6: Uniform Background (Checkbox)
+            let uni = uni == 1;
+            if self.uniform_background != uni {
+                self.uniform_background = uni;
+                changed = true;
+            }
+            // 7: Opacity (Slider, 0..100)
+            let op = op as f32 / 100.0;
+            if (self.network_opacity - op).abs() > 0.001 {
+                self.network_opacity = op;
+                changed = true;
+            }
+            // 8: Node Color (ColorSelector)
+            if let Some(col) = node_col {
+                let nr = col[0] as f32 / 255.0;
+                let ng = col[1] as f32 / 255.0;
+                let nb = col[2] as f32 / 255.0;
+                if (self.node_color[0] - nr).abs() > 0.001
+                    || (self.node_color[1] - ng).abs() > 0.001
+                    || (self.node_color[2] - nb).abs() > 0.001
+                {
+                    self.node_color = [nr, ng, nb];
+                    colors::set_node_color([nr, ng, nb, 1.0]);
+                    changed = true;
+                }
+            }
+            // 9: Cell Color (ColorSelector)
+            if let Some(col) = cell_col {
+                let cr = col[0] as f32 / 255.0;
+                let cg = col[1] as f32 / 255.0;
+                let cb = col[2] as f32 / 255.0;
+                if (self.cell_color[0] - cr).abs() > 0.001
+                    || (self.cell_color[1] - cg).abs() > 0.001
+                    || (self.cell_color[2] - cb).abs() > 0.001
+                {
+                    self.cell_color = [cr, cg, cb];
+                    changed = true;
+                }
+            }
+            // 10: Gap Color (ColorSelector)
+            if let Some(col) = gap_col {
+                let gr = col[0] as f32 / 255.0;
+                let gg = col[1] as f32 / 255.0;
+                let gb = col[2] as f32 / 255.0;
+                if (self.gap_color[0] - gr).abs() > 0.001
+                    || (self.gap_color[1] - gg).abs() > 0.001
+                    || (self.gap_color[2] - gb).abs() > 0.001
+                {
+                    self.gap_color = [gr, gg, gb];
+                    changed = true;
+                }
+            }
+        } else if let Some((sg, sc, so, cp, gt, os, cps, bgr, bgg, bgb, gcr, gcg, gcb)) = right_values {
+            // RIGHT pane Settings
+            // 0: Show Grid Guide (Checkbox)
+            let sg = sg == 1;
+            if self.show_grid != sg {
+                self.show_grid = sg;
+                self.widgets[RIGHT_MENUBAR_IDX].set_item_checked(2, 0, sg);
+                changed = true;
+            }
+            // 1: Show Reference Cube (Checkbox)
+            let sc = sc == 1;
+            if self.show_cube != sc {
+                self.show_cube = sc;
+                self.widgets[RIGHT_MENUBAR_IDX].set_item_checked(2, 1, sc);
+                changed = true;
+            }
+            // 2: Show Origin Axes (Checkbox)
+            let so = so == 1;
+            if self.show_origin != so {
+                self.show_origin = so;
+                self.widgets[RIGHT_MENUBAR_IDX].set_item_checked(2, 2, so);
+                changed = true;
+            }
+            // 3: Show Camera Pivot (Checkbox)
+            let cp = cp == 1;
+            if self.show_camera_pivot != cp {
+                self.show_camera_pivot = cp;
+                self.widgets[RIGHT_MENUBAR_IDX].set_item_checked(2, 3, cp);
+                changed = true;
+            }
+            // 4: Grid Thickness (Spinbox, decimals 3)
+            let gt = gt as f32 / 1000.0;
+            if (self.grid_thickness - gt).abs() > 0.0001 {
+                self.grid_thickness = gt;
+                self.update_grid_geometry();
+                changed = true;
+            }
+            // 5: Origin Guide Size (Spinbox, decimals 1)
+            let os = os as f32 / 10.0;
+            if (self.origin_size - os).abs() > 0.001 {
+                self.origin_size = os;
+                self.update_origin_geometry();
+                changed = true;
+            }
+            // 6: Camera Pivot Size (Spinbox, decimals 1)
+            let cps = cps as f32 / 10.0;
+            if (self.camera_pivot_size - cps).abs() > 0.001 {
+                self.camera_pivot_size = cps;
+                self.update_pivot_geometry();
+                changed = true;
+            }
+            // 7, 8, 9: BG Color (R, G, B Spinboxes, 0..255)
+            let bgr = bgr as f32 / 255.0;
+            let bgg = bgg as f32 / 255.0;
+            let bgb = bgb as f32 / 255.0;
+            if (self.viewport_bg_color[0] - bgr).abs() > 0.001
+                || (self.viewport_bg_color[1] - bgg).abs() > 0.001
+                || (self.viewport_bg_color[2] - bgb).abs() > 0.001
+            {
+                self.viewport_bg_color = [bgr, bgg, bgb];
+                changed = true;
+            }
+            // 10, 11, 12: Grid Color (R, G, B Spinboxes, 0..255)
+            let gcr = gcr as f32 / 255.0;
+            let gcg = gcg as f32 / 255.0;
+            let gcb = gcb as f32 / 255.0;
+            if (self.grid_color[0] - gcr).abs() > 0.001
+                || (self.grid_color[1] - gcg).abs() > 0.001
+                || (self.grid_color[2] - gcb).abs() > 0.001
+            {
+                self.grid_color = [gcr, gcg, gcb];
+                self.update_grid_geometry();
+                changed = true;
+            }
+        }
+
+        if changed {
+            self.sync_grid_settings();
+            self.save_settings();
+            self.upload_vertices();
+        }
     }
 
     fn update_grid_geometry(&mut self) {
@@ -869,14 +1287,14 @@ impl State {
     fn param_w(&self) -> f32 { self.get_col_geometries().5 }
     
     fn in_network_pane(&self) -> bool {
-        let node_area_y = self.positions[CONTENT_IDX].1;
         if self.circular_network_pane {
             self.circular_network_layout.hit_test_content(self.cursor_x, self.cursor_y, MENUBAR_H, BREADCRUMB_H)
         } else {
-            self.cursor_x >= 0.0
-                && self.cursor_x < self.content_left_w()
-                && self.cursor_y >= node_area_y
-                && self.cursor_y < self.height - STATUS_H
+            let (cx, cy, cw, ch) = self.positions[CONTENT_IDX];
+            self.cursor_x >= cx
+                && self.cursor_x < cx + cw
+                && self.cursor_y >= cy
+                && self.cursor_y < cy + ch
         }
     }
 
@@ -960,6 +1378,132 @@ impl State {
         );
     }
 
+    fn open_file_chooser(&self) {
+        std::thread::spawn(move || {
+            use std::process::{Command, Stdio};
+            use std::io::Write;
+
+            // Find executable path
+            let exe_path = if let Ok(cur_exe) = std::env::current_exe() {
+                let sibling = cur_exe.with_file_name("clear-filesystem-interface");
+                if sibling.exists() {
+                    sibling
+                } else {
+                    std::path::PathBuf::from("clear-filesystem-interface")
+                }
+            } else {
+                std::path::PathBuf::from("clear-filesystem-interface")
+            };
+
+            let child = match Command::new(exe_path)
+                .arg("--select")
+                .stdout(Stdio::piped())
+                .spawn()
+            {
+                Ok(c) => c,
+                Err(e) => {
+                    eprintln!("Failed to spawn clear-filesystem-interface: {:?}", e);
+                    return;
+                }
+            };
+
+            let output = match child.wait_with_output() {
+                Ok(o) => o,
+                Err(e) => {
+                    eprintln!("Failed to wait for clear-filesystem-interface: {:?}", e);
+                    return;
+                }
+            };
+
+            if output.status.success() {
+                let selected = String::from_utf8_lossy(&output.stdout).trim().to_string();
+                if !selected.is_empty() {
+                    let body = format!(
+                        "{{\"action\":\"load\",\"path\":\"{}\"}}",
+                        selected.replace('\\', "\\\\").replace('"', "\\\"")
+                    );
+                    let req = format!(
+                        "POST /action HTTP/1.1\r\n\
+                         Host: 127.0.0.1:3000\r\n\
+                         Content-Type: application/json\r\n\
+                         Content-Length: {}\r\n\
+                         Connection: close\r\n\r\n\
+                         {}",
+                        body.len(),
+                        body
+                    );
+                    if let Ok(mut stream) = std::net::TcpStream::connect("127.0.0.1:3000") {
+                        let _ = stream.write_all(req.as_bytes());
+                        let _ = stream.flush();
+                    }
+                }
+            }
+        });
+    }
+
+    fn save_file_chooser(&self) {
+        std::thread::spawn(move || {
+            use std::process::{Command, Stdio};
+            use std::io::Write;
+
+            // Find executable path
+            let exe_path = if let Ok(cur_exe) = std::env::current_exe() {
+                let sibling = cur_exe.with_file_name("clear-filesystem-interface");
+                if sibling.exists() {
+                    sibling
+                } else {
+                    std::path::PathBuf::from("clear-filesystem-interface")
+                }
+            } else {
+                std::path::PathBuf::from("clear-filesystem-interface")
+            };
+
+            let child = match Command::new(exe_path)
+                .arg("--save")
+                .stdout(Stdio::piped())
+                .spawn()
+            {
+                Ok(c) => c,
+                Err(e) => {
+                    eprintln!("Failed to spawn clear-filesystem-interface: {:?}", e);
+                    return;
+                }
+            };
+
+            let output = match child.wait_with_output() {
+                Ok(o) => o,
+                Err(e) => {
+                    eprintln!("Failed to wait for clear-filesystem-interface: {:?}", e);
+                    return;
+                }
+            };
+
+            if output.status.success() {
+                let selected = String::from_utf8_lossy(&output.stdout).trim().to_string();
+                if !selected.is_empty() {
+                    let body = format!(
+                        "{{\"action\":\"save\",\"path\":\"{}\"}}",
+                        selected.replace('\\', "\\\\").replace('"', "\\\"")
+                    );
+                    let req = format!(
+                        "POST /action HTTP/1.1\r\n\
+                         Host: 127.0.0.1:3000\r\n\
+                         Content-Type: application/json\r\n\
+                         Content-Length: {}\r\n\
+                         Connection: close\r\n\r\n\
+                         {}",
+                        body.len(),
+                        body
+                    );
+                    if let Ok(mut stream) = std::net::TcpStream::connect("127.0.0.1:3000") {
+                        let _ = stream.write_all(req.as_bytes());
+                        let _ = stream.flush();
+                    }
+                }
+            }
+        });
+    }
+
     fn open_node_palette(&mut self) {
         let templates: Vec<String> = self.node_templates.iter().map(|t| t.label.clone()).collect();
         let grid_col = self.grid_cursor_col;
@@ -1562,9 +2106,36 @@ fn geometry_to_spreadsheet_data(geom: &Geometry) -> (Vec<String>, Vec<Vec<String
         }
     }
 
-    fn save_to_file(&self, path: &Path) -> Result<(), Box<dyn std::error::Error>> {
+    fn save_to_file(&mut self, path: &Path) -> Result<(), Box<dyn std::error::Error>> {
+        if path.file_name().map_or(false, |n| n == "default_project.json") {
+            let proj = Project {
+                name: "Default Project".to_string(),
+                root: self.fs_root.clone(),
+                view_state: ProjectViewState {
+                    active_camera: self.active_camera.clone(),
+                    pan: (self.pan_x, self.pan_y),
+                    current_path: self.current_path.clone(),
+                    selected_node: self.widgets[CONTENT_IDX].selected_node(),
+                },
+            };
+            let content = serde_json::to_string_pretty(&proj)?;
+            fs::write(path, content)?;
+            self.last_saved_root_json = serde_json::to_string(&self.fs_root).unwrap_or_default();
+            return Ok(());
+        }
+
+        let project_dir = path;
+        let project_name = project_dir.file_name()
+            .and_then(|n| n.to_str())
+            .unwrap_or("Default Project")
+            .to_string();
+
+        fs::create_dir_all(project_dir)?;
+
+        let state_file_path = project_dir.join("state.json");
+
         let proj = Project {
-            name: "Default Project".to_string(),
+            name: project_name,
             root: self.fs_root.clone(),
             view_state: ProjectViewState {
                 active_camera: self.active_camera.clone(),
@@ -1573,13 +2144,76 @@ fn geometry_to_spreadsheet_data(geom: &Geometry) -> (Vec<String>, Vec<Vec<String
                 selected_node: self.widgets[CONTENT_IDX].selected_node(),
             },
         };
-        let content = serde_json::to_string_pretty(&proj)?;
-        fs::write(path, content)?;
-        Ok(())
-    }
+        let content = serde_json::to_string_pretty(&proj)?;
+        fs::write(&state_file_path, content)?;
+        self.last_saved_root_json = serde_json::to_string(&self.fs_root).unwrap_or_default();
+        Ok(())
+    }
+
+    fn load_from_file(&mut self, path: &Path) -> Result<(), Box<dyn std::error::Error>> {
+        if path.file_name().map_or(false, |n| n == "default_project.json") {
+            let content = fs::read_to_string(path)?;
+            let proj: Project = serde_json::from_str(&content)?;
+            self.fs_root = proj.root;
+            self.active_camera = proj.view_state.active_camera;
+            self.pan_x = proj.view_state.pan.0;
+            self.pan_y = proj.view_state.pan.1;
+            self.pan_velocity_x = 0.0;
+            self.pan_velocity_y = 0.0;
+            self.last_frame_pan_x = self.pan_x;
+            self.last_frame_pan_y = self.pan_y;
+            self.is_scrolling_trackpad = false;
+            self.scroll_accum_x = 0.0;
+            self.scroll_accum_y = 0.0;
+            self.current_path = proj.view_state.current_path;
+
+            let sel = proj.view_state.selected_node;
+            self.widgets[CONTENT_IDX].set_selected_node(sel);
+            if sel.is_some() {
+                self.focused_widget = Some(CONTENT_IDX);
+            } else {
+                self.focused_widget = None;
+            }
+            self.drag_widget = None;
+            self.last_click = None;
+
+            self.sync_grid_settings();
+            self.sync_nodes();
+
+            let params = if !self.is_detached_network {
+                self.widgets[CONTENT_IDX].selected_node().and_then(|sel_idx| {
+                    let dir = self.current_dir();
+                    if sel_idx < dir.children.len() {
+                        Some(param_display(&dir.children[sel_idx].params))
+                    } else { None }
+                }).unwrap_or_default()
+            } else {
+                vec![]
+            };
+            self.widgets[PARAM_IDX].set_display_params(&params);
+
+            self.rebuild_scene_geometry();
+            self.rebuild_positions();
+            self.apply_layout();
+            self.update_panel_bounds();
+            self.upload_vertices();
+            self.loaded_project_path = None;
+            self.last_saved_root_json = serde_json::to_string(&self.fs_root).unwrap_or_default();
+            self.update_window_title();
+            return Ok(());
+        }
+
+        let (state_file_path, project_dir) = if path.is_dir() {
+            (path.join("state.json"), path.to_path_buf())
+        } else {
+            if path.file_name().map_or(false, |name| name == "state.json") {
+                (path.to_path_buf(), path.parent().unwrap_or(path).to_path_buf())
+            } else {
+                (path.to_path_buf(), path.parent().unwrap_or(path).to_path_buf())
+            }
+        };
 
-    fn load_from_file(&mut self, path: &Path) -> Result<(), Box<dyn std::error::Error>> {
-        let content = fs::read_to_string(path)?;
+        let content = fs::read_to_string(&state_file_path)?;
         let proj: Project = serde_json::from_str(&content)?;
         self.fs_root = proj.root;
         self.active_camera = proj.view_state.active_camera;
@@ -1625,6 +2259,9 @@ fn geometry_to_spreadsheet_data(geom: &Geometry) -> (Vec<String>, Vec<Vec<String
         self.apply_layout();
         self.update_panel_bounds();
         self.upload_vertices();
+        self.loaded_project_path = Some(project_dir);
+        self.last_saved_root_json = serde_json::to_string(&self.fs_root).unwrap_or_default();
+        self.update_window_title();
         Ok(())
     }
 
@@ -1662,6 +2299,9 @@ fn geometry_to_spreadsheet_data(geom: &Geometry) -> (Vec<String>, Vec<Vec<String
         self.apply_layout();
         self.update_panel_bounds();
         self.upload_vertices();
+        self.loaded_project_path = None;
+        self.last_saved_root_json = serde_json::to_string(&self.fs_root).unwrap_or_default();
+        self.update_window_title();
     }
 
     fn create_depth_texture(&self) -> (wgpu::Texture, wgpu::TextureView) {
@@ -1679,6 +2319,21 @@ fn geometry_to_spreadsheet_data(geom: &Geometry) -> (Vec<String>, Vec<Vec<String
         (tex, view)
     }
 
+    fn create_backdrop_texture(&self) -> (wgpu::Texture, wgpu::TextureView) {
+        let tex = self.device.create_texture(&wgpu::TextureDescriptor {
+            label: Some("Backdrop Texture"),
+            size: wgpu::Extent3d { width: self.physical_width.max(1), height: self.physical_height.max(1), depth_or_array_layers: 1 },
+            mip_level_count: 1,
+            sample_count: 1,
+            dimension: wgpu::TextureDimension::D2,
+            format: self.config.format,
+            usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_SRC,
+            view_formats: &[],
+        });
+        let view = tex.create_view(&wgpu::TextureViewDescriptor::default());
+        (tex, view)
+    }
+
     async fn new(
         conn: &Connection,
         qh: &QueueHandle<AppState>,
@@ -1689,6 +2344,7 @@ fn geometry_to_spreadsheet_data(geom: &Geometry) -> (Vec<String>, Vec<Vec<String
         scale: f64,
         is_detached_network: bool,
     ) -> Self {
+        clear_ui::scale::set_scale_factor(scale as f32);
         let settings = DesignSettings::load();
         let lw = pw as f32 / scale as f32;
         let lh = ph as f32 / scale as f32;
@@ -1700,11 +2356,11 @@ fn geometry_to_spreadsheet_data(geom: &Geometry) -> (Vec<String>, Vec<Vec<String
         if is_detached_network {
             window.set_title("Network Pane");
             window.set_app_id("circular-network-pane");
-            window.set_min_size(Some(((200.0 * scale) as u32, (200.0 * scale) as u32)));
+            window.set_min_size(Some((200, 200)));
         } else {
             window.set_title("Clear Design Interface");
             window.set_app_id("clear-design-interface");
-            window.set_min_size(Some(((480.0 * scale) as u32, (320.0 * scale) as u32)));
+            window.set_min_size(Some((480, 320)));
         }
         window.commit();
 
@@ -1756,8 +2412,69 @@ fn geometry_to_spreadsheet_data(geom: &Geometry) -> (Vec<String>, Vec<Vec<String
             capabilities.alpha_modes[0]
         };
         config.alpha_mode = alpha_mode;
+        config.usage = wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::COPY_DST;
         surface.configure(&device, &config);
 
+        let backdrop_bind_group_layout = device.create_bind_group_layout(&wgpu::BindGroupLayoutDescriptor {
+            label: Some("Backdrop Bind Group Layout"),
+            entries: &[
+                wgpu::BindGroupLayoutEntry {
+                    binding: 0,
+                    visibility: wgpu::ShaderStages::FRAGMENT,
+                    ty: wgpu::BindingType::Texture {
+                        multisampled: false,
+                        view_dimension: wgpu::TextureViewDimension::D2,
+                        sample_type: wgpu::TextureSampleType::Float { filterable: true },
+                    },
+                    count: None,
+                },
+                wgpu::BindGroupLayoutEntry {
+                    binding: 1,
+                    visibility: wgpu::ShaderStages::FRAGMENT,
+                    ty: wgpu::BindingType::Sampler(wgpu::SamplerBindingType::Filtering),
+                    count: None,
+                },
+            ],
+        });
+
+        let backdrop_sampler = device.create_sampler(&wgpu::SamplerDescriptor {
+            label: Some("Backdrop Sampler"),
+            address_mode_u: wgpu::AddressMode::ClampToEdge,
+            address_mode_v: wgpu::AddressMode::ClampToEdge,
+            address_mode_w: wgpu::AddressMode::ClampToEdge,
+            mag_filter: wgpu::FilterMode::Linear,
+            min_filter: wgpu::FilterMode::Linear,
+            mipmap_filter: wgpu::FilterMode::Nearest,
+            ..Default::default()
+        });
+
+        let backdrop_texture = device.create_texture(&wgpu::TextureDescriptor {
+            label: Some("Backdrop Texture"),
+            size: wgpu::Extent3d { width: pw.max(1), height: ph.max(1), depth_or_array_layers: 1 },
+            mip_level_count: 1,
+            sample_count: 1,
+            dimension: wgpu::TextureDimension::D2,
+            format: config.format,
+            usage: wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::TEXTURE_BINDING | wgpu::TextureUsages::COPY_SRC,
+            view_formats: &[],
+        });
+        let backdrop_texture_view = backdrop_texture.create_view(&wgpu::TextureViewDescriptor::default());
+
+        let backdrop_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor {
+            label: Some("Backdrop Bind Group"),
+            layout: &backdrop_bind_group_layout,
+            entries: &[
+                wgpu::BindGroupEntry {
+                    binding: 0,
+                    resource: wgpu::BindingResource::TextureView(&backdrop_texture_view),
+                },
+                wgpu::BindGroupEntry {
+                    binding: 1,
+                    resource: wgpu::BindingResource::Sampler(&backdrop_sampler),
+                },
+            ],
+        });
+
         let shader = device.create_shader_module(wgpu::ShaderModuleDescriptor {
             label: Some("Shader"),
             source: wgpu::ShaderSource::Wgsl(include_str!("shader.wgsl").into()),
@@ -1765,7 +2482,7 @@ fn geometry_to_spreadsheet_data(geom: &Geometry) -> (Vec<String>, Vec<Vec<String
 
         let pipeline_layout = device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor {
             label: Some("Pipeline Layout"),
-            bind_group_layouts: &[],
+            bind_group_layouts: &[&backdrop_bind_group_layout],
             push_constant_ranges: &[],
         });
 
@@ -2157,15 +2874,16 @@ fn geometry_to_spreadsheet_data(geom: &Geometry) -> (Vec<String>, Vec<Vec<String
             vec![]
         };
         let mut widgets: Vec<Box<dyn Widget>> = vec![
-            Box::new(MenuBar::new(0.0, 0.0, 0.0, HEADER_H).with_title("Clear Design Interface").with_item("File", &["New Project", "Open", "Save", "Configure", "Exit"]).with_item("Edit", &["Undo", "Redo"]).with_item("View", &["Zoom In", "Zoom Out", "Reset Zoom", "Detach Circular Window", "Show Network Pane", "Show Viewport Pane", "Show Parameters Pane", "Show Spreadsheet Pane"]).with_item("Help", &["About"]).with_z_index(110)),
+            Box::new(MenuBar::new(0.0, 0.0, 0.0, HEADER_H).with_title("Clear Design Interface").with_item("File", &["New Project", "Open", "Save", "Save As", "Exit"]).with_item("Edit", &["Undo", "Redo"]).with_item("View", &["Zoom In", "Zoom Out", "Reset Zoom", "Detach Circular Window", "Show Network Pane", "Show Viewport Pane", "Show Parameters Pane", "Show Spreadsheet Pane"]).with_item("Help", &["About"]).with_z_index(110)),
             Box::new(Graph::new()),
             Box::new(Splitter::new(SPLITTER_W)),
             Box::new(ViewportBg::new()),
             Box::new(Splitter::new(SPLITTER_W)),
+            Box::new(Plate::new(0.0, 0.0, 0.0, 0.0).with_color(colors::PARAM_BG).with_blur(true)),
             Box::new(ParametersBg::new()),
             Box::new(Canvas::new()),
-            Box::new(MenuBar::new(0.0, 0.0, 0.0, MENUBAR_H).with_title("0: Network").with_item("File", &["New", "Open", "Save"]).with_item("Edit", &["Undo", "Redo"]).with_item("View", &["Zoom In", "Zoom Out", "Circular Pane", "Detach Pane", "Close Pane"])),
-            Box::new(MenuBar::new(0.0, 0.0, 0.0, MENUBAR_H).with_title("1: Viewport").with_item("Camera", &["Perspective", "Orthographic"]).with_item("Display", &["Square Aspect"]).with_item("Guides", &["Show Grid", "Cube", "Origin", "Camera Pivot"]).with_item("View", &["Close Pane"])),
+            Box::new(MenuBar::new(0.0, 0.0, 0.0, MENUBAR_H).with_title("0: Network").with_item("File", &["New", "Open", "Save", "Save As"]).with_item("Edit", &["Undo", "Redo"]).with_item("View", &["Zoom In", "Zoom Out", "Circular Pane", "Detach Pane", "Close Pane"]).with_item("Settings", &[])),
+            Box::new(MenuBar::new(0.0, 0.0, 0.0, MENUBAR_H).with_title("1: Viewport").with_item("Camera", &["Perspective", "Orthographic"]).with_item("Display", &["Square Aspect"]).with_item("Guides", &["Show Grid", "Cube", "Origin", "Camera Pivot"]).with_item("View", &["Close Pane"]).with_item("Settings", &[])),
             Box::new(MenuBar::new(0.0, 0.0, 0.0, MENUBAR_H).with_title("2: Parameters").with_item("Preset", &["Default", "Custom"]).with_item("Reset", &["All"]).with_item("View", &["Close Pane"])),
             Box::new(StatusBar::new().with_text("Ready")),
             Box::new(Breadcrumb::new()),
@@ -2177,8 +2895,25 @@ fn geometry_to_spreadsheet_data(geom: &Geometry) -> (Vec<String>, Vec<Vec<String
         spreadsheet_menubar.visible = false;
         widgets.push(Box::new(spreadsheet_menubar));
 
-        let mut positions = Vec::with_capacity(SPREADSHEET_MENUBAR_IDX + 1);
-        positions.resize_with(SPREADSHEET_MENUBAR_IDX + 1, || (0.0, 0.0, 0.0, 0.0));
+        let network_panel = Plate::new(0.0, 0.0, 0.0, 0.0).with_color([0.10, 0.10, 0.13, 0.95]);
+        widgets.push(Box::new(network_panel));
+
+        let paginator = Paginator::new(56.0, vec![]).with_sidebar_mode(true).with_column_layout(true);
+        widgets.push(Box::new(paginator));
+
+        let mut positions = Vec::with_capacity(PAGINATOR_IDX + 1);
+        positions.resize_with(PAGINATOR_IDX + 1, || (0.0, 0.0, 0.0, 0.0));
+
+        let recent_files = Self::load_recent_files();
+        let recent_files_list = ScrollingList::new(22.0, 2.0);
+        let mut recent_files_buttons = Vec::new();
+        for file in &recent_files {
+            let label = file.file_name()
+                .map(|n| n.to_string_lossy().to_string())
+                .unwrap_or_else(|| file.to_string_lossy().to_string());
+            let btn = Button::new_list_row(0.0, 0.0, 0.0, 0.0).with_label(&label);
+            recent_files_buttons.push(btn);
+        }
 
         let vertex_buffer = device.create_buffer(&wgpu::BufferDescriptor {
             label: Some("Vertex Buffer"),
@@ -2213,6 +2948,11 @@ fn geometry_to_spreadsheet_data(geom: &Geometry) -> (Vec<String>, Vec<Vec<String
             vertex_count_grid,
             depth_texture,
             depth_texture_view,
+            backdrop_texture,
+            backdrop_texture_view,
+            backdrop_sampler,
+            backdrop_bind_group_layout,
+            backdrop_bind_group,
             rotation_y: 0.0,
             rotation_x: 0.0,
             is_rotating_viewport: false,
@@ -2237,7 +2977,7 @@ fn geometry_to_spreadsheet_data(geom: &Geometry) -> (Vec<String>, Vec<Vec<String
             vertex_count_pivot,
             origin_size: settings.origin_size,
             camera_pivot_size: settings.camera_pivot_size,
-            fs_root,
+            fs_root: fs_root.clone(),
             node_templates,
             current_path,
             last_click: None,
@@ -2324,9 +3064,6 @@ fn geometry_to_spreadsheet_data(geom: &Geometry) -> (Vec<String>, Vec<Vec<String
             last_config_read: Instant::now(),
             circular_network_pane: is_detached_network,
             circular_network_layout: clear_ui::layout::CircularPaneLayout::new(250.0, 300.0, 180.0),
-            is_dragging_network_circle: false,
-            circle_drag_ox: 0.0,
-            circle_drag_oy: 0.0,
             is_detached_network,
             detached_circular_network: false,
             last_project_mod_time: {
@@ -2345,9 +3082,27 @@ fn geometry_to_spreadsheet_data(geom: &Geometry) -> (Vec<String>, Vec<Vec<String
                 let design_path = DesignSettings::file_path();
                 std::fs::metadata(&design_path).and_then(|m| m.modified()).ok()
             },
+            floating_network_layout: (18.0, 44.0, 400.0, 710.0),
+            is_resizing_network: None,
+            drag_start_rect: (0.0, 0.0, 0.0, 0.0),
+            drag_start_mouse: (0.0, 0.0),
+            floating_param_width: 300.0,
+            is_resizing_param: false,
+            drag_start_param_w: 0.0,
+            floating_spreadsheet_height: 250.0,
+            is_resizing_spreadsheet: false,
+            drag_start_spreadsheet_h: 0.0,
+            paginator_page_widgets: Vec::new(),
+            last_paginator_menubar: None,
+            loaded_project_path: None,
+            last_saved_root_json: serde_json::to_string(&fs_root).unwrap_or_default(),
+            recent_files,
+            recent_files_list,
+            recent_files_buttons,
         };
 
         state.update_inertial_settings();
+        state.update_window_title();
         colors::set_node_color([
             settings.node_color[0],
             settings.node_color[1],
@@ -2379,11 +3134,7 @@ fn geometry_to_spreadsheet_data(geom: &Geometry) -> (Vec<String>, Vec<Vec<String
 
     fn sync_grid_settings(&mut self) {
         let active_node_area_y = self.positions[CONTENT_IDX].1;
-        let active_node_area_x = if self.circular_network_pane {
-            self.circular_network_layout.x - self.circular_network_layout.r
-        } else {
-            0.0
-        };
+        let active_node_area_x = self.positions[CONTENT_IDX].0;
 
         self.widgets[CONTENT_IDX].set_show_network_grid(self.network_grid_visible);
         self.widgets[CONTENT_IDX].set_grid_sizes(self.grid_size_x, self.grid_size_y);
@@ -2396,6 +3147,7 @@ fn geometry_to_spreadsheet_data(geom: &Geometry) -> (Vec<String>, Vec<Vec<String
         self.widgets[CONTENT_IDX].set_gap_color(self.gap_color);
         self.widgets[LEFT_MENUBAR_IDX].set_network_opacity(self.network_opacity);
         self.widgets[BREADCRUMB_IDX].set_network_opacity(self.network_opacity);
+        self.widgets[NETWORK_PANEL_IDX].set_network_opacity(self.network_opacity);
     }
 
     fn update_inertial_settings(&mut self) {
@@ -2486,39 +3238,23 @@ fn geometry_to_spreadsheet_data(geom: &Geometry) -> (Vec<String>, Vec<Vec<String
 
 
     fn keep_cursor_in_view(&mut self) {
-        let active_node_area_y = self.positions[CONTENT_IDX].1;
-        let active_node_area_x = if self.circular_network_pane {
-            self.circular_network_layout.x - self.circular_network_layout.r
-        } else {
-            0.0
-        };
+        let (px, py, pw, ph) = self.positions[CONTENT_IDX];
 
-        let cx = active_node_area_x + self.grid_cursor_col as f32 * (self.grid_size_x + self.skipped_col_w) + self.pan_x;
-        let cy = active_node_area_y + self.grid_cursor_row as f32 * (self.grid_size_y + self.skipped_row_h) + self.pan_y;
+        let cx = px + self.grid_cursor_col as f32 * (self.grid_size_x + self.skipped_col_w) + self.pan_x;
+        let cy = py + self.grid_cursor_row as f32 * (self.grid_size_y + self.skipped_row_h) + self.pan_y;
         let cw = self.grid_size_x;
         let ch = self.grid_size_y;
 
-        let active_graph_w = if self.circular_network_pane {
-            2.0 * self.circular_network_layout.r
-        } else {
-            self.content_left_w()
-        };
-        let active_max_y = if self.circular_network_pane {
-            self.circular_network_layout.y + self.circular_network_layout.r
-        } else {
-            self.height - STATUS_H
-        };
-
-        if cx < active_node_area_x {
-            self.pan_x -= cx - active_node_area_x;
-        } else if cx + cw > active_node_area_x + active_graph_w {
-            self.pan_x -= cx + cw - (active_node_area_x + active_graph_w);
+        if cx < px {
+            self.pan_x -= cx - px;
+        } else if cx + cw > px + pw {
+            self.pan_x -= cx + cw - (px + pw);
         }
 
-        if cy < active_node_area_y {
-            self.pan_y -= cy - active_node_area_y;
-        } else if cy + ch > active_max_y {
-            self.pan_y -= cy + ch - active_max_y;
+        if cy < py {
+            self.pan_y -= cy - py;
+        } else if cy + ch > py + ph {
+            self.pan_y -= cy + ch - (py + ph);
         }
         self.sync_grid_settings();
     }
@@ -2528,12 +3264,6 @@ fn geometry_to_spreadsheet_data(geom: &Geometry) -> (Vec<String>, Vec<Vec<String
         self.widgets[LEFT_MENUBAR_IDX].set_center_items(self.circular_network_pane);
 
         let body_h = self.body_h();
-        let clw = self.content_left_w();
-        let crx = self.content_right_x();
-        let vw = self.viewport_w();
-        let px = self.param_x();
-
-        let node_area_y = HEADER_H;
 
         if self.is_detached_network {
             let cx = self.width / 2.0;
@@ -2549,6 +3279,9 @@ fn geometry_to_spreadsheet_data(geom: &Geometry) -> (Vec<String>, Vec<Vec<String
             self.widgets[LEFT_MENUBAR_IDX].set_curved_circle(Some((cx, cy, r)));
             self.positions[BREADCRUMB_IDX] = (cx - r, cy - r + 45.0 + MENUBAR_H, 2.0 * r, BREADCRUMB_H);
             self.positions[CONTENT_IDX] = (cx - r, cy - r + 45.0 + MENUBAR_H + BREADCRUMB_H, 2.0 * r, 2.0 * r - (45.0 + MENUBAR_H + BREADCRUMB_H));
+            self.positions[NETWORK_PANEL_IDX] = (cx - r, cy - r, 2.0 * r, 2.0 * r);
+            self.widgets[NETWORK_PANEL_IDX].set_rect(cx - r, cy - r, 2.0 * r, 2.0 * r);
+            self.widgets[NETWORK_PANEL_IDX].set_curved_circle(Some((cx, cy, r)));
             self.positions[SPLITTER1_IDX] = (0.0, 0.0, 0.0, 0.0);
             self.positions[SPLITTER2_IDX] = (0.0, 0.0, 0.0, 0.0);
             self.positions[PARAM_IDX] = (0.0, 0.0, 0.0, 0.0);
@@ -2568,7 +3301,7 @@ fn geometry_to_spreadsheet_data(geom: &Geometry) -> (Vec<String>, Vec<Vec<String
             let center_visible = viewport_visible || spreadsheet_visible;
             let right_visible = self.show_parameters;
 
-            let (col_l_x, col_l_w, s1_x, s1_w, col_c_x, col_c_w, s2_x, s2_w, col_r_x, col_r_w) =
+            let (_col_l_x, _col_l_w, _s1_x, _s1_w, col_c_x, col_c_w, s2_x, s2_w, col_r_x, col_r_w) =
                 match (left_visible, center_visible, right_visible) {
                     (false, true, true) => {
                         let c_w = self.splitter_layout.splitter2_x;
@@ -2620,6 +3353,8 @@ fn geometry_to_spreadsheet_data(geom: &Geometry) -> (Vec<String>, Vec<Vec<String
             self.positions[LEFT_MENUBAR_IDX] = (0.0, 0.0, 0.0, 0.0);
             self.positions[BREADCRUMB_IDX] = (0.0, 0.0, 0.0, 0.0);
             self.positions[CONTENT_IDX] = (0.0, 0.0, 0.0, 0.0);
+            self.positions[NETWORK_PANEL_IDX] = (0.0, 0.0, 0.0, 0.0);
+            self.widgets[NETWORK_PANEL_IDX].set_rect(0.0, 0.0, 0.0, 0.0);
             self.positions[SPLITTER1_IDX] = (0.0, 0.0, 0.0, 0.0);
             self.positions[SPLITTER2_IDX] = (s2_x, HEADER_H, s2_w, body_h);
             self.positions[PARAM_IDX] = (col_r_x, HEADER_H, col_r_w, body_h);
@@ -2636,8 +3371,9 @@ fn geometry_to_spreadsheet_data(geom: &Geometry) -> (Vec<String>, Vec<Vec<String
             self.positions[NODE_PALETTE_IDX] = (0.0, 0.0, self.width, self.height);
 
             self.widgets[0].set_visible(true);
-            self.widgets[STATUS_IDX].set_visible(true);
+            self.widgets[STATUS_IDX].set_visible(false);
             self.widgets[CONTENT_IDX].set_visible(false);
+            self.widgets[NETWORK_PANEL_IDX].set_visible(false);
             self.widgets[LEFT_MENUBAR_IDX].set_visible(false);
             self.widgets[BREADCRUMB_IDX].set_visible(false);
             self.widgets[SPLITTER1_IDX].set_visible(false);
@@ -2649,7 +3385,9 @@ fn geometry_to_spreadsheet_data(geom: &Geometry) -> (Vec<String>, Vec<Vec<String
             self.widgets[SPREADSHEET_IDX].set_visible(spreadsheet_visible);
             self.widgets[SPREADSHEET_MENUBAR_IDX].set_visible(spreadsheet_visible);
         } else {
-            self.positions[0] = (0.0, 0.0, self.width, HEADER_H);
+            let paginator_w = self.widgets[PAGINATOR_IDX].sidebar_w();
+            let body_h = self.height - STATUS_H;
+            self.positions[0] = (0.0, 0.0, 0.0, 0.0);
             if self.circular_network_pane {
                 let left_visible = false;
                 let viewport_visible = self.show_viewport;
@@ -2657,23 +3395,23 @@ fn geometry_to_spreadsheet_data(geom: &Geometry) -> (Vec<String>, Vec<Vec<String
                 let center_visible = viewport_visible || spreadsheet_visible;
                 let right_visible = self.show_parameters;
 
-                let (col_l_x, col_l_w, s1_x, s1_w, col_c_x, col_c_w, s2_x, s2_w, col_r_x, col_r_w) =
+                let (_col_l_x, _col_l_w, _s1_x, _s1_w, col_c_x, col_c_w, s2_x, s2_w, col_r_x, col_r_w) =
                     match (left_visible, center_visible, right_visible) {
                         (false, true, true) => {
-                            let viewport_w = self.splitter_layout.splitter2_x;
-                            let s2 = viewport_w;
+                            let s2 = self.splitter_layout.splitter2_x.max(paginator_w + MIN_COLUMN);
+                            let viewport_w = s2 - paginator_w;
                             let r_x = s2 + SPLITTER_W;
                             let r_w = (self.width - r_x).max(0.0);
-                            (0.0, 0.0, 0.0, 0.0, 0.0, viewport_w, s2, SPLITTER_W, r_x, r_w)
+                            (0.0, 0.0, 0.0, 0.0, paginator_w, viewport_w, s2, SPLITTER_W, r_x, r_w)
                         }
                         (false, true, false) => {
-                            (0.0, 0.0, 0.0, 0.0, 0.0, self.width, 0.0, 0.0, 0.0, 0.0)
+                            (0.0, 0.0, 0.0, 0.0, paginator_w, self.width - paginator_w, 0.0, 0.0, 0.0, 0.0)
                         }
                         (false, false, true) => {
-                            (0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, self.width)
+                            (0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, paginator_w, self.width - paginator_w)
                         }
                         _ => {
-                            (0.0, 0.0, 0.0, 0.0, 0.0, self.width, 0.0, 0.0, 0.0, 0.0)
+                            (0.0, 0.0, 0.0, 0.0, paginator_w, self.width - paginator_w, 0.0, 0.0, 0.0, 0.0)
                         }
                     };
 
@@ -2688,173 +3426,184 @@ fn geometry_to_spreadsheet_data(geom: &Geometry) -> (Vec<String>, Vec<Vec<String
                     if viewport_visible && spreadsheet_visible {
                         let viewport_h = body_h * 2.0 / 3.0;
                         let spreadsheet_h = body_h - viewport_h;
-                        vp_y = HEADER_H;
+                        vp_y = 0.0;
                         vp_h = viewport_h;
                         sp_menub_y = 0.0;
                         sp_menub_h = 0.0;
-                        sp_y = HEADER_H + viewport_h;
+                        sp_y = viewport_h;
                         sp_h = spreadsheet_h;
                     } else if viewport_visible {
-                        vp_y = HEADER_H;
+                        vp_y = 0.0;
                         vp_h = body_h;
                     } else if spreadsheet_visible {
                         sp_menub_y = 0.0;
                         sp_menub_h = 0.0;
-                        sp_y = HEADER_H;
+                        sp_y = 0.0;
                         sp_h = body_h;
                     }
                 }
 
+                if self.widgets[NETWORK_PANEL_IDX].is_dragging() {
+                    let (px, py, _pw, _ph) = self.widgets[NETWORK_PANEL_IDX].rect();
+                    let r = self.circular_network_layout.r;
+                    self.circular_network_layout.x = px + r;
+                    self.circular_network_layout.y = py + r;
+                }
+
+                let gap = 18.0;
+                let max_r = ((self.width - paginator_w - 2.0 * gap).min(self.height - STATUS_H - 2.0 * gap) / 2.0).max(50.0);
+                self.circular_network_layout.r = self.circular_network_layout.r.clamp(50.0, max_r);
+
+                let r = self.circular_network_layout.r;
+                let min_x = paginator_w + gap + r;
+                let max_x = (self.width - gap - r).max(min_x);
+                self.circular_network_layout.x = self.circular_network_layout.x.clamp(min_x, max_x);
+
+                let min_y = gap + r;
+                let max_y = (self.height - STATUS_H - gap - r).max(min_y);
+                self.circular_network_layout.y = self.circular_network_layout.y.clamp(min_y, max_y);
+
                 let cx = self.circular_network_layout.x;
                 let cy = self.circular_network_layout.y;
-                let r = self.circular_network_layout.r;
 
-                self.positions[LEFT_MENUBAR_IDX] = (cx - r, cy - r, 2.0 * r, 35.0);
+                self.positions[LEFT_MENUBAR_IDX] = (0.0, 0.0, 0.0, 0.0);
                 self.widgets[LEFT_MENUBAR_IDX].set_curved_circle(None);
-                self.positions[BREADCRUMB_IDX] = (cx - r, cy - r + 45.0 + MENUBAR_H, 2.0 * r, BREADCRUMB_H);
-                self.positions[CONTENT_IDX] = (cx - r, cy - r + 45.0 + MENUBAR_H + BREADCRUMB_H, 2.0 * r, 2.0 * r - (45.0 + MENUBAR_H + BREADCRUMB_H));
+                self.positions[BREADCRUMB_IDX] = (cx - r, cy - r + 45.0, 2.0 * r, BREADCRUMB_H);
+                self.positions[CONTENT_IDX] = (cx - r, cy - r + 45.0 + BREADCRUMB_H, 2.0 * r, 2.0 * r - (45.0 + BREADCRUMB_H));
+                self.positions[NETWORK_PANEL_IDX] = (cx - r, cy - r, 2.0 * r, 2.0 * r);
+                self.widgets[NETWORK_PANEL_IDX].set_rect(cx - r, cy - r, 2.0 * r, 2.0 * r);
+                self.widgets[NETWORK_PANEL_IDX].set_curved_circle(Some((cx, cy, r)));
+                self.widgets[NETWORK_PANEL_IDX].set_drag_bounds(0.0, 0.0, self.width, self.height);
                 self.positions[SPLITTER1_IDX] = (0.0, 0.0, 0.0, 0.0);
-                self.positions[SPLITTER2_IDX] = (s2_x, HEADER_H, s2_w, body_h);
-                self.positions[PARAM_IDX] = (col_r_x, HEADER_H, col_r_w, body_h);
+                self.positions[SPLITTER2_IDX] = (s2_x, 0.0, s2_w, body_h);
+                self.positions[PARAM_IDX] = (col_r_x, 0.0, col_r_w, body_h);
 
                 self.positions[VIEWPORT_IDX] = (col_c_x, vp_y, col_c_w, vp_h);
-                self.positions[RIGHT_MENUBAR_IDX] = (col_c_x, vp_y, col_c_w, if viewport_visible { MENUBAR_H } else { 0.0 });
+                self.positions[RIGHT_MENUBAR_IDX] = (0.0, 0.0, 0.0, 0.0);
 
-                self.positions[SPREADSHEET_MENUBAR_IDX] = (col_c_x, sp_menub_y, col_c_w, sp_menub_h);
+                self.positions[SPREADSHEET_MENUBAR_IDX] = (0.0, 0.0, 0.0, 0.0);
                 self.positions[SPREADSHEET_IDX] = (col_c_x, sp_y, col_c_w, sp_h);
 
-                self.positions[CANVAS_IDX] = (0.0, HEADER_H, self.width, body_h);
-                self.positions[PARAM_MENUBAR_IDX] = (col_r_x, HEADER_H, col_r_w, if right_visible { MENUBAR_H } else { 0.0 });
+                self.positions[CANVAS_IDX] = (0.0, 0.0, self.width, body_h);
+                self.positions[PARAM_MENUBAR_IDX] = (0.0, 0.0, 0.0, 0.0);
                 self.positions[STATUS_IDX] = (0.0, self.height - STATUS_H, self.width, STATUS_H);
                 self.positions[NODE_PALETTE_IDX] = (0.0, 0.0, self.width, self.height);
 
-                self.widgets[0].set_visible(true);
-                self.widgets[STATUS_IDX].set_visible(true);
+                self.positions[PAGINATOR_IDX] = (0.0, 0.0, paginator_w, self.height - STATUS_H);
+                self.widgets[PAGINATOR_IDX].set_rect(0.0, 0.0, paginator_w, self.height - STATUS_H);
+
+                self.widgets[0].set_visible(false);
+                self.widgets[STATUS_IDX].set_visible(false);
                 self.widgets[CONTENT_IDX].set_visible(self.show_network);
-                self.widgets[LEFT_MENUBAR_IDX].set_visible(self.show_network);
+                self.widgets[NETWORK_PANEL_IDX].set_visible(self.show_network);
+                self.widgets[LEFT_MENUBAR_IDX].set_visible(false);
                 self.widgets[BREADCRUMB_IDX].set_visible(self.show_network);
                 self.widgets[SPLITTER1_IDX].set_visible(false);
                 self.widgets[SPLITTER2_IDX].set_visible(s2_w > 0.0);
                 self.widgets[VIEWPORT_IDX].set_visible(viewport_visible);
-                self.widgets[RIGHT_MENUBAR_IDX].set_visible(viewport_visible);
+                self.widgets[RIGHT_MENUBAR_IDX].set_visible(false);
                 self.widgets[PARAM_IDX].set_visible(right_visible);
-                self.widgets[PARAM_MENUBAR_IDX].set_visible(right_visible);
+                self.widgets[PARAM_MENUBAR_IDX].set_visible(false);
                 self.widgets[SPREADSHEET_IDX].set_visible(spreadsheet_visible);
-                self.widgets[SPREADSHEET_MENUBAR_IDX].set_visible(spreadsheet_visible);
+                self.widgets[SPREADSHEET_MENUBAR_IDX].set_visible(false);
+                self.widgets[PAGINATOR_IDX].set_visible(true);
             } else {
-                let left_visible = self.show_network;
+                if !self.circular_network_pane && self.widgets[NETWORK_PANEL_IDX].is_dragging() {
+                    let (px, py, _pw, _ph) = self.widgets[NETWORK_PANEL_IDX].rect();
+                    self.floating_network_layout.0 = px;
+                    self.floating_network_layout.1 = py;
+                }
+
+                let gap = 18.0_f32;
+                let (mut _fx, mut _fy, mut fw, mut _fh) = self.floating_network_layout;
+                fw = fw.clamp(150.0, (self.width - paginator_w - 2.0 * gap).max(150.0));
+                let fx = paginator_w + gap;
+                let fy = gap;
+                let fh = (self.height - STATUS_H - 2.0 * gap).max(100.0);
+                self.floating_network_layout = (fx, fy, fw, fh);
+
+                let param_w = self.floating_param_width.clamp(150.0, (self.width - paginator_w - 2.0 * gap).max(150.0));
+                self.floating_param_width = param_w;
+                let param_x = self.width - gap - param_w;
+                let param_y = gap;
+                let param_h = (self.height - STATUS_H - 2.0 * gap).max(100.0);
+
+                let ss_x = if self.show_network { fx + fw + gap } else { paginator_w + gap };
+                let ss_w_end = if self.show_parameters { param_x - gap } else { self.width - gap };
+                let ss_w = (ss_w_end - ss_x).max(150.0);
+                let ss_y_end = self.height - STATUS_H - gap;
+                let ss_h = self.floating_spreadsheet_height.clamp(100.0, (ss_y_end - gap).max(100.0));
+                let ss_y = ss_y_end - ss_h;
+                self.floating_spreadsheet_height = ss_h;
+
                 let viewport_visible = self.show_viewport;
                 let spreadsheet_visible = self.show_spreadsheet;
-                let center_visible = viewport_visible || spreadsheet_visible;
                 let right_visible = self.show_parameters;
 
-                let (col_l_x, col_l_w, s1_x, s1_w, col_c_x, col_c_w, s2_x, s2_w, col_r_x, col_r_w) =
-                    match (left_visible, center_visible, right_visible) {
-                        (true, true, true) => {
-                            let l_w = self.splitter_layout.splitter1_x;
-                            let s1 = l_w;
-                            let c_x = s1 + SPLITTER_W;
-                            let c_w = self.splitter_layout.splitter2_x - c_x;
-                            let s2 = self.splitter_layout.splitter2_x;
-                            let r_x = s2 + SPLITTER_W;
-                            let r_w = (self.width - r_x).max(0.0);
-                            (0.0, l_w, s1, SPLITTER_W, c_x, c_w, s2, SPLITTER_W, r_x, r_w)
-                        }
-                        (true, true, false) => {
-                            let l_w = self.splitter_layout.splitter1_x;
-                            let s1 = l_w;
-                            let c_x = s1 + SPLITTER_W;
-                            let c_w = (self.width - c_x).max(0.0);
-                            (0.0, l_w, s1, SPLITTER_W, c_x, c_w, 0.0, 0.0, 0.0, 0.0)
-                        }
-                        (true, false, true) => {
-                            let l_w = self.splitter_layout.splitter2_x;
-                            let s2 = l_w;
-                            let r_x = s2 + SPLITTER_W;
-                            let r_w = (self.width - r_x).max(0.0);
-                            (0.0, l_w, 0.0, 0.0, 0.0, 0.0, s2, SPLITTER_W, r_x, r_w)
-                        }
-                        (false, true, true) => {
-                            let c_w = self.splitter_layout.splitter2_x;
-                            let s2 = c_w;
-                            let r_x = s2 + SPLITTER_W;
-                            let r_w = (self.width - r_x).max(0.0);
-                            (0.0, 0.0, 0.0, 0.0, 0.0, c_w, s2, SPLITTER_W, r_x, r_w)
-                        }
-                        (true, false, false) => {
-                            (0.0, self.width, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0)
-                        }
-                        (false, true, false) => {
-                            (0.0, 0.0, 0.0, 0.0, 0.0, self.width, 0.0, 0.0, 0.0, 0.0)
-                        }
-                        (false, false, true) => {
-                            (0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, self.width)
-                        }
-                        (false, false, false) => {
-                            (0.0, 0.0, 0.0, 0.0, 0.0, self.width, 0.0, 0.0, 0.0, 0.0)
-                        }
-                    };
-
-                let mut vp_y = 0.0;
-                let mut vp_h = 0.0;
-                let mut sp_menub_y = 0.0;
-                let mut sp_menub_h = 0.0;
-                let mut sp_y = 0.0;
-                let mut sp_h = 0.0;
+                let col_c_x = paginator_w;
+                let col_c_w = self.width - paginator_w;
+                let vp_y = 0.0;
+                let vp_h = if viewport_visible { body_h } else { 0.0 };
 
-                if center_visible {
-                    if viewport_visible && spreadsheet_visible {
-                        let viewport_h = body_h * 2.0 / 3.0;
-                        let spreadsheet_h = body_h - viewport_h;
-                        vp_y = HEADER_H;
-                        vp_h = viewport_h;
-                        sp_menub_y = 0.0;
-                        sp_menub_h = 0.0;
-                        sp_y = HEADER_H + viewport_h;
-                        sp_h = spreadsheet_h;
-                    } else if viewport_visible {
-                        vp_y = HEADER_H;
-                        vp_h = body_h;
-                    } else if spreadsheet_visible {
-                        sp_menub_y = 0.0;
-                        sp_menub_h = 0.0;
-                        sp_y = HEADER_H;
-                        sp_h = body_h;
-                    }
-                }
+                let (px, py, pw, ph) = if self.show_network {
+                    (fx, fy, fw, fh)
+                } else {
+                    (0.0, 0.0, 0.0, 0.0)
+                };
 
                 self.widgets[LEFT_MENUBAR_IDX].set_curved_circle(None);
-                self.positions[CONTENT_IDX] = (col_l_x, node_area_y + BREADCRUMB_H, col_l_w, self.height - STATUS_H - (node_area_y + BREADCRUMB_H));
-                self.positions[SPLITTER1_IDX] = (s1_x, HEADER_H, s1_w, body_h);
-                self.positions[SPLITTER2_IDX] = (s2_x, HEADER_H, s2_w, body_h);
-                self.positions[PARAM_IDX] = (col_r_x, HEADER_H, col_r_w, body_h);
+                
+                let mb_h = 0.0;
+                let bc_h = if self.show_network { BREADCRUMB_H } else { 0.0 };
+                let content_h = (ph - mb_h - bc_h).max(0.0);
+
+                self.positions[CONTENT_IDX] = (px, py + mb_h + bc_h, pw, content_h);
+                self.positions[NETWORK_PANEL_IDX] = (px, py, pw, ph);
+                self.widgets[NETWORK_PANEL_IDX].set_rect(px, py, pw, ph);
+                self.widgets[NETWORK_PANEL_IDX].set_curved_circle(None);
+                self.positions[SPLITTER1_IDX] = (0.0, 0.0, 0.0, 0.0);
+                self.positions[SPLITTER2_IDX] = (0.0, 0.0, 0.0, 0.0);
+                let p_rect = if self.show_parameters { (param_x, param_y, param_w, param_h) } else { (0.0, 0.0, 0.0, 0.0) };
+                self.positions[PARAM_IDX] = p_rect;
+                self.widgets[PARAM_IDX].set_rect(p_rect.0, p_rect.1, p_rect.2, p_rect.3);
 
                 self.positions[VIEWPORT_IDX] = (col_c_x, vp_y, col_c_w, vp_h);
-                self.positions[RIGHT_MENUBAR_IDX] = (col_c_x, vp_y, col_c_w, if viewport_visible { MENUBAR_H } else { 0.0 });
-                self.positions[BREADCRUMB_IDX] = (col_l_x, node_area_y, col_l_w, BREADCRUMB_H);
-                self.positions[LEFT_MENUBAR_IDX] = (col_l_x, HEADER_H, col_l_w, if left_visible { MENUBAR_H } else { 0.0 });
-
-                self.positions[SPREADSHEET_MENUBAR_IDX] = (col_c_x, sp_menub_y, col_c_w, sp_menub_h);
-                self.positions[SPREADSHEET_IDX] = (col_c_x, sp_y, col_c_w, sp_h);
-
-                self.positions[CANVAS_IDX] = (0.0, HEADER_H, self.width, body_h);
-                self.positions[PARAM_MENUBAR_IDX] = (col_r_x, HEADER_H, col_r_w, if right_visible { MENUBAR_H } else { 0.0 });
+                self.positions[RIGHT_MENUBAR_IDX] = (0.0, 0.0, 0.0, 0.0);
+                self.positions[BREADCRUMB_IDX] = (px, py + mb_h, pw, bc_h);
+                self.positions[LEFT_MENUBAR_IDX] = (0.0, 0.0, 0.0, 0.0);
+
+                self.positions[SPREADSHEET_MENUBAR_IDX] = (0.0, 0.0, 0.0, 0.0);
+                self.positions[SPREADSHEET_IDX] = if spreadsheet_visible { (ss_x, ss_y, ss_w, ss_h) } else { (0.0, 0.0, 0.0, 0.0) };
+                self.widgets[SPREADSHEET_IDX].set_rect(
+                    self.positions[SPREADSHEET_IDX].0,
+                    self.positions[SPREADSHEET_IDX].1,
+                    self.positions[SPREADSHEET_IDX].2,
+                    self.positions[SPREADSHEET_IDX].3,
+                );
+
+                self.positions[CANVAS_IDX] = (0.0, 0.0, self.width, body_h);
+                self.positions[PARAM_MENUBAR_IDX] = (0.0, 0.0, 0.0, 0.0);
                 self.positions[STATUS_IDX] = (0.0, self.height - STATUS_H, self.width, STATUS_H);
                 self.positions[NODE_PALETTE_IDX] = (0.0, 0.0, self.width, self.height);
 
-                self.widgets[0].set_visible(true);
-                self.widgets[STATUS_IDX].set_visible(true);
-                self.widgets[CONTENT_IDX].set_visible(left_visible);
-                self.widgets[LEFT_MENUBAR_IDX].set_visible(left_visible);
-                self.widgets[BREADCRUMB_IDX].set_visible(left_visible);
-                self.widgets[SPLITTER1_IDX].set_visible(s1_w > 0.0);
-                self.widgets[SPLITTER2_IDX].set_visible(s2_w > 0.0);
+                self.positions[PAGINATOR_IDX] = (0.0, 0.0, paginator_w, self.height - STATUS_H);
+                self.widgets[PAGINATOR_IDX].set_rect(0.0, 0.0, paginator_w, self.height - STATUS_H);
+
+                self.widgets[0].set_visible(false);
+                self.widgets[STATUS_IDX].set_visible(false);
+                self.widgets[CONTENT_IDX].set_visible(self.show_network);
+                self.widgets[NETWORK_PANEL_IDX].set_visible(self.show_network);
+                self.widgets[LEFT_MENUBAR_IDX].set_visible(false);
+                self.widgets[BREADCRUMB_IDX].set_visible(self.show_network);
+                self.widgets[SPLITTER1_IDX].set_visible(false);
+                self.widgets[SPLITTER2_IDX].set_visible(false);
                 self.widgets[VIEWPORT_IDX].set_visible(viewport_visible);
-                self.widgets[RIGHT_MENUBAR_IDX].set_visible(viewport_visible);
+                self.widgets[RIGHT_MENUBAR_IDX].set_visible(false);
                 self.widgets[PARAM_IDX].set_visible(right_visible);
-                self.widgets[PARAM_MENUBAR_IDX].set_visible(right_visible);
+                self.widgets[PARAM_MENUBAR_IDX].set_visible(false);
                 self.widgets[SPREADSHEET_IDX].set_visible(spreadsheet_visible);
-                self.widgets[SPREADSHEET_MENUBAR_IDX].set_visible(spreadsheet_visible);
+                self.widgets[SPREADSHEET_MENUBAR_IDX].set_visible(false);
+                self.widgets[PAGINATOR_IDX].set_visible(true);
             }
         }
 
@@ -2885,15 +3634,13 @@ fn geometry_to_spreadsheet_data(geom: &Geometry) -> (Vec<String>, Vec<Vec<String
                 SPREADSHEET_MENUBAR_IDX,
             ];
             for &idx in &menubars {
-                if idx == active_menubar {
-                    self.positions[idx] = (0.0, 0.0, self.width, HEADER_H);
-                    self.widgets[idx].set_visible(true);
-                } else {
-                    self.positions[idx] = (0.0, 0.0, 0.0, 0.0);
-                    self.widgets[idx].set_visible(false);
-                }
+                self.positions[idx] = (0.0, 0.0, 0.0, 0.0);
+                self.widgets[idx].set_visible(false);
             }
         }
+        self.positions[PARAM_PLATE_IDX] = self.positions[PARAM_IDX];
+        let p_visible = self.widgets[PARAM_IDX].visible();
+        self.widgets[PARAM_PLATE_IDX].set_visible(p_visible);
     }
 
 
@@ -2905,12 +3652,230 @@ fn geometry_to_spreadsheet_data(geom: &Geometry) -> (Vec<String>, Vec<Vec<String
                 widget.set_rect(x, y, w, h);
             }
         }
+        self.update_recent_files_layout();
     }
 
     fn update_panel_bounds(&mut self) {
         // Unbounded 2D canvas - no clamping
     }
 
+    fn update_paginator(&mut self) {
+        let active_menubar = self.focused_pane;
+        let title = self.widgets[active_menubar].label().unwrap_or_default();
+        let label_name = if let Some(colon_idx) = title.find(':') {
+            title.split_at(colon_idx + 1).1.trim().to_uppercase()
+        } else {
+            title.to_uppercase()
+        };
+        self.widgets[PAGINATOR_IDX].set_sidebar_label(Some(label_name));
+
+        let menu_names = self.widgets[active_menubar].menu_names();
+        let menu_items = self.widgets[active_menubar].menu_items_list();
+        let menu_checked = self.widgets[active_menubar].menu_checked_list();
+
+        let num_pages = menu_names.len();
+        self.widgets[PAGINATOR_IDX].set_pages(menu_names.clone());
+
+        // Check if we need to rebuild paginator page widgets.
+        let need_rebuild = self.last_paginator_menubar != Some(active_menubar)
+            || self.paginator_page_widgets.len() != num_pages;
+
+        if need_rebuild {
+            self.last_paginator_menubar = Some(active_menubar);
+            self.paginator_page_widgets.clear();
+            self.paginator_page_widgets.resize_with(num_pages, Vec::new);
+
+            for (page_idx, page_name) in menu_names.iter().enumerate() {
+                if page_name == "Settings" {
+                    if active_menubar == LEFT_MENUBAR_IDX {
+                        // 0: Snap to Grid (Checkbox)
+                        let mut cb = Checkbox::new().with_label("Snap to Grid");
+                        cb.set_checked(self.grid_snap_enabled);
+                        cb.set_rect(0.0, 0.0, 0.0, 42.0);
+                        self.paginator_page_widgets[page_idx].push(Box::new(cb));
+
+                        // 1: Grid Visible (Checkbox)
+                        let mut cb = Checkbox::new().with_label("Grid Visible");
+                        cb.set_checked(self.network_grid_visible);
+                        cb.set_rect(0.0, 0.0, 0.0, 42.0);
+                        self.paginator_page_widgets[page_idx].push(Box::new(cb));
+
+                        // 2: Grid X (Spinbox, range 10-200)
+                        let mut sb = Spinbox::new(self.grid_size_x as i32, 10, 200, 1).with_label("Grid X");
+                        sb.set_rect(0.0, 0.0, 0.0, 42.0);
+                        self.paginator_page_widgets[page_idx].push(Box::new(sb));
+
+                        // 3: Grid Y (Spinbox, range 5-100)
+                        let mut sb = Spinbox::new(self.grid_size_y as i32, 5, 100, 1).with_label("Grid Y");
+                        sb.set_rect(0.0, 0.0, 0.0, 42.0);
+                        self.paginator_page_widgets[page_idx].push(Box::new(sb));
+
+                        // 4: Skipped Row H (Spinbox, range 0-150)
+                        let mut sb = Spinbox::new(self.skipped_row_h as i32, 0, 150, 1).with_label("Skipped Row H");
+                        sb.set_rect(0.0, 0.0, 0.0, 42.0);
+                        self.paginator_page_widgets[page_idx].push(Box::new(sb));
+
+                        // 5: Skipped Col W (Spinbox, range 0-150)
+                        let mut sb = Spinbox::new(self.skipped_col_w as i32, 0, 150, 1).with_label("Skipped Col W");
+                        sb.set_rect(0.0, 0.0, 0.0, 42.0);
+                        self.paginator_page_widgets[page_idx].push(Box::new(sb));
+
+                        // 6: Uniform Background (Checkbox)
+                        let mut cb = Checkbox::new().with_label("Uniform Background");
+                        cb.set_checked(self.uniform_background);
+                        cb.set_rect(0.0, 0.0, 0.0, 42.0);
+                        self.paginator_page_widgets[page_idx].push(Box::new(cb));
+
+                        // 7: Opacity (Slider, range 0.0-1.0)
+                        let mut sl = Slider::new().with_range(0.0, 1.0).with_value(self.network_opacity).with_label("Opacity").with_readout(true).with_scroll(true);
+                        sl.set_rect(0.0, 0.0, 0.0, 42.0);
+                        self.paginator_page_widgets[page_idx].push(Box::new(sl));
+
+                        // 8: Node Color (ColorSelector)
+                        let mut cs = ColorSelector::new([
+                            (self.node_color[0] * 255.0) as u8,
+                            (self.node_color[1] * 255.0) as u8,
+                            (self.node_color[2] * 255.0) as u8,
+                        ]).with_label("Node Color");
+                        cs.set_rect(0.0, 0.0, 0.0, 42.0);
+                        self.paginator_page_widgets[page_idx].push(Box::new(cs));
+
+                        // 9: Cell Color (ColorSelector)
+                        let mut cs = ColorSelector::new([
+                            (self.cell_color[0] * 255.0) as u8,
+                            (self.cell_color[1] * 255.0) as u8,
+                            (self.cell_color[2] * 255.0) as u8,
+                        ]).with_label("Cell Color");
+                        cs.set_rect(0.0, 0.0, 0.0, 42.0);
+                        self.paginator_page_widgets[page_idx].push(Box::new(cs));
+
+                        // 10: Gap Color (ColorSelector)
+                        let mut cs = ColorSelector::new([
+                            (self.gap_color[0] * 255.0) as u8,
+                            (self.gap_color[1] * 255.0) as u8,
+                            (self.gap_color[2] * 255.0) as u8,
+                        ]).with_label("Gap Color");
+                        cs.set_rect(0.0, 0.0, 0.0, 42.0);
+                        self.paginator_page_widgets[page_idx].push(Box::new(cs));
+                    } else if active_menubar == RIGHT_MENUBAR_IDX {
+                        // 0: Show Grid Guide (Checkbox)
+                        let mut cb = Checkbox::new().with_label("Show Grid Guide");
+                        cb.set_checked(self.show_grid);
+                        cb.set_rect(0.0, 0.0, 0.0, 42.0);
+                        self.paginator_page_widgets[page_idx].push(Box::new(cb));
+
+                        // 1: Show Reference Cube (Checkbox)
+                        let mut cb = Checkbox::new().with_label("Show Reference Cube");
+                        cb.set_checked(self.show_cube);
+                        cb.set_rect(0.0, 0.0, 0.0, 42.0);
+                        self.paginator_page_widgets[page_idx].push(Box::new(cb));
+
+                        // 2: Show Origin Axes (Checkbox)
+                        let mut cb = Checkbox::new().with_label("Show Origin Axes");
+                        cb.set_checked(self.show_origin);
+                        cb.set_rect(0.0, 0.0, 0.0, 42.0);
+                        self.paginator_page_widgets[page_idx].push(Box::new(cb));
+
+                        // 3: Show Camera Pivot (Checkbox)
+                        let mut cb = Checkbox::new().with_label("Show Camera Pivot");
+                        cb.set_checked(self.show_camera_pivot);
+                        cb.set_rect(0.0, 0.0, 0.0, 42.0);
+                        self.paginator_page_widgets[page_idx].push(Box::new(cb));
+
+                        // 4: Grid Thickness (Spinbox, range 2-200, decimals 3)
+                        let mut sb = Spinbox::new((self.grid_thickness * 1000.0) as i32, 2, 200, 1).with_label("Grid Thickness").with_decimals(3);
+                        sb.set_rect(0.0, 0.0, 0.0, 42.0);
+                        self.paginator_page_widgets[page_idx].push(Box::new(sb));
+
+                        // 5: Origin Guide Size (Spinbox, range 1-50, decimals 1)
+                        let mut sb = Spinbox::new((self.origin_size * 10.0) as i32, 1, 50, 1).with_label("Origin Guide Size").with_decimals(1);
+                        sb.set_rect(0.0, 0.0, 0.0, 42.0);
+                        self.paginator_page_widgets[page_idx].push(Box::new(sb));
+
+                        // 6: Camera Pivot Size (Spinbox, range 1-50, decimals 1)
+                        let mut sb = Spinbox::new((self.camera_pivot_size * 10.0) as i32, 1, 50, 1).with_label("Camera Pivot Size").with_decimals(1);
+                        sb.set_rect(0.0, 0.0, 0.0, 42.0);
+                        self.paginator_page_widgets[page_idx].push(Box::new(sb));
+
+                        // 7, 8, 9: BG Color R, G, B (Spinbox, range 0-255)
+                        let mut sb = Spinbox::new((self.viewport_bg_color[0] * 255.0) as i32, 0, 255, 1).with_label("BG Color R");
+                        sb.set_rect(0.0, 0.0, 0.0, 42.0);
+                        self.paginator_page_widgets[page_idx].push(Box::new(sb));
+                        let mut sb = Spinbox::new((self.viewport_bg_color[1] * 255.0) as i32, 0, 255, 1).with_label("BG Color G");
+                        sb.set_rect(0.0, 0.0, 0.0, 42.0);
+                        self.paginator_page_widgets[page_idx].push(Box::new(sb));
+                        let mut sb = Spinbox::new((self.viewport_bg_color[2] * 255.0) as i32, 0, 255, 1).with_label("BG Color B");
+                        sb.set_rect(0.0, 0.0, 0.0, 42.0);
+                        self.paginator_page_widgets[page_idx].push(Box::new(sb));
+
+                        // 10, 11, 12: Grid Color R, G, B (Spinbox, range 0-255)
+                        let mut sb = Spinbox::new((self.grid_color[0] * 255.0) as i32, 0, 255, 1).with_label("Grid Color R");
+                        sb.set_rect(0.0, 0.0, 0.0, 42.0);
+                        self.paginator_page_widgets[page_idx].push(Box::new(sb));
+                        let mut sb = Spinbox::new((self.grid_color[1] * 255.0) as i32, 0, 255, 1).with_label("Grid Color G");
+                        sb.set_rect(0.0, 0.0, 0.0, 42.0);
+                        self.paginator_page_widgets[page_idx].push(Box::new(sb));
+                        let mut sb = Spinbox::new((self.grid_color[2] * 255.0) as i32, 0, 255, 1).with_label("Grid Color B");
+                        sb.set_rect(0.0, 0.0, 0.0, 42.0);
+                        self.paginator_page_widgets[page_idx].push(Box::new(sb));
+                    }
+                } else {
+                    let items = &menu_items[page_idx];
+                    let checked_list = menu_checked.get(page_idx);
+                    for (item_idx, item_name) in items.iter().enumerate() {
+                        let is_checked = checked_list.and_then(|l| l.get(item_idx).copied().flatten());
+                        if let Some(checked_val) = is_checked {
+                            let mut cb = Checkbox::new().with_label(item_name);
+                            cb.set_checked(checked_val);
+                            self.paginator_page_widgets[page_idx].push(Box::new(cb));
+                        } else {
+                            let btn = Button::new(0.0, 0.0, 150.0, 24.0).with_label(item_name);
+                            self.paginator_page_widgets[page_idx].push(Box::new(btn));
+                        }
+                    }
+
+                    if menu_names[page_idx] == "File" {
+                        let mut recent_lbl = Label::new("Recent Files").with_font_size(11.0).with_color([0xd4, 0xd4, 0xd4]);
+                        recent_lbl.set_rect(0.0, 0.0, 150.0, 16.0);
+                        self.paginator_page_widgets[page_idx].push(Box::new(recent_lbl));
+
+                        let mut recent_list = ScrollingList::new(22.0, 2.0);
+                        recent_list.set_rect(0.0, 0.0, 150.0, 100.0);
+                        self.paginator_page_widgets[page_idx].push(Box::new(recent_list));
+                    }
+                }
+            }
+
+            // Page widgets rebuild completed.
+        }
+
+        let sel_page = self.widgets[PAGINATOR_IDX].selected_page();
+        self.widgets[PARAM_IDX].clear_children();
+        if !self.widgets[PAGINATOR_IDX].is_page_hidden() {
+            if sel_page < self.paginator_page_widgets.len() {
+                for widget in &self.paginator_page_widgets[sel_page] {
+                    let ptr = &**widget as *const (dyn Widget + 'static) as *mut (dyn Widget + 'static);
+                    self.widgets[PARAM_IDX].add_child(ptr);
+                }
+                if menu_names.get(sel_page).map(|s| s.as_str()) == Some("File") {
+                    for btn in &mut self.recent_files_buttons {
+                        let ptr = btn as *mut Button as *mut (dyn Widget + 'static);
+                        self.widgets[PARAM_IDX].add_child(ptr);
+                    }
+                }
+            }
+        }
+
+        let (px, py, pw, ph) = self.positions[PAGINATOR_IDX];
+        self.widgets[PAGINATOR_IDX].set_rect(px, py, pw, ph);
+
+        // Layout the parameters plate with the new children immediately.
+        let (ppx, ppy, ppw, pph) = self.positions[PARAM_IDX];
+        self.widgets[PARAM_IDX].set_rect(ppx, ppy, ppw, pph);
+
+        self.update_recent_files_layout();
+    }
+
     fn sync_pane_focus(&mut self) {
         for &menubar_idx in &[HEADER_IDX, LEFT_MENUBAR_IDX, RIGHT_MENUBAR_IDX, PARAM_MENUBAR_IDX, SPREADSHEET_MENUBAR_IDX] {
             self.widgets[menubar_idx].set_selected(menubar_idx == self.focused_pane);
@@ -2919,6 +3884,7 @@ fn geometry_to_spreadsheet_data(geom: &Geometry) -> (Vec<String>, Vec<Vec<String
             self.widgets[PARAM_IDX].unfocus();
             self.sync_parameters_to_project();
         }
+        self.update_paginator();
     }
 
     fn sync_layout(&mut self) {
@@ -2926,241 +3892,60 @@ fn geometry_to_spreadsheet_data(geom: &Geometry) -> (Vec<String>, Vec<Vec<String
         let center_visible = self.show_viewport || self.show_spreadsheet;
         let right_visible = self.show_parameters;
 
-        if left_visible && center_visible {
+        if left_visible && center_visible && self.widgets[SPLITTER1_IDX].visible() && self.widgets[SPLITTER1_IDX].rect().2 > 0.0 {
             self.splitter_layout.splitter1_x = self.widgets[SPLITTER1_IDX].rect().0;
         }
-        if right_visible && (center_visible || left_visible) {
+        if right_visible && (center_visible || left_visible) && self.widgets[SPLITTER2_IDX].visible() && self.widgets[SPLITTER2_IDX].rect().2 > 0.0 {
             self.splitter_layout.splitter2_x = self.widgets[SPLITTER2_IDX].rect().0;
         }
         self.rebuild_positions();
         self.apply_layout();
         self.update_panel_bounds();
-        self.sync_pane_focus();
-    }
-
-    fn execute_action(&mut self, action: Action) {
-        let mut settings_changed = false;
-        match action {
-            Action::ToggleGrid => {
-                self.show_grid = !self.show_grid;
-                self.widgets[RIGHT_MENUBAR_IDX].set_item_checked(2, 0, self.show_grid);
-                settings_changed = true;
-            }
-            Action::ToggleCube => {
-                self.show_cube = !self.show_cube;
-                self.widgets[RIGHT_MENUBAR_IDX].set_item_checked(2, 1, self.show_cube);
-                settings_changed = true;
-            }
-            Action::ToggleOrigin => {
-                self.show_origin = !self.show_origin;
-                self.widgets[RIGHT_MENUBAR_IDX].set_item_checked(2, 2, self.show_origin);
-                settings_changed = true;
-            }
-            Action::ToggleCameraPivot => {
-                self.show_camera_pivot = !self.show_camera_pivot;
-                self.widgets[RIGHT_MENUBAR_IDX].set_item_checked(2, 3, self.show_camera_pivot);
-                settings_changed = true;
-            }
-            Action::ToggleSquareViewport => {
-                self.square_viewport = !self.square_viewport;
-                settings_changed = true;
-            }
-            Action::ToggleConfigure => {
-                use std::io::Write;
-                let json_config = serde_json::json!({
-                    "width": 550,
-                    "height": 550,
-                    "pages": [
-                        {
-                            "title": "Network",
-                            "widgets": [
-                                { "type": "checkbox", "id": "grid_snap_enabled", "text": "Snap to Grid", "checked": self.grid_snap_enabled },
-                                { "type": "checkbox", "id": "network_grid_visible", "text": "Network Grid Visible", "checked": self.network_grid_visible },
-                                { "type": "spinbox", "id": "grid_size_x", "text": "Grid X", "value": self.grid_size_x as i32, "min": 10, "max": 200, "step": 5 },
-                                { "type": "spinbox", "id": "grid_size_y", "text": "Grid Y", "value": self.grid_size_y as i32, "min": 5, "max": 100, "step": 5 },
-                                { "type": "spinbox", "id": "skipped_row_h", "text": "Skipped Row H", "value": self.skipped_row_h as i32, "min": 0, "max": 150, "step": 5 },
-                                { "type": "spinbox", "id": "skipped_col_w", "text": "Skipped Col W", "value": self.skipped_col_w as i32, "min": 0, "max": 150, "step": 5 },
-                                { "type": "color", "id": "node_color", "text": "Node Base Color", "color": [
-                                    (self.node_color[0] * 255.0).round().clamp(0.0, 255.0) as u8,
-                                    (self.node_color[1] * 255.0).round().clamp(0.0, 255.0) as u8,
-                                    (self.node_color[2] * 255.0).round().clamp(0.0, 255.0) as u8,
-                                ]},
-                                { "type": "color", "id": "cell_color", "text": "Cell Color", "color": [
-                                    (self.cell_color[0] * 255.0).round().clamp(0.0, 255.0) as u8,
-                                    (self.cell_color[1] * 255.0).round().clamp(0.0, 255.0) as u8,
-                                    (self.cell_color[2] * 255.0).round().clamp(0.0, 255.0) as u8,
-                                ]},
-                                { "type": "color", "id": "gap_color", "text": "Gap Color", "color": [
-                                    (self.gap_color[0] * 255.0).round().clamp(0.0, 255.0) as u8,
-                                    (self.gap_color[1] * 255.0).round().clamp(0.0, 255.0) as u8,
-                                    (self.gap_color[2] * 255.0).round().clamp(0.0, 255.0) as u8,
-                                ]},
-                                { "type": "checkbox", "id": "uniform_background", "text": "Uniform Background", "checked": self.uniform_background },
-                                { "type": "slider", "id": "network_opacity", "text": "Opacity", "value_f32": self.network_opacity, "min_f32": 0.0, "max_f32": 1.0 },
-                                { "type": "button", "id": "save", "text": "Save Settings" },
-                                { "type": "button", "id": "cancel", "text": "Cancel" }
-                            ]
-                        },
-                        {
-                            "title": "Viewport",
-                            "widgets": [
-                                { "type": "checkbox", "id": "show_grid", "text": "Show Grid Guide", "checked": self.show_grid },
-                                { "type": "checkbox", "id": "show_cube", "text": "Show Reference Cube", "checked": self.show_cube },
-                                { "type": "checkbox", "id": "show_origin", "text": "Show Origin Axes", "checked": self.show_origin },
-                                { "type": "checkbox", "id": "show_camera_pivot", "text": "Show Camera Pivot", "checked": self.show_camera_pivot },
-                                { "type": "spinbox", "id": "grid_thickness", "text": "Grid Thickness", "value": (self.grid_thickness * 1000.0).round() as i32, "min": 2, "max": 200, "step": 5, "decimals": 3 },
-                                { "type": "spinbox", "id": "origin_size", "text": "Origin Guide Size", "value": (self.origin_size * 10.0).round() as i32, "min": 1, "max": 50, "step": 1, "decimals": 1 },
-                                { "type": "spinbox", "id": "camera_pivot_size", "text": "Camera Pivot Size", "value": (self.camera_pivot_size * 10.0).round() as i32, "min": 1, "max": 50, "step": 1, "decimals": 1 },
-                                { "type": "color", "id": "viewport_bg_color", "text": "Viewport Background", "color": [
-                                    (self.viewport_bg_color[0] * 255.0).round().clamp(0.0, 255.0) as u8,
-                                    (self.viewport_bg_color[1] * 255.0).round().clamp(0.0, 255.0) as u8,
-                                    (self.viewport_bg_color[2] * 255.0).round().clamp(0.0, 255.0) as u8,
-                                ]},
-                                { "type": "color", "id": "grid_color", "text": "Grid Guide Color", "color": [
-                                    (self.grid_color[0] * 255.0).round().clamp(0.0, 255.0) as u8,
-                                    (self.grid_color[1] * 255.0).round().clamp(0.0, 255.0) as u8,
-                                    (self.grid_color[2] * 255.0).round().clamp(0.0, 255.0) as u8,
-                                ]},
-                                { "type": "button", "id": "save", "text": "Save Settings" },
-                                { "type": "button", "id": "cancel", "text": "Cancel" }
-                            ]
-                        },
-                        {
-                            "title": "Shortcuts",
-                            "widgets": [
-                                { "type": "label", "text": "Keyboard Shortcuts" },
-                                { "type": "label", "text": "  Ctrl+G  : Toggle Grid (viewport)" },
-                                { "type": "label", "text": "  Ctrl+E  : Toggle Cube" },
-                                { "type": "label", "text": "  Ctrl+A  : Square Viewport Aspect" },
-                                { "type": "label", "text": "  Ctrl+,  : Configure Dialog" },
-                                { "type": "button", "id": "save", "text": "Save Settings" },
-                                { "type": "button", "id": "cancel", "text": "Cancel" }
-                            ]
-                        }
-                    ]
-                });
-
-                if let Ok(mut child) = std::process::Command::new("clear-cloud")
-                    .arg("--layout")
-                    .stdin(std::process::Stdio::piped())
-                    .stdout(std::process::Stdio::piped())
-                    .spawn()
-                {
-                    if let Some(mut stdin) = child.stdin.take() {
-                        let _ = stdin.write_all(json_config.to_string().as_bytes());
-                    }
-                    if let Ok(output) = child.wait_with_output() {
-                        if output.status.success() {
-                            let out_str = String::from_utf8_lossy(&output.stdout);
-                            if let Ok(response) = serde_json::from_str::<serde_json::Value>(&out_str) {
-                                if response["button"] == "save" {
-                                    if let Some(val) = response["checkboxes"]["grid_snap_enabled"].as_bool() {
-                                        self.grid_snap_enabled = val;
-                                        self.widgets[CONTENT_IDX].set_grid_snap_enabled(val);
-                                    }
-                                    if let Some(val) = response["checkboxes"]["network_grid_visible"].as_bool() {
-                                        self.network_grid_visible = val;
-                                        self.widgets[CONTENT_IDX].set_show_network_grid(val);
-                                    }
-                                    if let Some(val) = response["checkboxes"]["show_grid"].as_bool() {
-                                        self.show_grid = val;
-                                        self.widgets[RIGHT_MENUBAR_IDX].set_item_checked(2, 0, val);
-                                    }
-                                    if let Some(val) = response["checkboxes"]["show_cube"].as_bool() {
-                                        self.show_cube = val;
-                                        self.widgets[RIGHT_MENUBAR_IDX].set_item_checked(2, 1, val);
-                                    }
-                                    if let Some(val) = response["checkboxes"]["show_origin"].as_bool() {
-                                        self.show_origin = val;
-                                        self.widgets[RIGHT_MENUBAR_IDX].set_item_checked(2, 2, val);
-                                    }
-                                    if let Some(val) = response["checkboxes"]["show_camera_pivot"].as_bool() {
-                                        self.show_camera_pivot = val;
-                                        self.widgets[RIGHT_MENUBAR_IDX].set_item_checked(2, 3, val);
-                                    }
-                                    if let Some(val) = response["checkboxes"]["uniform_background"].as_bool() {
-                                        self.uniform_background = val;
-                                    }
-                                    if let Some(val) = response["sliders"]["network_opacity"].as_f64() {
-                                        self.network_opacity = val as f32;
-                                    }
-                                    
-                                    if let Some(val) = response["spinboxes"]["grid_size_x"].as_f64() {
-                                        self.grid_size_x = val as f32;
-                                    }
-                                    if let Some(val) = response["spinboxes"]["grid_size_y"].as_f64() {
-                                        self.grid_size_y = val as f32;
-                                    }
-                                    if let Some(val) = response["spinboxes"]["skipped_row_h"].as_f64() {
-                                        self.skipped_row_h = val as f32;
-                                    }
-                                    if let Some(val) = response["spinboxes"]["skipped_col_w"].as_f64() {
-                                        self.skipped_col_w = val as f32;
-                                    }
-                                    if let Some(val) = response["spinboxes"]["origin_size"].as_f64() {
-                                        self.origin_size = val as f32 / 10.0;
-                                    }
-                                    if let Some(val) = response["spinboxes"]["grid_thickness"].as_f64() {
-                                        self.grid_thickness = val as f32 / 1000.0;
-                                    }
-                                    if let Some(val) = response["spinboxes"]["camera_pivot_size"].as_f64() {
-                                        self.camera_pivot_size = val as f32 / 10.0;
-                                    }
-
-                                    if let Some(arr) = response["colors"]["viewport_bg_color"].as_array() {
-                                        if arr.len() == 3 {
-                                            self.viewport_bg_color[0] = arr[0].as_f64().unwrap_or(0.0) as f32 / 255.0;
-                                            self.viewport_bg_color[1] = arr[1].as_f64().unwrap_or(0.0) as f32 / 255.0;
-                                            self.viewport_bg_color[2] = arr[2].as_f64().unwrap_or(0.0) as f32 / 255.0;
-                                        }
-                                    }
-
-                                    if let Some(arr) = response["colors"]["node_color"].as_array() {
-                                        if arr.len() == 3 {
-                                            self.node_color[0] = arr[0].as_f64().unwrap_or(0.0) as f32 / 255.0;
-                                            self.node_color[1] = arr[1].as_f64().unwrap_or(0.0) as f32 / 255.0;
-                                            self.node_color[2] = arr[2].as_f64().unwrap_or(0.0) as f32 / 255.0;
-                                            colors::set_node_color([self.node_color[0], self.node_color[1], self.node_color[2], 1.0]);
-                                        }
-                                    }
-
-                                    if let Some(arr) = response["colors"]["grid_color"].as_array() {
-                                        if arr.len() == 3 {
-                                            self.grid_color[0] = arr[0].as_f64().unwrap_or(0.0) as f32 / 255.0;
-                                            self.grid_color[1] = arr[1].as_f64().unwrap_or(0.0) as f32 / 255.0;
-                                            self.grid_color[2] = arr[2].as_f64().unwrap_or(0.0) as f32 / 255.0;
-                                        }
-                                    }
-
-                                    if let Some(arr) = response["colors"]["cell_color"].as_array() {
-                                        if arr.len() == 3 {
-                                            self.cell_color[0] = arr[0].as_f64().unwrap_or(0.0) as f32 / 255.0;
-                                            self.cell_color[1] = arr[1].as_f64().unwrap_or(0.0) as f32 / 255.0;
-                                            self.cell_color[2] = arr[2].as_f64().unwrap_or(0.0) as f32 / 255.0;
-                                        }
-                                    }
-
-                                    if let Some(arr) = response["colors"]["gap_color"].as_array() {
-                                        if arr.len() == 3 {
-                                            self.gap_color[0] = arr[0].as_f64().unwrap_or(0.0) as f32 / 255.0;
-                                            self.gap_color[1] = arr[1].as_f64().unwrap_or(0.0) as f32 / 255.0;
-                                            self.gap_color[2] = arr[2].as_f64().unwrap_or(0.0) as f32 / 255.0;
-                                        }
-                                    }
-
-                                    self.update_origin_geometry();
-                                    self.update_grid_geometry();
-                                    self.update_pivot_geometry();
-                                    self.sync_grid_settings();
-                                    self.save_settings();
-                                    self.sync_layout();
-                                    self.read_panel_offsets();
-                                    self.sync_cursor_and_selection();
-                                }
-                            }
-                        }
-                    }
-                }
+        self.sync_pane_focus();
+    }
+
+    fn execute_action(&mut self, action: Action) {
+        let mut settings_changed = false;
+        match action {
+            Action::ToggleGrid => {
+                self.show_grid = !self.show_grid;
+                self.widgets[RIGHT_MENUBAR_IDX].set_item_checked(2, 0, self.show_grid);
+                settings_changed = true;
+            }
+            Action::ToggleCube => {
+                self.show_cube = !self.show_cube;
+                self.widgets[RIGHT_MENUBAR_IDX].set_item_checked(2, 1, self.show_cube);
+                settings_changed = true;
+            }
+            Action::ToggleOrigin => {
+                self.show_origin = !self.show_origin;
+                self.widgets[RIGHT_MENUBAR_IDX].set_item_checked(2, 2, self.show_origin);
+                settings_changed = true;
+            }
+            Action::ToggleCameraPivot => {
+                self.show_camera_pivot = !self.show_camera_pivot;
+                self.widgets[RIGHT_MENUBAR_IDX].set_item_checked(2, 3, self.show_camera_pivot);
+                settings_changed = true;
+            }
+            Action::ToggleSquareViewport => {
+                self.square_viewport = !self.square_viewport;
+                settings_changed = true;
+            }
+            Action::ToggleConfigure => {
+                let target_pane = if self.show_network {
+                    LEFT_MENUBAR_IDX
+                } else if self.show_viewport {
+                    RIGHT_MENUBAR_IDX
+                } else {
+                    LEFT_MENUBAR_IDX
+                };
+                self.focused_pane = target_pane;
+                self.widgets[PAGINATOR_IDX].set_page_hidden(false);
+                let page_idx = if target_pane == LEFT_MENUBAR_IDX { 3 } else { 4 };
+                self.widgets[PAGINATOR_IDX].set_selected_page(page_idx);
+                self.sync_pane_focus();
+                self.rebuild_positions();
+                self.apply_layout();
                 self.upload_vertices();
                 return;
             }
@@ -3211,6 +3996,20 @@ fn geometry_to_spreadsheet_data(geom: &Geometry) -> (Vec<String>, Vec<Vec<String
                 self.apply_layout();
                 self.sync_grid_settings();
             }
+            Action::Save => {
+                let path_opt = self.loaded_project_path.clone();
+                if let Some(path) = path_opt {
+                    if let Err(e) = self.save_to_file(&path) {
+                        eprintln!("Failed to save project: {:?}", e);
+                        self.update_status_text(&format!("Failed to save: {:?}", e));
+                    } else {
+                        self.update_status_text(&format!("Project saved to {}", path.display()));
+                        self.add_recent_file(path);
+                    }
+                } else {
+                    self.save_file_chooser();
+                }
+            }
         }
         if settings_changed {
             self.save_settings();
@@ -3280,7 +4079,8 @@ fn geometry_to_spreadsheet_data(geom: &Geometry) -> (Vec<String>, Vec<Vec<String
                 self.circular_network_layout.y + self.circular_network_layout.r,
             )
         } else {
-            (0.0, node_area_y, self.content_left_w(), self.height - STATUS_H)
+            let (cx, cy, cw, ch) = self.positions[CONTENT_IDX];
+            (cx, cy, cx + cw, cy + ch)
         };
 
         let clip_circle_val = if self.circular_network_pane {
@@ -3290,17 +4090,25 @@ fn geometry_to_spreadsheet_data(geom: &Geometry) -> (Vec<String>, Vec<Vec<String
         };
 
         let mut draw_order: Vec<usize> = (0..self.widgets.len()).collect();
-        draw_order.sort_by_key(|&i| self.widgets[i].z_index());
+        draw_order.sort_by_key(|&i| {
+            if i == NETWORK_PANEL_IDX || i == PARAM_PLATE_IDX {
+                -5
+            } else if i == VIEWPORT_IDX || i == PARAM_IDX {
+                -4
+            } else {
+                self.widgets[i].z_index()
+            }
+        });
 
         for &i in &draw_order {
             let w = &self.widgets[i];
             if !w.visible() {
                 continue;
             }
-            let is_network_part = i == CONTENT_IDX || i == LEFT_MENUBAR_IDX || i == BREADCRUMB_IDX;
+            let is_network_part = i == CONTENT_IDX || i == LEFT_MENUBAR_IDX || i == BREADCRUMB_IDX || i == NETWORK_PANEL_IDX;
             let active_clip_circle = if is_network_part { clip_circle_val } else { [0.0, 0.0, 0.0] };
 
-            if i == CONTENT_IDX {
+            if i == NETWORK_PANEL_IDX {
                 if self.circular_network_pane {
                     verts.extend(circle_vertices(
                         self.circular_network_layout.x,
@@ -3308,19 +4116,10 @@ fn geometry_to_spreadsheet_data(geom: &Geometry) -> (Vec<String>, Vec<Vec<String
                         self.circular_network_layout.r,
                         sw,
                         sh,
-                        [0.10, 0.10, 0.13, self.network_opacity],
+                        w.color(),
                         64,
                         active_clip_circle,
                     ));
-                } else {
-                    verts.extend(widget_vertices(w.as_ref(), sw, sh, active_clip_circle));
-                }
-
-                for (qx, qy, qw, qh, qc) in w.extra_quads() {
-                    verts.extend(quad_vertices_clipped(qx, qy, qw, qh, sw, sh, qc, clip, active_clip_circle));
-                }
-
-                if self.circular_network_pane {
                     verts.extend(circle_border_vertices(
                         self.circular_network_layout.x,
                         self.circular_network_layout.y,
@@ -3332,6 +4131,16 @@ fn geometry_to_spreadsheet_data(geom: &Geometry) -> (Vec<String>, Vec<Vec<String
                         64,
                         active_clip_circle,
                     ));
+                } else {
+                    verts.extend(widget_vertices(w.as_ref(), sw, sh, active_clip_circle));
+                }
+            } else if i == CONTENT_IDX {
+                if !self.circular_network_pane {
+                    verts.extend(widget_vertices(w.as_ref(), sw, sh, active_clip_circle));
+                }
+
+                for (qx, qy, qw, qh, qc) in w.extra_quads() {
+                    verts.extend(extra_quad_vertices_clipped(w.as_ref(), qx, qy, qw, qh, sw, sh, qc, clip, active_clip_circle));
                 }
             } else if i == LEFT_MENUBAR_IDX && self.circular_network_pane {
                 let cx = self.circular_network_layout.x;
@@ -3363,7 +4172,7 @@ fn geometry_to_spreadsheet_data(geom: &Geometry) -> (Vec<String>, Vec<Vec<String
                 ));
                 
                 for (qx, qy, qw, qh, qc) in w.extra_quads() {
-                    verts.extend(quad_vertices(qx, qy, qw, qh, sw, sh, qc, active_clip_circle));
+                    verts.extend(extra_quad_vertices(w.as_ref(), qx, qy, qw, qh, sw, sh, qc, active_clip_circle));
                 }
                 for (acx, acy, ar, ath, a_start, a_end, acolor) in w.extra_arcs() {
                     verts.extend(arc_background_vertices(
@@ -3379,7 +4188,7 @@ fn geometry_to_spreadsheet_data(geom: &Geometry) -> (Vec<String>, Vec<Vec<String
             } else {
                 verts.extend(widget_vertices(w.as_ref(), sw, sh, active_clip_circle));
                 for (qx, qy, qw, qh, qc) in w.extra_quads() {
-                    verts.extend(quad_vertices(qx, qy, qw, qh, sw, sh, qc, active_clip_circle));
+                    verts.extend(extra_quad_vertices(w.as_ref(), qx, qy, qw, qh, sw, sh, qc, active_clip_circle));
                 }
                 for (acx, acy, ar, ath, a_start, a_end, acolor) in w.extra_arcs() {
                     verts.extend(arc_background_vertices(
@@ -3402,11 +4211,7 @@ fn geometry_to_spreadsheet_data(geom: &Geometry) -> (Vec<String>, Vec<Vec<String
                 } else {
                     node_area_y
                 };
-                let active_node_area_x = if self.circular_network_pane {
-                    self.circular_network_layout.x - self.circular_network_layout.r
-                } else {
-                    0.0
-                };
+                let active_node_area_x = self.positions[CONTENT_IDX].0;
                 let cx = active_node_area_x + self.grid_cursor_col as f32 * (self.grid_size_x + self.skipped_col_w) + self.pan_x;
                 let cy = active_node_area_y + self.grid_cursor_row as f32 * (self.grid_size_y + self.skipped_row_h) + self.pan_y;
                 let cw = self.grid_size_x;
@@ -3560,7 +4365,7 @@ fn geometry_to_spreadsheet_data(geom: &Geometry) -> (Vec<String>, Vec<Vec<String
                 continue;
             }
             let is_node = i == CONTENT_IDX;
-            let is_network_part = i == CONTENT_IDX || i == LEFT_MENUBAR_IDX || i == BREADCRUMB_IDX;
+            let is_network_part = i == CONTENT_IDX || i == LEFT_MENUBAR_IDX || i == BREADCRUMB_IDX || i == NETWORK_PANEL_IDX;
 
             let bounds = if is_node {
                 let node_area_y = self.positions[CONTENT_IDX].1;
@@ -3613,11 +4418,11 @@ fn geometry_to_spreadsheet_data(geom: &Geometry) -> (Vec<String>, Vec<Vec<String
                     });
                 }
             } else {
-                let labels = w.text_labels();
+                let labels = w.text_labels_with_font_and_bounds();
                 // if i == LEFT_MENUBAR_IDX {
                 //     eprintln!("DEBUG_PREPARE_ELSE: i={} labels.len={}", i, labels.len());
                 // }
-                for label in labels {
+                for (label, font_opt, label_bounds) in labels {
                     let mut is_curved = false;
                     if circular_network_pane && i == LEFT_MENUBAR_IDX && label.text.chars().count() == 1 {
                         let dx = label.x - network_circle_x;
@@ -3640,9 +4445,25 @@ fn geometry_to_spreadsheet_data(geom: &Geometry) -> (Vec<String>, Vec<Vec<String
                                 continue;
                             }
                         }
-                        legacy_buffers.push(make_text_buffer(font_system, &label.text, label.font_size));
+                        let mut item_bounds = bounds;
+                        if let Some([l, t, r, b]) = label_bounds {
+                            let pl = (l * s).round() as i32;
+                            let pt = (t * s).round() as i32;
+                            let pr = (r * s).round() as i32;
+                            let pb = (b * s).round() as i32;
+                            item_bounds = TextBounds {
+                                left: item_bounds.left.max(pl),
+                                top: item_bounds.top.max(pt),
+                                right: item_bounds.right.min(pr),
+                                bottom: item_bounds.bottom.min(pb),
+                            };
+                        }
+                        if label.text.contains("Grid") || label.text.contains("Background") || label.text.contains("Snap") || label.text.contains("Opacity") {
+                            eprintln!("DEBUG_PREPARE_LABEL: text='{}' x={} y={} font_size={} s={} left={} top={} bounds={:?}", label.text, label.x, label.y, label.font_size, s, label.x * s, label.y * s, item_bounds);
+                        }
+                        legacy_buffers.push(make_text_buffer_with_font(font_system, &label.text, label.font_size, font_opt.as_deref()));
                         legacy_labels.push(label);
-                        legacy_bounds.push(bounds);
+                        legacy_bounds.push(item_bounds);
                     }
                 }
             }
@@ -3853,12 +4674,32 @@ fn geometry_to_spreadsheet_data(geom: &Geometry) -> (Vec<String>, Vec<Vec<String
             self.height = height as f32 / self.scale as f32;
             self.config.width = width;
             self.config.height = height;
+            self.config.usage = wgpu::TextureUsages::RENDER_ATTACHMENT | wgpu::TextureUsages::COPY_DST;
             self.surface.configure(&self.device, &self.config);
 
             let (tex, view) = self.create_depth_texture();
             self.depth_texture = tex;
             self.depth_texture_view = view;
 
+            let (b_tex, b_view) = self.create_backdrop_texture();
+            self.backdrop_texture = b_tex;
+            self.backdrop_texture_view = b_view;
+
+            self.backdrop_bind_group = self.device.create_bind_group(&wgpu::BindGroupDescriptor {
+                label: Some("Backdrop Bind Group"),
+                layout: &self.backdrop_bind_group_layout,
+                entries: &[
+                    wgpu::BindGroupEntry {
+                        binding: 0,
+                        resource: wgpu::BindingResource::TextureView(&self.backdrop_texture_view),
+                    },
+                    wgpu::BindGroupEntry {
+                        binding: 1,
+                        resource: wgpu::BindingResource::Sampler(&self.backdrop_sampler),
+                    },
+                ],
+            });
+
             if old_width > 0.0 {
                 let r = self.width / old_width;
                 self.splitter_layout.scale(r);
@@ -3918,11 +4759,7 @@ fn geometry_to_spreadsheet_data(geom: &Geometry) -> (Vec<String>, Vec<Vec<String
                             handled = true;
                             if i == CONTENT_IDX {
                                 let active_node_area_y = self.positions[CONTENT_IDX].1;
-                                let active_node_area_x = if self.circular_network_pane {
-                                    self.circular_network_layout.x - self.circular_network_layout.r
-                                } else {
-                                    0.0
-                                };
+                                let active_node_area_x = self.positions[CONTENT_IDX].0;
                                 let (gx, gy) = w.grid_origin();
                                 let prev_pan_x = self.pan_x;
                                 let prev_pan_y = self.pan_y;
@@ -4172,14 +5009,7 @@ fn geometry_to_spreadsheet_data(geom: &Geometry) -> (Vec<String>, Vec<Vec<String
                 let _ = std::io::Write::flush(&mut std::io::stdout());
                 let mut changed = false;
 
-                if self.is_dragging_network_circle {
-                    self.circular_network_layout.x = self.cursor_x - self.circle_drag_ox;
-                    self.circular_network_layout.y = self.cursor_y - self.circle_drag_oy;
-                    self.rebuild_positions();
-                    self.apply_layout();
-                    self.sync_grid_settings();
-                    changed = true;
-                } else if self.is_panning {
+                if self.is_panning {
                     let dx = self.cursor_x - self.pan_start_cx;
                     let dy = self.cursor_y - self.pan_start_cy;
                     self.pan_x = self.pan_start_x + dx;
@@ -4191,15 +5021,69 @@ fn geometry_to_spreadsheet_data(geom: &Geometry) -> (Vec<String>, Vec<Vec<String
                     changed = true;
                 } else {
                     if let Some(idx) = self.drag_widget {
-                        if self.widgets[idx].drag_update(self.cursor_x, self.cursor_y) {
+                        if idx == NETWORK_PANEL_IDX && self.is_resizing_network.is_some() {
+                            let dir = self.is_resizing_network.unwrap();
+                            let dx = self.cursor_x - self.drag_start_mouse.0;
+                            let dy = self.cursor_y - self.drag_start_mouse.1;
+                            let (sx, sy, sw, sh) = self.drag_start_rect;
+
+                            let mut fx = sx;
+                            let mut fy = sy;
+                            let mut fw = sw;
+                            let mut fh = sh;
+
+                            if dir.left {
+                                let new_w = (sw - dx).max(150.0);
+                                fx = sx + sw - new_w;
+                                fw = new_w;
+                            } else if dir.right {
+                                fw = (sw + dx).max(150.0);
+                            }
+
+                            if dir.top {
+                                let new_h = (sh - dy).max(100.0);
+                                fy = sy + sh - new_h;
+                                fh = new_h;
+                            } else if dir.bottom {
+                                fh = (sh + dy).max(100.0);
+                            }
+
+                            self.floating_network_layout = (fx, fy, fw, fh);
+                            self.rebuild_positions();
+                            self.apply_layout();
+                            self.sync_grid_settings();
+                            changed = true;
+                        } else if idx == PARAM_IDX && self.is_resizing_param {
+                            let dx = self.cursor_x - self.drag_start_mouse.0;
+                            let sw = self.drag_start_param_w;
+                            let new_w = (sw - dx).max(150.0);
+                            self.floating_param_width = new_w;
+                            self.rebuild_positions();
+                            self.apply_layout();
+                            changed = true;
+                        } else if idx == SPREADSHEET_IDX && self.is_resizing_spreadsheet {
+                            let dy = self.cursor_y - self.drag_start_mouse.1;
+                            let sh = self.drag_start_spreadsheet_h;
+                            let new_h = (sh - dy).max(100.0);
+                            self.floating_spreadsheet_height = new_h;
+                            self.rebuild_positions();
+                            self.apply_layout();
                             changed = true;
+                        } else {
+                            self.widgets[idx].set_modifiers(self.modifiers.control_key(), self.modifiers.shift_key(), self.modifiers.alt_key());
+                            if self.widgets[idx].drag_update(self.cursor_x, self.cursor_y) {
+                                changed = true;
+                                if idx == NETWORK_PANEL_IDX {
+                                    self.sync_grid_settings();
+                                }
+                            }
                         }
                     }
 
                     if self.drag_widget.is_none() {
                         for i in 0..self.widgets.len() {
                             let (cx, cy) = (self.cursor_x, self.cursor_y);
-                            let is_network_part = i == CONTENT_IDX || i == LEFT_MENUBAR_IDX || i == BREADCRUMB_IDX;
+                            let is_network_part = i == CONTENT_IDX || i == LEFT_MENUBAR_IDX || i == BREADCRUMB_IDX || i == NETWORK_PANEL_IDX;
                             let inside = if self.circular_network_pane && is_network_part {
                                 if i == CONTENT_IDX {
                                     self.circular_network_layout.hit_test_content(cx, cy, MENUBAR_H, BREADCRUMB_H)
@@ -4207,6 +5091,8 @@ fn geometry_to_spreadsheet_data(geom: &Geometry) -> (Vec<String>, Vec<Vec<String
                                     self.circular_network_layout.hit_test_menubar(cx, cy, MENUBAR_H)
                                 } else if i == BREADCRUMB_IDX {
                                     self.circular_network_layout.hit_test_breadcrumb(cx, cy, MENUBAR_H, BREADCRUMB_H)
+                                } else if i == NETWORK_PANEL_IDX {
+                                    self.widgets[NETWORK_PANEL_IDX].hit_test(cx, cy)
                                 } else {
                                     false
                                 }
@@ -4244,6 +5130,7 @@ fn geometry_to_spreadsheet_data(geom: &Geometry) -> (Vec<String>, Vec<Vec<String
                 }
                 let dialog_open = self.node_palette_visible;
                 let in_network_pane = self.in_network_pane();
+                let node_area_x = self.positions[CONTENT_IDX].0;
                 let node_area_y = self.positions[CONTENT_IDX].1;
 
                 let is_pan_trigger = !dialog_open
@@ -4288,6 +5175,7 @@ fn geometry_to_spreadsheet_data(geom: &Geometry) -> (Vec<String>, Vec<Vec<String
                 if *button != MouseButton::Left && *button != MouseButton::Right { return false; }
                 let mut changed = false;
                 let old_focus = self.focused_widget;
+                let was_page_hidden = self.widgets[PAGINATOR_IDX].is_page_hidden();
 
                 let hits_widget = |state: &State, i: usize, x: f32, y: f32| -> bool {
                     if state.circular_network_pane && (i == CONTENT_IDX || i == LEFT_MENUBAR_IDX || i == BREADCRUMB_IDX) {
@@ -4346,9 +5234,57 @@ fn geometry_to_spreadsheet_data(geom: &Geometry) -> (Vec<String>, Vec<Vec<String
                                 }
                             }
                             if on_border || hit_menubar {
-                                self.is_dragging_network_circle = true;
-                                self.circle_drag_ox = self.cursor_x - self.circular_network_layout.x;
-                                self.circle_drag_oy = self.cursor_y - self.circular_network_layout.y;
+                                self.drag_widget = Some(NETWORK_PANEL_IDX);
+                                self.widgets[NETWORK_PANEL_IDX].set_modifiers(self.modifiers.control_key(), self.modifiers.shift_key(), self.modifiers.alt_key());
+                                self.widgets[NETWORK_PANEL_IDX].drag_begin(self.cursor_x, self.cursor_y);
+                                self.focused_pane = LEFT_MENUBAR_IDX;
+                                if let Some(old) = self.focused_widget {
+                                    self.widgets[old].unfocus();
+                                    self.focused_widget = None;
+                                }
+                                self.widgets[PARAM_IDX].unfocus();
+                                self.sync_parameters_to_project();
+                                return true;
+                            }
+                        }
+
+                        if *button == MouseButton::Left && !self.circular_network_pane && self.show_network {
+                            let (fx, fy, fw, fh) = self.floating_network_layout;
+                            let cx = self.cursor_x;
+                            let cy = self.cursor_y;
+
+                            let margin = 8.0_f32;
+                            let on_left = false;
+                            let on_right = cx >= fx + fw - margin && cx <= fx + fw + margin && cy >= fy - margin && cy <= fy + fh + margin;
+                            let on_top = false;
+                            let on_bottom = false;
+
+                            if on_left || on_right || on_top || on_bottom {
+                                self.is_resizing_network = Some(ResizeDirection {
+                                    left: on_left,
+                                    right: on_right,
+                                    top: on_top,
+                                    bottom: on_bottom,
+                                });
+                                self.drag_widget = Some(NETWORK_PANEL_IDX);
+                                self.drag_start_rect = self.floating_network_layout;
+                                self.drag_start_mouse = (cx, cy);
+                                self.focused_pane = LEFT_MENUBAR_IDX;
+                                if let Some(old) = self.focused_widget {
+                                    self.widgets[old].unfocus();
+                                    self.focused_widget = None;
+                                }
+                                self.widgets[PARAM_IDX].unfocus();
+                                self.sync_parameters_to_project();
+                                return true;
+                            } else if cx >= fx && cx < fx + fw && cy >= fy && cy < fy + (if self.show_network { BREADCRUMB_H } else { 0.0 }) {
+                                if self.widgets[BREADCRUMB_IDX].mouse_input(*button, *btn_state, cx, cy) {
+                                    return true;
+                                }
+                                self.is_resizing_network = None;
+                                self.drag_widget = Some(NETWORK_PANEL_IDX);
+                                self.widgets[NETWORK_PANEL_IDX].set_modifiers(self.modifiers.control_key(), self.modifiers.shift_key(), self.modifiers.alt_key());
+                                self.widgets[NETWORK_PANEL_IDX].drag_begin(cx, cy);
                                 self.focused_pane = LEFT_MENUBAR_IDX;
                                 if let Some(old) = self.focused_widget {
                                     self.widgets[old].unfocus();
@@ -4360,9 +5296,71 @@ fn geometry_to_spreadsheet_data(geom: &Geometry) -> (Vec<String>, Vec<Vec<String
                             }
                         }
 
+                        if *button == MouseButton::Left && self.show_parameters {
+                            let gap = 18.0_f32;
+                            let param_w = self.floating_param_width;
+                            let param_x = self.width - gap - param_w;
+                            let param_y = HEADER_H + gap;
+                            let param_h = (self.height - HEADER_H - STATUS_H - 2.0 * gap).max(100.0);
+                            let cx = self.cursor_x;
+                            let cy = self.cursor_y;
+
+                            let margin = 8.0_f32;
+                            let on_left = cx >= param_x - margin && cx <= param_x + margin && cy >= param_y - margin && cy <= param_y + param_h + margin;
+
+                            if on_left {
+                                self.is_resizing_param = true;
+                                self.drag_widget = Some(PARAM_IDX);
+                                self.drag_start_param_w = param_w;
+                                self.drag_start_mouse = (cx, cy);
+                                self.focused_pane = PARAM_MENUBAR_IDX;
+                                if let Some(old) = self.focused_widget {
+                                    self.widgets[old].unfocus();
+                                    self.focused_widget = None;
+                                }
+                                return true;
+                            }
+                        }
+
+                        if *button == MouseButton::Left && self.show_spreadsheet {
+                            let gap = 18.0_f32;
+                            let fx = gap;
+                            let (mut _n_fx, mut _n_fy, mut fw, mut _n_fh) = self.floating_network_layout;
+                            fw = fw.clamp(150.0, (self.width - 2.0 * gap).max(150.0));
+                            
+                            let param_w = self.floating_param_width.clamp(150.0, (self.width - 2.0 * gap).max(150.0));
+                            let param_x = self.width - gap - param_w;
+                            
+                            let ss_x = if self.show_network { fx + fw + gap } else { gap };
+                            let ss_w_end = if self.show_parameters { param_x - gap } else { self.width - gap };
+                            let ss_w = (ss_w_end - ss_x).max(150.0);
+                            
+                            let ss_y_end = self.height - STATUS_H - gap;
+                            let ss_h = self.floating_spreadsheet_height.clamp(100.0, (ss_y_end - HEADER_H - gap).max(100.0));
+                            let ss_y = ss_y_end - ss_h;
+                            
+                            let cx = self.cursor_x;
+                            let cy = self.cursor_y;
+                            let margin = 8.0_f32;
+                            let on_top = cx >= ss_x && cx <= ss_x + ss_w && cy >= ss_y - margin && cy <= ss_y + margin;
+                            
+                            if on_top {
+                                self.is_resizing_spreadsheet = true;
+                                self.drag_widget = Some(SPREADSHEET_IDX);
+                                self.drag_start_spreadsheet_h = ss_h;
+                                self.drag_start_mouse = (cx, cy);
+                                self.focused_pane = SPREADSHEET_MENUBAR_IDX;
+                                if let Some(old) = self.focused_widget {
+                                    self.widgets[old].unfocus();
+                                    self.focused_widget = None;
+                                }
+                                return true;
+                            }
+                        }
+
                         if *button == MouseButton::Right {
                             if !dialog_open && in_circle_network_pane {
-                                let col = ((self.cursor_x - self.pan_x) / (self.grid_size_x + self.skipped_col_w)).floor() as i32;
+                                let col = ((self.cursor_x - node_area_x - self.pan_x) / (self.grid_size_x + self.skipped_col_w)).floor() as i32;
                                 let row = ((self.cursor_y - node_area_y - self.pan_y) / (self.grid_size_y + self.skipped_row_h)).floor() as i32;
                                 self.grid_cursor_col = col;
                                 self.grid_cursor_row = row;
@@ -4379,18 +5377,29 @@ fn geometry_to_spreadsheet_data(geom: &Geometry) -> (Vec<String>, Vec<Vec<String
                             }
                         }
                         if click_target.is_none() {
-                            click_target = (0..self.widgets.len()).rev()
+                            let mut hit_order: Vec<usize> = (0..self.widgets.len()).collect();
+                            hit_order.sort_by_key(|&i| {
+                                let z = if i == NETWORK_PANEL_IDX || i == PARAM_PLATE_IDX {
+                                    -5
+                                } else if i == VIEWPORT_IDX || i == PARAM_IDX {
+                                    -4
+                                } else {
+                                    self.widgets[i].z_index()
+                                };
+                                -z
+                            });
+                            click_target = hit_order.into_iter()
                                 .find(|&i| hits_widget(self, i, self.cursor_x, self.cursor_y));
                         }
 
                         // Determine new focused pane
                         let mut new_pane = None;
                         if let Some(i) = click_target {
-                            if i == LEFT_MENUBAR_IDX || i == CONTENT_IDX || i == CANVAS_IDX || i == BREADCRUMB_IDX {
+                            if i == LEFT_MENUBAR_IDX || i == CONTENT_IDX || i == CANVAS_IDX || i == BREADCRUMB_IDX || i == NETWORK_PANEL_IDX {
                                 new_pane = Some(LEFT_MENUBAR_IDX);
                             } else if i == RIGHT_MENUBAR_IDX || i == VIEWPORT_IDX {
                                 new_pane = Some(RIGHT_MENUBAR_IDX);
-                            } else if i == PARAM_MENUBAR_IDX || i == PARAM_IDX {
+                            } else if i == PARAM_MENUBAR_IDX || i == PARAM_IDX || i == PARAM_PLATE_IDX {
                                 new_pane = Some(PARAM_MENUBAR_IDX);
                             } else if i == SPREADSHEET_MENUBAR_IDX || i == SPREADSHEET_IDX {
                                 new_pane = Some(SPREADSHEET_MENUBAR_IDX);
@@ -4447,7 +5456,7 @@ fn geometry_to_spreadsheet_data(geom: &Geometry) -> (Vec<String>, Vec<Vec<String
                             self.sync_parameters_to_project();
                         }
                         if click_target.is_none() && !dialog_open && in_circle_network_pane {
-                            let col = ((self.cursor_x - self.pan_x) / (self.grid_size_x + self.skipped_col_w)).floor() as i32;
+                            let col = ((self.cursor_x - node_area_x - self.pan_x) / (self.grid_size_x + self.skipped_col_w)).floor() as i32;
                             let row = ((self.cursor_y - node_area_y - self.pan_y) / (self.grid_size_y + self.skipped_row_h)).floor() as i32;
                             self.grid_cursor_col = col;
                             self.grid_cursor_row = row;
@@ -4458,13 +5467,26 @@ fn geometry_to_spreadsheet_data(geom: &Geometry) -> (Vec<String>, Vec<Vec<String
                                 self.spawn_menu_cloud(i, menu_idx, title, items, rx, ry, rw, rh);
                                 return true;
                             }
+                            self.widgets[i].set_modifiers(self.modifiers.control_key(), self.modifiers.shift_key(), self.modifiers.alt_key());
                             if self.widgets[i].mouse_input(*button, *btn_state, self.cursor_x, self.cursor_y) {
                                 changed = true;
                                 if i == PARAM_IDX {
                                     self.sync_parameters_to_project();
                                 }
+                                if i == PAGINATOR_IDX {
+                                    self.update_paginator();
+                                    self.rebuild_positions();
+                                    self.apply_layout();
+                                }
+                            } else if i == PAGINATOR_IDX {
+                                if *button == MouseButton::Left {
+                                    self.focused_pane = HEADER_IDX;
+                                    self.sync_pane_focus();
+                                    changed = true;
+                                }
                             }
                             if self.widgets[i].draggable() {
+                                self.widgets[i].set_modifiers(self.modifiers.control_key(), self.modifiers.shift_key(), self.modifiers.alt_key());
                                 self.widgets[i].drag_begin(self.cursor_x, self.cursor_y);
                                 self.drag_widget = Some(i);
                             }
@@ -4485,7 +5507,7 @@ fn geometry_to_spreadsheet_data(geom: &Geometry) -> (Vec<String>, Vec<Vec<String
                                         self.grid_cursor_row = pos.1 as i32;
                                     }
                                 } else {
-                                    let col = ((self.cursor_x - self.pan_x) / (self.grid_size_x + self.skipped_col_w)).floor() as i32;
+                                    let col = ((self.cursor_x - node_area_x - self.pan_x) / (self.grid_size_x + self.skipped_col_w)).floor() as i32;
                                     let row = ((self.cursor_y - node_area_y - self.pan_y) / (self.grid_size_y + self.skipped_row_h)).floor() as i32;
                                     self.grid_cursor_col = col;
                                     self.grid_cursor_row = row;
@@ -4505,16 +5527,25 @@ fn geometry_to_spreadsheet_data(geom: &Geometry) -> (Vec<String>, Vec<Vec<String
                         self.sync_pane_focus();
                     }
                     ElementState::Released => {
-                        if self.is_dragging_network_circle {
-                            self.is_dragging_network_circle = false;
-                            self.sync_layout();
-                            self.read_panel_offsets();
-                            self.upload_vertices();
-                            return true;
-                        }
                         if self.drag_widget.is_some() {
                             let idx = self.drag_widget.unwrap();
-                            if idx == CONTENT_IDX {
+                            if idx == NETWORK_PANEL_IDX {
+                                self.widgets[idx].drag_end();
+                                self.is_resizing_network = None;
+                                self.sync_layout();
+                                self.read_panel_offsets();
+                                self.upload_vertices();
+                            } else if idx == PARAM_IDX {
+                                self.widgets[idx].drag_end();
+                                self.is_resizing_param = false;
+                                self.sync_layout();
+                                self.upload_vertices();
+                            } else if idx == SPREADSHEET_IDX {
+                                self.widgets[idx].drag_end();
+                                self.is_resizing_spreadsheet = false;
+                                self.sync_layout();
+                                self.upload_vertices();
+                            } else if idx == CONTENT_IDX {
                                 self.widgets[idx].drag_end();
                                 let updated_nodes = self.widgets[CONTENT_IDX].get_nodes();
                                 let dir = self.current_dir_mut();
@@ -4540,6 +5571,7 @@ fn geometry_to_spreadsheet_data(geom: &Geometry) -> (Vec<String>, Vec<Vec<String
                             changed = true;
                         }
                         for w in &mut self.widgets {
+                            w.set_modifiers(self.modifiers.control_key(), self.modifiers.shift_key(), self.modifiers.alt_key());
                             if w.mouse_input(*button, *btn_state, self.cursor_x, self.cursor_y) {
                                 changed = true;
                             }
@@ -4554,6 +5586,14 @@ fn geometry_to_spreadsheet_data(geom: &Geometry) -> (Vec<String>, Vec<Vec<String
                     changed = true;
                 }
 
+                if self.widgets[PAGINATOR_IDX].is_page_hidden() != was_page_hidden {
+                    self.rebuild_positions();
+                    self.apply_layout();
+                    self.sync_pane_focus();
+                    self.sync_nodes();
+                    changed = true;
+                }
+
                 if self.focused_widget != old_focus {
                     changed = true;
                 }
@@ -4759,9 +5799,9 @@ fn geometry_to_spreadsheet_data(geom: &Geometry) -> (Vec<String>, Vec<Vec<String
                                                     let w_base = b_xmax - b_xmin;
                                                     let h_base = b_ymax - b_ymin;
 
-                                                    let viewport_w = self.content_left_w();
-                                                    let body_h = self.body_h();
-                                                    let viewport_h = body_h - MENUBAR_H - BREADCRUMB_H;
+                                                     let (_px, _py, pw, ph) = self.positions[CONTENT_IDX];
+                                                     let viewport_w = pw;
+                                                     let viewport_h = ph;
 
                                                     let padding = 40.0;
                                                     let padded_w = (viewport_w - 2.0 * padding).max(10.0);
@@ -4821,7 +5861,7 @@ fn geometry_to_spreadsheet_data(geom: &Geometry) -> (Vec<String>, Vec<Vec<String
                         self.keep_cursor_in_view();
                     }
 
-                    if !changed {
+                    if !changed && event.state == ElementState::Pressed {
                         if event.logical_key == Key::Named(NamedKey::Tab) {
                             self.open_node_palette();
                             return true;
@@ -4948,6 +5988,7 @@ fn geometry_to_spreadsheet_data(geom: &Geometry) -> (Vec<String>, Vec<Vec<String
                 tick_changed = true;
             }
         }
+        self.update_recent_files_layout();
 
         // Panning kinetic slide
         if !self.is_panning && !self.is_scrolling_trackpad && (self.pan_velocity_x.abs() > 0.01 || self.pan_velocity_y.abs() > 0.01) {
@@ -5023,27 +6064,25 @@ fn geometry_to_spreadsheet_data(geom: &Geometry) -> (Vec<String>, Vec<Vec<String
         let mut panned = false;
         if let Some(idx) = self.drag_widget {
             if idx == CONTENT_IDX {
-                let node_area_y = self.positions[CONTENT_IDX].1;
+                let (px, py, pw, ph) = self.positions[CONTENT_IDX];
                 let margin = 30.0_f32;
                 let pan_speed = 5.0_f32;
-                let clw = self.content_left_w();
-                let max_y = self.height - STATUS_H;
 
-                if self.cursor_x >= -50.0 && self.cursor_x < clw + 50.0
-                    && self.cursor_y >= node_area_y - 50.0 && self.cursor_y < max_y + 50.0
+                if self.cursor_x >= px - 50.0 && self.cursor_x < px + pw + 50.0
+                    && self.cursor_y >= py - 50.0 && self.cursor_y < py + ph + 50.0
                 {
-                    if self.cursor_x < margin {
+                    if self.cursor_x < px + margin {
                         self.pan_x += pan_speed;
                         panned = true;
-                    } else if self.cursor_x > clw - margin {
+                    } else if self.cursor_x > px + pw - margin {
                         self.pan_x -= pan_speed;
                         panned = true;
                     }
 
-                    if self.cursor_y < node_area_y + margin {
+                    if self.cursor_y < py + margin {
                         self.pan_y += pan_speed;
                         panned = true;
-                    } else if self.cursor_y > max_y - margin {
+                    } else if self.cursor_y > py + ph - margin {
                         self.pan_y -= pan_speed;
                         panned = true;
                     }
@@ -5059,6 +6098,7 @@ fn geometry_to_spreadsheet_data(geom: &Geometry) -> (Vec<String>, Vec<Vec<String
             self.scroll_accum_y = 0.0;
             self.sync_grid_settings();
             if let Some(idx) = self.drag_widget {
+                self.widgets[idx].set_modifiers(self.modifiers.control_key(), self.modifiers.shift_key(), self.modifiers.alt_key());
                 self.widgets[idx].drag_update(self.cursor_x, self.cursor_y);
             }
             self.sync_layout();
@@ -5189,7 +6229,7 @@ fn geometry_to_spreadsheet_data(geom: &Geometry) -> (Vec<String>, Vec<Vec<String
                 let mut pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
                     label: Some("3D Render Pass"),
                     color_attachments: &[Some(wgpu::RenderPassColorAttachment {
-                        view: &view,
+                        view: &self.backdrop_texture_view,
                         resolve_target: None,
                         ops: wgpu::Operations {
                             load: wgpu::LoadOp::Clear(wgpu::Color {
@@ -5245,7 +6285,65 @@ fn geometry_to_spreadsheet_data(geom: &Geometry) -> (Vec<String>, Vec<Vec<String
                     pass.set_vertex_buffer(0, self.vertex_buffer_spheres.slice(..));
                     pass.draw(0..self.vertex_count_spheres, 0..1);
                 }
+            } else {
+                let _pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
+                    label: Some("Backdrop Clear Pass"),
+                    color_attachments: &[Some(wgpu::RenderPassColorAttachment {
+                        view: &self.backdrop_texture_view,
+                        resolve_target: None,
+                        ops: wgpu::Operations {
+                            load: wgpu::LoadOp::Clear(wgpu::Color {
+                                r: self.viewport_bg_color[0] as f64,
+                                g: self.viewport_bg_color[1] as f64,
+                                b: self.viewport_bg_color[2] as f64,
+                                a: 1.0,
+                            }),
+                            store: wgpu::StoreOp::Store,
+                        },
+                    })],
+                    depth_stencil_attachment: None,
+                    timestamp_writes: None,
+                    occlusion_query_set: None,
+                });
             }
+        } else {
+            let _pass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor {
+                label: Some("Backdrop Clear Pass"),
+                color_attachments: &[Some(wgpu::RenderPassColorAttachment {
+                    view: &self.backdrop_texture_view,
+                    resolve_target: None,
+                    ops: wgpu::Operations {
+                        load: wgpu::LoadOp::Clear(wgpu::Color { r: 0.0, g: 0.0, b: 0.0, a: 0.0 }),
+                        store: wgpu::StoreOp::Store,
+                    },
+                })],
+                depth_stencil_attachment: None,
+                timestamp_writes: None,
+                occlusion_query_set: None,
+            });
+        }
+
+        // Copy backdrop to output swapchain texture
+        if !self.is_detached_network {
+            encoder.copy_texture_to_texture(
+                wgpu::ImageCopyTexture {
+                    texture: &self.backdrop_texture,
+                    mip_level: 0,
+                    origin: wgpu::Origin3d::ZERO,
+                    aspect: wgpu::TextureAspect::All,
+                },
+                wgpu::ImageCopyTexture {
+                    texture: &output.texture,
+                    mip_level: 0,
+                    origin: wgpu::Origin3d::ZERO,
+                    aspect: wgpu::TextureAspect::All,
+                },
+                wgpu::Extent3d {
+                    width: self.physical_width,
+                    height: self.physical_height,
+                    depth_or_array_layers: 1,
+                },
+            );
         }
 
         // UI render pass (foreground layer)
@@ -5272,6 +6370,7 @@ fn geometry_to_spreadsheet_data(geom: &Geometry) -> (Vec<String>, Vec<Vec<String
             });
 
             pass.set_pipeline(&self.render_pipeline);
+            pass.set_bind_group(0, &self.backdrop_bind_group, &[]);
             pass.set_vertex_buffer(0, self.vertex_buffer.slice(..));
             pass.draw(0..self.vertex_count, 0..1);
 
@@ -5316,6 +6415,14 @@ fn is_repeatable_key(key: &clear_ui::widget::Key) -> bool {
     }
 }
 
+struct PendingResize {
+    serial: u32,
+    edge: smithay_client_toolkit::reexports::protocols::xdg::shell::client::xdg_toplevel::ResizeEdge,
+    start_x: f32,
+    start_y: f32,
+    is_move: bool,
+}
+
 struct AppState {
     registry_state: RegistryState,
     compositor_state: CompositorState,
@@ -5336,6 +6443,7 @@ struct AppState {
     redraw: bool,
     pressed_key: Option<PressedKey>,
     inspector: Option<clear_ui::protocol::zclear_inspector_v1::ZclearInspectorV1>,
+    pending_resize: Option<PendingResize>,
     _sender: calloop::channel::Sender<CustomEvent>,
 }
 
@@ -5350,6 +6458,7 @@ impl CompositorHandler for AppState {
         _surface.set_buffer_scale(scale_factor);
         if let Some(state) = &mut self.state {
             state.scale = scale_factor as f64;
+            clear_ui::scale::set_scale_factor(scale_factor as f32);
             let pw = (state.width as f64 * state.scale) as u32;
             let ph = (state.height as f64 * state.scale) as u32;
             state.resize(pw, ph);
@@ -5542,6 +6651,29 @@ impl PointerHandler for AppState {
                             }
                         }
 
+                        if let Some(ref pending) = self.pending_resize {
+                            let lx = event.position.0 as f32;
+                            let ly = event.position.1 as f32;
+                            let rx = lx - pending.start_x;
+                            let ry = ly - pending.start_y;
+                            let rdist = (rx * rx + ry * ry).sqrt();
+                            if rdist > 4.0 {
+                                if let Some(ref window) = self.window {
+                                    let seat = self.seats.first().cloned().or_else(|| self.seat_state.seats().next());
+                                    if let Some(ref seat) = seat {
+                                        if pending.is_move {
+                                            eprintln!("DEBUG DRAG INITIATING window.move_ with serial={}", pending.serial);
+                                            window.move_(seat, pending.serial);
+                                        } else {
+                                            eprintln!("DEBUG RESIZE INITIATING window.resize with edge={:?}, serial={}", pending.edge, pending.serial);
+                                            window.resize(seat, pending.serial, pending.edge);
+                                        }
+                                    }
+                                }
+                                self.pending_resize = None;
+                            }
+                        }
+
                         let ev = WindowEvent::CursorMoved {
                             position: LocalPosition {
                                 x: event.position.0,
@@ -5575,9 +6707,9 @@ impl PointerHandler for AppState {
                                     dx, dy, dist, on_border, in_menubar_bg, hits_any_menu, self.seats.len(), self.window.is_some());
 
                                 if (on_border || in_menubar_bg) && !hits_any_menu {
-                                    if let Some(ref window) = self.window {
+                                    if let Some(ref _window) = self.window {
                                         let seat = self.seats.first().cloned().or_else(|| self.seat_state.seats().next());
-                                        if let Some(ref seat) = seat {
+                                        if let Some(ref _seat) = seat {
                                             if on_border {
                                                 use smithay_client_toolkit::reexports::protocols::xdg::shell::client::xdg_toplevel::ResizeEdge;
                                                 let nx = dx / dist;
@@ -5606,12 +6738,22 @@ impl PointerHandler for AppState {
                                                         edge = ResizeEdge::Right;
                                                     }
                                                 }
-                                                eprintln!("DEBUG RESIZE INITIATING window.resize with edge={:?}, serial={}", edge, serial);
-                                                window.resize(seat, *serial, edge);
+                                                self.pending_resize = Some(PendingResize {
+                                                    serial: *serial,
+                                                    edge,
+                                                    start_x: lx,
+                                                    start_y: ly,
+                                                    is_move: false,
+                                                });
                                                 continue;
                                             } else {
-                                                eprintln!("DEBUG DRAG INITIATING window.move_ with serial={}", serial);
-                                                window.move_(seat, *serial);
+                                                self.pending_resize = Some(PendingResize {
+                                                    serial: *serial,
+                                                    edge: smithay_client_toolkit::reexports::protocols::xdg::shell::client::xdg_toplevel::ResizeEdge::None,
+                                                    start_x: lx,
+                                                    start_y: ly,
+                                                    is_move: true,
+                                                });
                                                 continue;
                                             }
                                         }
@@ -5634,6 +6776,9 @@ impl PointerHandler for AppState {
                             _ => continue,
                         };
                         eprintln!("DEBUG MOUSE RELEASE: button={:?}, pos={:?}, local=({}, {})", btn, event.position, cx, cy);
+                        if btn == clear_ui::widget::MouseButton::Left {
+                            self.pending_resize = None;
+                        }
                         let ev = WindowEvent::MouseInput {
                             state: clear_ui::widget::ElementState::Released,
                             button: btn,
@@ -5842,6 +6987,7 @@ impl AppState {
             xkeysym::Keysym::j | xkeysym::Keysym::J => Key::Character("j".into()),
             xkeysym::Keysym::k | xkeysym::Keysym::K => Key::Character("k".into()),
             xkeysym::Keysym::l | xkeysym::Keysym::L => Key::Character("l".into()),
+            xkeysym::Keysym::s | xkeysym::Keysym::S => Key::Character("s".into()),
             xkeysym::Keysym::grave => Key::Character("`".into()),
             _ => {
                 if let Some(ref text) = event.utf8 {
@@ -5909,6 +7055,7 @@ impl AppState {
     fn process_event(&mut self, ev: WindowEvent) {
         if let Some(state) = &mut self.state {
             let mut changed = state.handle_event(&ev);
+            state.sync_settings_from_paginator();
 
             if let Some(seg) = state.widgets[BREADCRUMB_IDX].path_click() {
                 if seg < state.current_path.len() {
@@ -5923,6 +7070,47 @@ impl AppState {
                 changed = true;
             }
 
+            // Poll Paginator Clicks
+            let mut paginator_click = None;
+            let active_menubar = state.focused_pane;
+            let menu_names = state.widgets[active_menubar].menu_names();
+            for (page_idx, page) in state.paginator_page_widgets.iter_mut().enumerate() {
+                if page_idx < menu_names.len() && menu_names[page_idx] == "Settings" {
+                    continue;
+                }
+                for (item_idx, widget) in page.iter_mut().enumerate() {
+                    if widget.take_click() {
+                        paginator_click = Some((page_idx, item_idx));
+                        break;
+                    }
+                }
+                if paginator_click.is_some() {
+                    break;
+                }
+            }
+
+            if let Some((menu_idx, item_idx)) = paginator_click {
+                state.widgets[state.focused_pane].trigger_menu_click(menu_idx, item_idx);
+                changed = true;
+            }
+
+            // Check recent files buttons clicks
+            let mut clicked_file = None;
+            for (i, btn) in state.recent_files_buttons.iter_mut().enumerate() {
+                if btn.take_click() {
+                    clicked_file = Some(state.recent_files[i].clone());
+                }
+            }
+            if let Some(path) = clicked_file {
+                if let Err(e) = state.load_from_file(&path) {
+                    eprintln!("Failed to load recent project: {:?}", e);
+                    state.update_status_text(&format!("Failed to load: {:?}", e));
+                } else {
+                    state.update_status_text(&format!("Loaded project from {}", path.display()));
+                }
+                changed = true;
+            }
+
             if let Some((menu_idx, item_idx)) = state.widgets[HEADER_IDX].menu_click() {
                 if menu_idx == 0 { // File
                     match item_idx {
@@ -5931,21 +7119,26 @@ impl AppState {
                             changed = true;
                         }
                         1 => { // Open
-                            let proj_path = Path::new(env!("CARGO_MANIFEST_DIR")).join("project.json");
-                            if let Err(e) = state.load_from_file(&proj_path) {
-                                eprintln!("Failed to load project: {:?}", e);
-                            }
+                            state.open_file_chooser();
                             changed = true;
                         }
                         2 => { // Save
-                            let proj_path = Path::new(env!("CARGO_MANIFEST_DIR")).join("project.json");
-                            if let Err(e) = state.save_to_file(&proj_path) {
-                                eprintln!("Failed to save project: {:?}", e);
+                            let path_opt = state.loaded_project_path.clone();
+                            if let Some(path) = path_opt {
+                                if let Err(e) = state.save_to_file(&path) {
+                                    eprintln!("Failed to save project: {:?}", e);
+                                    state.update_status_text(&format!("Failed to save: {:?}", e));
+                                } else {
+                                    state.update_status_text(&format!("Project saved to {}", path.display()));
+                                    state.add_recent_file(path);
+                                }
+                            } else {
+                                state.save_file_chooser();
                             }
                             changed = true;
                         }
-                        3 => { // Configure
-                            state.execute_action(Action::ToggleConfigure);
+                        3 => { // Save As
+                            state.save_file_chooser();
                             changed = true;
                         }
                         4 => { // Exit
@@ -6057,19 +7250,28 @@ impl AppState {
                             changed = true;
                         }
                         1 => { // Open
-                            let proj_path = Path::new(env!("CARGO_MANIFEST_DIR")).join("project.json");
-                            if let Err(e) = state.load_from_file(&proj_path) {
-                                eprintln!("Failed to load project: {:?}", e);
-                            }
+                            state.open_file_chooser();
                             changed = true;
                         }
                         2 => { // Save
-                            let proj_path = Path::new(env!("CARGO_MANIFEST_DIR")).join("project.json");
-                            if let Err(e) = state.save_to_file(&proj_path) {
-                                eprintln!("Failed to save project: {:?}", e);
+                            let path_opt = state.loaded_project_path.clone();
+                            if let Some(path) = path_opt {
+                                if let Err(e) = state.save_to_file(&path) {
+                                    eprintln!("Failed to save project: {:?}", e);
+                                    state.update_status_text(&format!("Failed to save: {:?}", e));
+                                } else {
+                                    state.update_status_text(&format!("Project saved to {}", path.display()));
+                                    state.add_recent_file(path);
+                                }
+                            } else {
+                                state.save_file_chooser();
                             }
                             changed = true;
                         }
+                        3 => { // Save As
+                            state.save_file_chooser();
+                            changed = true;
+                        }
                         _ => {}
                     }
                 } else if menu_idx == 2 { // View
@@ -6254,6 +7456,7 @@ impl AppState {
             ));
 
             if changed {
+                state.update_window_title();
                 if state.is_detached_network || state.detached_circular_network {
                     let default_proj_path = std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("default_project.json");
                     if let Err(e) = state.save_to_file(&default_proj_path) {
@@ -6356,6 +7559,9 @@ impl AppState {
                             if let Err(e) = state.save_to_file(Path::new(&path)) {
                                 Err(format!("Save failed: {:?}", e))
                             } else {
+                                let path_buf = Path::new(&path).to_path_buf();
+                                state.loaded_project_path = Some(path_buf.clone());
+                                state.add_recent_file(path_buf);
                                 needs_redraw = true;
                                 Ok("Project saved".to_string())
                             }
@@ -6526,6 +7732,7 @@ impl AppState {
         }
         if needs_redraw {
             if let Some(state) = &mut self.state {
+                state.update_window_title();
                 if state.is_detached_network || state.detached_circular_network {
                     let default_proj_path = std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("default_project.json");
                     if let Err(e) = state.save_to_file(&default_proj_path) {
@@ -6575,6 +7782,7 @@ fn main() {
         redraw: true,
         pressed_key: None,
         inspector,
+        pending_resize: None,
         _sender: sender.clone(),
     };
 
@@ -7106,5 +8314,176 @@ mod tests {
         let decay_0 = friction.powf(0.0 * 60.0);
         assert_eq!(decay_0, 1.0);
     }
+
+    #[test]
+    fn test_circular_network_clamping_math() {
+        let header_h = 26.0;
+        let status_h = 0.0;
+        let gap = 18.0;
+
+        let clamp_layout = |width: f32, height: f32, layout_x: f32, layout_y: f32, layout_r: f32| -> (f32, f32, f32) {
+            let max_r = ((width - 2.0 * gap).min(height - header_h - status_h - 2.0 * gap) / 2.0).max(50.0);
+            let r = layout_r.clamp(50.0, max_r);
+
+            let min_x = gap + r;
+            let max_x = (width - gap - r).max(min_x);
+            let x = layout_x.clamp(min_x, max_x);
+
+            let min_y = header_h + gap + r;
+            let max_y = (height - status_h - gap - r).max(min_y);
+            let y = layout_y.clamp(min_y, max_y);
+
+            (x, y, r)
+        };
+
+        let (x, y, r) = clamp_layout(1000.0, 800.0, 500.0, 400.0, 180.0);
+        assert_eq!(r, 180.0);
+        assert_eq!(x, 500.0);
+        assert_eq!(y, 400.0);
+
+        let (x, y, r) = clamp_layout(1000.0, 800.0, 50.0, 400.0, 180.0);
+        assert_eq!(r, 180.0);
+        assert_eq!(x, 198.0);
+        assert_eq!(y, 400.0);
+
+        let (x, y, r) = clamp_layout(1000.0, 800.0, 500.0, 100.0, 180.0);
+        assert_eq!(r, 180.0);
+        assert_eq!(x, 500.0);
+        assert_eq!(y, 224.0);
+
+        let (x, y, r) = clamp_layout(1000.0, 800.0, 500.0, 750.0, 180.0);
+        assert_eq!(r, 180.0);
+        assert_eq!(x, 500.0);
+        assert_eq!(y, 602.0);
+
+        let (x, y, r) = clamp_layout(50.0, 50.0, 10.0, 10.0, 180.0);
+        assert!(r >= 50.0);
+        assert!(x >= 0.0);
+        assert!(y >= 0.0);
+    }
+
+    #[test]
+    fn test_floating_rectangular_pane_clamping() {
+        let width = 1000.0_f32;
+        let height = 800.0_f32;
+        let header_h = 26.0_f32;
+        let status_h = 0.0_f32;
+        let gap = 18.0_f32;
+
+        let clamp_floating = |_fx: f32, _fy: f32, fw: f32, _fh: f32| -> (f32, f32, f32, f32) {
+            let fw = fw.clamp(150.0, (width - 2.0 * gap).max(150.0));
+            let fx = gap;
+            let fy = header_h + gap;
+            let fh = (height - header_h - status_h - 2.0 * gap).max(100.0);
+            (fx, fy, fw, fh)
+        };
+
+        let expected_h = height - header_h - status_h - 2.0 * gap;
+
+        // Standard center case (fx anchored to gap, fy/fh to full height)
+        let (x, y, w, h) = clamp_floating(100.0, 200.0, 400.0, 300.0);
+        assert_eq!((x, y, w, h), (gap, header_h + gap, 400.0, expected_h));
+
+        // Off-screen left/top
+        let (x, y, w, h) = clamp_floating(-50.0, 0.0, 400.0, 300.0);
+        assert_eq!((x, y, w, h), (gap, header_h + gap, 400.0, expected_h));
+
+        // Off-screen right/bottom
+        let (x, y, w, h) = clamp_floating(900.0, 700.0, 400.0, 300.0);
+        assert_eq!((x, y, w, h), (gap, header_h + gap, 400.0, expected_h));
+
+        let clamp_param = |pw: f32| -> (f32, f32, f32, f32) {
+            let pw = pw.clamp(150.0, (width - 2.0 * gap).max(150.0));
+            let px = width - gap - pw;
+            let py = header_h + gap;
+            let ph = (height - header_h - status_h - 2.0 * gap).max(100.0);
+            (px, py, pw, ph)
+        };
+
+        // Standard param case
+        let (px, py, pw, ph) = clamp_param(300.0);
+        assert_eq!((px, py, pw, ph), (width - gap - 300.0, header_h + gap, 300.0, expected_h));
+
+        // Off-screen/overflow width param case
+        let (px, py, pw, ph) = clamp_param(1200.0);
+        let max_w = width - 2.0 * gap;
+        assert_eq!((px, py, pw, ph), (gap, header_h + gap, max_w, expected_h));
+
+        let clamp_ss = |ss_h_val: f32, show_net: bool, show_param: bool| -> (f32, f32, f32, f32) {
+            let fx = gap;
+            let fw = 400.0;
+            let param_w = 300.0;
+            let param_x = width - gap - param_w;
+
+            let ss_x = if show_net { fx + fw + gap } else { gap };
+            let ss_w_end = if show_param { param_x - gap } else { width - gap };
+            let ss_w = (ss_w_end - ss_x).max(150.0);
+            let ss_y_end = height - status_h - gap;
+            let ss_h = ss_h_val.clamp(100.0, (ss_y_end - header_h - gap).max(100.0));
+            let ss_y = ss_y_end - ss_h;
+            (ss_x, ss_y, ss_w, ss_h)
+        };
+
+        // Standard spreadsheet case (both net and param visible)
+        let (sx, sy, sw, sh) = clamp_ss(250.0, true, true);
+        assert_eq!(sx, gap + 400.0 + gap);
+        assert_eq!(sw, (width - gap - 300.0 - gap) - (gap + 400.0 + gap));
+        assert_eq!(sh, 250.0);
+        assert_eq!(sy, height - status_h - gap - 250.0);
+
+        // Neither net nor param visible
+        let (sx2, _sy2, sw2, sh2) = clamp_ss(200.0, false, false);
+        assert_eq!(sx2, gap);
+        assert_eq!(sw2, width - 2.0 * gap);
+        assert_eq!(sh2, 200.0);
+
+        // Clamping height to min (100.0)
+        let (_, _, _, sh_min) = clamp_ss(50.0, true, true);
+        assert_eq!(sh_min, 100.0);
+
+        // Clamping height to max
+        let max_possible_h = height - status_h - gap - header_h - gap;
+        let (_, _, _, sh_max) = clamp_ss(1000.0, true, true);
+        assert_eq!(sh_max, max_possible_h);
+    }
+
+    #[test]
+    fn test_paginator_collapsed_layout() {
+        let width = 1000.0_f32;
+
+        let get_paginator_w = |page_hidden: bool| -> f32 {
+            if page_hidden {
+                56.0_f32
+            } else {
+                236.0_f32
+            }
+        };
+
+        // Collapsed layout
+        let w_collapsed = get_paginator_w(true);
+        assert_eq!(w_collapsed, 56.0);
+
+        // Expanded layout
+        let w_expanded = get_paginator_w(false);
+        assert_eq!(w_expanded, 236.0);
+
+        // Verify how other viewport bounds adjust relative to paginator_w
+        let check_viewport_layout = |page_hidden: bool| -> (f32, f32) {
+            let paginator_w = get_paginator_w(page_hidden);
+            let col_c_x = paginator_w;
+            let col_c_w = width - paginator_w;
+            (col_c_x, col_c_w)
+        };
+
+        // When collapsed, viewport/canvas has more space
+        let (vx_c, vw_c) = check_viewport_layout(true);
+        assert_eq!(vx_c, 56.0);
+        assert_eq!(vw_c, 944.0);
+
+        // When expanded, viewport/canvas has default space
+        let (vx_e, vw_e) = check_viewport_layout(false);
+        assert_eq!(vx_e, 236.0);
+        assert_eq!(vw_e, 764.0);
+    }
 }
 
diff --git a/src/shader.wgsl b/src/shader.wgsl
index 47fc35b..9d132f2 100644
--- a/src/shader.wgsl
+++ b/src/shader.wgsl
@@ -1,3 +1,6 @@
+@group(0) @binding(0) var t_backdrop: texture_2d<f32>;
+@group(0) @binding(1) var s_backdrop: sampler;
+
 struct VertexOutput {
     @builtin(position) clip_position: vec4f,
     @location(0) color: vec4f,
@@ -26,6 +29,31 @@ fn fs_main(in: VertexOutput) -> @location(0) vec4f {
             discard;
         }
     }
+    
+    if (in.color.a < 0.0) {
+        let tex_size = vec2f(textureDimensions(t_backdrop));
+        let clean_backdrop = textureSample(t_backdrop, s_backdrop, in.clip_position.xy / tex_size);
+        
+        var blurred = vec4f(0.0);
+        var total_weight = 0.0;
+        
+        // 7x7 Gaussian blur kernel
+        for (var x = -3.0; x <= 3.0; x += 1.0) {
+            for (var y = -3.0; y <= 3.0; y += 1.0) {
+                let offset = vec2f(x, y) * 2.0; // sample every 2 pixels for a wider blur
+                let sample_uv = (in.clip_position.xy + offset) / tex_size;
+                let weight = exp(-(x*x + y*y) / (2.0 * 2.0 * 2.0));
+                blurred += textureSample(t_backdrop, s_backdrop, sample_uv) * weight;
+                total_weight += weight;
+            }
+        }
+        
+        let backdrop_color = blurred / total_weight;
+        let opacity = -in.color.a;
+        let plate_color = vec4f(in.color.rgb, 1.0);
+        let blurred_plate = mix(backdrop_color, plate_color, opacity);
+        return mix(clean_backdrop, blurred_plate, opacity);
+    }
+    
     return in.color;
 }
-