import { DeesElement, property, html, customElement, type TemplateResult, css, cssManager, unsafeCSS, } from '@design.estate/dees-element'; import * as domtools from '@design.estate/dees-domtools'; import * as sharedStyles from '../styles/shared.styles.js'; import './internal/uplinternal-miniheading.js'; import { demoFunc } from './upl-statuspage-statsgrid.demo.js'; declare global { interface HTMLElementTagNameMap { 'upl-statuspage-statsgrid': UplStatuspageStatsgrid; } } @customElement('upl-statuspage-statsgrid') export class UplStatuspageStatsgrid extends DeesElement { public static demo = demoFunc; @property({ type: Number }) accessor uptime: number = 99.99; @property({ type: Number }) accessor avgResponseTime: number = 125; @property({ type: Number }) accessor totalIncidents: number = 0; @property({ type: Number }) accessor affectedServices: number = 0; @property({ type: Number }) accessor totalServices: number = 0; @property({ type: String }) accessor currentStatus: string = 'operational'; @property({ type: Boolean }) accessor loading: boolean = false; @property({ type: String }) accessor timePeriod: string = '90 days'; constructor() { super(); } public static styles = [ domtools.elementBasic.staticStyles, sharedStyles.commonStyles, css` :host { display: block; background: transparent; font-family: ${unsafeCSS(sharedStyles.fonts.base)}; color: ${sharedStyles.colors.text.primary}; } .container { max-width: 1200px; margin: 0 auto; padding: 0 ${unsafeCSS(sharedStyles.spacing.lg)} ${unsafeCSS(sharedStyles.spacing.lg)} ${unsafeCSS(sharedStyles.spacing.lg)}; } .stats-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: ${unsafeCSS(sharedStyles.spacing.md)}; } .stat-card { background: ${sharedStyles.colors.background.card}; border: 1px solid ${sharedStyles.colors.border.default}; border-radius: ${unsafeCSS(sharedStyles.borderRadius.lg)}; padding: ${unsafeCSS(sharedStyles.spacing.lg)}; transition: all ${unsafeCSS(sharedStyles.durations.normal)} ${unsafeCSS(sharedStyles.easings.default)}; position: relative; overflow: hidden; animation: fadeInUp 0.4s ${unsafeCSS(sharedStyles.easings.default)} both; } .stat-card:nth-child(1) { animation-delay: 0ms; } .stat-card:nth-child(2) { animation-delay: 50ms; } .stat-card:nth-child(3) { animation-delay: 100ms; } .stat-card:nth-child(4) { animation-delay: 150ms; } @keyframes fadeInUp { from { opacity: 0; transform: translateY(12px); } to { opacity: 1; transform: translateY(0); } } /* Status-colored top accent */ .stat-card::before { content: ''; position: absolute; top: 0; left: 0; right: 0; height: 3px; background: ${sharedStyles.colors.border.muted}; transition: all ${unsafeCSS(sharedStyles.durations.fast)} ${unsafeCSS(sharedStyles.easings.default)}; } /* Dynamic status-based accent colors for all stat cards */ .stat-card.operational::before { background: ${sharedStyles.colors.status.operational}; } .stat-card.degraded::before { background: ${sharedStyles.colors.status.degraded}; } .stat-card.partial_outage::before { background: ${sharedStyles.colors.status.partial}; } .stat-card.major_outage::before { background: ${sharedStyles.colors.status.major}; } .stat-card.maintenance::before { background: ${sharedStyles.colors.status.maintenance}; } .stat-card:hover { border-color: ${sharedStyles.colors.border.muted}; box-shadow: ${unsafeCSS(sharedStyles.shadows.md)}; transform: translateY(-3px); } .stat-card:hover::before { height: 4px; } .stat-label { font-size: 11px; color: ${sharedStyles.colors.text.muted}; text-transform: uppercase; letter-spacing: 0.06em; font-weight: 600; margin-bottom: ${unsafeCSS(sharedStyles.spacing.sm)}; display: flex; align-items: center; gap: 6px; } .stat-label svg { width: 14px; height: 14px; opacity: 0.7; } .stat-value { font-size: 28px; font-weight: 700; color: ${sharedStyles.colors.text.primary}; font-variant-numeric: tabular-nums; line-height: 1.2; letter-spacing: -0.02em; transition: transform ${unsafeCSS(sharedStyles.durations.fast)} ${unsafeCSS(sharedStyles.easings.default)}; } .stat-card:hover .stat-value { transform: scale(1.02); } .stat-unit { font-size: 16px; font-weight: 500; color: ${sharedStyles.colors.text.secondary}; margin-left: 2px; } .stat-change { font-size: 12px; margin-top: ${unsafeCSS(sharedStyles.spacing.sm)}; display: flex; align-items: center; gap: 4px; padding: 4px 8px; border-radius: ${unsafeCSS(sharedStyles.borderRadius.sm)}; width: fit-content; } .stat-change.positive { color: ${sharedStyles.colors.status.operational}; background: ${cssManager.bdTheme('rgba(22, 163, 74, 0.08)', 'rgba(34, 197, 94, 0.12)')}; } .stat-change.negative { color: ${sharedStyles.colors.status.partial}; background: ${cssManager.bdTheme('rgba(239, 68, 68, 0.08)', 'rgba(248, 113, 113, 0.12)')}; } .stat-change.neutral { color: ${sharedStyles.colors.text.muted}; background: ${sharedStyles.colors.background.muted}; } /* Status text color variations */ .stat-value.operational { color: ${sharedStyles.colors.status.operational}; } .stat-value.degraded { color: ${sharedStyles.colors.status.degraded}; } .stat-value.partial_outage { color: ${sharedStyles.colors.status.partial}; } .stat-value.major_outage { color: ${sharedStyles.colors.status.major}; } .stat-value.maintenance { color: ${sharedStyles.colors.status.maintenance}; } .loading-skeleton { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: ${unsafeCSS(sharedStyles.spacing.md)}; } .skeleton-card { background: ${sharedStyles.colors.background.card}; border: 1px solid ${sharedStyles.colors.border.default}; border-radius: ${unsafeCSS(sharedStyles.borderRadius.lg)}; padding: ${unsafeCSS(sharedStyles.spacing.lg)}; height: 110px; position: relative; overflow: hidden; } .skeleton-card::before { content: ''; position: absolute; top: 0; left: 0; right: 0; height: 3px; background: ${sharedStyles.colors.background.muted}; } .skeleton-label { height: 12px; width: 80px; background: ${sharedStyles.colors.background.muted}; border-radius: 4px; margin-bottom: ${unsafeCSS(sharedStyles.spacing.sm)}; animation: shimmer 1.5s infinite; } .skeleton-value { height: 32px; width: 100px; background: ${sharedStyles.colors.background.muted}; border-radius: 6px; animation: shimmer 1.5s infinite; animation-delay: 0.1s; } .skeleton-change { height: 20px; width: 70px; background: ${sharedStyles.colors.background.muted}; border-radius: 4px; margin-top: ${unsafeCSS(sharedStyles.spacing.sm)}; animation: shimmer 1.5s infinite; animation-delay: 0.2s; } @keyframes shimmer { 0% { opacity: 0.5; } 50% { opacity: 1; } 100% { opacity: 0.5; } } .status-indicator { display: inline-block; width: 8px; height: 8px; border-radius: 50%; flex-shrink: 0; } .status-indicator.operational { background: ${sharedStyles.colors.status.operational}; box-shadow: 0 0 0 2px ${cssManager.bdTheme('rgba(22, 163, 74, 0.2)', 'rgba(34, 197, 94, 0.25)')}; animation: statusPulse 2s ease-in-out infinite; } @keyframes statusPulse { 0%, 100% { box-shadow: 0 0 0 2px ${cssManager.bdTheme('rgba(22, 163, 74, 0.2)', 'rgba(34, 197, 94, 0.25)')}; } 50% { box-shadow: 0 0 0 4px ${cssManager.bdTheme('rgba(22, 163, 74, 0.1)', 'rgba(34, 197, 94, 0.15)')}; } } .status-indicator.degraded { background: ${sharedStyles.colors.status.degraded}; box-shadow: 0 0 0 2px ${cssManager.bdTheme('rgba(217, 119, 6, 0.2)', 'rgba(251, 191, 36, 0.25)')}; } .status-indicator.partial_outage { background: ${sharedStyles.colors.status.partial}; box-shadow: 0 0 0 2px ${cssManager.bdTheme('rgba(239, 68, 68, 0.2)', 'rgba(248, 113, 113, 0.25)')}; } .status-indicator.major_outage { background: ${sharedStyles.colors.status.major}; box-shadow: 0 0 0 2px ${cssManager.bdTheme('rgba(185, 28, 28, 0.2)', 'rgba(239, 68, 68, 0.25)')}; animation: majorPulse 1s ease-in-out infinite; } @keyframes majorPulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.6; } } .status-indicator.maintenance { background: ${sharedStyles.colors.status.maintenance}; box-shadow: 0 0 0 2px ${cssManager.bdTheme('rgba(37, 99, 235, 0.2)', 'rgba(96, 165, 250, 0.25)')}; } @media (max-width: 640px) { .container { padding: 0 ${unsafeCSS(sharedStyles.spacing.md)} ${unsafeCSS(sharedStyles.spacing.md)} ${unsafeCSS(sharedStyles.spacing.md)}; } .stats-grid { grid-template-columns: repeat(2, 1fr); gap: ${unsafeCSS(sharedStyles.spacing.sm)}; } .stat-card { padding: ${unsafeCSS(sharedStyles.spacing.md)}; } .stat-value { font-size: 22px; } .stat-label { font-size: 10px; } .stat-label svg { width: 12px; height: 12px; } } `, ]; public render(): TemplateResult { return html`