mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-10-23 20:25:56 +00:00
style(portal): harden operator console shell
This commit is contained in:
+1
-1
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"source_hash": "087912d40011626d575748045a827f33b34abfd10a3a35ddf8f79ba776b2f549",
|
||||
"source_hash": "02f5d00566a4d55ae7999d1121d77557d884ffcedd01d2a278a62f90d61be7a4",
|
||||
"build_inputs": [
|
||||
"package.json",
|
||||
"tsconfig.json",
|
||||
|
||||
+123
-70
@@ -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;
|
||||
}
|
||||
|
||||
+27
-7
@@ -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 '<section class="portal-hero"><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><h1>" + title + "</h1><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">Workspaces</span><span class="portal-hero-stat-value">' + String(workspaceTotal) + "</span></div></div></section>";
|
||||
}
|
||||
function shellSectionButton(section, activeSection, title, copy) {
|
||||
@@ -1646,7 +1664,7 @@
|
||||
}
|
||||
function renderShellNavigation(accounts, supportEmail, activeSection) {
|
||||
var hosted = hasHostedAccounts(accounts);
|
||||
return '<aside class="portal-shell-nav" aria-label="Pulse Account sections"><div class="portal-shell-nav-group">' + 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") + "</div></aside>";
|
||||
return '<aside class="portal-shell-nav" aria-label="Pulse Account sections"><div class="portal-shell-nav-header"><div class="portal-shell-nav-eyebrow">Pulse Account</div><div class="portal-shell-nav-title">' + (hosted ? "Operator console" : "Account console") + '</div><div class="portal-shell-nav-support">' + escapeHTML(supportEmail || "support@pulserelay.pro") + '</div></div><div class="portal-shell-nav-group">' + 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") + "</div></aside>";
|
||||
}
|
||||
function renderWorkspaceCard(account, workspace, accountAPIBasePath) {
|
||||
var status = workspaceHealthState(workspace);
|
||||
@@ -1689,7 +1707,9 @@
|
||||
addWorkspaceForm = '<div class="add-workspace-form" id="add-ws-form-' + escapeAttr(account.id) + '"><label for="ws-name-' + escapeAttr(account.id) + '">Workspace name (for example, a client name)</label><input type="text" id="ws-name-' + escapeAttr(account.id) + '" placeholder="Acme Corp" maxlength="80" autocomplete="off"><div class="form-actions"><button type="button" class="btn-primary" data-action="create-workspace" data-account-id="' + escapeAttr(account.id) + '">Create workspace</button><button type="button" class="btn-secondary" data-action="toggle-add-workspace" data-account-id="' + escapeAttr(account.id) + '">Cancel</button><div class="spinner" id="ws-spinner-' + escapeAttr(account.id) + '" hidden></div></div></div>';
|
||||
}
|
||||
}
|
||||
return '<section class="account-content-panel account-content-panel-overview"><div class="account-command-deck"><div class="account-overview-card"><div class="account-panel-kicker">Hosted posture</div><h3>' + escapeHTML(account.name) + "</h3><p>" + escapeHTML(summaryText) + '</p></div><div class="account-metric-strip"><div class="account-stat-card account-stat-card-inline"><span class="account-stat-label">Active workspaces</span><span class="account-stat-value">' + String(activeCount) + '</span></div><div class="account-stat-card account-stat-card-inline"><span class="account-stat-label">Healthy</span><span class="account-stat-value account-stat-healthy">' + String(healthyCount) + '</span></div><div class="account-stat-card account-stat-card-inline"><span class="account-stat-label">Checking</span><span class="account-stat-value account-stat-checking">' + String(checkingCount) + '</span></div><div class="account-stat-card account-stat-card-inline"><span class="account-stat-label">Needs attention</span><span class="account-stat-value account-stat-unhealthy">' + String(unhealthyCount) + "</span></div></div>" + actions + addWorkspaceForm + "</div></section>";
|
||||
return '<section class="account-content-panel account-content-panel-overview"><div class="account-command-deck"><div class="account-overview-card"><div class="account-panel-kicker">Hosted posture</div><h3>' + escapeHTML(account.name) + "</h3><p>" + escapeHTML(summaryText) + '</p><div class="account-overview-callout">' + 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."
|
||||
) + '</div></div><div class="account-metric-strip"><div class="account-stat-card account-stat-card-inline"><span class="account-stat-label">Active workspaces</span><span class="account-stat-value">' + String(activeCount) + '</span></div><div class="account-stat-card account-stat-card-inline"><span class="account-stat-label">Healthy</span><span class="account-stat-value account-stat-healthy">' + String(healthyCount) + '</span></div><div class="account-stat-card account-stat-card-inline"><span class="account-stat-label">Checking</span><span class="account-stat-value account-stat-checking">' + String(checkingCount) + '</span></div><div class="account-stat-card account-stat-card-inline"><span class="account-stat-label">Needs attention</span><span class="account-stat-value account-stat-unhealthy">' + String(unhealthyCount) + "</span></div></div>" + actions + addWorkspaceForm + "</div></section>";
|
||||
}
|
||||
function renderAccountWorkspaceSection(account, accountAPIBasePath) {
|
||||
var workspaces = Array.isArray(account.workspaces) ? account.workspaces : [];
|
||||
@@ -1706,7 +1726,7 @@
|
||||
return '<section class="account-content-panel account-content-panel-team"><section class="team-management-panel team-section team-section-shell" id="team-section-' + escapeAttr(account.id) + '" data-actor-role="' + escapeAttr(account.role) + '"><div class="team-management-header"><div><div class="account-panel-kicker">Team management</div><h3>Control who can operate this account</h3><p>Owners manage billing and access. Admins and techs keep the hosted fleet running day to day.</p></div><button type="button" class="btn-secondary btn-compact" data-shell-action="activate-section" data-shell-section="workspaces">Done</button></div><div class="team-management-stats" id="team-stats-' + escapeAttr(account.id) + '"></div><div class="team-management-grid"><div class="team-roster"><div class="team-panel-heading"><h4>People on this account</h4><p>Keep the roster small and role assignment explicit. The people listed here are the ones who can operate the hosted fleet.</p></div><div class="team-roster-list" id="team-list-' + escapeAttr(account.id) + '"><div class="team-list-message">Loading\u2026</div></div></div><div class="team-invite-panel"><div class="team-panel-heading"><h4>Invite someone new</h4><p>Add another operator with the minimum role they need for this account.</p></div><div class="team-invite"><div><label for="invite-email-' + escapeAttr(account.id) + '">Email</label><input type="email" id="invite-email-' + escapeAttr(account.id) + '" placeholder="user@example.com" autocomplete="off"></div><div><label for="invite-role-' + escapeAttr(account.id) + '">Role</label><select id="invite-role-' + escapeAttr(account.id) + '"><option value="admin">Admin</option><option value="tech">Tech</option><option value="read_only">Read-only</option></select></div><button type="button" class="btn-primary btn-compact" data-action="invite-member" data-account-id="' + escapeAttr(account.id) + '">Invite</button></div></div></div></section></section>';
|
||||
}
|
||||
function renderSupportSection(context) {
|
||||
return '<section class="portal-support-panel"><div class="account-panel-kicker">Support</div><h2>Get help without leaving Pulse Account</h2><p>Use support when hosted access looks wrong, billing does not behave as expected, or you need help with commercial licensing and privacy actions.</p><div class="portal-support-card-grid"><div class="portal-support-card"><h3>Account support</h3><p>For access, tenant handoff, team, and billing issues, contact the hosted operations desk.</p><a class="portal-support-link" href="mailto:' + escapeAttr(context.bootstrap.support_email || "") + '">' + escapeHTML(context.bootstrap.support_email || "") + '</a></div><div class="portal-support-card"><h3>Commercial services</h3><p>Self-hosted subscriptions, license recovery, refunds, and privacy requests all route through the same account surface.</p><button type="button" class="btn-secondary" data-shell-action="activate-section" data-shell-section="services">Open account services</button></div></div></section>';
|
||||
return '<section class="portal-support-panel"><div class="account-panel-kicker">Support</div><h2>Support and escalation</h2><p>Use support when hosted access looks wrong, billing does not behave as expected, or you need help with commercial licensing and privacy actions.</p><div class="portal-support-card-grid"><div class="portal-support-card"><h3>Account support</h3><p>For access, tenant handoff, team, and billing issues, contact the hosted operations desk.</p><a class="portal-support-link" href="mailto:' + escapeAttr(context.bootstrap.support_email || "") + '">' + escapeHTML(context.bootstrap.support_email || "") + '</a></div><div class="portal-support-card"><h3>Commercial services</h3><p>Self-hosted subscriptions, license recovery, refunds, and privacy requests all route through the same account surface.</p><button type="button" class="btn-secondary" data-shell-action="activate-section" data-shell-section="services">Open account services</button></div></div></section>';
|
||||
}
|
||||
function renderHeaderHTML(context) {
|
||||
if (context.bootstrap.authenticated) {
|
||||
|
||||
@@ -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<T extends HTMLElement = HTMLElement>(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;
|
||||
}
|
||||
|
||||
@@ -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 (
|
||||
'<section class="portal-hero">' +
|
||||
@@ -155,6 +155,11 @@ function renderShellNavigation(accounts: PortalAccountSummary[], supportEmail: s
|
||||
var hosted = hasHostedAccounts(accounts);
|
||||
return (
|
||||
'<aside class="portal-shell-nav" aria-label="Pulse Account sections">' +
|
||||
'<div class="portal-shell-nav-header">' +
|
||||
'<div class="portal-shell-nav-eyebrow">Pulse Account</div>' +
|
||||
'<div class="portal-shell-nav-title">' + (hosted ? 'Operator console' : 'Account console') + '</div>' +
|
||||
'<div class="portal-shell-nav-support">' + escapeHTML(supportEmail || 'support@pulserelay.pro') + '</div>' +
|
||||
'</div>' +
|
||||
'<div class="portal-shell-nav-group">' +
|
||||
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 {
|
||||
'<div class="account-panel-kicker">Hosted posture</div>' +
|
||||
'<h3>' + escapeHTML(account.name) + '</h3>' +
|
||||
'<p>' + escapeHTML(summaryText) + '</p>' +
|
||||
'<div class="account-overview-callout">' + 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.'
|
||||
) + '</div>' +
|
||||
'</div>' +
|
||||
'<div class="account-metric-strip">' +
|
||||
'<div class="account-stat-card account-stat-card-inline">' +
|
||||
@@ -463,7 +472,7 @@ function renderSupportSection(context: ShellViewContext): string {
|
||||
return (
|
||||
'<section class="portal-support-panel">' +
|
||||
'<div class="account-panel-kicker">Support</div>' +
|
||||
'<h2>Get help without leaving Pulse Account</h2>' +
|
||||
'<h2>Support and escalation</h2>' +
|
||||
'<p>Use support when hosted access looks wrong, billing does not behave as expected, or you need help with commercial licensing and privacy actions.</p>' +
|
||||
'<div class="portal-support-card-grid">' +
|
||||
'<div class="portal-support-card">' +
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user