diff --git a/containers/libreportal/frontend/components/apps/overview/css/overview.css b/containers/libreportal/frontend/components/apps/overview/css/overview.css
index a7f810a..0a2c6d2 100644
--- a/containers/libreportal/frontend/components/apps/overview/css/overview.css
+++ b/containers/libreportal/frontend/components/apps/overview/css/overview.css
@@ -134,7 +134,7 @@
max-width: none;
height: auto;
min-height: 0;
- margin: 0 0 16px;
+ margin: 0; /* no gap — the strip joins the content card below */
padding: 0;
background: transparent;
border: none;
@@ -179,15 +179,23 @@
height: 16px;
flex: 0 0 auto;
}
+/* Round only the outer top corners to follow the strip's chrome (the global
+ .tab-button rule does this for app/Migrate sub-tabs; .category needs its own). */
+#overview-view #ov-pane-backups .backup-layout > .sidebar .category:first-child { border-top-left-radius: 12px; }
+#overview-view #ov-pane-backups .backup-layout > .sidebar .category:last-child { border-top-right-radius: 12px; }
+/* The content area becomes the connected .tabs-content card (visible surface,
+ rounded bottom) so the strip + body read as one unit, like the app Config tab. */
#overview-view #ov-pane-backups .backup-layout > .main {
width: 100%;
min-width: 0;
+ background: var(--card-bg);
+ border-radius: 0 0 12px 12px;
}
-/* ---- Migrate tab: nested segmented sub-tabs (per-app Config-tab design) -- */
-/* .tabs-wrapper/.tabs-list/.tab-button come from the global base.css; only the
- panel show/hide is scoped here. */
-#overview-view .ov-subtabs { margin-bottom: 16px; }
+/* ---- Migrate tab: segmented sub-tabs (per-app Config-tab design) -------- */
+/* .tabs-wrapper/.tabs-list/.tabs-content/.tab-button come from base.css; the
+ list + content share one .tabs-wrapper (markup) so they join with no gap,
+ exactly like the app Config tab. Only the panel show/hide is scoped here. */
#overview-view .ov-subtabs-content .tab-panel { display: none; }
#overview-view .ov-subtabs-content .tab-panel.active { display: block; }
diff --git a/containers/libreportal/frontend/components/apps/overview/js/overview-manager.js b/containers/libreportal/frontend/components/apps/overview/js/overview-manager.js
index 21f340f..e9db4ab 100644
--- a/containers/libreportal/frontend/components/apps/overview/js/overview-manager.js
+++ b/containers/libreportal/frontend/components/apps/overview/js/overview-manager.js
@@ -473,7 +473,10 @@ class OverviewManager {
// and #mobile-overlay). BackupPage selects its own nodes by class, so this
// is safe; it just keeps the document free of duplicate ids.
html = html.replace('id="sidebar"', '').replace('', '');
- pane.innerHTML = html;
+ // Lead with the shared in-content header (BackupPage supplies only its own
+ // per-section headers inside the layout), so Backups matches every other
+ // fleet tab. It persists across refreshes since revisits don't reset innerHTML.
+ pane.innerHTML = this.renderHeader('backups') + html;
if (typeof BackupPage === 'undefined') {
pane.innerHTML = '