@amiable-dev/docusaurus-plugin-stentorosaur 0.20.0 → 0.21.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,180 @@
1
+ /**
2
+ * ADR-004: SystemCardGroup Styles
3
+ *
4
+ * Container for grouping multiple SystemCards.
5
+ */
6
+
7
+ /* Group container */
8
+ .systemCardGroup {
9
+ margin-bottom: 1.5rem;
10
+ border: 1px solid var(--ifm-color-emphasis-200);
11
+ border-radius: 8px;
12
+ overflow: hidden;
13
+ }
14
+
15
+ /* Group header */
16
+ .groupHeader {
17
+ display: flex;
18
+ align-items: center;
19
+ gap: 0.75rem;
20
+ padding: 0.75rem 1rem;
21
+ background: var(--ifm-color-emphasis-100);
22
+ border-bottom: 1px solid var(--ifm-color-emphasis-200);
23
+ }
24
+
25
+ .collapsed .groupHeader {
26
+ border-bottom: none;
27
+ }
28
+
29
+ /* Toggle button */
30
+ .toggleButton {
31
+ display: flex;
32
+ align-items: center;
33
+ justify-content: center;
34
+ width: 28px;
35
+ height: 28px;
36
+ background: var(--ifm-color-emphasis-200);
37
+ border: none;
38
+ border-radius: 4px;
39
+ cursor: pointer;
40
+ transition: background-color 0.15s ease;
41
+ }
42
+
43
+ .toggleButton:hover {
44
+ background: var(--ifm-color-emphasis-300);
45
+ }
46
+
47
+ .toggleButton:focus-visible {
48
+ outline: 2px solid var(--ifm-color-primary);
49
+ outline-offset: 2px;
50
+ }
51
+
52
+ .expandIcon {
53
+ font-size: 0.625rem;
54
+ color: var(--ifm-color-content-secondary);
55
+ transition: transform 0.2s ease;
56
+ transform: rotate(-90deg);
57
+ }
58
+
59
+ .expandIconOpen {
60
+ transform: rotate(0deg);
61
+ }
62
+
63
+ /* Group info */
64
+ .groupInfo {
65
+ display: flex;
66
+ flex-direction: column;
67
+ gap: 0.125rem;
68
+ flex: 1;
69
+ }
70
+
71
+ .groupTitle {
72
+ margin: 0;
73
+ font-size: 1rem;
74
+ font-weight: 600;
75
+ color: var(--ifm-color-content);
76
+ }
77
+
78
+ .groupMeta {
79
+ display: flex;
80
+ align-items: center;
81
+ gap: 0.5rem;
82
+ font-size: 0.75rem;
83
+ color: var(--ifm-color-content-secondary);
84
+ }
85
+
86
+ .serviceCount {
87
+ color: var(--ifm-color-content-secondary);
88
+ }
89
+
90
+ .statusDot {
91
+ width: 6px;
92
+ height: 6px;
93
+ border-radius: 50%;
94
+ background: var(--status-operational, #22c55e);
95
+ }
96
+
97
+ .groupStatusDegraded .statusDot {
98
+ background: var(--status-degraded, #eab308);
99
+ }
100
+
101
+ .groupStatusDown .statusDot {
102
+ background: var(--status-outage, #ef4444);
103
+ }
104
+
105
+ .groupStatusMaintenance .statusDot {
106
+ background: var(--status-maintenance, #3b82f6);
107
+ }
108
+
109
+ .statusLabel {
110
+ font-weight: 500;
111
+ }
112
+
113
+ /* Status colors for labels */
114
+ .groupStatusUp .statusLabel {
115
+ color: var(--status-operational, #22c55e);
116
+ }
117
+
118
+ .groupStatusDegraded .statusLabel {
119
+ color: var(--status-degraded, #eab308);
120
+ }
121
+
122
+ .groupStatusDown .statusLabel {
123
+ color: var(--status-outage, #ef4444);
124
+ }
125
+
126
+ .groupStatusMaintenance .statusLabel {
127
+ color: var(--status-maintenance, #3b82f6);
128
+ }
129
+
130
+ /* Group content */
131
+ .groupContent {
132
+ display: flex;
133
+ flex-direction: column;
134
+ gap: 0.75rem;
135
+ padding: 1rem;
136
+ transition: max-height 0.3s ease, opacity 0.2s ease, padding 0.3s ease;
137
+ }
138
+
139
+ .contentHidden {
140
+ max-height: 0;
141
+ opacity: 0;
142
+ padding: 0 1rem;
143
+ overflow: hidden;
144
+ }
145
+
146
+ /* Dark mode */
147
+ [data-theme='dark'] .groupHeader {
148
+ background: var(--ifm-color-emphasis-200);
149
+ }
150
+
151
+ [data-theme='dark'] .toggleButton {
152
+ background: var(--ifm-color-emphasis-300);
153
+ }
154
+
155
+ [data-theme='dark'] .toggleButton:hover {
156
+ background: var(--ifm-color-emphasis-400);
157
+ }
158
+
159
+ /* Reduced motion */
160
+ @media (prefers-reduced-motion: reduce) {
161
+ .expandIcon,
162
+ .groupContent {
163
+ transition: none;
164
+ }
165
+
166
+ .contentHidden {
167
+ display: none;
168
+ }
169
+ }
170
+
171
+ /* Mobile responsiveness */
172
+ @media (max-width: 600px) {
173
+ .groupHeader {
174
+ padding: 0.5rem 0.75rem;
175
+ }
176
+
177
+ .groupContent {
178
+ padding: 0.75rem;
179
+ }
180
+ }
@@ -0,0 +1,69 @@
1
+ /**
2
+ * Copyright (c) Your Organization
3
+ *
4
+ * This source code is licensed under the MIT license found in the
5
+ * LICENSE file in the root directory of this source tree.
6
+ */
7
+ /**
8
+ * ADR-004: UptimeBar Component
9
+ *
10
+ * 90-day horizontal uptime bar visualization.
11
+ * Displays daily uptime status as colored cells.
12
+ *
13
+ * @see docs/adrs/ADR-004-simplified-status-card-ux.md
14
+ */
15
+ import React from 'react';
16
+ import { type DayStatus } from '../../context/StatusDataProvider';
17
+ /**
18
+ * Custom thresholds for status determination
19
+ */
20
+ export interface UptimeThresholds {
21
+ /** Uptime >= this is operational (default: 99) */
22
+ operational: number;
23
+ /** Uptime >= this is degraded, < is outage (default: 95) */
24
+ degraded: number;
25
+ }
26
+ /**
27
+ * Props for UptimeBar component
28
+ */
29
+ export interface UptimeBarProps {
30
+ /** Service name for data lookup (uses context) */
31
+ serviceName: string;
32
+ /** Override data for testing/Storybook */
33
+ data?: DayStatus[];
34
+ /** Number of days to display (default: 90) */
35
+ days?: number;
36
+ /** Height of the bar in pixels (default: 34) */
37
+ height?: number;
38
+ /** Gap between bars in pixels (default: 2) */
39
+ gap?: number;
40
+ /** Show uptime percentage text (default: true) */
41
+ showPercentage?: boolean;
42
+ /** Show date labels (default: true) */
43
+ showDateLabels?: boolean;
44
+ /** Customizable thresholds */
45
+ thresholds?: UptimeThresholds;
46
+ /** Loading state */
47
+ loading?: boolean;
48
+ /** Error state */
49
+ error?: Error | null;
50
+ /** Retry callback */
51
+ onRetry?: () => void;
52
+ /** Accessibility label */
53
+ ariaLabel?: string;
54
+ /** Interaction callbacks */
55
+ onDayClick?: (date: string, status: DayStatus) => void;
56
+ onDayHover?: (date: string, status: DayStatus | null) => void;
57
+ /** Optional additional className */
58
+ className?: string;
59
+ }
60
+ /**
61
+ * UptimeBar Component
62
+ *
63
+ * Displays a horizontal bar of daily uptime status cells.
64
+ * Each cell represents one day, colored by uptime percentage.
65
+ */
66
+ export declare function UptimeBar({ serviceName, data: providedData, days, height, gap, showPercentage, showDateLabels, thresholds, loading, error, onRetry, ariaLabel, onDayClick, onDayHover, className, }: UptimeBarProps): React.ReactElement;
67
+ export default UptimeBar;
68
+ export type { DayStatus };
69
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/theme/UptimeBar/index.tsx"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH;;;;;;;GAOG;AAEH,OAAO,KAAqE,MAAM,OAAO,CAAC;AAC1F,OAAO,EAAiB,KAAK,SAAS,EAAE,MAAM,kCAAkC,CAAC;AAGjF;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAC/B,kDAAkD;IAClD,WAAW,EAAE,MAAM,CAAC;IACpB,4DAA4D;IAC5D,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED;;GAEG;AACH,MAAM,WAAW,cAAc;IAC7B,kDAAkD;IAClD,WAAW,EAAE,MAAM,CAAC;IACpB,0CAA0C;IAC1C,IAAI,CAAC,EAAE,SAAS,EAAE,CAAC;IACnB,8CAA8C;IAC9C,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,gDAAgD;IAChD,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,8CAA8C;IAC9C,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,kDAAkD;IAClD,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,uCAAuC;IACvC,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,8BAA8B;IAC9B,UAAU,CAAC,EAAE,gBAAgB,CAAC;IAC9B,oBAAoB;IACpB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,kBAAkB;IAClB,KAAK,CAAC,EAAE,KAAK,GAAG,IAAI,CAAC;IACrB,qBAAqB;IACrB,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB,0BAA0B;IAC1B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,4BAA4B;IAC5B,UAAU,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,SAAS,KAAK,IAAI,CAAC;IACvD,UAAU,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,SAAS,GAAG,IAAI,KAAK,IAAI,CAAC;IAC9D,oCAAoC;IACpC,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAqED;;;;;GAKG;AACH,wBAAgB,SAAS,CAAC,EACxB,WAAW,EACX,IAAI,EAAE,YAAY,EAClB,IAAS,EACT,MAAW,EACX,GAAO,EACP,cAAqB,EACrB,cAAqB,EACrB,UAA+B,EAC/B,OAAe,EACf,KAAK,EACL,OAAO,EACP,SAAS,EACT,UAAU,EACV,UAAU,EACV,SAAS,GACV,EAAE,cAAc,GAAG,KAAK,CAAC,YAAY,CA8LrC;AAGD,eAAe,SAAS,CAAC;AAGzB,YAAY,EAAE,SAAS,EAAE,CAAC"}
@@ -0,0 +1,177 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.UptimeBar = UptimeBar;
7
+ const jsx_runtime_1 = require("react/jsx-runtime");
8
+ /**
9
+ * Copyright (c) Your Organization
10
+ *
11
+ * This source code is licensed under the MIT license found in the
12
+ * LICENSE file in the root directory of this source tree.
13
+ */
14
+ /**
15
+ * ADR-004: UptimeBar Component
16
+ *
17
+ * 90-day horizontal uptime bar visualization.
18
+ * Displays daily uptime status as colored cells.
19
+ *
20
+ * @see docs/adrs/ADR-004-simplified-status-card-ux.md
21
+ */
22
+ const react_1 = require("react");
23
+ const StatusDataProvider_1 = require("../../context/StatusDataProvider");
24
+ const styles_module_css_1 = __importDefault(require("./styles.module.css"));
25
+ /**
26
+ * Default thresholds per ADR-004
27
+ */
28
+ const DEFAULT_THRESHOLDS = {
29
+ operational: 99,
30
+ degraded: 95,
31
+ };
32
+ /**
33
+ * Get status class based on uptime and thresholds
34
+ */
35
+ function getStatusClass(uptimePercent, thresholds, originalStatus) {
36
+ // If original status is no-data, keep it
37
+ if (originalStatus === 'no-data') {
38
+ return styles_module_css_1.default.statusNoData;
39
+ }
40
+ // Apply threshold-based status
41
+ if (uptimePercent >= thresholds.operational) {
42
+ return styles_module_css_1.default.statusOperational;
43
+ }
44
+ if (uptimePercent >= thresholds.degraded) {
45
+ return styles_module_css_1.default.statusDegraded;
46
+ }
47
+ return styles_module_css_1.default.statusOutage;
48
+ }
49
+ /**
50
+ * Calculate average uptime percentage
51
+ */
52
+ function calculateAverageUptime(days) {
53
+ if (days.length === 0)
54
+ return 0;
55
+ const sum = days.reduce((acc, day) => acc + day.uptimePercent, 0);
56
+ return sum / days.length;
57
+ }
58
+ /**
59
+ * Generate summary for screen readers
60
+ */
61
+ function generateAriaLabel(serviceName, days, averageUptime) {
62
+ const operationalDays = days.filter((d) => d.status === 'operational').length;
63
+ const degradedDays = days.filter((d) => d.status === 'degraded').length;
64
+ const outageDays = days.filter((d) => d.status === 'outage').length;
65
+ let summary = `${days.length}-day uptime for ${serviceName}: ${averageUptime.toFixed(1)}%.`;
66
+ if (operationalDays > 0) {
67
+ summary += ` ${operationalDays} days operational.`;
68
+ }
69
+ if (degradedDays > 0) {
70
+ summary += ` ${degradedDays} days degraded.`;
71
+ }
72
+ if (outageDays > 0) {
73
+ summary += ` ${outageDays} days with outage.`;
74
+ }
75
+ return summary;
76
+ }
77
+ /**
78
+ * UptimeBar Component
79
+ *
80
+ * Displays a horizontal bar of daily uptime status cells.
81
+ * Each cell represents one day, colored by uptime percentage.
82
+ */
83
+ function UptimeBar({ serviceName, data: providedData, days = 90, height = 34, gap = 2, showPercentage = true, showDateLabels = true, thresholds = DEFAULT_THRESHOLDS, loading = false, error, onRetry, ariaLabel, onDayClick, onDayHover, className, }) {
84
+ // Get data from context or use provided data
85
+ const { getMerged90Days } = (0, StatusDataProvider_1.useStatusData)();
86
+ // Roving tabindex state - track which day has focus
87
+ const [focusedIndex, setFocusedIndex] = (0, react_1.useState)(0);
88
+ const dayCellRefs = (0, react_1.useRef)([]);
89
+ const dayData = (0, react_1.useMemo)(() => {
90
+ if (providedData) {
91
+ return providedData.slice(0, days);
92
+ }
93
+ return getMerged90Days(serviceName).slice(0, days);
94
+ }, [providedData, serviceName, days, getMerged90Days]);
95
+ // Calculate average uptime
96
+ const averageUptime = (0, react_1.useMemo)(() => calculateAverageUptime(dayData), [dayData]);
97
+ // Generate aria label
98
+ const computedAriaLabel = (0, react_1.useMemo)(() => {
99
+ if (ariaLabel)
100
+ return ariaLabel;
101
+ return generateAriaLabel(serviceName, dayData, averageUptime);
102
+ }, [ariaLabel, serviceName, dayData, averageUptime]);
103
+ // Event handlers
104
+ const handleDayClick = (0, react_1.useCallback)((day, index) => {
105
+ setFocusedIndex(index);
106
+ if (onDayClick) {
107
+ onDayClick(day.date, day);
108
+ }
109
+ }, [onDayClick]);
110
+ // Roving tabindex keyboard navigation
111
+ const handleBarKeyDown = (0, react_1.useCallback)((event) => {
112
+ const displayDays = [...dayData].reverse();
113
+ const maxIndex = displayDays.length - 1;
114
+ switch (event.key) {
115
+ case 'ArrowRight':
116
+ event.preventDefault();
117
+ setFocusedIndex((prev) => {
118
+ const newIndex = Math.min(prev + 1, maxIndex);
119
+ dayCellRefs.current[newIndex]?.focus();
120
+ return newIndex;
121
+ });
122
+ break;
123
+ case 'ArrowLeft':
124
+ event.preventDefault();
125
+ setFocusedIndex((prev) => {
126
+ const newIndex = Math.max(prev - 1, 0);
127
+ dayCellRefs.current[newIndex]?.focus();
128
+ return newIndex;
129
+ });
130
+ break;
131
+ case 'Home':
132
+ event.preventDefault();
133
+ setFocusedIndex(0);
134
+ dayCellRefs.current[0]?.focus();
135
+ break;
136
+ case 'End':
137
+ event.preventDefault();
138
+ setFocusedIndex(maxIndex);
139
+ dayCellRefs.current[maxIndex]?.focus();
140
+ break;
141
+ }
142
+ }, [dayData]);
143
+ const handleDayKeyDown = (0, react_1.useCallback)((event, day, index) => {
144
+ if (event.key === 'Enter' || event.key === ' ') {
145
+ event.preventDefault();
146
+ if (onDayClick) {
147
+ onDayClick(day.date, day);
148
+ }
149
+ }
150
+ }, [onDayClick]);
151
+ const handleDayHover = (0, react_1.useCallback)((day) => {
152
+ if (onDayHover && day) {
153
+ onDayHover(day.date, day);
154
+ }
155
+ else if (onDayHover && !day) {
156
+ // Pass the last hovered date (or empty string) with null
157
+ onDayHover('', null);
158
+ }
159
+ }, [onDayHover]);
160
+ // Loading state
161
+ if (loading) {
162
+ return ((0, jsx_runtime_1.jsx)("div", { className: `${styles_module_css_1.default.uptimeBarContainer} ${styles_module_css_1.default.loading} ${className || ''}`, children: (0, jsx_runtime_1.jsx)("div", { className: styles_module_css_1.default.skeleton, style: { height: `${height}px` } }) }));
163
+ }
164
+ // Error state
165
+ if (error) {
166
+ return ((0, jsx_runtime_1.jsxs)("div", { className: `${styles_module_css_1.default.uptimeBarContainer} ${styles_module_css_1.default.error} ${className || ''}`, children: [(0, jsx_runtime_1.jsx)("span", { className: styles_module_css_1.default.errorMessage, children: "Failed to load uptime data" }), onRetry && ((0, jsx_runtime_1.jsx)("button", { type: "button", className: styles_module_css_1.default.retryButton, onClick: onRetry, "aria-label": "Retry loading uptime data", children: "Retry" }))] }));
167
+ }
168
+ // Empty state
169
+ if (dayData.length === 0) {
170
+ return ((0, jsx_runtime_1.jsx)("div", { className: `${styles_module_css_1.default.uptimeBarContainer} ${styles_module_css_1.default.empty} ${className || ''}`, role: "img", "aria-label": `No uptime data available for ${serviceName}`, children: (0, jsx_runtime_1.jsx)("span", { className: styles_module_css_1.default.emptyMessage, children: "No data available" }) }));
171
+ }
172
+ // Reverse for display (oldest first, newest last)
173
+ const displayDays = [...dayData].reverse();
174
+ return ((0, jsx_runtime_1.jsxs)("div", { className: `${styles_module_css_1.default.uptimeBarContainer} ${className || ''}`, children: [showPercentage && ((0, jsx_runtime_1.jsxs)("div", { className: styles_module_css_1.default.uptimePercentage, children: [(0, jsx_runtime_1.jsxs)("span", { className: styles_module_css_1.default.percentageValue, children: [averageUptime.toFixed(2), "%"] }), (0, jsx_runtime_1.jsx)("span", { className: styles_module_css_1.default.percentageLabel, children: "uptime" })] })), (0, jsx_runtime_1.jsx)("div", { className: styles_module_css_1.default.uptimeBar, role: "group", "aria-label": computedAriaLabel, style: { height: `${height}px`, gap: `${gap}px` }, onKeyDown: handleBarKeyDown, children: displayDays.map((day, index) => ((0, jsx_runtime_1.jsx)("button", { ref: (el) => { dayCellRefs.current[index] = el; }, type: "button", className: `${styles_module_css_1.default.dayCell} ${getStatusClass(day.uptimePercent, thresholds, day.status)}`, tabIndex: index === focusedIndex ? 0 : -1, "aria-label": `${day.date}: ${day.uptimePercent.toFixed(1)}% uptime, ${day.incidents} incidents`, onClick: () => handleDayClick(day, index), onKeyDown: (e) => handleDayKeyDown(e, day, index), onMouseEnter: () => handleDayHover(day), onMouseLeave: () => handleDayHover(null), onFocus: () => setFocusedIndex(index) }, day.date))) }), showDateLabels && ((0, jsx_runtime_1.jsxs)("div", { className: styles_module_css_1.default.dateLabels, children: [(0, jsx_runtime_1.jsxs)("span", { className: styles_module_css_1.default.dateLabelStart, children: [days, " days ago"] }), (0, jsx_runtime_1.jsx)("span", { className: styles_module_css_1.default.dateLabelEnd, children: "Today" })] }))] }));
175
+ }
176
+ // Default export for easier imports
177
+ exports.default = UptimeBar;
@@ -0,0 +1,217 @@
1
+ /**
2
+ * ADR-004: UptimeBar Styles
3
+ *
4
+ * 90-day horizontal uptime bar visualization.
5
+ * Uses CSS variables for theming and flexbox for layout.
6
+ */
7
+
8
+ /* CSS Variables for status colors */
9
+ :root {
10
+ --status-operational: #22c55e;
11
+ --status-degraded: #eab308;
12
+ --status-outage: #ef4444;
13
+ --status-maintenance: #3b82f6;
14
+ --status-no-data: #9ca3af;
15
+ }
16
+
17
+ /* Container */
18
+ .uptimeBarContainer {
19
+ display: flex;
20
+ flex-direction: column;
21
+ gap: 0.5rem;
22
+ width: 100%;
23
+ }
24
+
25
+ /* Uptime percentage display */
26
+ .uptimePercentage {
27
+ display: flex;
28
+ align-items: baseline;
29
+ gap: 0.25rem;
30
+ }
31
+
32
+ .percentageValue {
33
+ font-size: 1.25rem;
34
+ font-weight: 600;
35
+ color: var(--ifm-color-content);
36
+ }
37
+
38
+ .percentageLabel {
39
+ font-size: 0.875rem;
40
+ color: var(--ifm-color-content-secondary, #666);
41
+ }
42
+
43
+ /* Main bar container */
44
+ .uptimeBar {
45
+ display: flex;
46
+ flex-direction: row;
47
+ align-items: stretch;
48
+ width: 100%;
49
+ border-radius: 4px;
50
+ overflow: hidden;
51
+ }
52
+
53
+ /* Individual day cell */
54
+ .dayCell {
55
+ flex: 1;
56
+ min-width: 2px;
57
+ border-radius: 2px;
58
+ cursor: pointer;
59
+ transition: transform 0.15s ease, opacity 0.15s ease;
60
+ }
61
+
62
+ .dayCell:hover {
63
+ transform: scaleY(1.1);
64
+ z-index: 1;
65
+ }
66
+
67
+ .dayCell:focus {
68
+ outline: 2px solid var(--ifm-color-primary);
69
+ outline-offset: 1px;
70
+ }
71
+
72
+ .dayCell:focus:not(:focus-visible) {
73
+ outline: none;
74
+ }
75
+
76
+ /* Status colors */
77
+ .statusOperational {
78
+ background-color: var(--status-operational);
79
+ }
80
+
81
+ .statusDegraded {
82
+ background-color: var(--status-degraded);
83
+ }
84
+
85
+ .statusOutage {
86
+ background-color: var(--status-outage);
87
+ }
88
+
89
+ .statusNoData {
90
+ background-color: var(--status-no-data);
91
+ }
92
+
93
+ /* Date labels */
94
+ .dateLabels {
95
+ display: flex;
96
+ justify-content: space-between;
97
+ font-size: 0.75rem;
98
+ color: var(--ifm-color-content-secondary, #666);
99
+ }
100
+
101
+ .dateLabelStart,
102
+ .dateLabelEnd {
103
+ white-space: nowrap;
104
+ }
105
+
106
+ /* Loading state */
107
+ .loading .skeleton {
108
+ background: linear-gradient(
109
+ 90deg,
110
+ var(--ifm-color-emphasis-200) 25%,
111
+ var(--ifm-color-emphasis-300) 50%,
112
+ var(--ifm-color-emphasis-200) 75%
113
+ );
114
+ background-size: 200% 100%;
115
+ animation: shimmer 1.5s infinite;
116
+ border-radius: 4px;
117
+ width: 100%;
118
+ }
119
+
120
+ @keyframes shimmer {
121
+ 0% {
122
+ background-position: 200% 0;
123
+ }
124
+ 100% {
125
+ background-position: -200% 0;
126
+ }
127
+ }
128
+
129
+ /* Error state */
130
+ .error {
131
+ display: flex;
132
+ align-items: center;
133
+ gap: 0.75rem;
134
+ padding: 0.75rem;
135
+ background-color: rgba(239, 68, 68, 0.1);
136
+ border-radius: 4px;
137
+ }
138
+
139
+ .errorMessage {
140
+ color: var(--status-outage);
141
+ font-size: 0.875rem;
142
+ }
143
+
144
+ .retryButton {
145
+ padding: 0.375rem 0.75rem;
146
+ font-size: 0.875rem;
147
+ background-color: var(--ifm-color-primary);
148
+ color: white;
149
+ border: none;
150
+ border-radius: 4px;
151
+ cursor: pointer;
152
+ transition: background-color 0.15s ease;
153
+ }
154
+
155
+ .retryButton:hover {
156
+ background-color: var(--ifm-color-primary-dark);
157
+ }
158
+
159
+ /* Empty state */
160
+ .empty {
161
+ display: flex;
162
+ align-items: center;
163
+ justify-content: center;
164
+ padding: 1rem;
165
+ background-color: var(--ifm-color-emphasis-100);
166
+ border-radius: 4px;
167
+ }
168
+
169
+ .emptyMessage {
170
+ color: var(--ifm-color-content-secondary);
171
+ font-size: 0.875rem;
172
+ }
173
+
174
+ /* Dark mode adjustments */
175
+ [data-theme='dark'] .statusOperational {
176
+ background-color: #16a34a;
177
+ }
178
+
179
+ [data-theme='dark'] .statusDegraded {
180
+ background-color: #ca8a04;
181
+ }
182
+
183
+ [data-theme='dark'] .statusOutage {
184
+ background-color: #dc2626;
185
+ }
186
+
187
+ [data-theme='dark'] .error {
188
+ background-color: rgba(239, 68, 68, 0.2);
189
+ }
190
+
191
+ /* Reduced motion support */
192
+ @media (prefers-reduced-motion: reduce) {
193
+ .dayCell {
194
+ transition: none;
195
+ }
196
+
197
+ .loading .skeleton {
198
+ animation: none;
199
+ background: var(--ifm-color-emphasis-200);
200
+ }
201
+ }
202
+
203
+ /* Mobile responsiveness */
204
+ @media (max-width: 600px) {
205
+ .uptimePercentage {
206
+ flex-direction: column;
207
+ gap: 0;
208
+ }
209
+
210
+ .percentageValue {
211
+ font-size: 1.125rem;
212
+ }
213
+
214
+ .dateLabels {
215
+ font-size: 0.625rem;
216
+ }
217
+ }
package/lib/types.d.ts CHANGED
@@ -401,6 +401,15 @@ export interface PluginOptions {
401
401
  * - 'upptime': Upptime-style structured view with configurable sections
402
402
  */
403
403
  statusView?: 'default' | 'upptime';
404
+ /**
405
+ * Status card layout style for individual system cards (ADR-004)
406
+ * - 'minimal': New simplified cards with 90-day uptime bars (default)
407
+ * - 'detailed': Original detailed status cards with more information
408
+ *
409
+ * @default 'minimal'
410
+ * @since 0.21.0
411
+ */
412
+ statusCardLayout?: 'minimal' | 'detailed';
404
413
  /**
405
414
  * Configuration for Upptime-style status page (only used when statusView is 'upptime')
406
415
  */
@@ -472,6 +481,12 @@ export interface StatusData {
472
481
  * Theme components use this to determine how to fetch live data
473
482
  */
474
483
  dataSource?: DataSource;
484
+ /**
485
+ * Status card layout style (ADR-004)
486
+ * - 'minimal': Simplified cards with 90-day uptime bars (default)
487
+ * - 'detailed': Original detailed cards with more information
488
+ */
489
+ statusCardLayout?: 'minimal' | 'detailed';
475
490
  }
476
491
  export interface UptimeStatusSection {
477
492
  id: 'active-incidents' | 'live-status' | 'charts' | 'scheduled-maintenance' | 'past-maintenance' | 'past-incidents';