mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-10-23 20:25:56 +00:00
style(portal): densify operator console surfaces
This commit is contained in:
+1
-1
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"source_hash": "c95bc530e614dc996aaee2d380fabea9d0d0c2546c9062d0c75414c4229d8acf",
|
||||
"source_hash": "db22f20e5c01de6c11aa19319736af2e3d32a613e07002fbcab79c4d07f08e5d",
|
||||
"build_inputs": [
|
||||
"package.json",
|
||||
"tsconfig.json",
|
||||
|
||||
+225
-142
@@ -164,17 +164,17 @@ header .logout-btn:hover,
|
||||
}
|
||||
.portal-hero {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1.8fr) minmax(360px, 0.92fr);
|
||||
gap: 16px;
|
||||
padding: 18px 20px;
|
||||
grid-template-columns: minmax(0, 1fr) auto;
|
||||
gap: 18px;
|
||||
padding: 12px 16px;
|
||||
border: 1px solid var(--line);
|
||||
border-radius: var(--radius-lg);
|
||||
background: #fbfcfd;
|
||||
box-shadow: var(--shadow-sm);
|
||||
background: #fff;
|
||||
box-shadow: 0 10px 24px rgba(10, 25, 41, 0.035);
|
||||
}
|
||||
.portal-hero-compact {
|
||||
align-items: center;
|
||||
padding: 14px 18px;
|
||||
padding: 12px 16px;
|
||||
}
|
||||
.portal-hero-copy {
|
||||
display: flex;
|
||||
@@ -233,20 +233,21 @@ header .logout-btn:hover,
|
||||
text-transform: uppercase;
|
||||
}
|
||||
.portal-hero-stats {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||
gap: 10px;
|
||||
align-self: stretch;
|
||||
display: flex;
|
||||
align-items: stretch;
|
||||
gap: 8px;
|
||||
align-self: center;
|
||||
}
|
||||
.portal-hero-stat {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
min-height: 72px;
|
||||
padding: 11px 13px;
|
||||
justify-content: center;
|
||||
min-width: 132px;
|
||||
min-height: 0;
|
||||
padding: 10px 12px;
|
||||
border: 1px solid var(--line);
|
||||
border-radius: var(--radius-md);
|
||||
background: #fcfdff;
|
||||
border-radius: 12px;
|
||||
background: #fbfcfd;
|
||||
}
|
||||
.portal-hero-stat-label {
|
||||
font-size: 12px;
|
||||
@@ -256,9 +257,9 @@ header .logout-btn:hover,
|
||||
text-transform: uppercase;
|
||||
}
|
||||
.portal-hero-stat-value {
|
||||
font-size: 18px;
|
||||
font-size: 16px;
|
||||
font-weight: 800;
|
||||
line-height: 1.2;
|
||||
line-height: 1.25;
|
||||
letter-spacing: -0.04em;
|
||||
}
|
||||
.portal-command-strip,
|
||||
@@ -272,13 +273,13 @@ header .logout-btn:hover,
|
||||
}
|
||||
.portal-shell-layout {
|
||||
display: grid;
|
||||
grid-template-columns: 240px minmax(0, 1fr);
|
||||
grid-template-columns: 224px minmax(0, 1fr);
|
||||
gap: 0;
|
||||
align-items: start;
|
||||
border: 1px solid var(--line);
|
||||
border-radius: var(--radius-lg);
|
||||
background: #f7fafc;
|
||||
box-shadow: var(--shadow-sm);
|
||||
background: #fff;
|
||||
box-shadow: 0 16px 40px rgba(10, 25, 41, 0.05);
|
||||
overflow: hidden;
|
||||
}
|
||||
.portal-shell-nav {
|
||||
@@ -294,8 +295,8 @@ header .logout-btn:hover,
|
||||
.portal-shell-nav-header {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
padding: 20px 20px 18px;
|
||||
gap: 7px;
|
||||
padding: 18px 18px 16px;
|
||||
border-bottom: 1px solid var(--nav-line);
|
||||
}
|
||||
.portal-shell-nav-eyebrow {
|
||||
@@ -306,7 +307,7 @@ header .logout-btn:hover,
|
||||
color: #78b6c0;
|
||||
}
|
||||
.portal-shell-nav-title {
|
||||
font-size: 20px;
|
||||
font-size: 19px;
|
||||
line-height: 1.02;
|
||||
letter-spacing: -0.03em;
|
||||
font-weight: 800;
|
||||
@@ -320,14 +321,14 @@ header .logout-btn:hover,
|
||||
.portal-shell-nav-group {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 8px 0;
|
||||
padding: 10px 0;
|
||||
}
|
||||
.portal-shell-nav-link {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
width: 100%;
|
||||
padding: 15px 20px;
|
||||
padding: 14px 18px;
|
||||
border: none;
|
||||
background: transparent;
|
||||
text-align: left;
|
||||
@@ -344,6 +345,30 @@ header .logout-btn:hover,
|
||||
background: rgba(120, 182, 192, 0.12);
|
||||
box-shadow: inset 3px 0 0 #78b6c0;
|
||||
}
|
||||
.portal-shell-nav-footer {
|
||||
margin-top: auto;
|
||||
padding: 16px 18px 18px;
|
||||
border-top: 1px solid var(--nav-line);
|
||||
background: rgba(255, 255, 255, 0.03);
|
||||
}
|
||||
.portal-shell-nav-footer-label {
|
||||
font-size: 11px;
|
||||
font-weight: 800;
|
||||
letter-spacing: 0.12em;
|
||||
text-transform: uppercase;
|
||||
color: #78b6c0;
|
||||
}
|
||||
.portal-shell-nav-footer-link {
|
||||
display: inline-block;
|
||||
margin-top: 8px;
|
||||
font-size: 12px;
|
||||
line-height: 1.5;
|
||||
color: #d9f4fb;
|
||||
text-decoration: none;
|
||||
}
|
||||
.portal-shell-nav-footer-link:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
.portal-shell-nav-label {
|
||||
display: block;
|
||||
font-size: 14px;
|
||||
@@ -359,8 +384,8 @@ header .logout-btn:hover,
|
||||
.portal-shell-main {
|
||||
min-width: 0;
|
||||
min-height: 760px;
|
||||
padding: 24px 26px 30px;
|
||||
background: #f7fafc;
|
||||
padding: 20px 22px 26px;
|
||||
background: #fbfcfd;
|
||||
}
|
||||
.portal-shell-main > .portal-content-panel {
|
||||
display: none !important;
|
||||
@@ -467,6 +492,16 @@ header .logout-btn:hover,
|
||||
gap: 10px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
.workspace-actions {
|
||||
justify-content: flex-end;
|
||||
}
|
||||
.workspace-actions form {
|
||||
margin: 0;
|
||||
}
|
||||
.workspace-actions .btn-primary,
|
||||
.workspace-actions .btn-secondary {
|
||||
min-width: 118px;
|
||||
}
|
||||
.account-surface-body {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr;
|
||||
@@ -536,10 +571,21 @@ header .logout-btn:hover,
|
||||
}
|
||||
.account-command-deck {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(320px, 1.08fr) minmax(0, 1.3fr);
|
||||
grid-template-columns: 1fr;
|
||||
gap: 14px;
|
||||
align-items: stretch;
|
||||
}
|
||||
.account-overview-card {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1.1fr) minmax(360px, 0.9fr);
|
||||
gap: 14px;
|
||||
align-items: stretch;
|
||||
}
|
||||
.account-overview-lead {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-width: 0;
|
||||
}
|
||||
.account-metric-strip {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, minmax(0, 1fr));
|
||||
@@ -786,7 +832,7 @@ header .logout-btn:hover,
|
||||
}
|
||||
.workspace-list-head {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1.55fr) 120px 120px 180px;
|
||||
grid-template-columns: minmax(0, 1.8fr) 132px 132px 176px;
|
||||
gap: 14px;
|
||||
padding: 2px 16px 6px;
|
||||
font-size: 11px;
|
||||
@@ -798,31 +844,34 @@ header .logout-btn:hover,
|
||||
.workspace-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
gap: 0;
|
||||
border: 1px solid var(--line);
|
||||
border-radius: 12px;
|
||||
background: #fff;
|
||||
overflow: hidden;
|
||||
}
|
||||
.workspace-row {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1fr) auto;
|
||||
grid-template-columns: minmax(0, 1.8fr) 132px 132px 176px;
|
||||
gap: 14px;
|
||||
align-items: center;
|
||||
padding: 16px;
|
||||
background: var(--panel-muted);
|
||||
padding: 14px 16px;
|
||||
background: #fff;
|
||||
border-top: 1px solid var(--line);
|
||||
}
|
||||
.workspace-row-main {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1.55fr) minmax(220px, 0.8fr);
|
||||
gap: 14px;
|
||||
align-items: center;
|
||||
.workspace-row:first-child {
|
||||
border-top: none;
|
||||
}
|
||||
.workspace-row-title {
|
||||
.workspace-row-primary {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
gap: 8px;
|
||||
min-width: 0;
|
||||
}
|
||||
.workspace-row-heading {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
gap: 6px;
|
||||
}
|
||||
.workspace-name,
|
||||
.workspace-management-content h4,
|
||||
@@ -840,41 +889,28 @@ header .logout-btn:hover,
|
||||
.workspace-meta-item {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
min-height: 28px;
|
||||
padding: 0 10px;
|
||||
min-height: 24px;
|
||||
padding: 0 9px;
|
||||
border-radius: 999px;
|
||||
background: var(--panel-muted);
|
||||
font-size: 12px;
|
||||
background: #f1f5f8;
|
||||
font-size: 11px;
|
||||
color: var(--ink-soft);
|
||||
}
|
||||
.workspace-summary,
|
||||
.workspace-row-note,
|
||||
.workspace-management-summary {
|
||||
margin: 0;
|
||||
font-size: 14px;
|
||||
line-height: 1.65;
|
||||
font-size: 12px;
|
||||
line-height: 1.5;
|
||||
color: var(--ink-soft);
|
||||
}
|
||||
.workspace-row-status-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
gap: 10px;
|
||||
}
|
||||
.workspace-row-status-cell {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
min-height: 68px;
|
||||
padding: 12px;
|
||||
border: 1px solid var(--line);
|
||||
border-radius: var(--radius-md);
|
||||
background: #fff;
|
||||
align-items: center;
|
||||
min-width: 0;
|
||||
}
|
||||
.workspace-row-status-label {
|
||||
font-size: 11px;
|
||||
font-weight: 800;
|
||||
letter-spacing: 0.12em;
|
||||
text-transform: uppercase;
|
||||
color: var(--ink-muted);
|
||||
.workspace-row-status-cell-badge .badge {
|
||||
width: 100%;
|
||||
justify-content: center;
|
||||
}
|
||||
.workspace-state-label {
|
||||
font-size: 13px;
|
||||
@@ -990,9 +1026,15 @@ header .logout-btn:hover,
|
||||
.team-management-stats {
|
||||
margin: 16px 0;
|
||||
}
|
||||
.team-stat-card {
|
||||
padding: 12px 14px;
|
||||
border: 1px solid var(--line);
|
||||
border-radius: 10px;
|
||||
background: #fbfcfd;
|
||||
}
|
||||
.team-stat-value {
|
||||
margin-top: 6px;
|
||||
font-size: 22px;
|
||||
margin-top: 8px;
|
||||
font-size: 24px;
|
||||
}
|
||||
.team-management-grid {
|
||||
display: grid;
|
||||
@@ -1007,16 +1049,23 @@ header .logout-btn:hover,
|
||||
.team-panel-heading {
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
.team-panel-heading p {
|
||||
max-width: 720px;
|
||||
}
|
||||
.team-roster-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
gap: 0;
|
||||
border: 1px solid var(--line);
|
||||
border-radius: 10px;
|
||||
background: #fff;
|
||||
overflow: hidden;
|
||||
}
|
||||
.team-roster-head {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1fr) auto;
|
||||
gap: 16px;
|
||||
padding: 0 8px 2px;
|
||||
padding: 0 6px 8px;
|
||||
font-size: 11px;
|
||||
font-weight: 800;
|
||||
letter-spacing: 0.12em;
|
||||
@@ -1026,41 +1075,44 @@ header .logout-btn:hover,
|
||||
.team-member-row {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1fr) auto;
|
||||
align-items: start;
|
||||
gap: 16px;
|
||||
padding: 14px 16px;
|
||||
border: 1px solid var(--line);
|
||||
border-radius: 10px;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
padding: 12px 14px;
|
||||
border: none;
|
||||
border-top: 1px solid var(--line);
|
||||
background: #fff;
|
||||
}
|
||||
.team-member-row:first-of-type {
|
||||
border-top: none;
|
||||
}
|
||||
.team-member-identity {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
gap: 6px;
|
||||
min-width: 0;
|
||||
}
|
||||
.team-member-topline {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 12px;
|
||||
justify-content: flex-start;
|
||||
gap: 10px;
|
||||
min-width: 0;
|
||||
}
|
||||
.team-member-email {
|
||||
font-size: 14px;
|
||||
font-size: 13px;
|
||||
font-weight: 700;
|
||||
color: var(--ink);
|
||||
}
|
||||
.team-member-caption {
|
||||
font-size: 13px;
|
||||
line-height: 1.55;
|
||||
font-size: 12px;
|
||||
line-height: 1.45;
|
||||
color: var(--ink-soft);
|
||||
}
|
||||
.team-member-controls {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: end;
|
||||
gap: 10px;
|
||||
gap: 8px;
|
||||
flex-wrap: wrap;
|
||||
min-width: 0;
|
||||
}
|
||||
@@ -1078,19 +1130,19 @@ header .logout-btn:hover,
|
||||
.team-inline-role-badge {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
min-height: 28px;
|
||||
padding: 0 10px;
|
||||
min-height: 24px;
|
||||
padding: 0 8px;
|
||||
border-radius: 999px;
|
||||
background: var(--panel-muted);
|
||||
color: var(--ink-soft);
|
||||
font-size: 11px;
|
||||
font-size: 10px;
|
||||
font-weight: 800;
|
||||
letter-spacing: 0.08em;
|
||||
text-transform: uppercase;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.team-role-select {
|
||||
min-width: 140px;
|
||||
min-width: 124px;
|
||||
}
|
||||
.team-list-message {
|
||||
display: flex;
|
||||
@@ -1123,8 +1175,8 @@ header .logout-btn:hover,
|
||||
.team-invite-guide {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
padding: 12px;
|
||||
gap: 6px;
|
||||
padding: 10px 12px;
|
||||
border: 1px solid var(--line);
|
||||
border-radius: 10px;
|
||||
background: #fff;
|
||||
@@ -1141,18 +1193,22 @@ header .logout-btn:hover,
|
||||
color: var(--ink-muted);
|
||||
}
|
||||
.team-invite-guide-row span {
|
||||
font-size: 13px;
|
||||
line-height: 1.5;
|
||||
font-size: 12px;
|
||||
line-height: 1.4;
|
||||
color: var(--ink-soft);
|
||||
}
|
||||
.service-action-list {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr;
|
||||
gap: 12px;
|
||||
gap: 0;
|
||||
border: 1px solid var(--line);
|
||||
border-radius: 12px;
|
||||
background: #fff;
|
||||
overflow: hidden;
|
||||
}
|
||||
.service-shell {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(320px, 0.72fr) minmax(0, 1.28fr);
|
||||
grid-template-columns: minmax(280px, 0.82fr) minmax(0, 1.18fr);
|
||||
gap: 16px;
|
||||
align-items: start;
|
||||
}
|
||||
@@ -1162,6 +1218,20 @@ header .logout-btn:hover,
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
}
|
||||
.service-list-intro,
|
||||
.service-inline-support {
|
||||
padding: 14px 16px;
|
||||
border: 1px solid var(--line);
|
||||
border-radius: 12px;
|
||||
background: #fff;
|
||||
}
|
||||
.service-list-intro p,
|
||||
.service-inline-support p {
|
||||
margin: 8px 0 0;
|
||||
font-size: 13px;
|
||||
line-height: 1.55;
|
||||
color: var(--ink-soft);
|
||||
}
|
||||
.service-detail-shell {
|
||||
min-height: 100%;
|
||||
border: 1px solid var(--line);
|
||||
@@ -1171,7 +1241,7 @@ header .logout-btn:hover,
|
||||
overflow: hidden;
|
||||
}
|
||||
.service-brief-card {
|
||||
padding: 18px;
|
||||
padding: 16px;
|
||||
border: 1px solid var(--line);
|
||||
border-radius: var(--radius-md);
|
||||
background: var(--panel-muted);
|
||||
@@ -1181,7 +1251,7 @@ header .logout-btn:hover,
|
||||
}
|
||||
.service-brief-card h3 {
|
||||
margin: 8px 0 0;
|
||||
font-size: 19px;
|
||||
font-size: 18px;
|
||||
line-height: 1.08;
|
||||
letter-spacing: -0.03em;
|
||||
}
|
||||
@@ -1192,38 +1262,48 @@ header .logout-btn:hover,
|
||||
color: var(--ink-soft);
|
||||
}
|
||||
.service-action-row {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1fr) auto;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
padding: 16px 18px;
|
||||
border: 1px solid var(--line);
|
||||
border-radius: var(--radius-md);
|
||||
padding: 14px 16px;
|
||||
border: none;
|
||||
border-top: 1px solid var(--line);
|
||||
background: #fff;
|
||||
transition:
|
||||
border-color 140ms ease,
|
||||
box-shadow 140ms ease,
|
||||
transform 140ms ease;
|
||||
}
|
||||
.service-action-row:first-child {
|
||||
border-top: none;
|
||||
}
|
||||
.service-action-row:hover,
|
||||
.service-action-row.active {
|
||||
border-color: rgba(15, 109, 115, 0.28);
|
||||
box-shadow: 0 10px 24px rgba(10, 25, 41, 0.08);
|
||||
}
|
||||
.service-action-main {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
min-width: 0;
|
||||
}
|
||||
.service-action-tags {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 10px;
|
||||
justify-content: flex-start;
|
||||
gap: 8px;
|
||||
}
|
||||
.service-action-tag {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
min-height: 26px;
|
||||
padding: 0 10px;
|
||||
min-height: 24px;
|
||||
padding: 0 9px;
|
||||
border-radius: 999px;
|
||||
background: #fff;
|
||||
color: var(--ink-muted);
|
||||
font-size: 11px;
|
||||
font-size: 10px;
|
||||
font-weight: 800;
|
||||
letter-spacing: 0.08em;
|
||||
text-transform: uppercase;
|
||||
@@ -1231,61 +1311,46 @@ header .logout-btn:hover,
|
||||
.service-action-copy {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
gap: 4px;
|
||||
}
|
||||
.service-action-copy h3 {
|
||||
margin: 0;
|
||||
font-size: 17px;
|
||||
font-size: 16px;
|
||||
line-height: 1.16;
|
||||
letter-spacing: -0.03em;
|
||||
}
|
||||
.service-action-copy p {
|
||||
margin: 0;
|
||||
font-size: 14px;
|
||||
line-height: 1.6;
|
||||
font-size: 13px;
|
||||
line-height: 1.5;
|
||||
color: var(--ink-soft);
|
||||
max-width: 760px;
|
||||
}
|
||||
.service-action-highlights {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
gap: 6px;
|
||||
}
|
||||
.service-action-highlight {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
min-height: 28px;
|
||||
padding: 0 10px;
|
||||
min-height: 24px;
|
||||
padding: 0 9px;
|
||||
border-radius: 999px;
|
||||
background: #fff;
|
||||
color: var(--ink-soft);
|
||||
font-size: 12px;
|
||||
font-size: 10px;
|
||||
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;
|
||||
}
|
||||
.service-action-button {
|
||||
min-width: 92px;
|
||||
min-width: 86px;
|
||||
}
|
||||
.service-panel {
|
||||
padding: 22px;
|
||||
padding: 18px;
|
||||
border-radius: var(--radius-md);
|
||||
background: #fff;
|
||||
min-height: 100%;
|
||||
@@ -1302,14 +1367,22 @@ header .logout-btn:hover,
|
||||
.service-empty-points {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||
gap: 12px;
|
||||
gap: 10px;
|
||||
margin-top: 20px;
|
||||
}
|
||||
.service-empty-support {
|
||||
margin-top: 16px;
|
||||
padding-top: 14px;
|
||||
border-top: 1px solid var(--line);
|
||||
font-size: 13px;
|
||||
line-height: 1.55;
|
||||
color: var(--ink-soft);
|
||||
}
|
||||
.service-empty-point {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
padding: 14px;
|
||||
padding: 12px;
|
||||
border: 1px solid var(--line);
|
||||
border-radius: 12px;
|
||||
background: var(--panel-muted);
|
||||
@@ -1321,8 +1394,8 @@ header .logout-btn:hover,
|
||||
color: var(--ink-muted);
|
||||
}
|
||||
.service-empty-point span {
|
||||
font-size: 13px;
|
||||
line-height: 1.55;
|
||||
font-size: 12px;
|
||||
line-height: 1.45;
|
||||
color: var(--ink-soft);
|
||||
}
|
||||
.service-section-auth {
|
||||
@@ -1488,6 +1561,7 @@ header .logout-btn:hover,
|
||||
.account-management-grid,
|
||||
.team-management-grid,
|
||||
.account-overview-secondary,
|
||||
.account-overview-card,
|
||||
.service-shell,
|
||||
.service-action-list,
|
||||
.service-empty-points,
|
||||
@@ -1520,14 +1594,14 @@ header .logout-btn:hover,
|
||||
padding: 20px;
|
||||
}
|
||||
.portal-hero-stats {
|
||||
grid-template-columns: 1fr;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
.workspace-row,
|
||||
.workspace-row-main {
|
||||
.workspace-row {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
.workspace-list-toolbar {
|
||||
grid-template-columns: 1fr;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
}
|
||||
}
|
||||
@media (max-width: 720px) {
|
||||
@@ -1554,7 +1628,6 @@ header .logout-btn:hover,
|
||||
.workspace-management-header,
|
||||
.team-management-header,
|
||||
.service-header,
|
||||
.service-action-footer,
|
||||
.service-action-tags {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
@@ -1570,15 +1643,25 @@ header .logout-btn:hover,
|
||||
width: 100%;
|
||||
}
|
||||
.team-member-row,
|
||||
.team-roster-head,
|
||||
.team-member-controls {
|
||||
.team-roster-head {
|
||||
grid-template-columns: 1fr;
|
||||
align-items: stretch;
|
||||
}
|
||||
.team-member-controls {
|
||||
justify-content: start;
|
||||
width: 100%;
|
||||
}
|
||||
.workspace-row-status-grid {
|
||||
.service-action-row,
|
||||
.service-shell,
|
||||
.workspace-operations-shell,
|
||||
.account-overview-card,
|
||||
.account-overview-secondary,
|
||||
.overview-quick-actions,
|
||||
.service-empty-points {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
.service-action-cta,
|
||||
.service-action-button {
|
||||
width: 100%;
|
||||
justify-content: start;
|
||||
}
|
||||
}
|
||||
|
||||
+12
-6
@@ -1696,7 +1696,7 @@
|
||||
var highlightHTML = highlights.map(function(item) {
|
||||
return '<span class="service-action-highlight">' + escapeHTML(item) + "</span>";
|
||||
}).join("");
|
||||
return '<article class="service-action-row"><div class="service-action-tags"><span class="service-card-kicker">' + kicker + '</span><span class="service-action-tag">Self-hosted</span></div><div class="service-action-copy"><h3>' + title + "</h3><p>" + description + '</p></div><div class="service-action-highlights">' + highlightHTML + '</div><div class="service-action-footer"><div class="service-action-caption">Open the dedicated account flow for this request.</div><div class="service-action-cta"><button class="btn-secondary service-action-button" type="button" id="' + id + '" data-account-service-action="open-service-panel" data-account-service-panel="' + panelID + '" data-account-service-focus="' + focusID + '" data-shell-target="services">Open</button></div></div></article>';
|
||||
return '<article class="service-action-row"><div class="service-action-main"><div class="service-action-tags"><span class="service-card-kicker">' + kicker + '</span><span class="service-action-tag">Self-hosted</span></div><div class="service-action-copy"><h3>' + title + "</h3><p>" + description + '</p></div><div class="service-action-highlights">' + highlightHTML + '</div></div><div class="service-action-cta"><button class="btn-secondary service-action-button" type="button" id="' + id + '" data-account-service-action="open-service-panel" data-account-service-panel="' + panelID + '" data-account-service-focus="' + focusID + '" data-shell-target="services">Open</button></div></article>';
|
||||
}
|
||||
function renderOverviewQuickAction(section, title, copy) {
|
||||
return '<button class="overview-quick-action" type="button" data-shell-action="activate-section" data-shell-section="' + section + '"><span class="overview-quick-action-title">' + title + '</span><span class="overview-quick-action-copy">' + copy + "</span></button>";
|
||||
@@ -1707,6 +1707,12 @@
|
||||
if (status === "unhealthy") return "This workspace needs attention before it is trustworthy.";
|
||||
return "This workspace is still waiting on a completed health check.";
|
||||
}
|
||||
function workspaceRowNote(workspace) {
|
||||
var status = workspaceHealthState(workspace);
|
||||
if (status === "healthy") return "Ready for operator work";
|
||||
if (status === "unhealthy") return "Review this workspace before treating it as stable";
|
||||
return "Awaiting a completed health check";
|
||||
}
|
||||
function attentionWorkspaces(workspaces) {
|
||||
var results = [];
|
||||
for (var i = 0; i < workspaces.length; i += 1) {
|
||||
@@ -1739,7 +1745,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-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>";
|
||||
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">' + (hosted ? "Hosted operations and account services" : "Commercial account services and support") + '</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><div class="portal-shell-nav-footer"><div class="portal-shell-nav-footer-label">Need help?</div><a class="portal-shell-nav-footer-link" href="mailto:' + escapeAttr(supportEmail || "support@pulserelay.pro") + '">' + escapeHTML(supportEmail || "support@pulserelay.pro") + "</a></div></aside>";
|
||||
}
|
||||
function renderWorkspaceCard(account, workspace, accountAPIBasePath) {
|
||||
var status = workspaceHealthState(workspace);
|
||||
@@ -1764,7 +1770,7 @@
|
||||
if (account.can_manage && (state === "active" || state === "suspended" || state === "failed")) {
|
||||
manageAction = '<button type="button" class="btn-secondary btn-workspace-manage" data-action="select-workspace" data-account-id="' + escapeAttr(account.id) + '" data-workspace-id="' + escapeAttr(workspace.id) + '">Manage</button>';
|
||||
}
|
||||
return '<article class="workspace-row"><div class="workspace-row-main"><div class="workspace-row-title"><div class="workspace-row-heading"><h4 class="workspace-name">' + escapeHTML(workspace.display_name) + '</h4><div class="workspace-meta">' + metaParts.join("") + '</div></div><p class="workspace-summary">' + escapeHTML(workspaceStatusCopy(workspace)) + '</p></div><div class="workspace-row-status-grid"><div class="workspace-row-status-cell"><span class="workspace-row-status-label">Health</span>' + healthBadgeHTML(workspace) + '</div><div class="workspace-row-status-cell"><span class="workspace-row-status-label">Lifecycle</span><span class="badge badge-' + escapeHTML(state || "unknown") + '">' + escapeHTML(titleCase(state || "Unknown")) + '</span></div></div></div><div class="workspace-actions">' + openAction + manageAction + "</div></article>";
|
||||
return '<article class="workspace-row"><div class="workspace-row-primary"><div class="workspace-row-heading"><h4 class="workspace-name">' + escapeHTML(workspace.display_name) + '</h4><div class="workspace-meta">' + metaParts.join("") + '</div></div><div class="workspace-row-note">' + escapeHTML(workspaceRowNote(workspace)) + '</div></div><div class="workspace-row-status-cell workspace-row-status-cell-badge">' + healthBadgeHTML(workspace) + '</div><div class="workspace-row-status-cell workspace-row-status-cell-badge"><span class="badge badge-' + escapeHTML(state || "unknown") + '">' + escapeHTML(titleCase(state || "Unknown")) + '</span></div><div class="workspace-actions">' + openAction + manageAction + "</div></article>";
|
||||
}
|
||||
function renderAccountOverviewSection(account) {
|
||||
var workspaces = Array.isArray(account.workspaces) ? account.workspaces : [];
|
||||
@@ -1783,9 +1789,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(postureTitle) + "</h3><p>" + escapeHTML(postureCopy) + '</p><div class="account-overview-callout">' + escapeHTML(
|
||||
return '<section class="account-content-panel account-content-panel-overview"><div class="account-command-deck"><div class="account-overview-card"><div class="account-overview-lead"><div class="account-panel-kicker">Hosted posture</div><h3>' + escapeHTML(postureTitle) + "</h3><p>" + escapeHTML(postureCopy) + '</p><div class="account-overview-callout">' + escapeHTML(
|
||||
account.kind === "msp" ? "Use this console to run client workspaces, account billing, and operator access from one place." : "Use this console to run hosted workspaces, account billing, and operator access from one place."
|
||||
) + '</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 class="account-overview-secondary"><div class="overview-side-card overview-side-card-primary"><div class="account-panel-kicker">Operator overview</div><h4>Start from the next action, not the whole account</h4><p>Use the overview for posture, then move into workspaces, team, or account services depending on what needs attention.</p><div class="overview-quick-actions">' + renderOverviewQuickAction("workspaces", "Open workspaces", "Review the hosted fleet and move into a workspace") + renderOverviewQuickAction("team", "Review team access", "Check who can operate billing and hosted workspaces") + renderOverviewQuickAction("services", "Open account services", "Handle billing, licenses, refunds, and privacy actions") + "</div></div>" + renderAttentionPanel(workspaces) + "</div></div></section>";
|
||||
) + '</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></div>" + actions + addWorkspaceForm + '<div class="account-overview-secondary"><div class="overview-side-card overview-side-card-primary"><div class="account-panel-kicker">Operator overview</div><h4>Start from the next action, not the whole account</h4><p>Use the overview for posture, then move into workspaces, team, or account services depending on what needs attention.</p><div class="overview-quick-actions">' + renderOverviewQuickAction("workspaces", "Open workspaces", "Review the hosted fleet and move into a workspace") + renderOverviewQuickAction("team", "Review team access", "Check who can operate billing and hosted workspaces") + renderOverviewQuickAction("services", "Open account services", "Handle billing, licenses, refunds, and privacy actions") + "</div></div>" + renderAttentionPanel(workspaces) + "</div></div></section>";
|
||||
}
|
||||
function renderAccountWorkspaceSection(account, accountAPIBasePath) {
|
||||
var workspaces = Array.isArray(account.workspaces) ? account.workspaces : [];
|
||||
@@ -1822,7 +1828,7 @@
|
||||
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>";
|
||||
}).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-brief-card service-brief-card-primary"><div class="account-panel-kicker">Scope</div><h3>Commercial account actions</h3><p>Use this area for self-hosted billing, license recovery, refunds, and privacy requests. Hosted workspace operations stay in the Workspaces and Team sections.</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><div class="service-brief-card"><div class="account-panel-kicker">Support</div><h3>Need help with a commercial request?</h3><p>Use support if billing, refund, privacy, or license actions do 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></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><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></div></section><section class="portal-content-panel portal-content-panel-support">' + renderSupportSection(context) + "</section></div></div></div>";
|
||||
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>";
|
||||
}
|
||||
function renderSignedOutPortalHTML(context) {
|
||||
var statusHTML = "";
|
||||
|
||||
@@ -156,7 +156,7 @@ describe('shell view', function() {
|
||||
expect(html).toContain('Healthy</span>');
|
||||
expect(html).toContain('Needs attention</span>');
|
||||
expect(html).toContain('Checking</span>');
|
||||
expect(html).toContain('Live updates and health checks are currently good.');
|
||||
expect(html).toContain('Ready for operator work');
|
||||
expect(html).toContain('This workspace needs attention before it is trustworthy.');
|
||||
expect(html).toContain('This workspace is still waiting on a completed health check.');
|
||||
expect(html).toContain('/api/accounts/acct_1/tenants/ws_active/handoff');
|
||||
@@ -188,7 +188,7 @@ describe('shell view', function() {
|
||||
|
||||
expect(html).toContain('<h1>Self-hosted Pulse Account</h1>');
|
||||
expect(html).toContain('No hosted workspace access is attached to this account yet.');
|
||||
expect(html).toContain('Use this area for self-hosted billing, license recovery, refunds, and privacy requests.');
|
||||
expect(html).toContain('Use these tools for self-hosted billing, license recovery, refunds, and privacy.');
|
||||
expect(html).toContain('Account services');
|
||||
expect(html).not.toContain('Self-hosted licenses and billing');
|
||||
});
|
||||
|
||||
@@ -116,20 +116,19 @@ function renderServiceActionRow(
|
||||
}).join('');
|
||||
return (
|
||||
'<article class="service-action-row">' +
|
||||
'<div class="service-action-tags">' +
|
||||
'<span class="service-card-kicker">' + kicker + '</span>' +
|
||||
'<span class="service-action-tag">Self-hosted</span>' +
|
||||
'</div>' +
|
||||
'<div class="service-action-copy">' +
|
||||
'<h3>' + title + '</h3>' +
|
||||
'<p>' + description + '</p>' +
|
||||
'</div>' +
|
||||
'<div class="service-action-highlights">' + highlightHTML + '</div>' +
|
||||
'<div class="service-action-footer">' +
|
||||
'<div class="service-action-caption">Open the dedicated account flow for this request.</div>' +
|
||||
'<div class="service-action-cta">' +
|
||||
'<button class="btn-secondary service-action-button" type="button" id="' + id + '" data-account-service-action="open-service-panel" data-account-service-panel="' + panelID + '" data-account-service-focus="' + focusID + '" data-shell-target="services">Open</button>' +
|
||||
'<div class="service-action-main">' +
|
||||
'<div class="service-action-tags">' +
|
||||
'<span class="service-card-kicker">' + kicker + '</span>' +
|
||||
'<span class="service-action-tag">Self-hosted</span>' +
|
||||
'</div>' +
|
||||
'<div class="service-action-copy">' +
|
||||
'<h3>' + title + '</h3>' +
|
||||
'<p>' + description + '</p>' +
|
||||
'</div>' +
|
||||
'<div class="service-action-highlights">' + highlightHTML + '</div>' +
|
||||
'</div>' +
|
||||
'<div class="service-action-cta">' +
|
||||
'<button class="btn-secondary service-action-button" type="button" id="' + id + '" data-account-service-action="open-service-panel" data-account-service-panel="' + panelID + '" data-account-service-focus="' + focusID + '" data-shell-target="services">Open</button>' +
|
||||
'</div>' +
|
||||
'</article>'
|
||||
);
|
||||
@@ -151,6 +150,13 @@ function workspaceStatusCopy(workspace: PortalWorkspaceSummary): string {
|
||||
return 'This workspace is still waiting on a completed health check.';
|
||||
}
|
||||
|
||||
function workspaceRowNote(workspace: PortalWorkspaceSummary): string {
|
||||
var status = workspaceHealthState(workspace);
|
||||
if (status === 'healthy') return 'Ready for operator work';
|
||||
if (status === 'unhealthy') return 'Review this workspace before treating it as stable';
|
||||
return 'Awaiting a completed health check';
|
||||
}
|
||||
|
||||
function attentionWorkspaces(workspaces: PortalWorkspaceSummary[]): PortalWorkspaceSummary[] {
|
||||
var results: PortalWorkspaceSummary[] = [];
|
||||
for (var i = 0; i < workspaces.length; i += 1) {
|
||||
@@ -248,7 +254,7 @@ function renderShellNavigation(accounts: PortalAccountSummary[], supportEmail: s
|
||||
'<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 class="portal-shell-nav-support">' + (hosted ? 'Hosted operations and account services' : 'Commercial account services and support') + '</div>' +
|
||||
'</div>' +
|
||||
'<div class="portal-shell-nav-group">' +
|
||||
shellSectionButton('overview', activeSection, 'Overview', hosted ? 'Posture, accounts, and quick actions' : 'Account summary and access state') +
|
||||
@@ -257,6 +263,10 @@ function renderShellNavigation(accounts: PortalAccountSummary[], supportEmail: s
|
||||
shellSectionButton('services', activeSection, 'Account services', 'Licenses, billing, refunds, and privacy') +
|
||||
shellSectionButton('support', activeSection, 'Support', supportEmail || 'Support contact') +
|
||||
'</div>' +
|
||||
'<div class="portal-shell-nav-footer">' +
|
||||
'<div class="portal-shell-nav-footer-label">Need help?</div>' +
|
||||
'<a class="portal-shell-nav-footer-link" href="mailto:' + escapeAttr(supportEmail || 'support@pulserelay.pro') + '">' + escapeHTML(supportEmail || 'support@pulserelay.pro') + '</a>' +
|
||||
'</div>' +
|
||||
'</aside>'
|
||||
);
|
||||
}
|
||||
@@ -299,24 +309,18 @@ function renderWorkspaceCard(account: PortalAccountSummary, workspace: PortalWor
|
||||
|
||||
return (
|
||||
'<article class="workspace-row">' +
|
||||
'<div class="workspace-row-main">' +
|
||||
'<div class="workspace-row-title">' +
|
||||
'<div class="workspace-row-heading">' +
|
||||
'<h4 class="workspace-name">' + escapeHTML(workspace.display_name) + '</h4>' +
|
||||
'<div class="workspace-meta">' + metaParts.join('') + '</div>' +
|
||||
'</div>' +
|
||||
'<p class="workspace-summary">' + escapeHTML(workspaceStatusCopy(workspace)) + '</p>' +
|
||||
'</div>' +
|
||||
'<div class="workspace-row-status-grid">' +
|
||||
'<div class="workspace-row-status-cell">' +
|
||||
'<span class="workspace-row-status-label">Health</span>' +
|
||||
healthBadgeHTML(workspace) +
|
||||
'</div>' +
|
||||
'<div class="workspace-row-status-cell">' +
|
||||
'<span class="workspace-row-status-label">Lifecycle</span>' +
|
||||
'<span class="badge badge-' + escapeHTML(state || 'unknown') + '">' + escapeHTML(titleCase(state || 'Unknown')) + '</span>' +
|
||||
'</div>' +
|
||||
'<div class="workspace-row-primary">' +
|
||||
'<div class="workspace-row-heading">' +
|
||||
'<h4 class="workspace-name">' + escapeHTML(workspace.display_name) + '</h4>' +
|
||||
'<div class="workspace-meta">' + metaParts.join('') + '</div>' +
|
||||
'</div>' +
|
||||
'<div class="workspace-row-note">' + escapeHTML(workspaceRowNote(workspace)) + '</div>' +
|
||||
'</div>' +
|
||||
'<div class="workspace-row-status-cell workspace-row-status-cell-badge">' +
|
||||
healthBadgeHTML(workspace) +
|
||||
'</div>' +
|
||||
'<div class="workspace-row-status-cell workspace-row-status-cell-badge">' +
|
||||
'<span class="badge badge-' + escapeHTML(state || 'unknown') + '">' + escapeHTML(titleCase(state || 'Unknown')) + '</span>' +
|
||||
'</div>' +
|
||||
'<div class="workspace-actions">' +
|
||||
openAction +
|
||||
@@ -406,30 +410,32 @@ function renderAccountOverviewSection(account: PortalAccountSummary): string {
|
||||
'<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(postureTitle) + '</h3>' +
|
||||
'<p>' + escapeHTML(postureCopy) + '</p>' +
|
||||
'<div class="account-overview-callout">' + escapeHTML(account.kind === 'msp'
|
||||
? 'Use this console to run client workspaces, account billing, and operator access from one place.'
|
||||
: 'Use this console to run hosted workspaces, account billing, and operator access from one place.'
|
||||
) + '</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 class="account-overview-lead">' +
|
||||
'<div class="account-panel-kicker">Hosted posture</div>' +
|
||||
'<h3>' + escapeHTML(postureTitle) + '</h3>' +
|
||||
'<p>' + escapeHTML(postureCopy) + '</p>' +
|
||||
'<div class="account-overview-callout">' + escapeHTML(account.kind === 'msp'
|
||||
? 'Use this console to run client workspaces, account billing, and operator access from one place.'
|
||||
: 'Use this console to run hosted workspaces, account billing, and operator access from one place.'
|
||||
) + '</div>' +
|
||||
'</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 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>' +
|
||||
'</div>' +
|
||||
actions +
|
||||
@@ -713,10 +719,9 @@ export function renderAuthenticatedPortalHTML(context: ShellViewContext): string
|
||||
'</div>' +
|
||||
'<div class="service-shell">' +
|
||||
'<aside class="service-shell-sidebar">' +
|
||||
'<div class="service-brief-card service-brief-card-primary">' +
|
||||
'<div class="account-panel-kicker">Scope</div>' +
|
||||
'<h3>Commercial account actions</h3>' +
|
||||
'<p>Use this area for self-hosted billing, license recovery, refunds, and privacy requests. Hosted workspace operations stay in the Workspaces and Team sections.</p>' +
|
||||
'<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']) +
|
||||
@@ -724,16 +729,6 @@ export function renderAuthenticatedPortalHTML(context: ShellViewContext): string
|
||||
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>' +
|
||||
'<div class="service-brief-card">' +
|
||||
'<div class="account-panel-kicker">Support</div>' +
|
||||
'<h3>Need help with a commercial request?</h3>' +
|
||||
'<p>Use support if billing, refund, privacy, or license actions do 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>' +
|
||||
'</aside>' +
|
||||
'<div class="service-shell-main">' +
|
||||
'<div class="service-detail-shell">' +
|
||||
@@ -746,6 +741,11 @@ export function renderAuthenticatedPortalHTML(context: ShellViewContext): string
|
||||
'<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>' +
|
||||
@@ -762,6 +762,15 @@ export function renderAuthenticatedPortalHTML(context: ShellViewContext): string
|
||||
'</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>' +
|
||||
|
||||
@@ -162,18 +162,18 @@ header .logout-btn:hover,
|
||||
|
||||
.portal-hero {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1.8fr) minmax(360px, 0.92fr);
|
||||
gap: 16px;
|
||||
padding: 18px 20px;
|
||||
grid-template-columns: minmax(0, 1fr) auto;
|
||||
gap: 18px;
|
||||
padding: 12px 16px;
|
||||
border: 1px solid var(--line);
|
||||
border-radius: var(--radius-lg);
|
||||
background: #fbfcfd;
|
||||
box-shadow: var(--shadow-sm);
|
||||
background: #fff;
|
||||
box-shadow: 0 10px 24px rgba(10, 25, 41, 0.035);
|
||||
}
|
||||
|
||||
.portal-hero-compact {
|
||||
align-items: center;
|
||||
padding: 14px 18px;
|
||||
padding: 12px 16px;
|
||||
}
|
||||
|
||||
.portal-hero-copy {
|
||||
@@ -241,21 +241,22 @@ header .logout-btn:hover,
|
||||
}
|
||||
|
||||
.portal-hero-stats {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||
gap: 10px;
|
||||
align-self: stretch;
|
||||
display: flex;
|
||||
align-items: stretch;
|
||||
gap: 8px;
|
||||
align-self: center;
|
||||
}
|
||||
|
||||
.portal-hero-stat {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
min-height: 72px;
|
||||
padding: 11px 13px;
|
||||
justify-content: center;
|
||||
min-width: 132px;
|
||||
min-height: 0;
|
||||
padding: 10px 12px;
|
||||
border: 1px solid var(--line);
|
||||
border-radius: var(--radius-md);
|
||||
background: #fcfdff;
|
||||
border-radius: 12px;
|
||||
background: #fbfcfd;
|
||||
}
|
||||
|
||||
.portal-hero-stat-label {
|
||||
@@ -267,9 +268,9 @@ header .logout-btn:hover,
|
||||
}
|
||||
|
||||
.portal-hero-stat-value {
|
||||
font-size: 18px;
|
||||
font-size: 16px;
|
||||
font-weight: 800;
|
||||
line-height: 1.2;
|
||||
line-height: 1.25;
|
||||
letter-spacing: -0.04em;
|
||||
}
|
||||
|
||||
@@ -285,13 +286,13 @@ header .logout-btn:hover,
|
||||
|
||||
.portal-shell-layout {
|
||||
display: grid;
|
||||
grid-template-columns: 240px minmax(0, 1fr);
|
||||
grid-template-columns: 224px minmax(0, 1fr);
|
||||
gap: 0;
|
||||
align-items: start;
|
||||
border: 1px solid var(--line);
|
||||
border-radius: var(--radius-lg);
|
||||
background: #f7fafc;
|
||||
box-shadow: var(--shadow-sm);
|
||||
background: #fff;
|
||||
box-shadow: 0 16px 40px rgba(10, 25, 41, 0.05);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
@@ -309,8 +310,8 @@ header .logout-btn:hover,
|
||||
.portal-shell-nav-header {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
padding: 20px 20px 18px;
|
||||
gap: 7px;
|
||||
padding: 18px 18px 16px;
|
||||
border-bottom: 1px solid var(--nav-line);
|
||||
}
|
||||
|
||||
@@ -323,7 +324,7 @@ header .logout-btn:hover,
|
||||
}
|
||||
|
||||
.portal-shell-nav-title {
|
||||
font-size: 20px;
|
||||
font-size: 19px;
|
||||
line-height: 1.02;
|
||||
letter-spacing: -0.03em;
|
||||
font-weight: 800;
|
||||
@@ -339,7 +340,7 @@ header .logout-btn:hover,
|
||||
.portal-shell-nav-group {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 8px 0;
|
||||
padding: 10px 0;
|
||||
}
|
||||
|
||||
.portal-shell-nav-link {
|
||||
@@ -347,7 +348,7 @@ header .logout-btn:hover,
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
width: 100%;
|
||||
padding: 15px 20px;
|
||||
padding: 14px 18px;
|
||||
border: none;
|
||||
background: transparent;
|
||||
text-align: left;
|
||||
@@ -368,6 +369,34 @@ header .logout-btn:hover,
|
||||
box-shadow: inset 3px 0 0 #78b6c0;
|
||||
}
|
||||
|
||||
.portal-shell-nav-footer {
|
||||
margin-top: auto;
|
||||
padding: 16px 18px 18px;
|
||||
border-top: 1px solid var(--nav-line);
|
||||
background: rgba(255, 255, 255, 0.03);
|
||||
}
|
||||
|
||||
.portal-shell-nav-footer-label {
|
||||
font-size: 11px;
|
||||
font-weight: 800;
|
||||
letter-spacing: 0.12em;
|
||||
text-transform: uppercase;
|
||||
color: #78b6c0;
|
||||
}
|
||||
|
||||
.portal-shell-nav-footer-link {
|
||||
display: inline-block;
|
||||
margin-top: 8px;
|
||||
font-size: 12px;
|
||||
line-height: 1.5;
|
||||
color: #d9f4fb;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.portal-shell-nav-footer-link:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.portal-shell-nav-label {
|
||||
display: block;
|
||||
font-size: 14px;
|
||||
@@ -385,8 +414,8 @@ header .logout-btn:hover,
|
||||
.portal-shell-main {
|
||||
min-width: 0;
|
||||
min-height: 760px;
|
||||
padding: 24px 26px 30px;
|
||||
background: #f7fafc;
|
||||
padding: 20px 22px 26px;
|
||||
background: #fbfcfd;
|
||||
}
|
||||
|
||||
.portal-shell-main > .portal-content-panel {
|
||||
@@ -510,6 +539,19 @@ header .logout-btn:hover,
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.workspace-actions {
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.workspace-actions form {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.workspace-actions .btn-primary,
|
||||
.workspace-actions .btn-secondary {
|
||||
min-width: 118px;
|
||||
}
|
||||
|
||||
.account-surface-body {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr;
|
||||
@@ -586,11 +628,24 @@ header .logout-btn:hover,
|
||||
|
||||
.account-command-deck {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(320px, 1.08fr) minmax(0, 1.3fr);
|
||||
grid-template-columns: 1fr;
|
||||
gap: 14px;
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
.account-overview-card {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1.1fr) minmax(360px, 0.9fr);
|
||||
gap: 14px;
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
.account-overview-lead {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.account-metric-strip {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, minmax(0, 1fr));
|
||||
@@ -872,7 +927,7 @@ header .logout-btn:hover,
|
||||
|
||||
.workspace-list-head {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1.55fr) 120px 120px 180px;
|
||||
grid-template-columns: minmax(0, 1.8fr) 132px 132px 176px;
|
||||
gap: 14px;
|
||||
padding: 2px 16px 6px;
|
||||
font-size: 11px;
|
||||
@@ -885,35 +940,38 @@ header .logout-btn:hover,
|
||||
.workspace-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
gap: 0;
|
||||
border: 1px solid var(--line);
|
||||
border-radius: 12px;
|
||||
background: #fff;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.workspace-row {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1fr) auto;
|
||||
grid-template-columns: minmax(0, 1.8fr) 132px 132px 176px;
|
||||
gap: 14px;
|
||||
align-items: center;
|
||||
padding: 16px;
|
||||
background: var(--panel-muted);
|
||||
padding: 14px 16px;
|
||||
background: #fff;
|
||||
border-top: 1px solid var(--line);
|
||||
}
|
||||
|
||||
.workspace-row-main {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1.55fr) minmax(220px, 0.8fr);
|
||||
gap: 14px;
|
||||
align-items: center;
|
||||
.workspace-row:first-child {
|
||||
border-top: none;
|
||||
}
|
||||
|
||||
.workspace-row-title {
|
||||
.workspace-row-primary {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
gap: 8px;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.workspace-row-heading {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.workspace-name,
|
||||
@@ -934,45 +992,31 @@ header .logout-btn:hover,
|
||||
.workspace-meta-item {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
min-height: 28px;
|
||||
padding: 0 10px;
|
||||
min-height: 24px;
|
||||
padding: 0 9px;
|
||||
border-radius: 999px;
|
||||
background: var(--panel-muted);
|
||||
font-size: 12px;
|
||||
background: #f1f5f8;
|
||||
font-size: 11px;
|
||||
color: var(--ink-soft);
|
||||
}
|
||||
|
||||
.workspace-summary,
|
||||
.workspace-row-note,
|
||||
.workspace-management-summary {
|
||||
margin: 0;
|
||||
font-size: 14px;
|
||||
line-height: 1.65;
|
||||
font-size: 12px;
|
||||
line-height: 1.5;
|
||||
color: var(--ink-soft);
|
||||
}
|
||||
|
||||
.workspace-row-status-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.workspace-row-status-cell {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
min-height: 68px;
|
||||
padding: 12px;
|
||||
border: 1px solid var(--line);
|
||||
border-radius: var(--radius-md);
|
||||
background: #fff;
|
||||
align-items: center;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.workspace-row-status-label {
|
||||
font-size: 11px;
|
||||
font-weight: 800;
|
||||
letter-spacing: 0.12em;
|
||||
text-transform: uppercase;
|
||||
color: var(--ink-muted);
|
||||
.workspace-row-status-cell-badge .badge {
|
||||
width: 100%;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.workspace-state-label {
|
||||
@@ -1108,9 +1152,16 @@ header .logout-btn:hover,
|
||||
margin: 16px 0;
|
||||
}
|
||||
|
||||
.team-stat-card {
|
||||
padding: 12px 14px;
|
||||
border: 1px solid var(--line);
|
||||
border-radius: 10px;
|
||||
background: #fbfcfd;
|
||||
}
|
||||
|
||||
.team-stat-value {
|
||||
margin-top: 6px;
|
||||
font-size: 22px;
|
||||
margin-top: 8px;
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
.team-management-grid {
|
||||
@@ -1129,17 +1180,25 @@ header .logout-btn:hover,
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.team-panel-heading p {
|
||||
max-width: 720px;
|
||||
}
|
||||
|
||||
.team-roster-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
gap: 0;
|
||||
border: 1px solid var(--line);
|
||||
border-radius: 10px;
|
||||
background: #fff;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.team-roster-head {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1fr) auto;
|
||||
gap: 16px;
|
||||
padding: 0 8px 2px;
|
||||
padding: 0 6px 8px;
|
||||
font-size: 11px;
|
||||
font-weight: 800;
|
||||
letter-spacing: 0.12em;
|
||||
@@ -1150,38 +1209,42 @@ header .logout-btn:hover,
|
||||
.team-member-row {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1fr) auto;
|
||||
align-items: start;
|
||||
gap: 16px;
|
||||
padding: 14px 16px;
|
||||
border: 1px solid var(--line);
|
||||
border-radius: 10px;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
padding: 12px 14px;
|
||||
border: none;
|
||||
border-top: 1px solid var(--line);
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.team-member-row:first-of-type {
|
||||
border-top: none;
|
||||
}
|
||||
|
||||
.team-member-identity {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
gap: 6px;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.team-member-topline {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 12px;
|
||||
justify-content: flex-start;
|
||||
gap: 10px;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.team-member-email {
|
||||
font-size: 14px;
|
||||
font-size: 13px;
|
||||
font-weight: 700;
|
||||
color: var(--ink);
|
||||
}
|
||||
|
||||
.team-member-caption {
|
||||
font-size: 13px;
|
||||
line-height: 1.55;
|
||||
font-size: 12px;
|
||||
line-height: 1.45;
|
||||
color: var(--ink-soft);
|
||||
}
|
||||
|
||||
@@ -1189,7 +1252,7 @@ header .logout-btn:hover,
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: end;
|
||||
gap: 10px;
|
||||
gap: 8px;
|
||||
flex-wrap: wrap;
|
||||
min-width: 0;
|
||||
}
|
||||
@@ -1209,12 +1272,12 @@ header .logout-btn:hover,
|
||||
.team-inline-role-badge {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
min-height: 28px;
|
||||
padding: 0 10px;
|
||||
min-height: 24px;
|
||||
padding: 0 8px;
|
||||
border-radius: 999px;
|
||||
background: var(--panel-muted);
|
||||
color: var(--ink-soft);
|
||||
font-size: 11px;
|
||||
font-size: 10px;
|
||||
font-weight: 800;
|
||||
letter-spacing: 0.08em;
|
||||
text-transform: uppercase;
|
||||
@@ -1222,7 +1285,7 @@ header .logout-btn:hover,
|
||||
}
|
||||
|
||||
.team-role-select {
|
||||
min-width: 140px;
|
||||
min-width: 124px;
|
||||
}
|
||||
|
||||
.team-list-message {
|
||||
@@ -1261,8 +1324,8 @@ header .logout-btn:hover,
|
||||
.team-invite-guide {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
padding: 12px;
|
||||
gap: 6px;
|
||||
padding: 10px 12px;
|
||||
border: 1px solid var(--line);
|
||||
border-radius: 10px;
|
||||
background: #fff;
|
||||
@@ -1282,20 +1345,24 @@ header .logout-btn:hover,
|
||||
}
|
||||
|
||||
.team-invite-guide-row span {
|
||||
font-size: 13px;
|
||||
line-height: 1.5;
|
||||
font-size: 12px;
|
||||
line-height: 1.4;
|
||||
color: var(--ink-soft);
|
||||
}
|
||||
|
||||
.service-action-list {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr;
|
||||
gap: 12px;
|
||||
gap: 0;
|
||||
border: 1px solid var(--line);
|
||||
border-radius: 12px;
|
||||
background: #fff;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.service-shell {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(320px, 0.72fr) minmax(0, 1.28fr);
|
||||
grid-template-columns: minmax(280px, 0.82fr) minmax(0, 1.18fr);
|
||||
gap: 16px;
|
||||
align-items: start;
|
||||
}
|
||||
@@ -1307,6 +1374,22 @@ header .logout-btn:hover,
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.service-list-intro,
|
||||
.service-inline-support {
|
||||
padding: 14px 16px;
|
||||
border: 1px solid var(--line);
|
||||
border-radius: 12px;
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.service-list-intro p,
|
||||
.service-inline-support p {
|
||||
margin: 8px 0 0;
|
||||
font-size: 13px;
|
||||
line-height: 1.55;
|
||||
color: var(--ink-soft);
|
||||
}
|
||||
|
||||
.service-detail-shell {
|
||||
min-height: 100%;
|
||||
border: 1px solid var(--line);
|
||||
@@ -1317,7 +1400,7 @@ header .logout-btn:hover,
|
||||
}
|
||||
|
||||
.service-brief-card {
|
||||
padding: 18px;
|
||||
padding: 16px;
|
||||
border: 1px solid var(--line);
|
||||
border-radius: var(--radius-md);
|
||||
background: var(--panel-muted);
|
||||
@@ -1329,7 +1412,7 @@ header .logout-btn:hover,
|
||||
|
||||
.service-brief-card h3 {
|
||||
margin: 8px 0 0;
|
||||
font-size: 19px;
|
||||
font-size: 18px;
|
||||
line-height: 1.08;
|
||||
letter-spacing: -0.03em;
|
||||
}
|
||||
@@ -1342,38 +1425,50 @@ header .logout-btn:hover,
|
||||
}
|
||||
|
||||
.service-action-row {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1fr) auto;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
padding: 16px 18px;
|
||||
border: 1px solid var(--line);
|
||||
border-radius: var(--radius-md);
|
||||
padding: 14px 16px;
|
||||
border: none;
|
||||
border-top: 1px solid var(--line);
|
||||
background: #fff;
|
||||
transition: border-color 140ms ease, box-shadow 140ms ease, transform 140ms ease;
|
||||
}
|
||||
|
||||
.service-action-row:first-child {
|
||||
border-top: none;
|
||||
}
|
||||
|
||||
.service-action-row:hover,
|
||||
.service-action-row.active {
|
||||
border-color: rgba(15, 109, 115, 0.28);
|
||||
box-shadow: 0 10px 24px rgba(10, 25, 41, 0.08);
|
||||
}
|
||||
|
||||
.service-action-main {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.service-action-tags {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 10px;
|
||||
justify-content: flex-start;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.service-action-tag {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
min-height: 26px;
|
||||
padding: 0 10px;
|
||||
min-height: 24px;
|
||||
padding: 0 9px;
|
||||
border-radius: 999px;
|
||||
background: #fff;
|
||||
color: var(--ink-muted);
|
||||
font-size: 11px;
|
||||
font-size: 10px;
|
||||
font-weight: 800;
|
||||
letter-spacing: 0.08em;
|
||||
text-transform: uppercase;
|
||||
@@ -1382,20 +1477,20 @@ header .logout-btn:hover,
|
||||
.service-action-copy {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.service-action-copy h3 {
|
||||
margin: 0;
|
||||
font-size: 17px;
|
||||
font-size: 16px;
|
||||
line-height: 1.16;
|
||||
letter-spacing: -0.03em;
|
||||
}
|
||||
|
||||
.service-action-copy p {
|
||||
margin: 0;
|
||||
font-size: 14px;
|
||||
line-height: 1.6;
|
||||
font-size: 13px;
|
||||
line-height: 1.5;
|
||||
color: var(--ink-soft);
|
||||
max-width: 760px;
|
||||
}
|
||||
@@ -1403,49 +1498,32 @@ header .logout-btn:hover,
|
||||
.service-action-highlights {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.service-action-highlight {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
min-height: 28px;
|
||||
padding: 0 10px;
|
||||
min-height: 24px;
|
||||
padding: 0 9px;
|
||||
border-radius: 999px;
|
||||
background: #fff;
|
||||
color: var(--ink-soft);
|
||||
font-size: 12px;
|
||||
font-size: 10px;
|
||||
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;
|
||||
}
|
||||
|
||||
.service-action-button {
|
||||
min-width: 92px;
|
||||
min-width: 86px;
|
||||
}
|
||||
|
||||
.service-panel {
|
||||
padding: 22px;
|
||||
padding: 18px;
|
||||
border-radius: var(--radius-md);
|
||||
background: #fff;
|
||||
min-height: 100%;
|
||||
@@ -1466,15 +1544,24 @@ header .logout-btn:hover,
|
||||
.service-empty-points {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||
gap: 12px;
|
||||
gap: 10px;
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
.service-empty-support {
|
||||
margin-top: 16px;
|
||||
padding-top: 14px;
|
||||
border-top: 1px solid var(--line);
|
||||
font-size: 13px;
|
||||
line-height: 1.55;
|
||||
color: var(--ink-soft);
|
||||
}
|
||||
|
||||
.service-empty-point {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
padding: 14px;
|
||||
padding: 12px;
|
||||
border: 1px solid var(--line);
|
||||
border-radius: 12px;
|
||||
background: var(--panel-muted);
|
||||
@@ -1488,8 +1575,8 @@ header .logout-btn:hover,
|
||||
}
|
||||
|
||||
.service-empty-point span {
|
||||
font-size: 13px;
|
||||
line-height: 1.55;
|
||||
font-size: 12px;
|
||||
line-height: 1.45;
|
||||
color: var(--ink-soft);
|
||||
}
|
||||
|
||||
@@ -1678,6 +1765,7 @@ header .logout-btn:hover,
|
||||
.account-management-grid,
|
||||
.team-management-grid,
|
||||
.account-overview-secondary,
|
||||
.account-overview-card,
|
||||
.service-shell,
|
||||
.service-action-list,
|
||||
.service-empty-points,
|
||||
@@ -1718,16 +1806,16 @@ header .logout-btn:hover,
|
||||
}
|
||||
|
||||
.portal-hero-stats {
|
||||
grid-template-columns: 1fr;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.workspace-row,
|
||||
.workspace-row-main {
|
||||
.workspace-row {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.workspace-list-toolbar {
|
||||
grid-template-columns: 1fr;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1759,7 +1847,6 @@ header .logout-btn:hover,
|
||||
.workspace-management-header,
|
||||
.team-management-header,
|
||||
.service-header,
|
||||
.service-action-footer,
|
||||
.service-action-tags {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
@@ -1778,17 +1865,28 @@ header .logout-btn:hover,
|
||||
}
|
||||
|
||||
.team-member-row,
|
||||
.team-roster-head,
|
||||
.team-member-controls {
|
||||
.team-roster-head {
|
||||
grid-template-columns: 1fr;
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
.team-member-controls {
|
||||
justify-content: start;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.workspace-row-status-grid {
|
||||
.service-action-row,
|
||||
.service-shell,
|
||||
.workspace-operations-shell,
|
||||
.account-overview-card,
|
||||
.account-overview-secondary,
|
||||
.overview-quick-actions,
|
||||
.service-empty-points {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.service-action-cta,
|
||||
.service-action-button {
|
||||
width: 100%;
|
||||
justify-content: start;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user