diff --git a/src/client/components/charts/LatencySparkline.tsx b/src/client/components/charts/LatencySparkline.tsx index 2871ee8..b4ada75 100644 --- a/src/client/components/charts/LatencySparkline.tsx +++ b/src/client/components/charts/LatencySparkline.tsx @@ -26,7 +26,7 @@ export function LatencySparkline({ checks }: { checks: Check[] }) { if (data.length < 2) return ( - Not enough latency data + Not enough latency data More checks are needed to draw latency. ); @@ -35,7 +35,11 @@ export function LatencySparkline({ checks }: { checks: Check[] }) { const maximum = Math.max(...values); return ( -
+
@@ -50,7 +54,7 @@ export function LatencySparkline({ checks }: { checks: Check[] }) { new Date((payload[0].payload as LatencyDatum).t).toLocaleString()} /> } @@ -71,7 +75,7 @@ export function LatencySparkline({ checks }: { checks: Check[] }) { /> -
+
{maximum} ms {minimum} ms
diff --git a/src/client/components/charts/UptimeBar.tsx b/src/client/components/charts/UptimeBar.tsx index 82beff5..27016d1 100644 --- a/src/client/components/charts/UptimeBar.tsx +++ b/src/client/components/charts/UptimeBar.tsx @@ -37,7 +37,7 @@ const formatUptimeTooltip: NonNullable Successful - + {point.successfulChecks}/{point.totalChecks}
@@ -83,7 +83,7 @@ export function UptimeBar({ checks }: { checks: Check[] }) { if (data.length === 0) return ( - No availability data yet + No availability data yet No availability checks recorded yet. ); @@ -91,7 +91,11 @@ export function UptimeBar({ checks }: { checks: Check[] }) { const successfulChecks = checks.filter((check) => check.ok).length; return (
-
+
@@ -99,7 +103,7 @@ export function UptimeBar({ checks }: { checks: Check[] }) { formatBucketTime(payload[0].payload as UptimeDatum)} @@ -123,12 +127,13 @@ export function UptimeBar({ checks }: { checks: Check[] }) {
-
- Oldest - - Up Degraded Down +
+ Oldest + + Up {' '} + Degraded Down - Latest + Latest
); diff --git a/src/client/pages/MonitorDetailPage.tsx b/src/client/pages/MonitorDetailPage.tsx index 7c38eca..64cce22 100644 --- a/src/client/pages/MonitorDetailPage.tsx +++ b/src/client/pages/MonitorDetailPage.tsx @@ -5,6 +5,7 @@ import { Button } from '@/components/ui/button'; import { Card } from '@/components/ui/card'; import { Empty, EmptyContent, EmptyDescription, EmptyTitle } from '@/components/ui/empty'; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table'; +import { cn } from '@/lib/utils'; import { AppHeader } from '../components/AppHeader'; import { LatencySparkline } from '../components/charts/LatencySparkline'; import { UptimeBar } from '../components/charts/UptimeBar'; @@ -23,6 +24,18 @@ import { useRunCheckMutation, useUpdateMonitorMutation, } from '../queries/monitors'; + +const statusOrbVariants: Record = { + online: + 'border-status-online-border bg-primary-deep shadow-[0_0_0_8px_white] dark:border-brand/22 dark:bg-brand dark:shadow-[0_0_0_8px_var(--color-night-base),0_0_24px_rgb(62_207_142/0.38)]', + offline: + 'border-status-offline-border bg-status-offline-bg shadow-[0_0_0_8px_white] dark:border-red-400/24 dark:bg-red-400 dark:shadow-[0_0_0_8px_var(--color-night-base),0_0_22px_rgb(248_113_113/0.32)]', + pending: + 'border-status-pending-border bg-status-pending-bg shadow-[0_0_0_8px_white] dark:border-amber-500/24 dark:bg-status-pending-dark dark:shadow-[0_0_0_8px_var(--color-night-base),0_0_22px_rgb(245_158_11/0.28)]', + checking: + 'border-status-checking-border bg-status-checking-bg shadow-[0_0_0_8px_white] dark:border-yellow-400/22 dark:bg-status-checking-dark dark:shadow-[0_0_0_8px_var(--color-night-base),0_0_22px_rgb(250_204_21/0.25)]', +}; + function formatInterval(seconds: number) { return INTERVAL_OPTIONS.find((option) => Number(option.value) === seconds)?.label ?? `${Math.round(seconds / 60)} min`; } @@ -76,29 +89,36 @@ export function MonitorDetailPage({ id }: { id: number }) { onCancel={() => setIsConfirmingDelete(false)} onConfirm={() => deleteMutation.mutate(monitor.id, { onSuccess: () => navigate('/dashboard') })} /> -
+
-
-
- +
+
+

Monitor #{monitor.id}

-

{monitor.name}

- +

+ {monitor.name} +

+
{monitor.url} - +
-
+
{status.label} {!monitor.alertsEnabled && ( - - Alerts muted + + Alerts muted )}
-
+
{(['24h', '7d', '30d', '90d'] as const).map((key) => { const window = statsQuery.data?.windows[key]; return ( -
-

{key} uptime

- {window?.uptimePct == null ? '—' : `${window.uptimePct.toFixed(3)}%`} - +
+

{key} uptime

+ + {window?.uptimePct == null ? '—' : `${window.uptimePct.toFixed(3)}%`} + + {window?.totalChecks ?? 0} checks · {window?.avgLatencyMs ?? '—'} ms avg
@@ -129,32 +154,51 @@ export function MonitorDetailPage({ id }: { id: number }) { ); })} -
-

Current incident

- {openIncident ? formatDuration(null, openIncident.startedAt) : 'None'} - {openIncident ? `Open since ${formatDate(openIncident.startedAt)}` : 'Everything is operational'} +
+

Current incident

+ + {openIncident ? formatDuration(null, openIncident.startedAt) : 'None'} + + + {openIncident ? `Open since ${formatDate(openIncident.startedAt)}` : 'Everything is operational'} +
-
-
-
+
+
+
-

Response time

-

Latency

+

Response time

+

Latency

- Last {checks.length} checks + + Last {checks.length} checks +
-
-
+
+
-

Availability

-

Recent uptime

+

Availability

+

Recent uptime

- + {checks.filter((check) => check.ok).length}/{checks.length} successful
@@ -162,18 +206,20 @@ export function MonitorDetailPage({ id }: { id: number }) {
-
-
-
+
+
+
-

Event stream

-

Recent checks

+

Event stream

+

Recent checks

- {Math.min(checks.length, 20)} shown + + {Math.min(checks.length, 20)} shown +
{/* Keyboard focus makes this horizontally scrollable region accessible without a pointer. */} {/* eslint-disable-next-line jsx-a11y/no-noninteractive-tabindex */} -
+
@@ -185,12 +231,12 @@ export function MonitorDetailPage({ id }: { id: number }) { {checks.slice(0, 20).map((check) => ( - + {check.ok ? 'Up' : 'Down'} - + {check.statusCode ? `HTTP ${check.statusCode}` : (check.error ?? 'Failed')} @@ -202,38 +248,62 @@ export function MonitorDetailPage({ id }: { id: number }) {
{checks.length === 0 && ( - No checks yet + No checks yet No checks recorded. )}
-
-
+
+
-

Downtime

-

Incidents

+

Downtime

+

Incidents

- {incidents.length} recorded + + {incidents.length} recorded +
{/* Keyboard focus makes this scrollable region accessible without a pointer. */} {/* eslint-disable-next-line jsx-a11y/no-noninteractive-tabindex */} -
+
{incidents.map((incident) => ( -
- {incident.resolvedAt ? : } -
- -

+

{incident.latestUpdate?.body ?? incident.startError ?? (incident.startStatusCode ? `HTTP ${incident.startStatusCode}` : 'Endpoint became unavailable')}

- {incident.latestUpdate && incident.startError && {incident.startError}} - + {incident.latestUpdate && incident.startError && ( + + {incident.startError} + + )} + {formatDate(incident.startedAt)} · {formatDuration(incident.durationMs, incident.startedAt)}
@@ -241,7 +311,7 @@ export function MonitorDetailPage({ id }: { id: number }) { ))} {incidents.length === 0 && ( - No incidents yet + No incidents yet No downtime incidents recorded. )} @@ -249,56 +319,71 @@ export function MonitorDetailPage({ id }: { id: number }) {
-
-
+
+
-

Setup

-

Configuration

+

Setup

+

Configuration

-
-
-
Method
-
{monitor.method}
+
+
+
Method
+
{monitor.method}
-
-
Expected status
-
{monitor.expectedStatus}
+
+
Expected status
+
{monitor.expectedStatus}
-
-
Check interval
-
{formatInterval(monitor.intervalSeconds)}
+
+
Check interval
+
+ {formatInterval(monitor.intervalSeconds)} +
-
-
Timeout
-
{monitor.timeoutMs.toLocaleString()} ms
+
+
Timeout
+
+ {monitor.timeoutMs.toLocaleString()} ms +
-
-
Scheduled checks
-
- {monitor.enabled ? 'Running' : 'Paused'} +
+
Scheduled checks
+
+ {monitor.enabled ? 'Running' : 'Paused'}
-
-
Incident alerts
-
{monitor.alertsEnabled ? 'Enabled' : 'Muted'}
+
+
Incident alerts
+
+ {monitor.alertsEnabled ? 'Enabled' : 'Muted'} +
-
-

This permanently deletes the monitor and its check history.

+
+

+ This permanently deletes the monitor and its check history. +

diff --git a/src/client/styles.css b/src/client/styles.css index 8a54ea7..60b6fe8 100644 --- a/src/client/styles.css +++ b/src/client/styles.css @@ -3,7 +3,6 @@ @import 'tw-animate-css'; @import 'shadcn/tailwind.css'; @import './styles/StatusPage.css'; -@import './styles/MonitorDetailPage.css'; @import './styles/SettingsPage.css'; @custom-variant dark (&:is(.dark *)); @@ -537,6 +536,16 @@ width: 15px; height: 15px; } +.dark .back-link { + color: #858d9a; + transition: color 160ms ease; +} +.dark .back-link:hover { + color: #f3f4f6; +} +.dark .back-link svg { + color: #4ade80; +} .table-empty { display: grid; min-height: 150px; @@ -583,6 +592,16 @@ transform: translateY(0); } } +@keyframes monitor-detail-enter { + from { + opacity: 0; + transform: translateY(8px); + } + to { + opacity: 1; + transform: translateY(0); + } +} @media (max-width: 1000px) { .form-actions { @@ -661,10 +680,61 @@ } } +@utility monitor-scroll { + max-height: clamp(320px, 52vh, 556px); + overflow: auto; + overscroll-behavior: contain; + scrollbar-color: #b8c7c0 #f4f6f5; + scrollbar-gutter: stable; + scrollbar-width: thin; + + &:focus-visible { + outline: 2px solid rgb(36 180 126 / 0.45); + outline-offset: -2px; + } + + &::-webkit-scrollbar { + width: 9px; + height: 9px; + } + + &::-webkit-scrollbar-track { + background: #f4f6f5; + } + + &::-webkit-scrollbar-thumb { + border: 2px solid #f4f6f5; + border-radius: 999px; + background: #b8c7c0; + } + + &::-webkit-scrollbar-thumb:hover { + background: #91a69d; + } +} + +.dark .monitor-scroll { + scrollbar-color: #3c4650 #0c0e11; + + &::-webkit-scrollbar-track { + background: #0c0e11; + } + + &::-webkit-scrollbar-thumb { + border-color: #0c0e11; + background: #3c4650; + } + + &::-webkit-scrollbar-thumb:hover { + background: #53606b; + } +} + @theme inline { --font-sans: 'DM Sans', 'Helvetica Neue', sans-serif; --font-mono: 'IBM Plex Mono', monospace; --animate-enter: enter 500ms cubic-bezier(0.16, 1, 0.3, 1) both; + --animate-monitor-enter: monitor-detail-enter 420ms cubic-bezier(0.22, 1, 0.36, 1) both; /* Brand emerald palette */ --color-brand: #3ecf8e; @@ -672,6 +742,7 @@ --color-brand-hover: #36c487; --color-brand-border: #35c586; --color-brand-light: #4ae09e; + --color-brand-soft: #4ade80; --color-brand-muted: #55816e; --color-brand-dark-text: #090a0c; @@ -681,6 +752,7 @@ --color-faint: var(--faint); --color-soft: var(--soft); --color-night: var(--night); + --color-primary-deep: var(--primary-deep); /* Neutral text and icon shades */ --color-ink-label: #353535; @@ -689,6 +761,7 @@ --color-icon-muted: #5f5f5f; /* Dark mode surfaces */ + --color-night-base: #090b0d; --color-night-root: #090a0c; --color-night-card: #111419; --color-night-input: #0b0d0f; @@ -699,6 +772,24 @@ --color-night-item-hover: #1d222b; --color-night-item-active: #20252e; --color-night-dialog: #0e1116; + --color-night-chart: #0d0f13; + --color-night-elevated: #0f1115; + --color-night-muted: #111215; + --color-night-surface: #13161b; + --color-night-card-alt: #15171b; + --color-night-incident: #151114; + + /* Dark mode text tones */ + --color-night-metric: #f4f7f6; + --color-night-heading: #f3f5f7; + --color-night-title: #e8ebef; + --color-night-body: #e5e7eb; + --color-night-code: #c7ced7; + --color-night-muted-text: #929aa7; + --color-night-subtle-text: #7e8794; + --color-night-faint: #737c89; + --color-night-faint-alt: #747d89; + --color-night-scale: #68717e; /* Light surfaces & backgrounds */ --color-surface-soft: #fafafa; @@ -707,6 +798,28 @@ --color-surface-hover: #fdfdfd; --color-surface-accent: #eef9f4; --color-surface-accent-hover: #e4f6ed; + --color-surface-sparkline: #fdfefe; + --color-surface-uptime: #fcfefd; + --color-surface-danger-subtle: #fdfafa; + + /* Status orb colors */ + --color-status-online-border: #c5f1df; + --color-status-offline-border: #f2d4d4; + --color-status-offline-bg: #c74f4f; + --color-status-pending-border: #f6e2c8; + --color-status-pending-bg: #d8892f; + --color-status-pending-dark: #f0a440; + --color-status-checking-border: #eee5bd; + --color-status-checking-bg: #c6aa38; + --color-status-checking-dark: #e6c94b; + + /* Incident status & stream colors */ + --color-incident-border: #e8c3c3; + --color-incident-bg: #fff8f8; + --color-incident-text: #a83e3e; + --color-incident-active-bg: #fff0f0; + --color-incident-active-text: #aa4141; + --color-incident-resolved-bg: #eaf9f2; /* Borders & Dividers */ --color-border-subtle: #dedede; @@ -717,6 +830,8 @@ --color-border-input-hover: #aaaaaa; --color-border-accent: #c9eadb; --color-border-accent-checked: #8dd9b9; + --color-border-hero: #e7e7e7; + --color-border-divider: #f0f0f0; /* Text & foreground tones */ --color-ink-muted-dark: #888888; diff --git a/src/client/styles/MonitorDetailPage.css b/src/client/styles/MonitorDetailPage.css deleted file mode 100644 index 7c66978..0000000 --- a/src/client/styles/MonitorDetailPage.css +++ /dev/null @@ -1,879 +0,0 @@ -/* ========================================================================== - Monitor Detail - ========================================================================== */ - -.detail-main { - padding-top: 34px; -} - -.detail-hero { - display: flex; - align-items: flex-end; - justify-content: space-between; - gap: 32px; - margin-top: 34px; - padding-bottom: 36px; - border-bottom: 1px solid #e7e7e7; -} - -.detail-title { - display: flex; - align-items: center; - gap: 18px; - min-width: 0; -} - -.status-orb { - flex: 0 0 auto; - width: 16px; - height: 16px; - border: 4px solid #f2d4d4; - border-radius: 50%; - background: #c74f4f; - box-shadow: 0 0 0 8px #fff; -} - -.status-orb.online { - border-color: #c5f1df; - background: var(--primary-deep); -} - -.status-orb.pending { - border-color: #f6e2c8; - background: #d8892f; -} - -.status-orb.checking { - border-color: #eee5bd; - background: #c6aa38; -} - -.detail-title h1 { - margin: 0; - overflow: hidden; - font-size: 40px; - font-weight: 500; - line-height: 1.1; - letter-spacing: -1.5px; - text-overflow: ellipsis; - white-space: nowrap; -} - -.detail-title a { - display: inline-flex; - align-items: center; - gap: 6px; - max-width: min(70vw, 680px); - margin-top: 9px; - overflow: hidden; - font: - 400 13px/1.4 'IBM Plex Mono', - monospace; - color: var(--muted); - text-overflow: ellipsis; - white-space: nowrap; -} - -.detail-title a:hover { - color: var(--primary-deep); -} - -.detail-title a svg { - flex: 0 0 auto; - width: 13px; - height: 13px; -} - -.detail-actions { - display: flex; - align-items: center; - gap: 18px; -} - -.muted-alert { - display: inline-flex; - align-items: center; - gap: 6px; - font-size: 12px; - color: var(--muted); -} - -.muted-alert svg { - width: 14px; - height: 14px; -} - -.sla-grid { - display: grid; - grid-template-columns: repeat(5, 1fr); - gap: 12px; - margin-top: 24px; -} - -.sla-card { - min-width: 0; - padding: 20px; - border: 1px solid #e3e3e3; - border-radius: 8px; - background: #fff; -} - -.sla-card p { - margin: 0 0 17px; - font-size: 12px; - color: var(--muted); -} - -.sla-card strong { - display: block; - font: - 500 23px/1 'IBM Plex Mono', - monospace; - letter-spacing: -1px; -} - -.sla-card span { - display: block; - margin-top: 11px; - overflow: hidden; - font-size: 11px; - color: var(--faint); - text-overflow: ellipsis; - white-space: nowrap; -} - -.sla-card.incident-summary { - background: #fafafa; -} - -.sla-card.incident-summary.has-incident { - border-color: #e8c3c3; - background: #fff8f8; -} - -.sla-card.incident-summary.has-incident strong { - color: #a83e3e; -} - -.detail-grid { - display: grid; - grid-template-columns: minmax(0, 1.55fr) minmax(300px, 0.75fr); - gap: 16px; - margin-top: 16px; -} - -.detail-grid.lower-grid { - align-items: stretch; -} - -.lower-grid > .data-panel { - height: 100%; -} - -.data-panel { - overflow: hidden; - border: 1px solid #dedede; - border-radius: 8px; - background: #fff; -} - -.data-panel-heading { - display: flex; - align-items: center; - justify-content: space-between; - gap: 20px; - min-height: 82px; - padding: 18px 22px; - border-bottom: 1px solid #ededed; -} - -.data-panel-heading .overline { - margin-bottom: 5px; -} - -.data-panel-heading h2 { - margin: 0; - font-size: 18px; - font-weight: 500; -} - -.data-panel-heading > span { - font: - 400 11px/1.4 'IBM Plex Mono', - monospace; - color: var(--faint); -} - -.configuration-panel { - margin-top: 16px; -} - -.config-list { - margin: 0; -} - -.config-row { - display: flex; - align-items: center; - justify-content: space-between; - gap: 20px; - padding: 14px 22px; - border-bottom: 1px solid #f0f0f0; -} - -.config-row:last-child { - border-bottom: 0; -} - -.config-row dt { - font-size: 12px; - color: var(--muted); -} - -.config-row dd { - margin: 0; - font: - 400 13px/1.4 'IBM Plex Mono', - monospace; - color: var(--ink); -} - -.config-row .config-row-actions { - display: flex; - align-items: center; - gap: 10px; -} - -.config-row-actions .row-action { - font-family: 'DM Sans', 'Helvetica Neue', sans-serif; -} - -.config-danger { - display: flex; - align-items: center; - justify-content: space-between; - gap: 16px; - padding: 16px 22px; - border-top: 1px solid #ededed; - background: #fdfafa; -} - -.config-danger p { - margin: 0; - font-size: 12px; - color: var(--muted); -} - -.chart-panel, -.uptime-panel { - min-height: 330px; -} - -.sparkline-wrap { - position: relative; - height: 245px; - padding: 26px 24px 24px; - background: linear-gradient(180deg, #fdfefe, #fff); -} - -.chart-scale { - position: absolute; - inset: 19px 22px 20px auto; - display: flex; - flex-direction: column; - justify-content: space-between; - pointer-events: none; - font: - 400 9px/1 'IBM Plex Mono', - monospace; - color: #aaa; -} - -.uptime-bar { - height: 148px; - padding: 24px 22px 18px; - background: linear-gradient(180deg, #fcfefd 0%, #fff 100%); -} - -.uptime-legend { - display: flex; - justify-content: space-between; - padding: 14px 22px 0; - border-top: 1px solid #f0f0f0; - font-size: 10px; - color: #999; -} - -.uptime-legend span { - display: inline-flex; - align-items: center; - gap: 5px; -} - -.uptime-legend i { - width: 6px; - height: 6px; - border-radius: 50%; - background: var(--primary-deep); -} - -.uptime-legend i.legend-down { - margin-left: 5px; - background: var(--chart-down); -} - -.uptime-legend i.legend-degraded { - margin-left: 5px; - background: var(--chart-degraded); -} - -.data-table-wrap { - overflow-x: auto; -} - -.recent-checks-panel { - display: flex; - min-height: 0; - flex-direction: column; -} - -.recent-checks-scroll, -.incident-list { - max-height: clamp(320px, 52vh, 556px); - overflow: auto; - overscroll-behavior: contain; - scrollbar-color: #b8c7c0 #f4f6f5; - scrollbar-gutter: stable; - scrollbar-width: thin; -} - -.recent-checks-scroll:focus-visible, -.incident-list:focus-visible { - outline: 2px solid rgb(36 180 126 / 0.45); - outline-offset: -2px; -} - -.recent-checks-scroll::-webkit-scrollbar, -.incident-list::-webkit-scrollbar { - width: 9px; - height: 9px; -} - -.recent-checks-scroll::-webkit-scrollbar-track, -.incident-list::-webkit-scrollbar-track { - background: #f4f6f5; -} - -.recent-checks-scroll::-webkit-scrollbar-thumb, -.incident-list::-webkit-scrollbar-thumb { - border: 2px solid #f4f6f5; - border-radius: 999px; - background: #b8c7c0; -} - -.recent-checks-scroll::-webkit-scrollbar-thumb:hover, -.incident-list::-webkit-scrollbar-thumb:hover { - background: #91a69d; -} - -.incident-list { - min-height: 0; -} - -.incident-list article { - display: flex; - gap: 13px; - padding: 18px 20px; -} - -.incident-list article > div { - min-width: 0; -} - -.incident-list article + article { - border-top: 1px solid #ededed; -} - -.incident-list article > span { - display: grid; - flex: 0 0 auto; - width: 30px; - height: 30px; - place-items: center; - border-radius: 50%; - color: #16885b; - background: #eaf9f2; -} - -.incident-list article.open > span { - color: #aa4141; - background: #fff0f0; -} - -.incident-list article svg { - width: 15px; - height: 15px; -} - -.incident-list strong { - font-size: 13px; - font-weight: 500; -} - -.incident-list p { - margin: 4px 0 6px; - overflow-wrap: anywhere; - font-size: 12px; - line-height: 1.45; - color: var(--muted); -} - -.incident-list small { - font: - 400 10px/1.4 'IBM Plex Mono', - monospace; - color: #aaa; -} - -.incident-list .incident-raw { - display: block; - margin: -1px 0 5px; - overflow-wrap: anywhere; -} - -.incident-detail-link { - display: inline-flex; - align-items: center; - gap: 10px; - padding: 0; - border: 0; - color: inherit; - background: transparent; - cursor: pointer; -} - -@media (max-width: 1000px) { - .sla-grid { - grid-template-columns: repeat(3, 1fr); - } - - .detail-grid, - .detail-grid.lower-grid { - grid-template-columns: 1fr; - } -} - -@media (max-width: 760px) { - .detail-hero { - align-items: flex-start; - flex-direction: column; - } - - .detail-actions { - flex-wrap: wrap; - } - - .config-row { - flex-wrap: wrap; - } - - .config-danger { - align-items: flex-start; - flex-direction: column; - } - - .detail-title h1 { - font-size: 34px; - } - - .sla-grid { - grid-template-columns: repeat(2, 1fr); - } - - .sla-card.incident-summary { - grid-column: 1 / -1; - } -} - -@media (max-width: 520px) { - .detail-title { - align-items: flex-start; - } - - .status-orb { - margin-top: 11px; - } - - .sla-grid { - grid-template-columns: 1fr; - } - - .sla-card.incident-summary { - grid-column: auto; - } - - .data-panel-heading { - padding: 16px; - } -} - -/* Dark operations console. */ - -.dark .detail-main { - position: relative; - isolation: isolate; -} - -.dark .detail-main::before { - position: absolute; - z-index: -1; - top: -110px; - left: 50%; - width: min(920px, 92vw); - height: 430px; - background: - radial-gradient(circle at 35% 20%, rgb(62 207 142 / 0.075), transparent 45%), - linear-gradient(90deg, rgb(255 255 255 / 0.018) 1px, transparent 1px), linear-gradient(rgb(255 255 255 / 0.018) 1px, transparent 1px); - background-size: - auto, - 44px 44px, - 44px 44px; - content: ''; - mask-image: linear-gradient(to bottom, #000 0%, transparent 82%); - pointer-events: none; - transform: translateX(-50%); -} - -.dark .back-link { - color: #858d9a; - transition: color 160ms ease; -} - -.dark .back-link:hover { - color: #f3f4f6; -} - -.dark .back-link svg { - color: #4ade80; -} - -.dark .detail-hero { - border-bottom-color: rgb(255 255 255 / 0.09); -} - -.dark .detail-title h1 { - color: #f8fafc; - text-shadow: 0 1px 18px rgb(0 0 0 / 0.22); -} - -.dark .detail-title a { - color: #929aa7; -} - -.dark .detail-title a:hover { - color: #4ade80; -} - -.dark .status-orb { - border-color: rgb(248 113 113 / 0.24); - background: #f87171; - box-shadow: - 0 0 0 8px #090b0d, - 0 0 22px rgb(248 113 113 / 0.32); -} - -.dark .status-orb.online { - border-color: rgb(62 207 142 / 0.22); - background: #3ecf8e; - box-shadow: - 0 0 0 8px #090b0d, - 0 0 24px rgb(62 207 142 / 0.38); -} - -.dark .status-orb.pending { - border-color: rgb(245 158 11 / 0.24); - background: #f0a440; - box-shadow: - 0 0 0 8px #090b0d, - 0 0 22px rgb(245 158 11 / 0.28); -} - -.dark .status-orb.checking { - border-color: rgb(250 204 21 / 0.22); - background: #e6c94b; - box-shadow: - 0 0 0 8px #090b0d, - 0 0 22px rgb(250 204 21 / 0.25); -} - -.dark .muted-alert { - color: #929aa7; -} - -.dark .sla-card { - position: relative; - border-color: rgb(255 255 255 / 0.085); - background: linear-gradient(145deg, #13161b 0%, #0f1115 100%); - box-shadow: - 0 10px 26px rgb(0 0 0 / 0.22), - inset 0 1px rgb(255 255 255 / 0.035); - transition: - border-color 180ms ease, - transform 180ms ease, - box-shadow 180ms ease; -} - -.dark .sla-card::before { - position: absolute; - top: -1px; - left: 20px; - width: 34px; - height: 1px; - background: rgb(62 207 142 / 0.65); - content: ''; -} - -.dark .sla-card p { - color: #929aa7; -} - -.dark .sla-card strong { - color: #f4f7f6; -} - -.dark .sla-card span { - color: #737c89; -} - -.dark .sla-card.incident-summary { - background: linear-gradient(145deg, #15171b 0%, #101216 100%); -} - -.dark .sla-card.incident-summary::before { - background: rgb(148 163 184 / 0.48); -} - -.dark .sla-card.incident-summary.has-incident { - border-color: rgb(248 113 113 / 0.3); - background: linear-gradient(145deg, rgb(73 28 31 / 0.58), #151114 100%); -} - -.dark .sla-card.incident-summary.has-incident::before { - background: #f87171; -} - -.dark .sla-card.incident-summary.has-incident strong { - color: #fca5a5; -} - -@media (hover: hover) { - .dark .sla-card:hover { - border-color: rgb(62 207 142 / 0.24); - box-shadow: - 0 14px 34px rgb(0 0 0 / 0.3), - 0 0 0 1px rgb(62 207 142 / 0.035), - inset 0 1px rgb(255 255 255 / 0.05); - transform: translateY(-2px); - } -} - -.dark .data-panel { - border-color: rgb(255 255 255 / 0.085); - background: #101216; - box-shadow: - 0 16px 38px rgb(0 0 0 / 0.27), - inset 0 1px rgb(255 255 255 / 0.03); -} - -.dark .data-panel-heading { - border-bottom-color: rgb(255 255 255 / 0.075); - background: linear-gradient(180deg, rgb(255 255 255 / 0.022), transparent); -} - -.dark .data-panel-heading h2 { - color: #f3f5f7; -} - -.dark .data-panel-heading > span { - color: #7e8794; -} - -.dark .sparkline-wrap, -.dark .uptime-bar { - background: linear-gradient(180deg, rgb(62 207 142 / 0.025), transparent 55%), #0d0f13; -} - -.dark .sparkline-wrap .recharts-cartesian-grid line { - stroke: rgb(255 255 255 / 0.075); -} - -.dark .chart-scale { - color: #68717e; -} - -.dark .monitor-chart-tooltip { - border-color: rgb(255 255 255 / 0.12); - color: #e5e7eb; - background: rgb(19 22 27 / 0.96); - box-shadow: - 0 16px 40px rgb(0 0 0 / 0.55), - inset 0 1px rgb(255 255 255 / 0.045); -} - -.dark .uptime-legend { - border-top-color: rgb(255 255 255 / 0.065); - color: #747d89; - background: #0f1115; -} - -.dark .recent-checks-scroll, -.dark .incident-list { - scrollbar-color: #3c4650 #0c0e11; -} - -.dark .recent-checks-scroll::-webkit-scrollbar-track, -.dark .incident-list::-webkit-scrollbar-track { - background: #0c0e11; -} - -.dark .recent-checks-scroll::-webkit-scrollbar-thumb, -.dark .incident-list::-webkit-scrollbar-thumb { - border-color: #0c0e11; - background: #3c4650; -} - -.dark .recent-checks-scroll::-webkit-scrollbar-thumb:hover, -.dark .incident-list::-webkit-scrollbar-thumb:hover { - background: #53606b; -} - -.dark .recent-checks-panel [data-slot='table-row'] { - transition: background-color 140ms ease; -} - -.dark .recent-checks-panel [data-slot='table-body'] [data-slot='table-row']:hover { - background: rgb(255 255 255 / 0.022); -} - -.dark .recent-checks-panel code { - color: #c7ced7; -} - -.dark .incident-list article + article { - border-top-color: rgb(255 255 255 / 0.065); -} - -.dark .incident-list article { - transition: background-color 140ms ease; -} - -.dark .incident-list article:hover { - background: rgb(255 255 255 / 0.022); -} - -.dark .incident-list article > span { - color: #4ade80; - background: rgb(62 207 142 / 0.105); - box-shadow: inset 0 0 0 1px rgb(62 207 142 / 0.12); -} - -.dark .incident-list article.open > span { - color: #f87171; - background: rgb(248 113 113 / 0.1); - box-shadow: inset 0 0 0 1px rgb(248 113 113 / 0.14); -} - -.dark .incident-list strong { - color: #e8ebef; -} - -.dark .incident-detail-link:hover strong { - color: #4ade80; -} - -.dark .incident-list p { - color: #9ca3af; -} - -.dark .incident-list small { - color: #747d89; -} - -.dark .config-row { - border-bottom-color: rgb(255 255 255 / 0.065); - transition: background-color 140ms ease; -} - -.dark .config-row:hover { - background: rgb(255 255 255 / 0.018); -} - -.dark .config-row dt { - color: #929aa7; -} - -.dark .config-row dd { - color: #e5e7eb; -} - -.dark .config-row-actions > span { - color: #4ade80; -} - -.dark .config-danger { - border-top-color: rgb(248 113 113 / 0.14); - background: linear-gradient(90deg, rgb(127 29 29 / 0.11), transparent 52%), #111215; -} - -.dark .config-danger p { - color: #929aa7; -} - -.dark .detail-hero, -.dark .sla-grid, -.dark .detail-grid, -.dark .configuration-panel { - animation: monitor-detail-enter 420ms both cubic-bezier(0.22, 1, 0.36, 1); -} - -.dark .sla-grid { - animation-delay: 45ms; -} - -.dark .detail-grid { - animation-delay: 90ms; -} - -.dark .detail-grid.lower-grid { - animation-delay: 135ms; -} - -.dark .configuration-panel { - animation-delay: 180ms; -} - -@keyframes monitor-detail-enter { - from { - opacity: 0; - transform: translateY(8px); - } - to { - opacity: 1; - transform: translateY(0); - } -} - -@media (max-width: 520px) { - .dark .detail-main::before { - top: -80px; - height: 330px; - background-size: - auto, - 34px 34px, - 34px 34px; - } - - .dark .sla-card { - padding: 18px; - } -}