mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-10-23 20:25:56 +00:00
style(portal): tighten top shell rhythm
This commit is contained in:
+1
-1
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"source_hash": "db22f20e5c01de6c11aa19319736af2e3d32a613e07002fbcab79c4d07f08e5d",
|
||||
"source_hash": "52cc4e938d4e2d9faf8baccb4ba63be6dfcbf6172126273c48ed6661ac7231ea",
|
||||
"build_inputs": [
|
||||
"package.json",
|
||||
"tsconfig.json",
|
||||
|
||||
+52
-30
@@ -165,8 +165,8 @@ header .logout-btn:hover,
|
||||
.portal-hero {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1fr) auto;
|
||||
gap: 18px;
|
||||
padding: 12px 16px;
|
||||
gap: 14px;
|
||||
padding: 10px 14px;
|
||||
border: 1px solid var(--line);
|
||||
border-radius: var(--radius-lg);
|
||||
background: #fff;
|
||||
@@ -174,20 +174,27 @@ header .logout-btn:hover,
|
||||
}
|
||||
.portal-hero-compact {
|
||||
align-items: center;
|
||||
padding: 12px 16px;
|
||||
padding: 10px 14px;
|
||||
}
|
||||
.portal-hero-copy {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
gap: 2px;
|
||||
max-width: 900px;
|
||||
}
|
||||
.portal-hero-heading-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
gap: 10px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
.portal-hero-brand {
|
||||
font-size: 26px;
|
||||
font-weight: 800;
|
||||
line-height: 1;
|
||||
letter-spacing: -0.045em;
|
||||
color: var(--ink);
|
||||
}
|
||||
.portal-hero-kicker,
|
||||
.account-panel-kicker,
|
||||
.service-card-kicker,
|
||||
@@ -205,29 +212,26 @@ header .logout-btn:hover,
|
||||
.account-eyebrow {
|
||||
color: var(--ink-muted);
|
||||
}
|
||||
.portal-hero h1,
|
||||
.intro-card h1 {
|
||||
margin: 0;
|
||||
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: 13px;
|
||||
line-height: 1.5;
|
||||
font-size: 12px;
|
||||
line-height: 1.45;
|
||||
color: var(--ink-soft);
|
||||
}
|
||||
.portal-hero p strong {
|
||||
color: var(--ink);
|
||||
font-weight: 700;
|
||||
}
|
||||
.portal-hero-chip {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
min-height: 28px;
|
||||
padding: 0 12px;
|
||||
min-height: 24px;
|
||||
padding: 0 10px;
|
||||
border-radius: 999px;
|
||||
background: #edf5f7;
|
||||
color: var(--accent-strong);
|
||||
font-size: 11px;
|
||||
font-size: 10px;
|
||||
font-weight: 800;
|
||||
letter-spacing: 0.08em;
|
||||
text-transform: uppercase;
|
||||
@@ -235,29 +239,29 @@ header .logout-btn:hover,
|
||||
.portal-hero-stats {
|
||||
display: flex;
|
||||
align-items: stretch;
|
||||
gap: 8px;
|
||||
gap: 6px;
|
||||
align-self: center;
|
||||
}
|
||||
.portal-hero-stat {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
min-width: 132px;
|
||||
min-width: 120px;
|
||||
min-height: 0;
|
||||
padding: 10px 12px;
|
||||
padding: 8px 10px;
|
||||
border: 1px solid var(--line);
|
||||
border-radius: 12px;
|
||||
background: #fbfcfd;
|
||||
}
|
||||
.portal-hero-stat-label {
|
||||
font-size: 12px;
|
||||
font-size: 11px;
|
||||
font-weight: 700;
|
||||
color: var(--ink-muted);
|
||||
letter-spacing: 0.06em;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
.portal-hero-stat-value {
|
||||
font-size: 16px;
|
||||
font-size: 14px;
|
||||
font-weight: 800;
|
||||
line-height: 1.25;
|
||||
letter-spacing: -0.04em;
|
||||
@@ -459,11 +463,11 @@ header .logout-btn:hover,
|
||||
margin-top: 22px;
|
||||
}
|
||||
.account-surface-header {
|
||||
display: flex;
|
||||
align-items: start;
|
||||
justify-content: space-between;
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1fr) auto;
|
||||
align-items: end;
|
||||
gap: 16px;
|
||||
padding: 12px 4px 14px;
|
||||
padding: 6px 4px 12px;
|
||||
border: none;
|
||||
border-bottom: 1px solid var(--line);
|
||||
border-radius: var(--radius-md);
|
||||
@@ -471,17 +475,18 @@ header .logout-btn:hover,
|
||||
box-shadow: none;
|
||||
}
|
||||
.account-heading h2 {
|
||||
margin: 4px 0 0;
|
||||
font-size: 27px;
|
||||
margin: 2px 0 0;
|
||||
font-size: 24px;
|
||||
line-height: 1.02;
|
||||
letter-spacing: -0.05em;
|
||||
}
|
||||
.account-summary {
|
||||
margin-top: 6px;
|
||||
font-size: 13px;
|
||||
margin-top: 5px;
|
||||
font-size: 12px;
|
||||
color: var(--ink-soft);
|
||||
}
|
||||
.account-badges,
|
||||
.account-context-strip,
|
||||
.workspace-badges,
|
||||
.workspace-actions,
|
||||
.account-actions,
|
||||
@@ -492,6 +497,23 @@ header .logout-btn:hover,
|
||||
gap: 10px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
.account-context-strip {
|
||||
justify-content: flex-end;
|
||||
}
|
||||
.account-context-chip {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
min-height: 26px;
|
||||
padding: 0 10px;
|
||||
border: 1px solid var(--line);
|
||||
border-radius: 999px;
|
||||
background: #fbfcfd;
|
||||
color: var(--ink-soft);
|
||||
font-size: 11px;
|
||||
font-weight: 800;
|
||||
letter-spacing: 0.08em;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
.workspace-actions {
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
+5
-7
@@ -1646,9 +1646,6 @@
|
||||
if (workspace.last_health_check) return "unhealthy";
|
||||
return "checking";
|
||||
}
|
||||
function roleBadgeHTML(role) {
|
||||
return '<span class="badge badge-role">' + escapeHTML(titleCase(role || "member")) + "</span>";
|
||||
}
|
||||
function accountKindLabel(account) {
|
||||
if (account.kind === "msp") return "MSP account";
|
||||
if (account.kind === "cloud") return "Cloud account";
|
||||
@@ -1736,9 +1733,9 @@
|
||||
function renderOverviewBand(accounts) {
|
||||
var hosted = hasHostedAccounts(accounts);
|
||||
var workspaceTotal = countWorkspaces(accounts);
|
||||
var title = hosted ? "Pulse Account" : "Self-hosted Pulse Account";
|
||||
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 '<section class="portal-hero portal-hero-compact"><div class="portal-hero-copy"><div class="portal-hero-kicker">' + (hosted ? "Hosted access is active on this account." : "No hosted workspace access is attached to this account yet.") + '</div><div class="portal-hero-heading-row"><h1>' + title + '</h1><span class="portal-hero-chip">' + (hosted ? "Operator ready" : "Self-hosted only") + "</span></div><p>" + summary + '</p></div><div class="portal-hero-stats"><div class="portal-hero-stat"><span class="portal-hero-stat-label">Hosted access</span><span class="portal-hero-stat-value">' + (hosted ? "Active" : "Not attached") + '</span></div><div class="portal-hero-stat"><span class="portal-hero-stat-label">Accounts</span><span class="portal-hero-stat-value">' + (accounts.length === 1 ? "1 account" : String(accounts.length) + " accounts") + '</span></div><div class="portal-hero-stat"><span class="portal-hero-stat-label">Workspace fleet</span><span class="portal-hero-stat-value">' + (workspaceTotal ? workspaceCountLabel(workspaceTotal) : "0 workspaces") + "</span></div></div></section>";
|
||||
var statusText = hosted ? "Hosted access is active on this account." : "No hosted workspace access is attached to this account yet.";
|
||||
var summary = hosted ? "Hosted operations, operator access, and commercial account services." : "Billing, license recovery, refunds, and privacy actions until hosted access is attached.";
|
||||
return '<section class="portal-hero portal-hero-compact"><div class="portal-hero-copy"><div class="portal-hero-heading-row"><div class="portal-hero-brand">Pulse Account</div><span class="portal-hero-chip">' + (hosted ? "Operator ready" : "Self-hosted only") + "</span></div><p><strong>" + statusText + "</strong> " + summary + '</p></div><div class="portal-hero-stats"><div class="portal-hero-stat"><span class="portal-hero-stat-label">Hosted access</span><span class="portal-hero-stat-value">' + (hosted ? "Active" : "Not attached") + '</span></div><div class="portal-hero-stat"><span class="portal-hero-stat-label">Accounts</span><span class="portal-hero-stat-value">' + (accounts.length === 1 ? "1 account" : String(accounts.length) + " accounts") + '</span></div><div class="portal-hero-stat"><span class="portal-hero-stat-label">Workspace fleet</span><span class="portal-hero-stat-value">' + (workspaceTotal ? workspaceCountLabel(workspaceTotal) : "0 workspaces") + "</span></div></div></section>";
|
||||
}
|
||||
function shellSectionButton(section, activeSection, title, copy) {
|
||||
return '<button class="portal-shell-nav-link' + (activeSection === section ? " active" : "") + '" type="button" data-shell-action="activate-section" data-shell-section="' + section + '"><span class="portal-shell-nav-label">' + title + '</span><span class="portal-shell-nav-copy">' + copy + "</span></button>";
|
||||
@@ -1826,7 +1823,8 @@
|
||||
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.";
|
||||
var hostedContent = accounts.map(function(account) {
|
||||
return '<section class="account-surface"><div class="account-surface-header"><div class="account-heading"><div class="account-eyebrow">' + escapeHTML(accountKindLabel(account)) + "</div><h2>" + escapeHTML(account.name) + '</h2><div class="account-summary">' + escapeHTML(accountKindLabel(account) + " \xB7 " + titleCase(account.role) + " \xB7 " + workspaceCountLabel((account.workspaces || []).length)) + '</div></div><div class="account-badges"><span class="badge badge-' + escapeHTML(account.kind) + '">' + escapeHTML(account.kind_label) + "</span>" + roleBadgeHTML(account.role) + '</div></div><div class="account-surface-body">' + renderAccountOverviewSection(account) + renderAccountWorkspaceSection(account, context.accountAPIBasePath) + renderAccountTeamSection(account) + "</div></section>";
|
||||
var workspaceLabel = workspaceCountLabel((account.workspaces || []).length);
|
||||
return '<section class="account-surface"><div class="account-surface-header"><div class="account-heading"><div class="account-eyebrow">' + escapeHTML(accountKindLabel(account)) + "</div><h2>" + escapeHTML(account.name) + '</h2><div class="account-summary">' + escapeHTML(account.kind === "msp" ? "Operator workspace account" : "Hosted account operations") + '</div></div><div class="account-context-strip"><span class="account-context-chip">' + escapeHTML(account.kind_label) + '</span><span class="account-context-chip">' + escapeHTML(titleCase(account.role)) + '</span><span class="account-context-chip">' + escapeHTML(workspaceLabel) + '</span></div></div><div class="account-surface-body">' + renderAccountOverviewSection(account) + renderAccountWorkspaceSection(account, context.accountAPIBasePath) + renderAccountTeamSection(account) + "</div></section>";
|
||||
}).join("");
|
||||
return '<div class="portal-shell" data-shell-section="' + activeSection + '">' + renderOverviewBand(accounts) + '<div class="portal-shell-layout">' + renderShellNavigation(accounts, context.bootstrap.support_email || "", activeSection) + '<div class="portal-shell-main"><section class="portal-content-panel portal-content-panel-overview"><div id="accounts-root">' + hostedContent + '</div></section><section class="portal-content-panel portal-content-panel-services service-section" id="account-services-section"><div class="service-header"><div><div class="account-panel-kicker">Account services</div><h2>' + serviceHeading + '</h2></div><div class="service-note">' + serviceNote + '</div></div><div class="service-shell"><aside class="service-shell-sidebar"><div class="service-list-intro"><div class="account-panel-kicker">Commercial actions</div><p>Use these tools for self-hosted billing, license recovery, refunds, and privacy. Hosted workspace operations stay in Workspaces and Team.</p></div><div class="service-action-list">' + 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", ["Invoices and plan changes", "Subscription self-service"]) + 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", ["Latest active license", "Invoice lookup"]) + 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", ["Eligibility check", "Explicit revocation"]) + 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", ["Export or deletion", "Support escalation path"]) + '</div></aside><div class="service-shell-main"><div class="service-detail-shell"><div class="service-panel service-panel-empty visible" id="service-panel-empty"><div class="account-panel-kicker">Select a service</div><h3>Choose a commercial account action</h3><p>Open a billing, license, refund, or privacy flow from the service navigator. The active request stays here so the account-services area behaves like one working surface instead of a list of disconnected tools.</p><div class="service-empty-points"><div class="service-empty-point"><strong>Billing</strong><span>Open Stripe customer portal access after verification.</span></div><div class="service-empty-point"><strong>Licenses</strong><span>Recover the latest active self-hosted license and invoice link.</span></div><div class="service-empty-point"><strong>Privacy</strong><span>Request export or deletion without leaving Pulse Account.</span></div></div><div class="service-empty-support">Need help with billing, refund, privacy, or license actions? <a class="portal-support-link" href="mailto:' + escapeAttr(context.bootstrap.support_email || "") + '">' + escapeHTML(context.bootstrap.support_email || "") + '</a></div></div><div class="service-panel" id="manage-service-panel"><div id="manage-service-root"></div></div><div class="service-panel" id="retrieve-service-panel"><div id="retrieve-service-root"></div></div><div class="service-panel" id="refund-service-panel"><div id="refund-service-root"></div></div><div class="service-panel" id="data-service-panel"><h3>Data and privacy</h3><p>Request export or deletion of the commercial data tied to an email address. Payment data held directly by Stripe still requires support handling.</p><div class="subsection"><div id="data-export-root"></div></div><div class="subsection"><div id="data-delete-root"></div></div><div class="helper-text">Payment-card data stays with Stripe. For Stripe deletion support, contact <a href="mailto:' + escapeAttr(context.bootstrap.support_email || "") + '">' + escapeHTML(context.bootstrap.support_email || "") + '</a>.</div></div></div><div class="service-inline-support"><div class="account-panel-kicker">Support</div><p>Use support if a billing, refund, privacy, or license flow does not behave as expected for this account.</p><a class="portal-support-link" href="mailto:' + escapeAttr(context.bootstrap.support_email || "") + '">' + escapeHTML(context.bootstrap.support_email || "") + '</a></div></div></div></section><section class="portal-content-panel portal-content-panel-support">' + renderSupportSection(context) + "</section></div></div></div>";
|
||||
}
|
||||
|
||||
@@ -125,7 +125,7 @@ describe('shell view', function() {
|
||||
})
|
||||
);
|
||||
|
||||
expect(html).toContain('<h1>Pulse Account</h1>');
|
||||
expect(html).toContain('portal-hero-brand">Pulse Account</div>');
|
||||
expect(html).toContain('Hosted access is active on this account.');
|
||||
expect(html).toContain('portal-shell-nav');
|
||||
expect(html).toContain('Overview');
|
||||
@@ -136,8 +136,9 @@ describe('shell view', function() {
|
||||
expect(html).toContain('Self-hosted licenses and billing');
|
||||
expect(html).toContain('id="accounts-root"');
|
||||
expect(html).toContain('MSP account');
|
||||
expect(html).toContain('MSP account · Owner · 3 workspaces');
|
||||
expect(html).toContain('Acme MSP');
|
||||
expect(html).toContain('Operator workspace account');
|
||||
expect(html).toContain('3 workspaces');
|
||||
expect(html).toContain('Account operations');
|
||||
expect(html).toContain('Manage the client fleet from this account surface.');
|
||||
expect(html).toContain('Operator overview');
|
||||
@@ -186,7 +187,8 @@ describe('shell view', function() {
|
||||
})
|
||||
);
|
||||
|
||||
expect(html).toContain('<h1>Self-hosted Pulse Account</h1>');
|
||||
expect(html).toContain('Pulse Account');
|
||||
expect(html).toContain('Self-hosted only');
|
||||
expect(html).toContain('No hosted workspace access is attached to this account yet.');
|
||||
expect(html).toContain('Use these tools for self-hosted billing, license recovery, refunds, and privacy.');
|
||||
expect(html).toContain('Account services');
|
||||
|
||||
@@ -205,20 +205,19 @@ function renderAttentionPanel(workspaces: PortalWorkspaceSummary[]): string {
|
||||
function renderOverviewBand(accounts: PortalAccountSummary[]): string {
|
||||
var hosted = hasHostedAccounts(accounts);
|
||||
var workspaceTotal = countWorkspaces(accounts);
|
||||
var title = hosted ? 'Pulse Account' : 'Self-hosted Pulse Account';
|
||||
var statusText = hosted ? 'Hosted access is active on this account.' : 'No hosted workspace access is attached to this account yet.';
|
||||
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.';
|
||||
? 'Hosted operations, operator access, and commercial account services.'
|
||||
: 'Billing, license recovery, refunds, and privacy actions until hosted access is attached.';
|
||||
|
||||
return (
|
||||
'<section class="portal-hero portal-hero-compact">' +
|
||||
'<div class="portal-hero-copy">' +
|
||||
'<div class="portal-hero-kicker">' + (hosted ? 'Hosted access is active on this account.' : 'No hosted workspace access is attached to this account yet.') + '</div>' +
|
||||
'<div class="portal-hero-heading-row">' +
|
||||
'<h1>' + title + '</h1>' +
|
||||
'<div class="portal-hero-brand">Pulse Account</div>' +
|
||||
'<span class="portal-hero-chip">' + (hosted ? 'Operator ready' : 'Self-hosted only') + '</span>' +
|
||||
'</div>' +
|
||||
'<p>' + summary + '</p>' +
|
||||
'<p><strong>' + statusText + '</strong> ' + summary + '</p>' +
|
||||
'</div>' +
|
||||
'<div class="portal-hero-stats">' +
|
||||
'<div class="portal-hero-stat">' +
|
||||
@@ -678,17 +677,19 @@ export function renderAuthenticatedPortalHTML(context: ShellViewContext): string
|
||||
? '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.';
|
||||
var hostedContent = accounts.map(function(account) {
|
||||
var workspaceLabel = workspaceCountLabel((account.workspaces || []).length);
|
||||
return (
|
||||
'<section class="account-surface">' +
|
||||
'<div class="account-surface-header">' +
|
||||
'<div class="account-heading">' +
|
||||
'<div class="account-eyebrow">' + escapeHTML(accountKindLabel(account)) + '</div>' +
|
||||
'<h2>' + escapeHTML(account.name) + '</h2>' +
|
||||
'<div class="account-summary">' + escapeHTML(accountKindLabel(account) + ' · ' + titleCase(account.role) + ' · ' + workspaceCountLabel((account.workspaces || []).length)) + '</div>' +
|
||||
'<div class="account-summary">' + escapeHTML(account.kind === 'msp' ? 'Operator workspace account' : 'Hosted account operations') + '</div>' +
|
||||
'</div>' +
|
||||
'<div class="account-badges">' +
|
||||
'<span class="badge badge-' + escapeHTML(account.kind) + '">' + escapeHTML(account.kind_label) + '</span>' +
|
||||
roleBadgeHTML(account.role) +
|
||||
'<div class="account-context-strip">' +
|
||||
'<span class="account-context-chip">' + escapeHTML(account.kind_label) + '</span>' +
|
||||
'<span class="account-context-chip">' + escapeHTML(titleCase(account.role)) + '</span>' +
|
||||
'<span class="account-context-chip">' + escapeHTML(workspaceLabel) + '</span>' +
|
||||
'</div>' +
|
||||
'</div>' +
|
||||
'<div class="account-surface-body">' +
|
||||
|
||||
@@ -163,8 +163,8 @@ header .logout-btn:hover,
|
||||
.portal-hero {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1fr) auto;
|
||||
gap: 18px;
|
||||
padding: 12px 16px;
|
||||
gap: 14px;
|
||||
padding: 10px 14px;
|
||||
border: 1px solid var(--line);
|
||||
border-radius: var(--radius-lg);
|
||||
background: #fff;
|
||||
@@ -173,23 +173,31 @@ header .logout-btn:hover,
|
||||
|
||||
.portal-hero-compact {
|
||||
align-items: center;
|
||||
padding: 12px 16px;
|
||||
padding: 10px 14px;
|
||||
}
|
||||
|
||||
.portal-hero-copy {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
gap: 2px;
|
||||
max-width: 900px;
|
||||
}
|
||||
|
||||
.portal-hero-heading-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
gap: 10px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.portal-hero-brand {
|
||||
font-size: 26px;
|
||||
font-weight: 800;
|
||||
line-height: 1;
|
||||
letter-spacing: -0.045em;
|
||||
color: var(--ink);
|
||||
}
|
||||
|
||||
.portal-hero-kicker,
|
||||
.account-panel-kicker,
|
||||
.service-card-kicker,
|
||||
@@ -210,31 +218,28 @@ header .logout-btn:hover,
|
||||
color: var(--ink-muted);
|
||||
}
|
||||
|
||||
.portal-hero h1,
|
||||
.intro-card h1 {
|
||||
margin: 0;
|
||||
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: 13px;
|
||||
line-height: 1.5;
|
||||
font-size: 12px;
|
||||
line-height: 1.45;
|
||||
color: var(--ink-soft);
|
||||
}
|
||||
|
||||
.portal-hero p strong {
|
||||
color: var(--ink);
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.portal-hero-chip {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
min-height: 28px;
|
||||
padding: 0 12px;
|
||||
min-height: 24px;
|
||||
padding: 0 10px;
|
||||
border-radius: 999px;
|
||||
background: #edf5f7;
|
||||
color: var(--accent-strong);
|
||||
font-size: 11px;
|
||||
font-size: 10px;
|
||||
font-weight: 800;
|
||||
letter-spacing: 0.08em;
|
||||
text-transform: uppercase;
|
||||
@@ -243,7 +248,7 @@ header .logout-btn:hover,
|
||||
.portal-hero-stats {
|
||||
display: flex;
|
||||
align-items: stretch;
|
||||
gap: 8px;
|
||||
gap: 6px;
|
||||
align-self: center;
|
||||
}
|
||||
|
||||
@@ -251,16 +256,16 @@ header .logout-btn:hover,
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
min-width: 132px;
|
||||
min-width: 120px;
|
||||
min-height: 0;
|
||||
padding: 10px 12px;
|
||||
padding: 8px 10px;
|
||||
border: 1px solid var(--line);
|
||||
border-radius: 12px;
|
||||
background: #fbfcfd;
|
||||
}
|
||||
|
||||
.portal-hero-stat-label {
|
||||
font-size: 12px;
|
||||
font-size: 11px;
|
||||
font-weight: 700;
|
||||
color: var(--ink-muted);
|
||||
letter-spacing: 0.06em;
|
||||
@@ -268,7 +273,7 @@ header .logout-btn:hover,
|
||||
}
|
||||
|
||||
.portal-hero-stat-value {
|
||||
font-size: 16px;
|
||||
font-size: 14px;
|
||||
font-weight: 800;
|
||||
line-height: 1.25;
|
||||
letter-spacing: -0.04em;
|
||||
@@ -502,11 +507,11 @@ header .logout-btn:hover,
|
||||
}
|
||||
|
||||
.account-surface-header {
|
||||
display: flex;
|
||||
align-items: start;
|
||||
justify-content: space-between;
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1fr) auto;
|
||||
align-items: end;
|
||||
gap: 16px;
|
||||
padding: 12px 4px 14px;
|
||||
padding: 6px 4px 12px;
|
||||
border: none;
|
||||
border-bottom: 1px solid var(--line);
|
||||
border-radius: var(--radius-md);
|
||||
@@ -515,19 +520,20 @@ header .logout-btn:hover,
|
||||
}
|
||||
|
||||
.account-heading h2 {
|
||||
margin: 4px 0 0;
|
||||
font-size: 27px;
|
||||
margin: 2px 0 0;
|
||||
font-size: 24px;
|
||||
line-height: 1.02;
|
||||
letter-spacing: -0.05em;
|
||||
}
|
||||
|
||||
.account-summary {
|
||||
margin-top: 6px;
|
||||
font-size: 13px;
|
||||
margin-top: 5px;
|
||||
font-size: 12px;
|
||||
color: var(--ink-soft);
|
||||
}
|
||||
|
||||
.account-badges,
|
||||
.account-context-strip,
|
||||
.workspace-badges,
|
||||
.workspace-actions,
|
||||
.account-actions,
|
||||
@@ -539,6 +545,25 @@ header .logout-btn:hover,
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.account-context-strip {
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.account-context-chip {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
min-height: 26px;
|
||||
padding: 0 10px;
|
||||
border: 1px solid var(--line);
|
||||
border-radius: 999px;
|
||||
background: #fbfcfd;
|
||||
color: var(--ink-soft);
|
||||
font-size: 11px;
|
||||
font-weight: 800;
|
||||
letter-spacing: 0.08em;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.workspace-actions {
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user