diff --git a/app/sovran_systemsos_web/static/css/header.css b/app/sovran_systemsos_web/static/css/header.css
index 4ec98f0..37105c0 100644
--- a/app/sovran_systemsos_web/static/css/header.css
+++ b/app/sovran_systemsos_web/static/css/header.css
@@ -56,49 +56,6 @@
.search-box input::placeholder { color: var(--text-3); }
-/* ── Network chip — LAN + WAN always on one line ────────────────── */
-
-.net-chip {
- display: flex;
- align-items: center;
- gap: 10px;
- padding: 8px 17px;
- border-radius: 99px;
- background: var(--card);
- border: 1px solid var(--border);
- font-family: var(--mono);
- font-size: 0.73rem;
- color: var(--text-3);
- white-space: nowrap;
- order: 4;
-}
-
-.net-chip svg { width: 15px; height: 15px; color: var(--text-3); flex-shrink: 0; }
-.net-chip .int-wrap, .net-chip .ext-wrap { display: inline-flex; align-items: center; gap: 7px; }
-
-.net-chip .ip-label {
- font-size: 0.6rem;
- font-weight: 800;
- letter-spacing: 0.08em;
- color: var(--text-3);
- margin-right: 7px;
-}
-
-.net-chip .ip-value {
- color: var(--text);
- font-weight: 600;
- font-family: var(--mono);
-}
-
-.net-separator {
- width: 1px;
- height: 15px;
- background: var(--border-strong);
- margin: 0 3px;
- flex-shrink: 0;
- color: transparent;
-}
-
.top-actions {
display: flex;
gap: 9px;
@@ -106,17 +63,6 @@
order: 5;
}
-/* Below 1160px the chip wraps to its own full-width topbar row —
- it never stacks LAN over WAN and never hides the external IP. */
-@media (max-width: 1160px) {
- .topbar { flex-wrap: wrap; row-gap: 10px; }
- .net-chip {
- order: 6;
- flex-basis: 100%;
- justify-content: center;
- }
-}
-
/* ── Legacy badges (still used inside dialogs) ──────────────────── */
.role-badge {
diff --git a/app/sovran_systemsos_web/static/css/layout.css b/app/sovran_systemsos_web/static/css/layout.css
index 4e26ab1..6e3596d 100644
--- a/app/sovran_systemsos_web/static/css/layout.css
+++ b/app/sovran_systemsos_web/static/css/layout.css
@@ -204,30 +204,131 @@
.sidebar-spacer { flex: 1; min-height: 18px; }
-.sidebar-footer {
- border-top: 1px solid var(--border);
- padding-top: 14px;
+/* ── Welcome dashboard (default view) ───────────────────────────── */
+
+.welcome {
+ position: relative;
+ max-width: 1040px;
+ margin: 0 auto;
+ padding: 44px 8px 24px;
+ overflow: hidden;
+ min-height: 100%;
}
-.host-note {
- text-align: center;
- font-size: 0.66rem;
+/* Soft drifting glow — calm, GPU-cheap (transforms only) */
+.welcome-bg {
+ position: absolute;
+ inset: 0;
+ pointer-events: none;
+}
+
+.orb {
+ position: absolute;
+ border-radius: 50%;
+ will-change: transform;
+}
+
+.orb-a {
+ width: 560px; height: 560px;
+ left: -140px; top: -180px;
+ background: radial-gradient(circle, rgba(62, 207, 142, 0.10), transparent 70%);
+ animation: orb-drift-a 26s ease-in-out infinite alternate;
+}
+
+.orb-b {
+ width: 680px; height: 680px;
+ right: -200px; top: 20%;
+ background: radial-gradient(circle, rgba(120, 174, 237, 0.07), transparent 70%);
+ animation: orb-drift-b 34s ease-in-out infinite alternate;
+}
+
+.orb-c {
+ width: 460px; height: 460px;
+ left: 34%; bottom: -200px;
+ background: radial-gradient(circle, rgba(66, 243, 154, 0.06), transparent 70%);
+ animation: orb-drift-c 42s ease-in-out infinite alternate;
+}
+
+@keyframes orb-drift-a { to { transform: translate(70px, 50px) scale(1.14); } }
+@keyframes orb-drift-b { to { transform: translate(-60px, -70px) scale(1.10); } }
+@keyframes orb-drift-c { to { transform: translate(50px, -40px) scale(1.18); } }
+
+@media (prefers-reduced-motion: reduce) {
+ .orb { animation: none; }
+ .welcome-inner { animation: none; }
+}
+
+.welcome-inner {
+ position: relative;
+ animation: welcome-in 0.5s ease both;
+}
+
+@keyframes welcome-in {
+ from { opacity: 0; transform: translateY(14px); }
+}
+
+.welcome-greeting {
+ font-size: 0.98rem;
+ font-weight: 650;
+ color: var(--text-2);
+}
+
+.welcome-title {
+ margin-top: 6px;
+ font-size: clamp(1.7rem, 2.8vw, 2.25rem);
+ font-weight: 800;
+ letter-spacing: -0.02em;
+ line-height: 1.18;
+}
+
+.welcome-title em { font-style: normal; color: var(--accent); }
+
+.welcome-meta {
+ margin-top: 12px;
+ font-size: 0.8rem;
color: var(--text-3);
font-family: var(--mono);
- margin-top: 9px;
}
-/* ── Widgets row (dashboard.js) ─────────────────────────────────── */
+.welcome-meta-sep { margin: 0 8px; opacity: 0.6; }
-.widgets {
+.welcome-cards {
display: grid;
- grid-template-columns: 1.1fr 1.45fr;
+ grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
gap: 16px;
- margin-bottom: 32px;
+ margin-top: 34px;
}
-@media (max-width: 900px) {
- .widgets { grid-template-columns: 1fr; }
+/* wrappers whose children become grid items */
+.welcome-dyn { display: contents; }
+
+.w-network .net-row {
+ display: flex;
+ align-items: baseline;
+ gap: 10px;
+ margin-top: 6px;
+ font-family: var(--mono);
+}
+
+.w-network .net-k {
+ font-size: 0.6rem;
+ font-weight: 800;
+ letter-spacing: 0.08em;
+ color: var(--text-3);
+ width: 30px;
+ flex-shrink: 0;
+}
+
+.w-network .ip-value {
+ font-size: 0.82rem;
+ font-weight: 600;
+ color: var(--text);
+}
+
+.w-network .sub { margin-top: 8px; }
+
+.welcome-browse {
+ margin-top: 26px;
}
.widget {
@@ -275,6 +376,10 @@
background: linear-gradient(160deg, #f7931a, #b96a0a);
}
+.widget-chip.chip-amber {
+ background: linear-gradient(160deg, #b98426, #8a5f16);
+}
+
.widget-chip.chip-sovran {
background: linear-gradient(160deg, #42f39a, #1aa45d);
}
diff --git a/app/sovran_systemsos_web/static/css/modals.css b/app/sovran_systemsos_web/static/css/modals.css
index 83f972a..0ec3792 100644
--- a/app/sovran_systemsos_web/static/css/modals.css
+++ b/app/sovran_systemsos_web/static/css/modals.css
@@ -1505,6 +1505,15 @@
margin-top: 5px;
}
+.sysprefs {
+ padding: 0 24px 20px;
+ border-top: 1px solid var(--border);
+ margin-top: 16px;
+ padding-top: 14px;
+}
+
+.sysprefs .autolaunch-section .feature-card { padding: 2px 0 0; }
+
.sysfineprint {
font-size: 0.72rem;
color: var(--text-3);
diff --git a/app/sovran_systemsos_web/static/js/dashboard.js b/app/sovran_systemsos_web/static/js/dashboard.js
index 632ecf2..ad7448d 100644
--- a/app/sovran_systemsos_web/static/js/dashboard.js
+++ b/app/sovran_systemsos_web/static/js/dashboard.js
@@ -1,23 +1,29 @@
"use strict";
/* ── The Hub dashboard chrome ──────────────────────────────────────
- Category navigation, service search, status widgets, and the
- Systems Operational modal (router ports 80/443 + the live domain
- diagnostics checklist).
+ Welcome dashboard (default view), category navigation, service
+ search, status cards, and the Systems Operational modal.
- Everything lives in an IIFE so no globals leak into the other
- Hub scripts; tiles.js calls window.dashboardServicesUpdated()
- whenever the service list refreshes. */
+ Everything lives in an IIFE so no globals leak into the other Hub
+ scripts; tiles.js calls window.dashboardServicesUpdated() whenever
+ the service list refreshes. */
(function () {
var $nav = document.getElementById("sidebar-nav");
var $pageTitle = document.getElementById("page-title");
- var $widgets = document.getElementById("widgets");
var $search = document.getElementById("search-input");
var $sysModal = document.getElementById("systems-modal");
var $sysBody = document.getElementById("systems-body");
+ var $welcome = document.getElementById("welcome-view");
+ var $tilesArea = document.getElementById("tiles-area");
+ var $wcSystems = document.getElementById("wc-systems");
+ var $wcMore = document.getElementById("wc-more");
+ var $greeting = document.getElementById("welcome-greeting");
+ var $welcomeRole = document.getElementById("welcome-role");
+ var $browseBtn = document.getElementById("welcome-browse-btn");
+ var _view = "dashboard"; // "dashboard" | "services"
var _cat = "all";
var _query = "";
@@ -65,7 +71,42 @@
});
}
- /* ── Category navigation ─────────────────────────────────────── */
+ /* ── Views ────────────────────────────────────────────────────── */
+
+ function setTitle(t) {
+ if ($pageTitle) $pageTitle.textContent = t;
+ }
+
+ function syncNav() {
+ if (!$nav) return;
+ $nav.querySelectorAll(".nav-item").forEach(function (b) {
+ var isActive;
+ if (b.dataset.cat === "__dash") isActive = (_view === "dashboard");
+ else isActive = (_view === "services" && b.dataset.cat === _cat);
+ b.classList.toggle("active", isActive);
+ });
+ }
+
+ function showDashboard() {
+ _view = "dashboard";
+ _cat = "all";
+ if ($welcome) $welcome.style.display = "";
+ if ($tilesArea) $tilesArea.style.display = "none";
+ setTitle("Dashboard");
+ syncNav();
+ }
+
+ function showServices(cat) {
+ _view = "services";
+ if (cat) _cat = cat;
+ if ($welcome) $welcome.style.display = "none";
+ if ($tilesArea) $tilesArea.style.display = "";
+ setTitle(_cat === "all" ? "All services" : catLabel(_cat));
+ syncNav();
+ applyFilter();
+ }
+
+ /* ── Category navigation ──────────────────────────────────────── */
function renderNav() {
if (!$nav) return;
@@ -79,7 +120,12 @@
});
var total = visibleServices().length;
- var html = '
Services
';
+ var html = 'Menu
';
+ html += '';
+ html += 'Services
';
html += navItem("all", "All services", total);
order.forEach(function (cat) {
html += navItem(cat, catLabel(cat), counts[cat]);
@@ -88,20 +134,14 @@
$nav.querySelectorAll(".nav-item").forEach(function (btn) {
btn.addEventListener("click", function () {
- _cat = btn.dataset.cat;
- $nav.querySelectorAll(".nav-item").forEach(function (b) {
- b.classList.toggle("active", b === btn);
- });
- if ($pageTitle) {
- $pageTitle.textContent = _cat === "all" ? "Dashboard" : catLabel(_cat);
- }
- applyFilter();
+ if (btn.dataset.cat === "__dash") showDashboard();
+ else showServices(btn.dataset.cat);
});
});
}
function navItem(cat, label, count) {
- return '