diff --git a/internal/cloudcp/portal/dist/build_manifest.json b/internal/cloudcp/portal/dist/build_manifest.json
index 5582d7b9bc..5b7d62c95d 100644
--- a/internal/cloudcp/portal/dist/build_manifest.json
+++ b/internal/cloudcp/portal/dist/build_manifest.json
@@ -1,5 +1,5 @@
{
- "source_hash": "b5bf3d15198c6bb317c1446735208077a02ef32a47dca5b012200aba131071ef",
+ "source_hash": "5a37d7a95cfb4c6cfa06bac62785c5638c391bcf52d6472e05c265f548f367e7",
"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 2e397e3631..f865d526f1 100644
--- a/internal/cloudcp/portal/dist/portal_app.css
+++ b/internal/cloudcp/portal/dist/portal_app.css
@@ -172,12 +172,22 @@ header .logout-btn:hover,
background: #fbfcfd;
box-shadow: var(--shadow-sm);
}
+.portal-hero-compact {
+ align-items: center;
+ padding: 14px 18px;
+}
.portal-hero-copy {
display: flex;
flex-direction: column;
- gap: 8px;
+ gap: 6px;
max-width: 900px;
}
+.portal-hero-heading-row {
+ display: flex;
+ align-items: center;
+ gap: 12px;
+ flex-wrap: wrap;
+}
.portal-hero-kicker,
.account-panel-kicker,
.service-card-kicker,
@@ -198,17 +208,30 @@ header .logout-btn:hover,
.portal-hero h1,
.intro-card h1 {
margin: 0;
- font-size: clamp(1.75rem, 2.6vw, 2.6rem);
- line-height: 0.98;
+ font-size: clamp(1.55rem, 2.2vw, 2.15rem);
+ line-height: 1;
letter-spacing: -0.045em;
}
.portal-hero p,
.intro-card p {
margin: 0;
- font-size: 14px;
- line-height: 1.55;
+ font-size: 13px;
+ line-height: 1.5;
color: var(--ink-soft);
}
+.portal-hero-chip {
+ display: inline-flex;
+ align-items: center;
+ min-height: 28px;
+ padding: 0 12px;
+ border-radius: 999px;
+ background: #edf5f7;
+ color: var(--accent-strong);
+ font-size: 11px;
+ font-weight: 800;
+ letter-spacing: 0.08em;
+ text-transform: uppercase;
+}
.portal-hero-stats {
display: grid;
grid-template-columns: repeat(3, minmax(0, 1fr));
@@ -219,8 +242,8 @@ header .logout-btn:hover,
display: flex;
flex-direction: column;
justify-content: space-between;
- min-height: 80px;
- padding: 12px 14px;
+ min-height: 72px;
+ padding: 11px 13px;
border: 1px solid var(--line);
border-radius: var(--radius-md);
background: #fcfdff;
@@ -233,9 +256,9 @@ header .logout-btn:hover,
text-transform: uppercase;
}
.portal-hero-stat-value {
- font-size: 22px;
+ font-size: 18px;
font-weight: 800;
- line-height: 1.05;
+ line-height: 1.2;
letter-spacing: -0.04em;
}
.portal-command-strip,
@@ -1112,13 +1135,13 @@ header .logout-btn:hover,
color: var(--ink-soft);
}
.service-action-list {
- display: flex;
- flex-direction: column;
- gap: 10px;
+ display: grid;
+ grid-template-columns: 1fr;
+ gap: 12px;
}
.service-shell {
display: grid;
- grid-template-columns: minmax(0, 1.45fr) minmax(280px, 0.7fr);
+ grid-template-columns: minmax(320px, 0.72fr) minmax(0, 1.28fr);
gap: 16px;
align-items: start;
}
@@ -1134,6 +1157,9 @@ header .logout-btn:hover,
border-radius: var(--radius-md);
background: var(--panel-muted);
}
+.service-brief-card-primary {
+ background: #fff;
+}
.service-brief-card h3 {
margin: 8px 0 0;
font-size: 19px;
@@ -1147,14 +1173,32 @@ header .logout-btn:hover,
color: var(--ink-soft);
}
.service-action-row {
- display: grid;
- grid-template-columns: minmax(0, 1fr) auto;
- gap: 16px;
- align-items: center;
+ display: flex;
+ flex-direction: column;
+ gap: 12px;
padding: 16px 18px;
border: 1px solid var(--line);
border-radius: var(--radius-md);
- background: var(--panel-muted);
+ background: #fff;
+}
+.service-action-tags {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ gap: 10px;
+}
+.service-action-tag {
+ display: inline-flex;
+ align-items: center;
+ min-height: 26px;
+ padding: 0 10px;
+ border-radius: 999px;
+ background: #fff;
+ color: var(--ink-muted);
+ font-size: 11px;
+ font-weight: 800;
+ letter-spacing: 0.08em;
+ text-transform: uppercase;
}
.service-action-copy {
display: flex;
@@ -1163,8 +1207,8 @@ header .logout-btn:hover,
}
.service-action-copy h3 {
margin: 0;
- font-size: 19px;
- line-height: 1.08;
+ font-size: 17px;
+ line-height: 1.16;
letter-spacing: -0.03em;
}
.service-action-copy p {
@@ -1174,6 +1218,37 @@ header .logout-btn:hover,
color: var(--ink-soft);
max-width: 760px;
}
+.service-action-highlights {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 8px;
+}
+.service-action-highlight {
+ display: inline-flex;
+ align-items: center;
+ min-height: 28px;
+ padding: 0 10px;
+ border-radius: 999px;
+ background: #fff;
+ color: var(--ink-soft);
+ font-size: 12px;
+ font-weight: 600;
+}
+.service-action-footer {
+ display: flex;
+ align-items: end;
+ justify-content: space-between;
+ gap: 16px;
+ margin-top: auto;
+ padding-top: 4px;
+ border-top: 1px solid var(--line);
+}
+.service-action-caption {
+ max-width: 320px;
+ font-size: 12px;
+ line-height: 1.5;
+ color: var(--ink-muted);
+}
.service-action-cta {
display: flex;
align-items: center;
@@ -1184,6 +1259,7 @@ header .logout-btn:hover,
.service-panel {
padding: 22px;
border-radius: var(--radius-md);
+ background: #fff;
}
.service-panel.visible.auth-panel {
display: block;
@@ -1351,7 +1427,8 @@ header .logout-btn:hover,
.account-management-grid,
.team-management-grid,
.account-overview-secondary,
- .service-shell {
+ .service-shell,
+ .service-action-list {
grid-template-columns: 1fr;
}
.portal-shell-layout,
@@ -1386,8 +1463,7 @@ header .logout-btn:hover,
.workspace-row-main {
grid-template-columns: 1fr;
}
- .workspace-list-toolbar,
- .service-action-row {
+ .workspace-list-toolbar {
grid-template-columns: 1fr;
}
}
@@ -1414,7 +1490,9 @@ header .logout-btn:hover,
.account-surface-header,
.workspace-management-header,
.team-management-header,
- .service-header {
+ .service-header,
+ .service-action-footer,
+ .service-action-tags {
flex-direction: column;
align-items: flex-start;
}
diff --git a/internal/cloudcp/portal/dist/portal_app.js b/internal/cloudcp/portal/dist/portal_app.js
index 61767cff5d..9a7fcf4d5c 100644
--- a/internal/cloudcp/portal/dist/portal_app.js
+++ b/internal/cloudcp/portal/dist/portal_app.js
@@ -1676,8 +1676,11 @@
}
return 'Checking ';
}
- function renderServiceActionRow(id, kicker, title, description, panelID, focusID) {
- return '
' + kicker + " " + title + " " + description + '
Open
';
+ function renderServiceActionRow(id, kicker, title, description, panelID, focusID, highlights) {
+ var highlightHTML = highlights.map(function(item) {
+ return '' + escapeHTML(item) + " ";
+ }).join("");
+ return '' + kicker + ' Self-hosted
' + title + " " + description + '
' + highlightHTML + '
';
}
function renderOverviewQuickAction(section, title, copy) {
return '' + title + ' ' + copy + " ";
@@ -1712,8 +1715,8 @@
var hosted = hasHostedAccounts(accounts);
var workspaceTotal = countWorkspaces(accounts);
var title = hosted ? "Pulse Account" : "Self-hosted Pulse Account";
- 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) + "
";
+ var summary = hosted ? "Hosted operations, operator access, and commercial account services live in one account console." : "Billing, license recovery, refunds, and privacy actions live here until hosted access 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 + ' ' + (hosted ? "Operator ready" : "Self-hosted only") + " " + summary + '
Hosted access ' + (hosted ? "Active" : "Not attached") + '
Accounts ' + (accounts.length === 1 ? "1 account" : String(accounts.length) + " accounts") + '
Workspace fleet ' + (workspaceTotal ? workspaceCountLabel(workspaceTotal) : "0 workspaces") + "
";
}
function shellSectionButton(section, activeSection, title, copy) {
return '' + title + ' ' + copy + " ";
@@ -1802,7 +1805,7 @@
var hostedContent = accounts.map(function(account) {
return '' + renderAccountOverviewSection(account) + renderAccountWorkspaceSection(account, context.accountAPIBasePath) + renderAccountTeamSection(account) + "
";
}).join("");
- return '' + renderOverviewBand(accounts) + '
' + renderShellNavigation(accounts, context.bootstrap.support_email || "", activeSection) + '
' + renderServiceActionRow("open-manage-service", "Billing", "Manage subscriptions", "Open Stripe billing access for existing self-hosted subscriptions without leaving the Pulse Account shell.", "manage-service-panel", "manage-inline-email") + renderServiceActionRow("open-retrieve-service", "Licenses", "Retrieve licenses", "Recover the latest active self-hosted license and invoice link for a commercial email address.", "retrieve-service-panel", "retrieve-inline-email") + renderServiceActionRow("open-refund-service", "Refunds", "Refund requests", "Request an immediate self-serve refund for eligible self-hosted purchases with explicit revocation confirmation.", "refund-service-panel", "refund-inline-email") + renderServiceActionRow("open-data-service", "Privacy", "Data and privacy", "Request commercial data export or deletion without leaving the account shell.", "data-service-panel", "data-export-email") + '
Data and privacy Request export or deletion of the commercial data tied to an email address. Payment data held directly by Stripe still requires support handling.
Payment-card data stays with Stripe. For Stripe deletion support, contact
' + escapeHTML(context.bootstrap.support_email || "") + ' .
' + renderSupportSection(context) + " ";
+ return '' + renderOverviewBand(accounts) + '
' + renderShellNavigation(accounts, context.bootstrap.support_email || "", activeSection) + '
Data and privacy Request export or deletion of the commercial data tied to an email address. Payment data held directly by Stripe still requires support handling.
Payment-card data stays with Stripe. For Stripe deletion support, contact
' + escapeHTML(context.bootstrap.support_email || "") + ' .
' + renderSupportSection(context) + " ";
}
function renderSignedOutPortalHTML(context) {
var statusHTML = "";
diff --git a/internal/cloudcp/portal/frontend/src/shell_view.ts b/internal/cloudcp/portal/frontend/src/shell_view.ts
index 082e265bdb..fcc4c328c7 100644
--- a/internal/cloudcp/portal/frontend/src/shell_view.ts
+++ b/internal/cloudcp/portal/frontend/src/shell_view.ts
@@ -108,19 +108,30 @@ function renderServiceActionRow(
title: string,
description: string,
panelID: string,
- focusID: string
+ focusID: string,
+ highlights: string[]
): string {
+ var highlightHTML = highlights.map(function(item) {
+ return '' + escapeHTML(item) + ' ';
+ }).join('');
return (
- '' +
- '
' +
+ '
' +
+ '' +
'' + kicker + ' ' +
+ 'Self-hosted ' +
+ '
' +
+ '' +
'
' + title + ' ' +
'
' + description + '
' +
'
' +
- '' +
- '
Open ' +
+ '
' + highlightHTML + '
' +
+ '' +
- '
'
+ ' '
);
}
@@ -190,14 +201,17 @@ function renderOverviewBand(accounts: PortalAccountSummary[]): string {
var workspaceTotal = countWorkspaces(accounts);
var title = hosted ? 'Pulse Account' : 'Self-hosted Pulse Account';
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.';
+ ? 'Hosted operations, operator access, and commercial account services live in one account console.'
+ : 'Billing, license recovery, refunds, and privacy actions live here until hosted access 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 + ' ' +
+ '
' +
+ '
' + title + ' ' +
+ '' + (hosted ? 'Operator ready' : 'Self-hosted only') + ' ' +
+ '' +
'
' + summary + '
' +
'
' +
'' +
@@ -210,8 +224,8 @@ function renderOverviewBand(accounts: PortalAccountSummary[]): string {
'' + (accounts.length === 1 ? '1 account' : String(accounts.length) + ' accounts') + ' ' +
'
' +
'' +
- 'Workspaces ' +
- '' + String(workspaceTotal) + ' ' +
+ 'Workspace fleet ' +
+ '' + (workspaceTotal ? workspaceCountLabel(workspaceTotal) : '0 workspaces') + ' ' +
'
' +
' ' +
''
@@ -685,13 +699,30 @@ export function renderAuthenticatedPortalHTML(context: ShellViewContext): string
'
' + serviceNote + '
' +
'
' +
'' +
- '
' +
- '
' +
- renderServiceActionRow('open-manage-service', 'Billing', 'Manage subscriptions', 'Open Stripe billing access for existing self-hosted subscriptions without leaving the Pulse Account shell.', 'manage-service-panel', 'manage-inline-email') +
- renderServiceActionRow('open-retrieve-service', 'Licenses', 'Retrieve licenses', 'Recover the latest active self-hosted license and invoice link for a commercial email address.', 'retrieve-service-panel', 'retrieve-inline-email') +
- renderServiceActionRow('open-refund-service', 'Refunds', 'Refund requests', 'Request an immediate self-serve refund for eligible self-hosted purchases with explicit revocation confirmation.', 'refund-service-panel', 'refund-inline-email') +
- renderServiceActionRow('open-data-service', 'Privacy', 'Data and privacy', 'Request commercial data export or deletion without leaving the account shell.', 'data-service-panel', 'data-export-email') +
+ '' +
+ '
' +
'
' +
'
' +
'
' +
@@ -707,23 +738,6 @@ export function renderAuthenticatedPortalHTML(context: ShellViewContext): string
'.
' +
'
' +
'
' +
- '' +
'
' +
'' +
'' +
diff --git a/internal/cloudcp/portal/frontend/src/styles.css b/internal/cloudcp/portal/frontend/src/styles.css
index 85946c285b..a0796483b2 100644
--- a/internal/cloudcp/portal/frontend/src/styles.css
+++ b/internal/cloudcp/portal/frontend/src/styles.css
@@ -171,13 +171,25 @@ header .logout-btn:hover,
box-shadow: var(--shadow-sm);
}
+.portal-hero-compact {
+ align-items: center;
+ padding: 14px 18px;
+}
+
.portal-hero-copy {
display: flex;
flex-direction: column;
- gap: 8px;
+ gap: 6px;
max-width: 900px;
}
+.portal-hero-heading-row {
+ display: flex;
+ align-items: center;
+ gap: 12px;
+ flex-wrap: wrap;
+}
+
.portal-hero-kicker,
.account-panel-kicker,
.service-card-kicker,
@@ -201,19 +213,33 @@ header .logout-btn:hover,
.portal-hero h1,
.intro-card h1 {
margin: 0;
- font-size: clamp(1.75rem, 2.6vw, 2.6rem);
- line-height: 0.98;
+ font-size: clamp(1.55rem, 2.2vw, 2.15rem);
+ line-height: 1;
letter-spacing: -0.045em;
}
.portal-hero p,
.intro-card p {
margin: 0;
- font-size: 14px;
- line-height: 1.55;
+ font-size: 13px;
+ line-height: 1.5;
color: var(--ink-soft);
}
+.portal-hero-chip {
+ display: inline-flex;
+ align-items: center;
+ min-height: 28px;
+ padding: 0 12px;
+ border-radius: 999px;
+ background: #edf5f7;
+ color: var(--accent-strong);
+ font-size: 11px;
+ font-weight: 800;
+ letter-spacing: 0.08em;
+ text-transform: uppercase;
+}
+
.portal-hero-stats {
display: grid;
grid-template-columns: repeat(3, minmax(0, 1fr));
@@ -225,8 +251,8 @@ header .logout-btn:hover,
display: flex;
flex-direction: column;
justify-content: space-between;
- min-height: 80px;
- padding: 12px 14px;
+ min-height: 72px;
+ padding: 11px 13px;
border: 1px solid var(--line);
border-radius: var(--radius-md);
background: #fcfdff;
@@ -241,9 +267,9 @@ header .logout-btn:hover,
}
.portal-hero-stat-value {
- font-size: 22px;
+ font-size: 18px;
font-weight: 800;
- line-height: 1.05;
+ line-height: 1.2;
letter-spacing: -0.04em;
}
@@ -1249,14 +1275,14 @@ header .logout-btn:hover,
}
.service-action-list {
- display: flex;
- flex-direction: column;
- gap: 10px;
+ display: grid;
+ grid-template-columns: 1fr;
+ gap: 12px;
}
.service-shell {
display: grid;
- grid-template-columns: minmax(0, 1.45fr) minmax(280px, 0.7fr);
+ grid-template-columns: minmax(320px, 0.72fr) minmax(0, 1.28fr);
gap: 16px;
align-items: start;
}
@@ -1275,6 +1301,10 @@ header .logout-btn:hover,
background: var(--panel-muted);
}
+.service-brief-card-primary {
+ background: #fff;
+}
+
.service-brief-card h3 {
margin: 8px 0 0;
font-size: 19px;
@@ -1290,14 +1320,34 @@ header .logout-btn:hover,
}
.service-action-row {
- display: grid;
- grid-template-columns: minmax(0, 1fr) auto;
- gap: 16px;
- align-items: center;
+ display: flex;
+ flex-direction: column;
+ gap: 12px;
padding: 16px 18px;
border: 1px solid var(--line);
border-radius: var(--radius-md);
- background: var(--panel-muted);
+ background: #fff;
+}
+
+.service-action-tags {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ gap: 10px;
+}
+
+.service-action-tag {
+ display: inline-flex;
+ align-items: center;
+ min-height: 26px;
+ padding: 0 10px;
+ border-radius: 999px;
+ background: #fff;
+ color: var(--ink-muted);
+ font-size: 11px;
+ font-weight: 800;
+ letter-spacing: 0.08em;
+ text-transform: uppercase;
}
.service-action-copy {
@@ -1308,8 +1358,8 @@ header .logout-btn:hover,
.service-action-copy h3 {
margin: 0;
- font-size: 19px;
- line-height: 1.08;
+ font-size: 17px;
+ line-height: 1.16;
letter-spacing: -0.03em;
}
@@ -1321,6 +1371,41 @@ header .logout-btn:hover,
max-width: 760px;
}
+.service-action-highlights {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 8px;
+}
+
+.service-action-highlight {
+ display: inline-flex;
+ align-items: center;
+ min-height: 28px;
+ padding: 0 10px;
+ border-radius: 999px;
+ background: #fff;
+ color: var(--ink-soft);
+ font-size: 12px;
+ font-weight: 600;
+}
+
+.service-action-footer {
+ display: flex;
+ align-items: end;
+ justify-content: space-between;
+ gap: 16px;
+ margin-top: auto;
+ padding-top: 4px;
+ border-top: 1px solid var(--line);
+}
+
+.service-action-caption {
+ max-width: 320px;
+ font-size: 12px;
+ line-height: 1.5;
+ color: var(--ink-muted);
+}
+
.service-action-cta {
display: flex;
align-items: center;
@@ -1333,6 +1418,7 @@ header .logout-btn:hover,
.service-panel {
padding: 22px;
border-radius: var(--radius-md);
+ background: #fff;
}
.service-panel.visible.auth-panel {
@@ -1524,7 +1610,8 @@ header .logout-btn:hover,
.account-management-grid,
.team-management-grid,
.account-overview-secondary,
- .service-shell {
+ .service-shell,
+ .service-action-list {
grid-template-columns: 1fr;
}
@@ -1569,8 +1656,7 @@ header .logout-btn:hover,
grid-template-columns: 1fr;
}
- .workspace-list-toolbar,
- .service-action-row {
+ .workspace-list-toolbar {
grid-template-columns: 1fr;
}
}
@@ -1602,7 +1688,9 @@ header .logout-btn:hover,
.account-surface-header,
.workspace-management-header,
.team-management-header,
- .service-header {
+ .service-header,
+ .service-action-footer,
+ .service-action-tags {
flex-direction: column;
align-items: flex-start;
}