diff --git a/internal/cloudcp/portal/dist/build_manifest.json b/internal/cloudcp/portal/dist/build_manifest.json
index 9ed4d67ac1..3609eaccc2 100644
--- a/internal/cloudcp/portal/dist/build_manifest.json
+++ b/internal/cloudcp/portal/dist/build_manifest.json
@@ -1,5 +1,5 @@
{
- "source_hash": "087912d40011626d575748045a827f33b34abfd10a3a35ddf8f79ba776b2f549",
+ "source_hash": "02f5d00566a4d55ae7999d1121d77557d884ffcedd01d2a278a62f90d61be7a4",
"build_inputs": [
"package.json",
"tsconfig.json",
diff --git a/internal/cloudcp/portal/dist/portal_app.css b/internal/cloudcp/portal/dist/portal_app.css
index 6ae2e3efe3..731c612ab6 100644
--- a/internal/cloudcp/portal/dist/portal_app.css
+++ b/internal/cloudcp/portal/dist/portal_app.css
@@ -1,15 +1,15 @@
/* src/styles.css */
:root {
color-scheme: light;
- --bg: #f3f4f6;
+ --bg: #edf1f5;
--panel: #ffffff;
- --panel-strong: #fbfbfc;
- --panel-muted: #f6f7f9;
- --ink: #17202a;
- --ink-soft: #5a6675;
- --ink-muted: #7d8895;
- --line: #dde3ea;
- --line-strong: #c5d0da;
+ --panel-strong: #f8fafc;
+ --panel-muted: #f3f6f9;
+ --ink: #17212b;
+ --ink-soft: #4d5b6a;
+ --ink-muted: #6d7784;
+ --line: #d6dfe8;
+ --line-strong: #bcc8d3;
--accent: #0b6a72;
--accent-strong: #084d53;
--accent-soft: #dceef0;
@@ -19,11 +19,15 @@
--danger-soft: #fdeaea;
--success: #19603c;
--success-soft: #e3f3e8;
+ --nav-bg: #111b24;
+ --nav-line: #203240;
+ --nav-copy: #8fa0b0;
+ --nav-ink: #edf4f8;
--shadow-sm: 0 1px 2px rgba(23, 32, 42, 0.06);
--shadow-md: 0 10px 24px rgba(23, 32, 42, 0.08);
--radius-sm: 6px;
- --radius-md: 12px;
- --radius-lg: 18px;
+ --radius-md: 10px;
+ --radius-lg: 16px;
}
* {
box-sizing: border-box;
@@ -35,14 +39,16 @@ body {
margin: 0;
min-height: 100vh;
font-family:
- Inter,
+ "SF Pro Text",
+ "SF Pro Display",
"Segoe UI Variable Text",
"Segoe UI",
-apple-system,
BlinkMacSystemFont,
"Helvetica Neue",
+ Arial,
sans-serif;
- background: #f3f4f6;
+ background: var(--bg);
color: var(--ink);
}
a {
@@ -56,9 +62,9 @@ header {
align-items: center;
justify-content: space-between;
gap: 16px;
- padding: 18px 28px;
- border-bottom: 1px solid rgba(197, 208, 218, 0.8);
- background: rgba(248, 249, 251, 0.92);
+ padding: 14px 24px;
+ border-bottom: 1px solid rgba(197, 208, 218, 0.75);
+ background: rgba(247, 249, 252, 0.94);
backdrop-filter: blur(14px);
}
header .brand {
@@ -147,30 +153,30 @@ header .logout-btn:hover,
font-size: 13px;
}
.main {
- max-width: 1360px;
+ max-width: 1480px;
margin: 0 auto;
- padding: 28px 24px 56px;
+ padding: 22px 24px 56px;
}
.portal-shell {
display: flex;
flex-direction: column;
- gap: 24px;
+ gap: 18px;
}
.portal-hero {
display: grid;
- grid-template-columns: minmax(0, 1.7fr) minmax(300px, 0.95fr);
- gap: 20px;
- padding: 30px;
+ grid-template-columns: minmax(0, 1.8fr) minmax(360px, 0.92fr);
+ gap: 16px;
+ padding: 22px 24px;
border: 1px solid var(--line);
border-radius: var(--radius-lg);
- background: rgba(255, 255, 255, 0.98);
- box-shadow: var(--shadow-md);
+ background: var(--panel);
+ box-shadow: var(--shadow-sm);
}
.portal-hero-copy {
display: flex;
flex-direction: column;
- gap: 10px;
- max-width: 820px;
+ gap: 8px;
+ max-width: 900px;
}
.portal-hero-kicker,
.account-panel-kicker,
@@ -192,32 +198,32 @@ header .logout-btn:hover,
.portal-hero h1,
.intro-card h1 {
margin: 0;
- font-size: clamp(1.85rem, 3.1vw, 2.9rem);
+ font-size: clamp(1.75rem, 2.6vw, 2.6rem);
line-height: 0.98;
letter-spacing: -0.045em;
}
.portal-hero p,
.intro-card p {
margin: 0;
- font-size: 15px;
- line-height: 1.6;
+ font-size: 14px;
+ line-height: 1.55;
color: var(--ink-soft);
}
.portal-hero-stats {
display: grid;
grid-template-columns: repeat(3, minmax(0, 1fr));
- gap: 12px;
+ gap: 10px;
align-self: stretch;
}
.portal-hero-stat {
display: flex;
flex-direction: column;
justify-content: space-between;
- min-height: 120px;
- padding: 16px;
+ min-height: 92px;
+ padding: 14px;
border: 1px solid var(--line);
border-radius: var(--radius-md);
- background: rgba(255, 255, 255, 0.92);
+ background: var(--panel-strong);
}
.portal-hero-stat-label {
font-size: 12px;
@@ -227,7 +233,7 @@ header .logout-btn:hover,
text-transform: uppercase;
}
.portal-hero-stat-value {
- font-size: 24px;
+ font-size: 22px;
font-weight: 800;
line-height: 1.05;
letter-spacing: -0.04em;
@@ -243,19 +249,45 @@ header .logout-btn:hover,
}
.portal-shell-layout {
display: grid;
- grid-template-columns: 248px minmax(0, 1fr);
- gap: 24px;
+ grid-template-columns: 240px minmax(0, 1fr);
+ gap: 18px;
align-items: start;
}
.portal-shell-nav {
position: sticky;
top: 88px;
- border: 1px solid var(--line);
+ border: 1px solid var(--nav-line);
border-radius: var(--radius-md);
- background: rgba(255, 255, 255, 0.98);
+ background: var(--nav-bg);
box-shadow: var(--shadow-sm);
overflow: hidden;
}
+.portal-shell-nav-header {
+ display: flex;
+ flex-direction: column;
+ gap: 6px;
+ padding: 18px 18px 16px;
+ border-bottom: 1px solid var(--nav-line);
+}
+.portal-shell-nav-eyebrow {
+ font-size: 11px;
+ font-weight: 800;
+ letter-spacing: 0.14em;
+ text-transform: uppercase;
+ color: #78b6c0;
+}
+.portal-shell-nav-title {
+ font-size: 20px;
+ line-height: 1.02;
+ letter-spacing: -0.03em;
+ font-weight: 800;
+ color: var(--nav-ink);
+}
+.portal-shell-nav-support {
+ font-size: 12px;
+ line-height: 1.45;
+ color: var(--nav-copy);
+}
.portal-shell-nav-group {
display: flex;
flex-direction: column;
@@ -265,9 +297,9 @@ header .logout-btn:hover,
flex-direction: column;
gap: 6px;
width: 100%;
- padding: 15px 16px;
+ padding: 14px 18px;
border: none;
- border-bottom: 1px solid var(--line);
+ border-bottom: 1px solid var(--nav-line);
background: transparent;
text-align: left;
cursor: pointer;
@@ -277,21 +309,23 @@ header .logout-btn:hover,
border-bottom: none;
}
.portal-shell-nav-link:hover {
- background: #f7fafc;
+ background: rgba(255, 255, 255, 0.04);
}
.portal-shell-nav-link.active {
- background: #eef6f7;
+ background: rgba(120, 182, 192, 0.12);
+ box-shadow: inset 3px 0 0 #78b6c0;
}
.portal-shell-nav-label {
display: block;
font-size: 14px;
font-weight: 700;
+ color: var(--nav-ink);
}
.portal-shell-nav-copy {
display: block;
font-size: 12px;
line-height: 1.45;
- color: var(--ink-soft);
+ color: var(--nav-copy);
}
.portal-shell-main {
min-width: 0;
@@ -339,7 +373,7 @@ header .logout-btn:hover,
.portal-top-section-header h2,
.service-header h2 {
margin: 6px 0 0;
- font-size: clamp(1.7rem, 2.2vw, 2.35rem);
+ font-size: clamp(1.55rem, 2vw, 2.15rem);
line-height: 1.02;
letter-spacing: -0.05em;
}
@@ -360,7 +394,7 @@ header .logout-btn:hover,
}
.account-surface {
border-radius: var(--radius-lg);
- padding: 26px;
+ padding: 22px;
}
.account-surface + .account-surface {
margin-top: 18px;
@@ -371,7 +405,7 @@ header .logout-btn:hover,
justify-content: space-between;
gap: 16px;
padding-bottom: 18px;
- border-bottom: 1px solid rgba(203, 183, 153, 0.6);
+ border-bottom: 1px solid var(--line);
}
.account-heading h2 {
margin: 4px 0 0;
@@ -427,7 +461,7 @@ header .logout-btn:hover,
.service-panel,
.team-roster,
.team-invite-panel {
- padding: 18px;
+ padding: 16px;
}
.account-overview-card h3,
.account-stage-header h3,
@@ -456,8 +490,8 @@ header .logout-btn:hover,
}
.account-command-deck {
display: grid;
- grid-template-columns: minmax(280px, 1.05fr) minmax(0, 1.35fr);
- gap: 14px;
+ grid-template-columns: minmax(320px, 1.08fr) minmax(0, 1.3fr);
+ gap: 12px;
align-items: stretch;
}
.account-metric-strip {
@@ -503,7 +537,7 @@ header .logout-btn:hover,
justify-content: space-between;
gap: 18px;
grid-column: 1 / -1;
- padding: 16px 18px 0;
+ padding: 14px 16px 0;
border-top: 1px solid var(--line);
}
.account-action-copy {
@@ -526,7 +560,7 @@ header .logout-btn:hover,
.portal-support-panel {
border: 1px solid var(--line);
border-radius: var(--radius-lg);
- padding: 26px;
+ padding: 22px;
background: rgba(255, 255, 255, 0.98);
box-shadow: var(--shadow-sm);
}
@@ -572,19 +606,19 @@ header .logout-btn:hover,
.workspace-list {
display: flex;
flex-direction: column;
- gap: 12px;
+ gap: 10px;
}
.workspace-row {
display: grid;
grid-template-columns: minmax(0, 1fr) auto;
- gap: 16px;
+ gap: 14px;
align-items: center;
- padding: 20px;
+ padding: 16px;
}
.workspace-row-main {
display: grid;
- grid-template-columns: minmax(0, 1.4fr) minmax(220px, 0.85fr);
- gap: 18px;
+ grid-template-columns: minmax(0, 1.55fr) minmax(220px, 0.8fr);
+ gap: 14px;
align-items: center;
}
.workspace-row-title {
@@ -601,7 +635,7 @@ header .logout-btn:hover,
.workspace-management-content h4,
.team-panel-heading h4 {
margin: 0;
- font-size: 18px;
+ font-size: 17px;
line-height: 1.1;
letter-spacing: -0.025em;
}
@@ -636,8 +670,8 @@ header .logout-btn:hover,
display: flex;
flex-direction: column;
gap: 8px;
- min-height: 74px;
- padding: 14px;
+ min-height: 68px;
+ padding: 12px;
border: 1px solid var(--line);
border-radius: var(--radius-md);
background: var(--panel-muted);
@@ -759,7 +793,7 @@ header .logout-btn:hover,
}
.team-management-grid {
display: grid;
- grid-template-columns: minmax(0, 1.6fr) minmax(320px, 0.95fr);
+ grid-template-columns: minmax(0, 1.65fr) minmax(320px, 0.92fr);
gap: 14px;
}
.team-panel-heading {
@@ -771,11 +805,11 @@ header .logout-btn:hover,
gap: 10px;
}
.team-member-row {
- display: flex;
+ display: grid;
+ grid-template-columns: minmax(0, 1fr) 120px auto;
align-items: center;
- justify-content: space-between;
gap: 16px;
- padding: 14px 16px;
+ padding: 12px 14px;
border: 1px solid var(--line);
border-radius: 10px;
background: var(--panel-muted);
@@ -798,9 +832,17 @@ header .logout-btn:hover,
.team-member-controls {
display: flex;
align-items: center;
+ justify-content: end;
gap: 10px;
flex-wrap: wrap;
}
+.team-member-role-summary {
+ font-size: 12px;
+ font-weight: 800;
+ letter-spacing: 0.1em;
+ text-transform: uppercase;
+ color: var(--ink-muted);
+}
.team-role-badge {
display: inline-flex;
align-items: center;
@@ -846,13 +888,13 @@ header .logout-btn:hover,
.service-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
- gap: 14px;
+ gap: 12px;
}
.service-card {
display: flex;
flex-direction: column;
gap: 8px;
- padding: 18px;
+ padding: 16px;
}
.service-card-button {
width: 100%;
@@ -862,7 +904,7 @@ header .logout-btn:hover,
.service-card:hover,
.service-card-button:hover {
border-color: var(--accent);
- box-shadow: var(--shadow-md);
+ box-shadow: var(--shadow-sm);
}
.service-panel {
padding: 22px;
@@ -943,6 +985,14 @@ header .logout-btn:hover,
.support-copy {
margin-top: 10px !important;
}
+.account-overview-callout {
+ margin-top: 14px;
+ padding-top: 14px;
+ border-top: 1px solid var(--line);
+ font-size: 13px;
+ line-height: 1.65;
+ color: var(--ink-soft);
+}
.badge {
display: inline-flex;
align-items: center;
@@ -1044,7 +1094,7 @@ header .logout-btn:hover,
@media (max-width: 920px) {
.portal-hero {
grid-template-columns: 1fr;
- padding: 28px;
+ padding: 20px;
}
.portal-hero-stats {
grid-template-columns: 1fr;
@@ -1060,12 +1110,12 @@ header .logout-btn:hover,
}
@media (max-width: 720px) {
header {
- padding: 16px 18px;
+ padding: 14px 16px;
align-items: flex-start;
flex-direction: column;
}
.main {
- padding: 18px 16px 40px;
+ padding: 18px 14px 40px;
}
.portal-shell-layout,
.portal-support-card-grid,
@@ -1076,7 +1126,7 @@ header .logout-btn:hover,
.portal-hero,
.intro-card,
.service-panel {
- padding: 20px;
+ padding: 18px;
}
.account-surface-header,
.workspace-management-header,
@@ -1097,9 +1147,12 @@ header .logout-btn:hover,
}
.team-member-row,
.team-member-controls {
- flex-direction: column;
+ grid-template-columns: 1fr;
align-items: stretch;
}
+ .team-member-controls {
+ justify-content: start;
+ }
.workspace-row-status-grid {
grid-template-columns: 1fr;
}
diff --git a/internal/cloudcp/portal/dist/portal_app.js b/internal/cloudcp/portal/dist/portal_app.js
index c6f62a81d0..d4f4cc557e 100644
--- a/internal/cloudcp/portal/dist/portal_app.js
+++ b/internal/cloudcp/portal/dist/portal_app.js
@@ -1,5 +1,19 @@
(() => {
// src/account_view.ts
+ function roleLabel(role) {
+ switch (role) {
+ case "owner":
+ return "Owner";
+ case "admin":
+ return "Admin";
+ case "tech":
+ return "Tech";
+ case "read_only":
+ return "Read-only";
+ default:
+ return role || "Member";
+ }
+ }
function getElement(id) {
return document.getElementById(id);
}
@@ -104,7 +118,7 @@
if (member.role === "owner" && !isOwner) {
var locked = document.createElement("span");
locked.className = "team-role-badge";
- locked.textContent = "Owner";
+ locked.textContent = roleLabel(member.role);
return locked;
}
var sel = document.createElement("select");
@@ -113,7 +127,7 @@
for (var j = 0; j < roles.length; j += 1) {
var opt = document.createElement("option");
opt.value = roles[j];
- opt.textContent = roles[j].replace("_", " ");
+ opt.textContent = roleLabel(roles[j]);
if (member.role === roles[j]) opt.selected = true;
sel.appendChild(opt);
}
@@ -147,14 +161,18 @@
identity.appendChild(email);
var caption = document.createElement("div");
caption.className = "team-member-caption";
- caption.textContent = member.role === "owner" ? "Full account owner access" : "Hosted account operator";
+ caption.textContent = member.role === "owner" ? "Full account control" : "Hosted account access";
identity.appendChild(caption);
+ var roleSummary = document.createElement("div");
+ roleSummary.className = "team-member-role-summary";
+ roleSummary.textContent = roleLabel(member.role);
var controls = document.createElement("div");
controls.className = "team-member-controls";
controls.appendChild(renderTeamRoleControl(accountID, member, isOwner));
var action = renderTeamMemberAction(accountID, member, isOwner);
if (action) controls.appendChild(action);
row.appendChild(identity);
+ row.appendChild(roleSummary);
row.appendChild(controls);
return row;
}
@@ -1638,7 +1656,7 @@
var hosted = hasHostedAccounts(accounts);
var workspaceTotal = countWorkspaces(accounts);
var title = hosted ? "Pulse Account" : "Self-hosted Pulse Account";
- var summary = hosted ? "Open hosted workspaces, manage account access, and handle commercial account services from one place." : "Use this account for self-hosted billing, license recovery, refunds, and privacy actions. Hosted workspace access will appear here when it is attached to this email.";
+ var summary = hosted ? "Run hosted operations, operator access, and commercial account services from one control surface." : "Use this account for billing, license recovery, refunds, and privacy actions. Hosted workspace access will appear here when it is attached to this email.";
return '' + (hosted ? "Hosted access is active on this account." : "No hosted workspace access is attached to this account yet.") + "
" + title + " " + summary + '
Hosted access ' + (hosted ? "Active" : "Not attached") + '
Accounts ' + (accounts.length === 1 ? "1 account" : String(accounts.length) + " accounts") + '
Workspaces ' + String(workspaceTotal) + "
";
}
function shellSectionButton(section, activeSection, title, copy) {
@@ -1646,7 +1664,7 @@
}
function renderShellNavigation(accounts, supportEmail, activeSection) {
var hosted = hasHostedAccounts(accounts);
- return '' + shellSectionButton("overview", activeSection, "Overview", hosted ? "Posture, accounts, and quick actions" : "Account summary and access state") + shellSectionButton("workspaces", activeSection, hosted ? "Workspaces" : "Hosted access", hosted ? "Hosted fleet and lifecycle actions" : "No hosted workspaces are attached yet") + shellSectionButton("team", activeSection, "Team", hosted ? "Access and operator roster" : "Account membership") + shellSectionButton("services", activeSection, "Account services", "Licenses, billing, refunds, and privacy") + shellSectionButton("support", activeSection, "Support", supportEmail || "Support contact") + "
";
+ return '' + shellSectionButton("overview", activeSection, "Overview", hosted ? "Posture, accounts, and quick actions" : "Account summary and access state") + shellSectionButton("workspaces", activeSection, hosted ? "Workspaces" : "Hosted access", hosted ? "Hosted fleet and lifecycle actions" : "No hosted workspaces are attached yet") + shellSectionButton("team", activeSection, "Team", hosted ? "Access and operator roster" : "Account membership") + shellSectionButton("services", activeSection, "Account services", "Licenses, billing, refunds, and privacy") + shellSectionButton("support", activeSection, "Support", supportEmail || "Support contact") + "
";
}
function renderWorkspaceCard(account, workspace, accountAPIBasePath) {
var status = workspaceHealthState(workspace);
@@ -1689,7 +1707,9 @@
addWorkspaceForm = '
';
}
}
- return 'Hosted posture
' + escapeHTML(account.name) + " " + escapeHTML(summaryText) + '
Active workspaces ' + String(activeCount) + '
Healthy ' + String(healthyCount) + '
Checking ' + String(checkingCount) + '
Needs attention ' + String(unhealthyCount) + "
" + actions + addWorkspaceForm + "
";
+ return 'Hosted posture
' + escapeHTML(account.name) + " " + escapeHTML(summaryText) + '
' + escapeHTML(
+ account.kind === "msp" ? "This is the control surface for hosted clients, account billing, and operator access." : "This is the control surface for hosted workspaces, billing, and account access."
+ ) + '
Active workspaces ' + String(activeCount) + '
Healthy ' + String(healthyCount) + '
Checking ' + String(checkingCount) + '
Needs attention ' + String(unhealthyCount) + "
" + actions + addWorkspaceForm + "
";
}
function renderAccountWorkspaceSection(account, accountAPIBasePath) {
var workspaces = Array.isArray(account.workspaces) ? account.workspaces : [];
@@ -1706,7 +1726,7 @@
return '
People on this account Keep the roster small and role assignment explicit. The people listed here are the ones who can operate the hosted fleet.
Invite someone new Add another operator with the minimum role they need for this account.
';
}
function renderSupportSection(context) {
- return 'Support
Get help without leaving Pulse Account Use support when hosted access looks wrong, billing does not behave as expected, or you need help with commercial licensing and privacy actions.
Commercial services Self-hosted subscriptions, license recovery, refunds, and privacy requests all route through the same account surface.
Open account services ';
+ return 'Support
Support and escalation Use support when hosted access looks wrong, billing does not behave as expected, or you need help with commercial licensing and privacy actions.
Commercial services Self-hosted subscriptions, license recovery, refunds, and privacy requests all route through the same account surface.
Open account services ';
}
function renderHeaderHTML(context) {
if (context.bootstrap.authenticated) {
diff --git a/internal/cloudcp/portal/frontend/src/account_view.ts b/internal/cloudcp/portal/frontend/src/account_view.ts
index 44c688899c..9ce7850503 100644
--- a/internal/cloudcp/portal/frontend/src/account_view.ts
+++ b/internal/cloudcp/portal/frontend/src/account_view.ts
@@ -8,6 +8,21 @@ import type {
type FormValueElement = HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement;
+function roleLabel(role: string): string {
+ switch (role) {
+ case 'owner':
+ return 'Owner';
+ case 'admin':
+ return 'Admin';
+ case 'tech':
+ return 'Tech';
+ case 'read_only':
+ return 'Read-only';
+ default:
+ return role || 'Member';
+ }
+}
+
export function getElement(id: string): T | null {
return document.getElementById(id) as T | null;
}
@@ -131,7 +146,7 @@ function renderTeamRoleControl(accountID: string, member: PortalTeamMember, isOw
if (member.role === 'owner' && !isOwner) {
var locked = document.createElement('span');
locked.className = 'team-role-badge';
- locked.textContent = 'Owner';
+ locked.textContent = roleLabel(member.role);
return locked;
}
@@ -141,7 +156,7 @@ function renderTeamRoleControl(accountID: string, member: PortalTeamMember, isOw
for (var j = 0; j < roles.length; j += 1) {
var opt = document.createElement('option');
opt.value = roles[j];
- opt.textContent = roles[j].replace('_', ' ');
+ opt.textContent = roleLabel(roles[j]);
if (member.role === roles[j]) opt.selected = true;
sel.appendChild(opt);
}
@@ -181,9 +196,13 @@ function renderTeamMemberRow(accountID: string, member: PortalTeamMember, isOwne
var caption = document.createElement('div');
caption.className = 'team-member-caption';
- caption.textContent = member.role === 'owner' ? 'Full account owner access' : 'Hosted account operator';
+ caption.textContent = member.role === 'owner' ? 'Full account control' : 'Hosted account access';
identity.appendChild(caption);
+ var roleSummary = document.createElement('div');
+ roleSummary.className = 'team-member-role-summary';
+ roleSummary.textContent = roleLabel(member.role);
+
var controls = document.createElement('div');
controls.className = 'team-member-controls';
controls.appendChild(renderTeamRoleControl(accountID, member, isOwner));
@@ -191,6 +210,7 @@ function renderTeamMemberRow(accountID: string, member: PortalTeamMember, isOwne
if (action) controls.appendChild(action);
row.appendChild(identity);
+ row.appendChild(roleSummary);
row.appendChild(controls);
return row;
}
diff --git a/internal/cloudcp/portal/frontend/src/shell_view.ts b/internal/cloudcp/portal/frontend/src/shell_view.ts
index dc7a33dca7..5901db2fda 100644
--- a/internal/cloudcp/portal/frontend/src/shell_view.ts
+++ b/internal/cloudcp/portal/frontend/src/shell_view.ts
@@ -114,8 +114,8 @@ function renderOverviewBand(accounts: PortalAccountSummary[]): string {
var workspaceTotal = countWorkspaces(accounts);
var title = hosted ? 'Pulse Account' : 'Self-hosted Pulse Account';
var summary = hosted
- ? 'Open hosted workspaces, manage account access, and handle commercial account services from one place.'
- : 'Use this account for self-hosted billing, license recovery, refunds, and privacy actions. Hosted workspace access will appear here when it is attached to this email.';
+ ? 'Run hosted operations, operator access, and commercial account services from one control surface.'
+ : 'Use this account for billing, license recovery, refunds, and privacy actions. Hosted workspace access will appear here when it is attached to this email.';
return (
'' +
@@ -155,6 +155,11 @@ function renderShellNavigation(accounts: PortalAccountSummary[], supportEmail: s
var hosted = hasHostedAccounts(accounts);
return (
'' +
+ '' +
'' +
shellSectionButton('overview', activeSection, 'Overview', hosted ? 'Posture, accounts, and quick actions' : 'Account summary and access state') +
shellSectionButton('workspaces', activeSection, hosted ? 'Workspaces' : 'Hosted access', hosted ? 'Hosted fleet and lifecycle actions' : 'No hosted workspaces are attached yet') +
@@ -305,6 +310,10 @@ function renderAccountOverviewSection(account: PortalAccountSummary): string {
'
Hosted posture
' +
'
' + escapeHTML(account.name) + ' ' +
'
' + escapeHTML(summaryText) + '
' +
+ '
' + escapeHTML(account.kind === 'msp'
+ ? 'This is the control surface for hosted clients, account billing, and operator access.'
+ : 'This is the control surface for hosted workspaces, billing, and account access.'
+ ) + '
' +
'
' +
'' +
'
' +
@@ -463,7 +472,7 @@ function renderSupportSection(context: ShellViewContext): string {
return (
'
' +
'Support
' +
- 'Get help without leaving Pulse Account ' +
+ 'Support and escalation ' +
'Use support when hosted access looks wrong, billing does not behave as expected, or you need help with commercial licensing and privacy actions.
' +
'' +
'
' +
diff --git a/internal/cloudcp/portal/frontend/src/styles.css b/internal/cloudcp/portal/frontend/src/styles.css
index 13e73e011f..010aca3189 100644
--- a/internal/cloudcp/portal/frontend/src/styles.css
+++ b/internal/cloudcp/portal/frontend/src/styles.css
@@ -1,14 +1,14 @@
:root {
color-scheme: light;
- --bg: #f3f4f6;
+ --bg: #edf1f5;
--panel: #ffffff;
- --panel-strong: #fbfbfc;
- --panel-muted: #f6f7f9;
- --ink: #17202a;
- --ink-soft: #5a6675;
- --ink-muted: #7d8895;
- --line: #dde3ea;
- --line-strong: #c5d0da;
+ --panel-strong: #f8fafc;
+ --panel-muted: #f3f6f9;
+ --ink: #17212b;
+ --ink-soft: #4d5b6a;
+ --ink-muted: #6d7784;
+ --line: #d6dfe8;
+ --line-strong: #bcc8d3;
--accent: #0b6a72;
--accent-strong: #084d53;
--accent-soft: #dceef0;
@@ -18,11 +18,15 @@
--danger-soft: #fdeaea;
--success: #19603c;
--success-soft: #e3f3e8;
+ --nav-bg: #111b24;
+ --nav-line: #203240;
+ --nav-copy: #8fa0b0;
+ --nav-ink: #edf4f8;
--shadow-sm: 0 1px 2px rgba(23, 32, 42, 0.06);
--shadow-md: 0 10px 24px rgba(23, 32, 42, 0.08);
--radius-sm: 6px;
- --radius-md: 12px;
- --radius-lg: 18px;
+ --radius-md: 10px;
+ --radius-lg: 16px;
}
* { box-sizing: border-box; }
@@ -34,8 +38,8 @@ html {
body {
margin: 0;
min-height: 100vh;
- font-family: Inter, "Segoe UI Variable Text", "Segoe UI", -apple-system, BlinkMacSystemFont, "Helvetica Neue", sans-serif;
- background: #f3f4f6;
+ font-family: "SF Pro Text", "SF Pro Display", "Segoe UI Variable Text", "Segoe UI", -apple-system, BlinkMacSystemFont, "Helvetica Neue", Arial, sans-serif;
+ background: var(--bg);
color: var(--ink);
}
@@ -49,9 +53,9 @@ header {
align-items: center;
justify-content: space-between;
gap: 16px;
- padding: 18px 28px;
- border-bottom: 1px solid rgba(197, 208, 218, 0.8);
- background: rgba(248, 249, 251, 0.92);
+ padding: 14px 24px;
+ border-bottom: 1px solid rgba(197, 208, 218, 0.75);
+ background: rgba(247, 249, 252, 0.94);
backdrop-filter: blur(14px);
}
@@ -145,33 +149,33 @@ header .logout-btn:hover,
}
.main {
- max-width: 1360px;
+ max-width: 1480px;
margin: 0 auto;
- padding: 28px 24px 56px;
+ padding: 22px 24px 56px;
}
.portal-shell {
display: flex;
flex-direction: column;
- gap: 24px;
+ gap: 18px;
}
.portal-hero {
display: grid;
- grid-template-columns: minmax(0, 1.7fr) minmax(300px, 0.95fr);
- gap: 20px;
- padding: 30px;
+ grid-template-columns: minmax(0, 1.8fr) minmax(360px, 0.92fr);
+ gap: 16px;
+ padding: 22px 24px;
border: 1px solid var(--line);
border-radius: var(--radius-lg);
- background: rgba(255, 255, 255, 0.98);
- box-shadow: var(--shadow-md);
+ background: var(--panel);
+ box-shadow: var(--shadow-sm);
}
.portal-hero-copy {
display: flex;
flex-direction: column;
- gap: 10px;
- max-width: 820px;
+ gap: 8px;
+ max-width: 900px;
}
.portal-hero-kicker,
@@ -197,7 +201,7 @@ header .logout-btn:hover,
.portal-hero h1,
.intro-card h1 {
margin: 0;
- font-size: clamp(1.85rem, 3.1vw, 2.9rem);
+ font-size: clamp(1.75rem, 2.6vw, 2.6rem);
line-height: 0.98;
letter-spacing: -0.045em;
}
@@ -205,15 +209,15 @@ header .logout-btn:hover,
.portal-hero p,
.intro-card p {
margin: 0;
- font-size: 15px;
- line-height: 1.6;
+ font-size: 14px;
+ line-height: 1.55;
color: var(--ink-soft);
}
.portal-hero-stats {
display: grid;
grid-template-columns: repeat(3, minmax(0, 1fr));
- gap: 12px;
+ gap: 10px;
align-self: stretch;
}
@@ -221,11 +225,11 @@ header .logout-btn:hover,
display: flex;
flex-direction: column;
justify-content: space-between;
- min-height: 120px;
- padding: 16px;
+ min-height: 92px;
+ padding: 14px;
border: 1px solid var(--line);
border-radius: var(--radius-md);
- background: rgba(255, 255, 255, 0.92);
+ background: var(--panel-strong);
}
.portal-hero-stat-label {
@@ -237,7 +241,7 @@ header .logout-btn:hover,
}
.portal-hero-stat-value {
- font-size: 24px;
+ font-size: 22px;
font-weight: 800;
line-height: 1.05;
letter-spacing: -0.04em;
@@ -255,21 +259,51 @@ header .logout-btn:hover,
.portal-shell-layout {
display: grid;
- grid-template-columns: 248px minmax(0, 1fr);
- gap: 24px;
+ grid-template-columns: 240px minmax(0, 1fr);
+ gap: 18px;
align-items: start;
}
.portal-shell-nav {
position: sticky;
top: 88px;
- border: 1px solid var(--line);
+ border: 1px solid var(--nav-line);
border-radius: var(--radius-md);
- background: rgba(255, 255, 255, 0.98);
+ background: var(--nav-bg);
box-shadow: var(--shadow-sm);
overflow: hidden;
}
+.portal-shell-nav-header {
+ display: flex;
+ flex-direction: column;
+ gap: 6px;
+ padding: 18px 18px 16px;
+ border-bottom: 1px solid var(--nav-line);
+}
+
+.portal-shell-nav-eyebrow {
+ font-size: 11px;
+ font-weight: 800;
+ letter-spacing: 0.14em;
+ text-transform: uppercase;
+ color: #78b6c0;
+}
+
+.portal-shell-nav-title {
+ font-size: 20px;
+ line-height: 1.02;
+ letter-spacing: -0.03em;
+ font-weight: 800;
+ color: var(--nav-ink);
+}
+
+.portal-shell-nav-support {
+ font-size: 12px;
+ line-height: 1.45;
+ color: var(--nav-copy);
+}
+
.portal-shell-nav-group {
display: flex;
flex-direction: column;
@@ -280,9 +314,9 @@ header .logout-btn:hover,
flex-direction: column;
gap: 6px;
width: 100%;
- padding: 15px 16px;
+ padding: 14px 18px;
border: none;
- border-bottom: 1px solid var(--line);
+ border-bottom: 1px solid var(--nav-line);
background: transparent;
text-align: left;
cursor: pointer;
@@ -294,24 +328,26 @@ header .logout-btn:hover,
}
.portal-shell-nav-link:hover {
- background: #f7fafc;
+ background: rgba(255, 255, 255, 0.04);
}
.portal-shell-nav-link.active {
- background: #eef6f7;
+ background: rgba(120, 182, 192, 0.12);
+ box-shadow: inset 3px 0 0 #78b6c0;
}
.portal-shell-nav-label {
display: block;
font-size: 14px;
font-weight: 700;
+ color: var(--nav-ink);
}
.portal-shell-nav-copy {
display: block;
font-size: 12px;
line-height: 1.45;
- color: var(--ink-soft);
+ color: var(--nav-copy);
}
.portal-shell-main {
@@ -368,7 +404,7 @@ header .logout-btn:hover,
.portal-top-section-header h2,
.service-header h2 {
margin: 6px 0 0;
- font-size: clamp(1.7rem, 2.2vw, 2.35rem);
+ font-size: clamp(1.55rem, 2vw, 2.15rem);
line-height: 1.02;
letter-spacing: -0.05em;
}
@@ -392,7 +428,7 @@ header .logout-btn:hover,
.account-surface {
border-radius: var(--radius-lg);
- padding: 26px;
+ padding: 22px;
}
.account-surface + .account-surface {
@@ -405,7 +441,7 @@ header .logout-btn:hover,
justify-content: space-between;
gap: 16px;
padding-bottom: 18px;
- border-bottom: 1px solid rgba(203, 183, 153, 0.6);
+ border-bottom: 1px solid var(--line);
}
.account-heading h2 {
@@ -468,7 +504,7 @@ header .logout-btn:hover,
.service-panel,
.team-roster,
.team-invite-panel {
- padding: 18px;
+ padding: 16px;
}
.account-overview-card h3,
@@ -500,8 +536,8 @@ header .logout-btn:hover,
.account-command-deck {
display: grid;
- grid-template-columns: minmax(280px, 1.05fr) minmax(0, 1.35fr);
- gap: 14px;
+ grid-template-columns: minmax(320px, 1.08fr) minmax(0, 1.3fr);
+ gap: 12px;
align-items: stretch;
}
@@ -547,7 +583,7 @@ header .logout-btn:hover,
justify-content: space-between;
gap: 18px;
grid-column: 1 / -1;
- padding: 16px 18px 0;
+ padding: 14px 16px 0;
border-top: 1px solid var(--line);
}
@@ -575,7 +611,7 @@ header .logout-btn:hover,
.portal-support-panel {
border: 1px solid var(--line);
border-radius: var(--radius-lg);
- padding: 26px;
+ padding: 22px;
background: rgba(255, 255, 255, 0.98);
box-shadow: var(--shadow-sm);
}
@@ -629,21 +665,21 @@ header .logout-btn:hover,
.workspace-list {
display: flex;
flex-direction: column;
- gap: 12px;
+ gap: 10px;
}
.workspace-row {
display: grid;
grid-template-columns: minmax(0, 1fr) auto;
- gap: 16px;
+ gap: 14px;
align-items: center;
- padding: 20px;
+ padding: 16px;
}
.workspace-row-main {
display: grid;
- grid-template-columns: minmax(0, 1.4fr) minmax(220px, 0.85fr);
- gap: 18px;
+ grid-template-columns: minmax(0, 1.55fr) minmax(220px, 0.8fr);
+ gap: 14px;
align-items: center;
}
@@ -663,7 +699,7 @@ header .logout-btn:hover,
.workspace-management-content h4,
.team-panel-heading h4 {
margin: 0;
- font-size: 18px;
+ font-size: 17px;
line-height: 1.1;
letter-spacing: -0.025em;
}
@@ -703,8 +739,8 @@ header .logout-btn:hover,
display: flex;
flex-direction: column;
gap: 8px;
- min-height: 74px;
- padding: 14px;
+ min-height: 68px;
+ padding: 12px;
border: 1px solid var(--line);
border-radius: var(--radius-md);
background: var(--panel-muted);
@@ -846,7 +882,7 @@ header .logout-btn:hover,
.team-management-grid {
display: grid;
- grid-template-columns: minmax(0, 1.6fr) minmax(320px, 0.95fr);
+ grid-template-columns: minmax(0, 1.65fr) minmax(320px, 0.92fr);
gap: 14px;
}
@@ -861,11 +897,11 @@ header .logout-btn:hover,
}
.team-member-row {
- display: flex;
+ display: grid;
+ grid-template-columns: minmax(0, 1fr) 120px auto;
align-items: center;
- justify-content: space-between;
gap: 16px;
- padding: 14px 16px;
+ padding: 12px 14px;
border: 1px solid var(--line);
border-radius: 10px;
background: var(--panel-muted);
@@ -892,10 +928,19 @@ header .logout-btn:hover,
.team-member-controls {
display: flex;
align-items: center;
+ justify-content: end;
gap: 10px;
flex-wrap: wrap;
}
+.team-member-role-summary {
+ font-size: 12px;
+ font-weight: 800;
+ letter-spacing: 0.1em;
+ text-transform: uppercase;
+ color: var(--ink-muted);
+}
+
.team-role-badge {
display: inline-flex;
align-items: center;
@@ -948,14 +993,14 @@ header .logout-btn:hover,
.service-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
- gap: 14px;
+ gap: 12px;
}
.service-card {
display: flex;
flex-direction: column;
gap: 8px;
- padding: 18px;
+ padding: 16px;
}
.service-card-button {
@@ -967,7 +1012,7 @@ header .logout-btn:hover,
.service-card:hover,
.service-card-button:hover {
border-color: var(--accent);
- box-shadow: var(--shadow-md);
+ box-shadow: var(--shadow-sm);
}
.service-panel {
@@ -1068,6 +1113,15 @@ header .logout-btn:hover,
margin-top: 10px !important;
}
+.account-overview-callout {
+ margin-top: 14px;
+ padding-top: 14px;
+ border-top: 1px solid var(--line);
+ font-size: 13px;
+ line-height: 1.65;
+ color: var(--ink-soft);
+}
+
.badge {
display: inline-flex;
align-items: center;
@@ -1178,7 +1232,7 @@ header .logout-btn:hover,
@media (max-width: 920px) {
.portal-hero {
grid-template-columns: 1fr;
- padding: 28px;
+ padding: 20px;
}
.portal-hero-stats {
@@ -1198,13 +1252,13 @@ header .logout-btn:hover,
@media (max-width: 720px) {
header {
- padding: 16px 18px;
+ padding: 14px 16px;
align-items: flex-start;
flex-direction: column;
}
.main {
- padding: 18px 16px 40px;
+ padding: 18px 14px 40px;
}
.portal-shell-layout,
@@ -1217,7 +1271,7 @@ header .logout-btn:hover,
.portal-hero,
.intro-card,
.service-panel {
- padding: 20px;
+ padding: 18px;
}
.account-surface-header,
@@ -1242,10 +1296,14 @@ header .logout-btn:hover,
.team-member-row,
.team-member-controls {
- flex-direction: column;
+ grid-template-columns: 1fr;
align-items: stretch;
}
+ .team-member-controls {
+ justify-content: start;
+ }
+
.workspace-row-status-grid {
grid-template-columns: 1fr;
}