feat(statuspage-ui): improve styling and animations across status page components

This commit is contained in:
2025-12-23 19:16:17 +00:00
parent 213323073f
commit 183a1d0658
12 changed files with 1462 additions and 467 deletions

View File

@@ -108,36 +108,79 @@ export class UplStatuspageIncidents extends DeesElement {
background: ${sharedStyles.colors.background.card};
padding: ${unsafeCSS(sharedStyles.spacing.xl)};
margin-bottom: ${unsafeCSS(sharedStyles.spacing.lg)};
border-radius: ${unsafeCSS(sharedStyles.borderRadius.md)};
border-radius: ${unsafeCSS(sharedStyles.borderRadius.lg)};
border: 1px solid ${sharedStyles.colors.border.default};
text-align: center;
color: ${sharedStyles.colors.text.secondary};
box-shadow: ${unsafeCSS(sharedStyles.shadows.sm)};
animation: fadeInUp 0.4s ${unsafeCSS(sharedStyles.easings.default)} both;
}
/* Staggered entrance animations */
.incident-card {
background: ${sharedStyles.colors.background.card};
border-radius: ${unsafeCSS(sharedStyles.borderRadius.md)};
border-radius: ${unsafeCSS(sharedStyles.borderRadius.lg)};
margin-bottom: ${unsafeCSS(sharedStyles.spacing.lg)};
overflow: hidden;
box-shadow: ${unsafeCSS(sharedStyles.shadows.sm)};
border: 1px solid ${sharedStyles.colors.border.default};
transition: all 0.2s ease;
transition: all ${unsafeCSS(sharedStyles.durations.normal)} ${unsafeCSS(sharedStyles.easings.default)};
animation: fadeInUp 0.4s ${unsafeCSS(sharedStyles.easings.default)} both;
}
.incident-card:nth-child(1) { animation-delay: 0ms; }
.incident-card:nth-child(2) { animation-delay: 50ms; }
.incident-card:nth-child(3) { animation-delay: 100ms; }
.incident-card:nth-child(4) { animation-delay: 150ms; }
.incident-card:nth-child(5) { animation-delay: 200ms; }
.incident-card:hover {
transform: translateY(-2px);
box-shadow: ${unsafeCSS(sharedStyles.shadows.md)};
border-color: ${sharedStyles.colors.border.muted};
}
.incident-card.expanded {
box-shadow: ${unsafeCSS(sharedStyles.shadows.md)};
box-shadow: ${unsafeCSS(sharedStyles.shadows.lg)};
}
/* Active incident pulse effect */
.incident-card.active-incident {
animation: fadeInUp 0.4s ${unsafeCSS(sharedStyles.easings.default)} both,
incident-pulse 3s ease-in-out infinite;
}
@keyframes fadeInUp {
from {
opacity: 0;
transform: translateY(16px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
@keyframes incident-pulse {
0%, 100% {
box-shadow: ${unsafeCSS(sharedStyles.shadows.sm)};
}
50% {
box-shadow: ${unsafeCSS(sharedStyles.shadows.md)},
0 0 0 2px ${cssManager.bdTheme('rgba(239, 68, 68, 0.15)', 'rgba(248, 113, 113, 0.2)')};
}
}
.incident-header {
padding: ${unsafeCSS(sharedStyles.spacing.lg)} ${unsafeCSS(sharedStyles.spacing.lg)};
padding: ${unsafeCSS(sharedStyles.spacing.lg)} ${unsafeCSS(sharedStyles.spacing.xl)};
border-left: 4px solid;
display: flex;
align-items: start;
justify-content: space-between;
gap: ${unsafeCSS(sharedStyles.spacing.md)};
cursor: pointer;
transition: background-color 0.2s ease;
transition: background-color ${unsafeCSS(sharedStyles.durations.fast)} ${unsafeCSS(sharedStyles.easings.default)};
position: relative;
}
.incident-header:hover {
@@ -161,10 +204,11 @@ export class UplStatuspageIncidents extends DeesElement {
}
.incident-title {
font-size: 18px;
font-size: 17px;
font-weight: 600;
margin: 0;
line-height: 1.3;
line-height: 1.4;
letter-spacing: -0.01em;
}
.incident-meta {
@@ -176,17 +220,24 @@ export class UplStatuspageIncidents extends DeesElement {
flex-wrap: wrap;
}
.incident-meta span {
display: flex;
align-items: center;
gap: 4px;
}
.incident-status {
display: inline-flex;
align-items: center;
gap: ${unsafeCSS(sharedStyles.spacing.xs)};
padding: ${unsafeCSS(sharedStyles.spacing.xs)} ${unsafeCSS(sharedStyles.spacing.md)};
gap: 6px;
padding: 6px 12px;
border-radius: ${unsafeCSS(sharedStyles.borderRadius.full)};
font-size: 12px;
font-size: 11px;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.02em;
letter-spacing: 0.04em;
flex-shrink: 0;
transition: all ${unsafeCSS(sharedStyles.durations.fast)} ${unsafeCSS(sharedStyles.easings.default)};
}
.incident-status.investigating {
@@ -214,68 +265,164 @@ export class UplStatuspageIncidents extends DeesElement {
color: ${cssManager.bdTheme('#4b5563', '#d1d5db')};
}
/* Pulse for investigating status */
.incident-status.investigating .status-dot {
animation: status-pulse 1.5s ease-in-out infinite;
}
@keyframes status-pulse {
0%, 100% { opacity: 1; transform: scale(1); }
50% { opacity: 0.6; transform: scale(1.2); }
}
.incident-body {
padding: 0 ${unsafeCSS(sharedStyles.spacing.lg)} ${unsafeCSS(sharedStyles.spacing.lg)} ${unsafeCSS(sharedStyles.spacing.lg)};
padding: 0 ${unsafeCSS(sharedStyles.spacing.xl)} ${unsafeCSS(sharedStyles.spacing.xl)} ${unsafeCSS(sharedStyles.spacing.xl)};
animation: slideDown 0.3s ${unsafeCSS(sharedStyles.easings.default)};
}
@keyframes slideDown {
from {
opacity: 0;
transform: translateY(-8px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.incident-impact {
margin: ${unsafeCSS(sharedStyles.spacing.md)} 0;
padding: ${unsafeCSS(sharedStyles.spacing.md)};
padding: ${unsafeCSS(sharedStyles.spacing.md)} ${unsafeCSS(sharedStyles.spacing.lg)};
background: ${sharedStyles.colors.background.secondary};
border-radius: ${unsafeCSS(sharedStyles.borderRadius.base)};
font-size: 14px;
line-height: 1.6;
border-left: 3px solid ${sharedStyles.colors.border.muted};
}
.affected-services {
margin-top: ${unsafeCSS(sharedStyles.spacing.md)};
margin-top: ${unsafeCSS(sharedStyles.spacing.lg)};
}
.affected-services-title {
font-size: 13px;
font-size: 12px;
font-weight: 600;
margin-bottom: ${unsafeCSS(sharedStyles.spacing.sm)};
color: ${sharedStyles.colors.text.primary};
color: ${sharedStyles.colors.text.secondary};
text-transform: uppercase;
letter-spacing: 0.04em;
}
.service-tag {
display: inline-block;
padding: ${unsafeCSS(sharedStyles.spacing.xs)} ${unsafeCSS(sharedStyles.spacing.sm)};
padding: 4px 10px;
margin: 2px;
background: ${sharedStyles.colors.background.muted};
border-radius: ${unsafeCSS(sharedStyles.borderRadius.sm)};
font-size: 12px;
color: ${sharedStyles.colors.text.secondary};
transition: all ${unsafeCSS(sharedStyles.durations.fast)} ${unsafeCSS(sharedStyles.easings.default)};
}
.service-tag:hover {
background: ${sharedStyles.colors.background.secondary};
color: ${sharedStyles.colors.text.primary};
}
/* Timeline visualization for updates */
.incident-updates {
margin-top: ${unsafeCSS(sharedStyles.spacing.lg)};
margin-top: ${unsafeCSS(sharedStyles.spacing.xl)};
border-top: 1px solid ${sharedStyles.colors.border.default};
padding-top: ${unsafeCSS(sharedStyles.spacing.lg)};
}
.updates-title {
font-size: 12px;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.04em;
margin: 0 0 ${unsafeCSS(sharedStyles.spacing.lg)} 0;
color: ${sharedStyles.colors.text.secondary};
}
.timeline {
position: relative;
padding-left: 24px;
}
/* Vertical connector line */
.timeline::before {
content: '';
position: absolute;
left: 5px;
top: 8px;
bottom: 8px;
width: 2px;
background: ${cssManager.bdTheme(
'linear-gradient(to bottom, #e5e7eb 0%, #d1d5db 50%, #e5e7eb 100%)',
'linear-gradient(to bottom, #27272a 0%, #3f3f46 50%, #27272a 100%)'
)};
border-radius: 1px;
}
.update-item {
position: relative;
padding-left: ${unsafeCSS(sharedStyles.spacing.lg)};
margin-bottom: ${unsafeCSS(sharedStyles.spacing.md)};
padding-bottom: ${unsafeCSS(sharedStyles.spacing.lg)};
animation: fadeInUp 0.3s ${unsafeCSS(sharedStyles.easings.default)} both;
}
.update-item:last-child {
padding-bottom: 0;
}
/* Timeline dot */
.update-item::before {
content: '';
position: absolute;
left: 0;
top: 6px;
width: 8px;
height: 8px;
border-radius: ${unsafeCSS(sharedStyles.borderRadius.full)};
background: ${sharedStyles.colors.border.muted};
left: -22px;
top: 4px;
width: 12px;
height: 12px;
border-radius: 50%;
background: ${sharedStyles.colors.background.card};
border: 2px solid ${sharedStyles.colors.border.muted};
z-index: 1;
transition: all ${unsafeCSS(sharedStyles.durations.fast)} ${unsafeCSS(sharedStyles.easings.default)};
}
.update-item:first-child::before {
border-color: ${sharedStyles.colors.status.operational};
background: ${sharedStyles.colors.status.operational};
box-shadow: 0 0 0 3px ${cssManager.bdTheme('rgba(22, 163, 74, 0.15)', 'rgba(34, 197, 94, 0.2)')};
}
.update-item:hover::before {
transform: scale(1.2);
border-color: ${sharedStyles.colors.text.secondary};
}
.update-time {
font-size: 12px;
color: ${sharedStyles.colors.text.secondary};
margin-bottom: ${unsafeCSS(sharedStyles.spacing.xs)};
font-size: 11px;
color: ${sharedStyles.colors.text.muted};
margin-bottom: 4px;
font-family: ${unsafeCSS(sharedStyles.fonts.mono)};
display: flex;
align-items: center;
gap: 8px;
}
.update-status-badge {
display: inline-flex;
padding: 2px 6px;
border-radius: 4px;
font-size: 10px;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.02em;
background: ${sharedStyles.colors.background.muted};
color: ${sharedStyles.colors.text.secondary};
}
.update-message {
@@ -286,9 +433,11 @@ export class UplStatuspageIncidents extends DeesElement {
.update-author {
font-size: 12px;
color: ${sharedStyles.colors.text.secondary};
margin-top: ${unsafeCSS(sharedStyles.spacing.xs)};
font-style: italic;
color: ${sharedStyles.colors.text.muted};
margin-top: 4px;
display: flex;
align-items: center;
gap: 4px;
}
.loading-skeleton {
@@ -298,33 +447,34 @@ export class UplStatuspageIncidents extends DeesElement {
'linear-gradient(90deg, #1f1f1f 25%, #262626 50%, #1f1f1f 75%)'
)};
background-size: 200% 100%;
animation: loading 1.5s infinite;
border-radius: ${unsafeCSS(sharedStyles.borderRadius.md)};
animation: shimmer 1.5s infinite;
border-radius: ${unsafeCSS(sharedStyles.borderRadius.lg)};
margin-bottom: ${unsafeCSS(sharedStyles.spacing.lg)};
}
@keyframes loading {
@keyframes shimmer {
0% { background-position: 200% 0; }
100% { background-position: -200% 0; }
}
.show-more {
text-align: center;
margin-top: ${unsafeCSS(sharedStyles.spacing.lg)};
margin-top: ${unsafeCSS(sharedStyles.spacing.xl)};
}
.show-more-button {
display: inline-flex;
align-items: center;
justify-content: center;
padding: ${unsafeCSS(sharedStyles.spacing.sm)} ${unsafeCSS(sharedStyles.spacing.lg)};
gap: 8px;
padding: 10px 20px;
background: transparent;
border: 1px solid ${sharedStyles.colors.border.default};
border-radius: ${unsafeCSS(sharedStyles.borderRadius.base)};
cursor: pointer;
font-size: 14px;
font-weight: 500;
transition: all 0.2s ease;
transition: all ${unsafeCSS(sharedStyles.durations.fast)} ${unsafeCSS(sharedStyles.easings.default)};
color: ${sharedStyles.colors.text.primary};
font-family: ${unsafeCSS(sharedStyles.fonts.base)};
}
@@ -332,7 +482,8 @@ export class UplStatuspageIncidents extends DeesElement {
.show-more-button:hover {
background: ${sharedStyles.colors.background.secondary};
border-color: ${sharedStyles.colors.border.muted};
transform: translateY(-1px);
transform: translateY(-2px);
box-shadow: ${unsafeCSS(sharedStyles.shadows.sm)};
}
.show-more-button:active {
@@ -351,15 +502,15 @@ export class UplStatuspageIncidents extends DeesElement {
.subscribe-button {
display: inline-flex;
align-items: center;
gap: ${unsafeCSS(sharedStyles.spacing.xs)};
padding: ${unsafeCSS(sharedStyles.spacing.xs)} ${unsafeCSS(sharedStyles.spacing.md)};
gap: 6px;
padding: 8px 14px;
background: transparent;
border: 1px solid ${sharedStyles.colors.border.default};
border-radius: ${unsafeCSS(sharedStyles.borderRadius.base)};
cursor: pointer;
font-size: 13px;
font-weight: 400;
transition: all 0.2s ease;
font-weight: 500;
transition: all ${unsafeCSS(sharedStyles.durations.fast)} ${unsafeCSS(sharedStyles.easings.default)};
color: ${sharedStyles.colors.text.primary};
font-family: ${unsafeCSS(sharedStyles.fonts.base)};
}
@@ -367,14 +518,15 @@ export class UplStatuspageIncidents extends DeesElement {
.subscribe-button:hover {
background: ${sharedStyles.colors.background.secondary};
border-color: ${sharedStyles.colors.border.muted};
transform: translateY(-1px);
}
.subscribe-button.subscribed {
background: ${cssManager.bdTheme('#f0fdf4', '#064e3b')};
border-color: ${cssManager.bdTheme('#86efac', '#047857')};
color: ${cssManager.bdTheme('#047857', '#86efac')};
}
.subscribe-button.subscribed:hover {
background: ${cssManager.bdTheme('#dcfce7', '#065f46')};
}
@@ -387,6 +539,15 @@ export class UplStatuspageIncidents extends DeesElement {
opacity: 0.8;
}
/* Expand icon animation */
.expand-icon {
transition: transform ${unsafeCSS(sharedStyles.durations.normal)} ${unsafeCSS(sharedStyles.easings.default)};
}
.expand-icon.rotated {
transform: rotate(180deg);
}
@media (max-width: 640px) {
.container {
padding: 0 ${unsafeCSS(sharedStyles.spacing.md)} ${unsafeCSS(sharedStyles.spacing.md)} ${unsafeCSS(sharedStyles.spacing.md)};
@@ -396,10 +557,28 @@ export class UplStatuspageIncidents extends DeesElement {
padding: ${unsafeCSS(sharedStyles.spacing.md)};
}
.incident-body {
padding: 0 ${unsafeCSS(sharedStyles.spacing.md)} ${unsafeCSS(sharedStyles.spacing.md)} ${unsafeCSS(sharedStyles.spacing.md)};
}
.incident-meta {
flex-direction: column;
gap: ${unsafeCSS(sharedStyles.spacing.xs)};
}
.timeline {
padding-left: 20px;
}
.timeline::before {
left: 4px;
}
.update-item::before {
left: -18px;
width: 10px;
height: 10px;
}
}
`,
];
@@ -438,12 +617,14 @@ export class UplStatuspageIncidents extends DeesElement {
private renderIncident(incident: IIncidentDetails, isCurrent: boolean): TemplateResult {
const latestUpdate = incident.updates[incident.updates.length - 1];
const duration = incident.endTime ?
const duration = incident.endTime ?
this.formatDuration(incident.endTime - incident.startTime) :
this.formatDuration(Date.now() - incident.startTime);
const isActive = isCurrent && latestUpdate?.status !== 'resolved';
return html`
<div class="incident-card ${this.expandedIncidents.has(incident.id) ? 'expanded' : ''}">
<div class="incident-card ${this.expandedIncidents.has(incident.id) ? 'expanded' : ''} ${isActive ? 'active-incident' : ''}">
<div class="incident-header ${incident.severity}" @click=${() => this.toggleIncident(incident.id)}>
<div>
<h3 class="incident-title">${incident.title}</h3>
@@ -520,8 +701,10 @@ export class UplStatuspageIncidents extends DeesElement {
${incident.updates.length > 0 ? html`
<div class="incident-updates">
<h4 style="font-size: 14px; margin: 0 0 12px 0;">Updates</h4>
${incident.updates.slice(-3).reverse().map(update => this.renderUpdate(update))}
<h4 class="updates-title">Updates</h4>
<div class="timeline">
${incident.updates.slice(-3).reverse().map((update, index) => this.renderUpdate(update, index))}
</div>
</div>
` : ''}
@@ -571,25 +754,33 @@ export class UplStatuspageIncidents extends DeesElement {
`;
}
private renderUpdate(update: any): TemplateResult {
private renderUpdate(update: any, index: number = 0): TemplateResult {
return html`
<div class="update-item">
<div class="update-time">${this.formatDate(update.timestamp)}</div>
<div class="update-item" style="animation-delay: ${index * 50}ms">
<div class="update-time">
${this.formatDate(update.timestamp)}
${update.status ? html`<span class="update-status-badge">${update.status}</span>` : ''}
</div>
<div class="update-message">${update.message}</div>
${update.author ? html`
<div class="update-author">${update.author}</div>
<div class="update-author">
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"></path>
<circle cx="12" cy="7" r="4"></circle>
</svg>
${update.author}
</div>
` : ''}
</div>
`;
}
private getStatusIcon(status: string): TemplateResult {
return html`<span style="
return html`<span class="status-dot" style="
display: inline-block;
width: 6px;
height: 6px;
border-radius: 50%;
margin-right: 4px;
background: ${status === 'resolved' ? sharedStyles.colors.status.operational :
status === 'monitoring' ? sharedStyles.colors.status.maintenance :
status === 'identified' ? sharedStyles.colors.status.degraded :