diff --git a/internal/cloudcp/portal/dist/build_manifest.json b/internal/cloudcp/portal/dist/build_manifest.json
index 33326721cd..5756dd9287 100644
--- a/internal/cloudcp/portal/dist/build_manifest.json
+++ b/internal/cloudcp/portal/dist/build_manifest.json
@@ -1,5 +1,5 @@
{
- "source_hash": "f7b726f8e8d6c01630420d83fad1e59f7947f386a570d1715f0165f958c39410",
+ "source_hash": "3ee5dcea0783b4085b4a7feb3fd0dcee628b4280042752af29256a685db8a466",
"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 6c24d87dd6..d7c997966e 100644
--- a/internal/cloudcp/portal/dist/portal_app.css
+++ b/internal/cloudcp/portal/dist/portal_app.css
@@ -232,26 +232,18 @@ header .logout-btn:hover,
line-height: 1.05;
letter-spacing: -0.04em;
}
-.portal-frame {
+.portal-shell-bar {
display: grid;
- grid-template-columns: minmax(220px, 250px) minmax(0, 1fr);
- gap: 22px;
- align-items: start;
-}
-.portal-rail {
- position: sticky;
- top: 88px;
- display: flex;
- flex-direction: column;
+ grid-template-columns: minmax(0, 1fr) auto;
gap: 16px;
+ align-items: stretch;
}
.portal-section-nav {
- display: flex;
- flex-direction: column;
+ display: grid;
+ grid-template-columns: repeat(3, minmax(0, 1fr));
gap: 10px;
}
.portal-section-link,
-.portal-rail-panel,
.intro-card,
.service-panel,
.account-surface,
@@ -287,30 +279,41 @@ header .logout-btn:hover,
line-height: 1.5;
color: var(--ink-soft);
}
-.portal-rail-panel {
- border-radius: var(--radius-md);
- padding: 20px;
-}
-.portal-rail-panel h3 {
- margin: 8px 0 10px;
- font-size: 23px;
- line-height: 1.05;
- letter-spacing: -0.04em;
-}
-.portal-rail-panel p {
- margin: 0;
- color: var(--ink-soft);
- line-height: 1.6;
-}
.portal-support-link,
.support-link,
.service-panel .helper-text a {
color: var(--accent-strong);
font-weight: 600;
}
-.portal-support-link {
- display: inline-block;
- margin-top: 14px;
+.portal-support-strip {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ gap: 16px;
+ min-width: 340px;
+ padding: 16px 18px;
+ border: 1px solid var(--line);
+ border-radius: var(--radius-md);
+ background: rgba(255, 255, 255, 0.98);
+ box-shadow: var(--shadow-sm);
+}
+.portal-support-copy {
+ display: flex;
+ flex-direction: column;
+ gap: 4px;
+}
+.portal-support-label {
+ font-size: 11px;
+ font-weight: 800;
+ letter-spacing: 0.14em;
+ text-transform: uppercase;
+ color: var(--ink-muted);
+}
+.portal-support-text {
+ max-width: 280px;
+ font-size: 13px;
+ line-height: 1.5;
+ color: var(--ink-soft);
}
.portal-stage {
display: flex;
@@ -896,17 +899,15 @@ header .logout-btn:hover,
background: #6e1414;
}
@media (max-width: 1180px) {
- .portal-frame,
.account-management-grid,
.team-management-grid {
grid-template-columns: 1fr;
}
- .portal-rail {
- position: static;
+ .portal-shell-bar {
+ grid-template-columns: 1fr;
}
- .portal-section-nav {
- display: grid;
- grid-template-columns: repeat(3, minmax(0, 1fr));
+ .portal-support-strip {
+ min-width: 0;
}
.account-overview-rail {
grid-template-columns: 1fr;
diff --git a/internal/cloudcp/portal/dist/portal_app.js b/internal/cloudcp/portal/dist/portal_app.js
index c7b243c08a..c2f7dca77b 100644
--- a/internal/cloudcp/portal/dist/portal_app.js
+++ b/internal/cloudcp/portal/dist/portal_app.js
@@ -1672,7 +1672,7 @@
var hosted = hasHostedAccounts(accounts);
var serviceHeading = hosted ? "Self-hosted licenses and billing" : "Account services";
var serviceNote = hosted ? "Hosted operations live above. Use these commercial tools for self-hosted licenses, billing, refunds, and privacy actions." : "Use these account tools for self-hosted licenses, billing, refunds, and privacy actions.";
- return '
' + renderOverviewBand(accounts) + '
' + renderAccountsHTML(context) + '
Billing Manage subscriptions Open Stripe billing access for existing self-hosted subscriptions without leaving the Pulse Account shell.
Licenses Retrieve licenses Recover the latest active self-hosted license and invoice link for a commercial email address.
Refunds Refund requests Request an immediate self-serve refund for eligible self-hosted purchases with explicit revocation confirmation.
Privacy Data and privacy Request commercial data export or deletion without leaving the account shell.
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 || "") + " .
";
+ return '' + renderOverviewBand(accounts) + '
' + renderShellNavigation(accounts, context.bootstrap.support_email || "") + '
' + renderAccountsHTML(context) + '
Billing Manage subscriptions Open Stripe billing access for existing self-hosted subscriptions without leaving the Pulse Account shell.
Licenses Retrieve licenses Recover the latest active self-hosted license and invoice link for a commercial email address.
Refunds Refund requests Request an immediate self-serve refund for eligible self-hosted purchases with explicit revocation confirmation.
Privacy Data and privacy Request commercial data export or deletion without leaving the account shell.
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 || "") + " .
";
}
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 80801bd743..ff00f3ccaf 100644
--- a/internal/cloudcp/portal/frontend/src/shell_view.ts
+++ b/internal/cloudcp/portal/frontend/src/shell_view.ts
@@ -497,74 +497,71 @@ export function renderAuthenticatedPortalHTML(context: ShellViewContext): string
return (
'' +
renderOverviewBand(accounts) +
- '
' +
- '
' +
- renderShellNavigation(accounts, context.bootstrap.support_email || '') +
- '' +
- '
Support
' +
- '
One account surface ' +
- '
This portal should be the place where hosted access and commercial account actions meet. Hosted workspaces sit above. Self-hosted services remain available below.
' +
- '
' + escapeHTML(context.bootstrap.support_email || '') + ' ' +
+ '
' +
+ renderShellNavigation(accounts, context.bootstrap.support_email || '') +
+ '
' +
+ '
' +
+ 'Support ' +
+ 'Hosted operations and commercial account tools now share one account surface. ' +
'
' +
- '' +
- '
' +
- '
' +
- '' +
- '' + renderAccountsHTML(context) + '
' +
- ' ' +
- '
' +
- '' +
- '' +
- '
' +
- 'Billing ' +
- 'Manage subscriptions ' +
- 'Open Stripe billing access for existing self-hosted subscriptions without leaving the Pulse Account shell.
' +
- ' ' +
- '
' +
- 'Licenses ' +
- 'Retrieve licenses ' +
- 'Recover the latest active self-hosted license and invoice link for a commercial email address.
' +
- ' ' +
- '
' +
- 'Refunds ' +
- 'Refund requests ' +
- 'Request an immediate self-serve refund for eligible self-hosted purchases with explicit revocation confirmation.
' +
- ' ' +
- '
' +
- 'Privacy ' +
- 'Data and privacy ' +
- 'Request commercial data export or deletion without leaving the account shell.
' +
- ' ' +
- '
' +
- '' +
- '' +
- '' +
- '' +
- '
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 || '') +
- ' .
' +
- '
' +
- ' ' +
+ '
' + escapeHTML(context.bootstrap.support_email || '') + ' ' +
'
' +
'
' +
+ '
' +
+ '' +
+ '' + renderAccountsHTML(context) + '
' +
+ ' ' +
+ '
' +
+ '' +
+ '' +
+ '
' +
+ 'Billing ' +
+ 'Manage subscriptions ' +
+ 'Open Stripe billing access for existing self-hosted subscriptions without leaving the Pulse Account shell.
' +
+ ' ' +
+ '
' +
+ 'Licenses ' +
+ 'Retrieve licenses ' +
+ 'Recover the latest active self-hosted license and invoice link for a commercial email address.
' +
+ ' ' +
+ '
' +
+ 'Refunds ' +
+ 'Refund requests ' +
+ 'Request an immediate self-serve refund for eligible self-hosted purchases with explicit revocation confirmation.
' +
+ ' ' +
+ '
' +
+ 'Privacy ' +
+ 'Data and privacy ' +
+ 'Request commercial data export or deletion without leaving the account shell.
' +
+ ' ' +
+ '
' +
+ '' +
+ '' +
+ '' +
+ '' +
+ '
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 || '') +
+ ' .
' +
+ '
' +
+ ' ' +
'
'
);
}
diff --git a/internal/cloudcp/portal/frontend/src/styles.css b/internal/cloudcp/portal/frontend/src/styles.css
index cb60557838..20f33336cd 100644
--- a/internal/cloudcp/portal/frontend/src/styles.css
+++ b/internal/cloudcp/portal/frontend/src/styles.css
@@ -243,29 +243,20 @@ header .logout-btn:hover,
letter-spacing: -0.04em;
}
-.portal-frame {
+.portal-shell-bar {
display: grid;
- grid-template-columns: minmax(220px, 250px) minmax(0, 1fr);
- gap: 22px;
- align-items: start;
-}
-
-.portal-rail {
- position: sticky;
- top: 88px;
- display: flex;
- flex-direction: column;
+ grid-template-columns: minmax(0, 1fr) auto;
gap: 16px;
+ align-items: stretch;
}
.portal-section-nav {
- display: flex;
- flex-direction: column;
+ display: grid;
+ grid-template-columns: repeat(3, minmax(0, 1fr));
gap: 10px;
}
.portal-section-link,
-.portal-rail-panel,
.intro-card,
.service-panel,
.account-surface,
@@ -303,24 +294,6 @@ header .logout-btn:hover,
color: var(--ink-soft);
}
-.portal-rail-panel {
- border-radius: var(--radius-md);
- padding: 20px;
-}
-
-.portal-rail-panel h3 {
- margin: 8px 0 10px;
- font-size: 23px;
- line-height: 1.05;
- letter-spacing: -0.04em;
-}
-
-.portal-rail-panel p {
- margin: 0;
- color: var(--ink-soft);
- line-height: 1.6;
-}
-
.portal-support-link,
.support-link,
.service-panel .helper-text a {
@@ -328,9 +301,38 @@ header .logout-btn:hover,
font-weight: 600;
}
-.portal-support-link {
- display: inline-block;
- margin-top: 14px;
+.portal-support-strip {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ gap: 16px;
+ min-width: 340px;
+ padding: 16px 18px;
+ border: 1px solid var(--line);
+ border-radius: var(--radius-md);
+ background: rgba(255, 255, 255, 0.98);
+ box-shadow: var(--shadow-sm);
+}
+
+.portal-support-copy {
+ display: flex;
+ flex-direction: column;
+ gap: 4px;
+}
+
+.portal-support-label {
+ font-size: 11px;
+ font-weight: 800;
+ letter-spacing: 0.14em;
+ text-transform: uppercase;
+ color: var(--ink-muted);
+}
+
+.portal-support-text {
+ max-width: 280px;
+ font-size: 13px;
+ line-height: 1.5;
+ color: var(--ink-soft);
}
.portal-stage {
@@ -1002,19 +1004,17 @@ header .logout-btn:hover,
.toast.error { background: #6e1414; }
@media (max-width: 1180px) {
- .portal-frame,
.account-management-grid,
.team-management-grid {
grid-template-columns: 1fr;
}
- .portal-rail {
- position: static;
+ .portal-shell-bar {
+ grid-template-columns: 1fr;
}
- .portal-section-nav {
- display: grid;
- grid-template-columns: repeat(3, minmax(0, 1fr));
+ .portal-support-strip {
+ min-width: 0;
}
.account-overview-rail {