Systems Operational on roles with no enabled domain services (a
fresh Desktop-only install, or everything turned off) rendered no
Router card but still showed a "Who uses these ports" note listing
services the machine does not have. Any role with no enabled domain
service now gets the calm "No router setup needed yet" card with
role-appropriate wording, and the who-uses note only appears when
a domain service is actually enabled.
Narrow viewports (phones, half-screen RDP) had no layout at all:
the sidebar and topbar forced ~900px of horizontal scroll on every
role. The sidebar now collapses to a 76px icon rail below 920px,
the topbar wraps its search onto a second row below 640px, and the
welcome column reflows to one card per row. Legacy pre-redesign
media rules in onboarding.css (which targeted the old DOM and set
.sidebar{width:100%}) are removed — they silently overrode the new
layout, and one shrank the Zeus QR to 200px; QR codes keep their
repo-original 240px at every width.
676 lines
14 KiB
CSS
676 lines
14 KiB
CSS
/* ── App shell: sidebar + main column ───────────────────────────── */
|
|
|
|
.app {
|
|
display: flex;
|
|
height: 100vh;
|
|
position: relative;
|
|
z-index: 1;
|
|
}
|
|
|
|
.main {
|
|
flex: 1;
|
|
display: flex;
|
|
flex-direction: column;
|
|
min-width: 0;
|
|
}
|
|
|
|
.content {
|
|
flex: 1;
|
|
overflow-y: auto;
|
|
padding: 26px 30px 70px;
|
|
min-width: 0;
|
|
}
|
|
|
|
/* ── Sidebar ────────────────────────────────────────────────────── */
|
|
|
|
.sidebar {
|
|
width: 250px;
|
|
flex-shrink: 0;
|
|
background: var(--surface);
|
|
border-right: 1px solid var(--border);
|
|
display: flex;
|
|
flex-direction: column;
|
|
padding: 20px 14px 14px;
|
|
overflow-y: auto;
|
|
}
|
|
|
|
.brand {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 12px;
|
|
padding: 2px 8px 20px;
|
|
}
|
|
|
|
.brand-logo {
|
|
width: 42px;
|
|
height: 42px;
|
|
flex-shrink: 0;
|
|
filter: drop-shadow(0 4px 14px rgba(28, 196, 120, 0.25));
|
|
}
|
|
|
|
.brand-text { display: flex; flex-direction: column; min-width: 0; }
|
|
|
|
.brand-title {
|
|
font-size: 1.02rem;
|
|
font-weight: 800;
|
|
letter-spacing: -0.01em;
|
|
line-height: 1.2;
|
|
}
|
|
|
|
.brand-title em { font-style: normal; color: var(--accent); }
|
|
|
|
.brand-sub {
|
|
font-size: 0.7rem;
|
|
color: var(--text-3);
|
|
font-family: var(--mono);
|
|
margin-top: 3px;
|
|
}
|
|
|
|
.nav-label {
|
|
font-size: 0.66rem;
|
|
font-weight: 750;
|
|
letter-spacing: 0.14em;
|
|
text-transform: uppercase;
|
|
color: var(--text-3);
|
|
padding: 14px 10px 7px;
|
|
}
|
|
|
|
/* Category nav (dashboard.js) */
|
|
.nav-item {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 11px;
|
|
width: 100%;
|
|
padding: 8px 10px;
|
|
border-radius: 10px;
|
|
border: 0;
|
|
background: none;
|
|
color: var(--text-2);
|
|
font-size: 0.87rem;
|
|
font-weight: 600;
|
|
cursor: pointer;
|
|
text-align: left;
|
|
transition: background 0.15s, color 0.15s;
|
|
}
|
|
|
|
.nav-item svg { width: 17px; height: 17px; flex-shrink: 0; }
|
|
|
|
.nav-item:hover {
|
|
background: rgba(255, 255, 255, 0.05);
|
|
color: var(--text);
|
|
}
|
|
|
|
.nav-item.active {
|
|
background: rgba(255, 255, 255, 0.06);
|
|
color: var(--text);
|
|
box-shadow: inset 2.5px 0 0 var(--accent);
|
|
}
|
|
|
|
.nav-text {
|
|
min-width: 0;
|
|
white-space: nowrap;
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
}
|
|
|
|
.nav-count {
|
|
margin-left: auto;
|
|
font-size: 0.67rem;
|
|
font-weight: 750;
|
|
flex-shrink: 0;
|
|
color: var(--text-3);
|
|
background: rgba(255, 255, 255, 0.05);
|
|
padding: 2px 8px;
|
|
border-radius: 99px;
|
|
}
|
|
|
|
.nav-item.active .nav-count {
|
|
background: rgba(255, 255, 255, 0.10);
|
|
color: var(--text-2);
|
|
}
|
|
|
|
/* System items (tiles.js renders these as .sidebar-support-btn) */
|
|
.sidebar-support-btn {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 11px;
|
|
width: 100%;
|
|
padding: 8px 10px;
|
|
border-radius: 10px;
|
|
border: 0;
|
|
background: none;
|
|
color: var(--text-2);
|
|
font-size: 0.87rem;
|
|
font-weight: 600;
|
|
cursor: pointer;
|
|
text-align: left;
|
|
transition: background 0.15s, color 0.15s, border-color 0.15s;
|
|
}
|
|
|
|
.sidebar-support-btn:hover {
|
|
background: rgba(255, 255, 255, 0.05);
|
|
color: var(--text);
|
|
}
|
|
|
|
.sidebar-support-btn + .sidebar-support-btn {
|
|
margin-top: 2px;
|
|
}
|
|
|
|
.sidebar-support-icon {
|
|
font-size: 1rem;
|
|
width: 17px;
|
|
height: 17px;
|
|
display: grid;
|
|
place-items: center;
|
|
flex-shrink: 0;
|
|
}
|
|
|
|
.sidebar-support-icon svg { width: 17px; height: 17px; }
|
|
|
|
.sidebar-support-text {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 1px;
|
|
min-width: 0;
|
|
}
|
|
|
|
.sidebar-support-title {
|
|
font-size: 0.87rem;
|
|
font-weight: 600;
|
|
line-height: 1.3;
|
|
color: var(--text);
|
|
white-space: nowrap;
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
}
|
|
|
|
.sidebar-support-hint {
|
|
font-size: 0.68rem;
|
|
font-weight: 650;
|
|
color: var(--text-3);
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 6px;
|
|
white-space: nowrap;
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
}
|
|
|
|
.sidebar-divider {
|
|
border: none;
|
|
border-top: 1px solid var(--border);
|
|
margin: 14px 0 0;
|
|
}
|
|
|
|
.sidebar-spacer { flex: 1; min-height: 18px; }
|
|
|
|
/* ── Welcome dashboard (default view) ───────────────────────────── */
|
|
|
|
.welcome {
|
|
position: relative;
|
|
/* Full-bleed: cancel .content's padding so the glow reaches
|
|
every edge of the panel (sidebar border, topbar, viewport). */
|
|
margin: -26px -30px -70px;
|
|
padding: 70px 38px 70px;
|
|
overflow: hidden;
|
|
min-height: calc(100% + 96px);
|
|
}
|
|
|
|
/* 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; }
|
|
}
|
|
|
|
/* ── Boot splash ────────────────────────────────────────────────── */
|
|
/* Covers the shell while the first services/config data loads; the
|
|
inline script in index.html lifts it (window.__liftAppSplash). */
|
|
#app-splash {
|
|
position: fixed;
|
|
inset: 0;
|
|
z-index: 2000;
|
|
background: var(--bg);
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
transition: opacity 0.45s ease;
|
|
}
|
|
|
|
#app-splash.done { opacity: 0; pointer-events: none; }
|
|
|
|
.splash-card {
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
gap: 10px;
|
|
padding: 24px;
|
|
}
|
|
|
|
.splash-ring {
|
|
position: relative;
|
|
width: 96px;
|
|
height: 96px;
|
|
display: grid;
|
|
place-items: center;
|
|
margin-bottom: 8px;
|
|
}
|
|
|
|
.splash-ring img { width: 60px; height: 60px; opacity: 0.95; }
|
|
|
|
.splash-ring-arc {
|
|
position: absolute;
|
|
inset: 0;
|
|
border-radius: 50%;
|
|
border: 3px solid rgba(255, 255, 255, 0.10);
|
|
border-top-color: var(--accent);
|
|
animation: splash-spin 1s linear infinite;
|
|
}
|
|
|
|
@keyframes splash-spin { to { transform: rotate(360deg); } }
|
|
|
|
.splash-title {
|
|
font-size: 1.02rem;
|
|
font-weight: 700;
|
|
color: var(--text);
|
|
}
|
|
|
|
.splash-sub {
|
|
font-size: 0.84rem;
|
|
color: var(--text-3);
|
|
}
|
|
|
|
@media (prefers-reduced-motion: reduce) {
|
|
.splash-ring-arc { animation-duration: 2.5s; }
|
|
}
|
|
|
|
.welcome-inner {
|
|
position: relative;
|
|
max-width: 1040px;
|
|
margin: 0 auto;
|
|
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);
|
|
}
|
|
|
|
.welcome-meta-sep { margin: 0 8px; opacity: 0.6; }
|
|
|
|
.welcome-cards {
|
|
display: grid;
|
|
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
|
|
gap: 16px;
|
|
margin-top: 34px;
|
|
}
|
|
|
|
/* 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 {
|
|
background: var(--card);
|
|
border: 1px solid var(--border);
|
|
border-radius: 20px;
|
|
padding: 17px 19px;
|
|
display: flex;
|
|
gap: 14px;
|
|
align-items: center;
|
|
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.03), var(--shadow-card);
|
|
transition: border-color 0.18s;
|
|
position: relative;
|
|
overflow: hidden;
|
|
min-width: 0;
|
|
}
|
|
|
|
.widget:hover { border-color: var(--border-strong); }
|
|
|
|
.widget.clickable { cursor: pointer; }
|
|
.widget.clickable:focus-visible {
|
|
outline: 2px solid rgba(66, 243, 154, 0.45);
|
|
outline-offset: 2px;
|
|
}
|
|
|
|
.widget-chip {
|
|
width: 46px;
|
|
height: 46px;
|
|
border-radius: 14px;
|
|
flex-shrink: 0;
|
|
display: grid;
|
|
place-items: center;
|
|
color: #fff;
|
|
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.25), 0 6px 16px rgba(0, 0, 0, 0.3);
|
|
}
|
|
|
|
.widget-chip svg { width: 22px; height: 22px; }
|
|
.widget-chip img { width: 46px; height: 46px; display: block; object-fit: contain; }
|
|
|
|
.widget-chip.chip-green {
|
|
background: linear-gradient(160deg, #2f9f6b, #17683f);
|
|
}
|
|
|
|
.widget-chip.chip-btc {
|
|
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);
|
|
}
|
|
|
|
.widget-chip.chip-neutral {
|
|
background: linear-gradient(160deg, #2e333a, #23272c);
|
|
}
|
|
|
|
.w-body { min-width: 0; }
|
|
|
|
.widget h3 {
|
|
font-size: 0.9rem;
|
|
font-weight: 700;
|
|
letter-spacing: -0.005em;
|
|
white-space: nowrap;
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
}
|
|
|
|
.widget .sub {
|
|
margin-top: 4px;
|
|
font-size: 0.78rem;
|
|
color: var(--text-2);
|
|
white-space: nowrap;
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
}
|
|
|
|
.widget .sub b { color: var(--text); font-weight: 650; }
|
|
.widget .sub .good { color: var(--accent); font-weight: 650; }
|
|
.widget .sub .warn { color: var(--amber); font-weight: 650; }
|
|
|
|
.w-bar {
|
|
height: 7px;
|
|
border-radius: 99px;
|
|
background: rgba(255, 255, 255, 0.07);
|
|
overflow: hidden;
|
|
margin: 8px 0 7px;
|
|
}
|
|
|
|
.w-bar-fill {
|
|
height: 100%;
|
|
border-radius: 99px;
|
|
background: linear-gradient(90deg, #42f39a, #1aa45d);
|
|
transition: width 0.6s cubic-bezier(0.3, 0.7, 0.3, 1);
|
|
}
|
|
|
|
.w-chev {
|
|
margin-left: auto;
|
|
width: 30px;
|
|
height: 30px;
|
|
border-radius: 50%;
|
|
display: grid;
|
|
place-items: center;
|
|
color: var(--text-3);
|
|
flex-shrink: 0;
|
|
}
|
|
|
|
.w-chev svg { width: 15px; height: 15px; }
|
|
.widget.clickable:hover .w-chev {
|
|
color: var(--text-2);
|
|
background: rgba(255, 255, 255, 0.07);
|
|
}
|
|
|
|
/* ── Tiles area ─────────────────────────────────────────────────── */
|
|
|
|
#tiles-area {
|
|
min-width: 0;
|
|
}
|
|
|
|
.dashboard-loading {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
gap: 12px;
|
|
padding: 72px 24px;
|
|
color: var(--text-3);
|
|
font-size: 0.9rem;
|
|
}
|
|
|
|
.dashboard-loading-spinner {
|
|
width: 20px;
|
|
height: 20px;
|
|
border-radius: 50%;
|
|
border: 2.5px solid rgba(255, 255, 255, 0.12);
|
|
border-top-color: var(--accent);
|
|
animation: spin 0.8s linear infinite;
|
|
}
|
|
|
|
@keyframes spin { to { transform: rotate(360deg); } }
|
|
|
|
/* ── Category sections ──────────────────────────────────────────── */
|
|
|
|
.category-section {
|
|
margin-bottom: 32px;
|
|
}
|
|
|
|
.section-header {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 10px;
|
|
margin-bottom: 15px;
|
|
font-size: 0.72rem;
|
|
font-weight: 750;
|
|
letter-spacing: 0.14em;
|
|
text-transform: uppercase;
|
|
color: var(--text-2);
|
|
white-space: nowrap;
|
|
}
|
|
|
|
.section-divider {
|
|
border: none;
|
|
flex: 1;
|
|
height: 1px;
|
|
background: linear-gradient(90deg, var(--border), transparent);
|
|
margin: 0;
|
|
}
|
|
|
|
.tiles-grid {
|
|
display: grid;
|
|
grid-template-columns: repeat(auto-fill, minmax(158px, 1fr));
|
|
gap: 15px;
|
|
}
|
|
|
|
/* ── Empty state ────────────────────────────────────────────────── */
|
|
|
|
.empty-state {
|
|
text-align: center;
|
|
padding: 64px 24px;
|
|
color: var(--text-3);
|
|
}
|
|
|
|
.empty-state p {
|
|
font-size: 1rem;
|
|
margin-bottom: 8px;
|
|
}
|
|
|
|
/* ── Upgrade modal (kept from previous layout) ──────────────────── */
|
|
|
|
.upgrade-dialog {
|
|
max-width: 480px;
|
|
}
|
|
|
|
.upgrade-info-box {
|
|
background: rgba(0, 0, 0, 0.16);
|
|
border: 1px solid var(--border-strong);
|
|
border-radius: 16px;
|
|
padding: 14px 16px;
|
|
margin: 14px 0;
|
|
}
|
|
|
|
.upgrade-info-title {
|
|
font-size: 0.88rem;
|
|
font-weight: 700;
|
|
color: var(--text);
|
|
margin-bottom: 8px;
|
|
}
|
|
|
|
.upgrade-info-list {
|
|
padding-left: 20px;
|
|
font-size: 0.85rem;
|
|
color: var(--text-2);
|
|
line-height: 1.7;
|
|
margin: 0;
|
|
}
|
|
|
|
.upgrade-info-list a {
|
|
color: var(--accent);
|
|
}
|
|
|
|
.upgrade-rebuild-note {
|
|
font-style: italic;
|
|
color: var(--text-3);
|
|
font-size: 0.82rem;
|
|
}
|
|
|
|
/* ── First-login security banner (inside .content) ──────────────── */
|
|
|
|
.security-first-login-banner {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 12px;
|
|
background: rgba(233, 182, 74, 0.08);
|
|
border: 1px solid rgba(233, 182, 74, 0.30);
|
|
border-radius: 16px;
|
|
padding: 12px 16px;
|
|
color: var(--text-2);
|
|
font-size: 0.85rem;
|
|
margin-bottom: 24px;
|
|
}
|
|
|
|
/* ── Narrow viewports (phone / half-screen RDP) ──────────────────── */
|
|
/* The sidebar becomes an icon rail so the content keeps room, and the
|
|
topbar wraps its search onto a second row instead of overflowing. */
|
|
@media (max-width: 920px) {
|
|
.sidebar {
|
|
width: 76px;
|
|
padding: 16px 8px 12px;
|
|
}
|
|
|
|
.brand { justify-content: center; padding: 2px 0 18px; gap: 0; }
|
|
.brand-text,
|
|
.nav-label,
|
|
.nav-text,
|
|
.nav-count,
|
|
.sidebar-support-text,
|
|
.sidebar-divider,
|
|
#sidebar-features { display: none; }
|
|
|
|
.nav-item { justify-content: center; gap: 0; padding: 10px 0; }
|
|
.sidebar-support-btn { justify-content: center; gap: 0; padding: 10px 0; }
|
|
|
|
.content { padding: 18px 18px 40px; }
|
|
|
|
/* keep the welcome background full-bleed against the new padding */
|
|
.welcome {
|
|
margin: -18px -18px -40px;
|
|
padding: 48px 20px 40px;
|
|
min-height: calc(100% + 58px);
|
|
}
|
|
}
|
|
|
|
@media (max-width: 640px) {
|
|
.topbar { flex-wrap: wrap; row-gap: 10px; padding: 12px 16px; }
|
|
.search-box { order: 5; flex-basis: 100%; }
|
|
|
|
.welcome { padding: 40px 16px 36px; }
|
|
.welcome-cards { grid-template-columns: 1fr; }
|
|
}
|