From 0984c34c5c0bb79fb6d379e3a0f2eacd7e9de1ca Mon Sep 17 00:00:00 2001 From: Bradley Saucier Date: Wed, 6 May 2026 12:36:55 -0400 Subject: [PATCH 01/11] Update design tokens for surface colors and notches Signed-off-by: Bradley Saucier --- docs/design-tokens.md | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/docs/design-tokens.md b/docs/design-tokens.md index d581d568..64e0f97f 100644 --- a/docs/design-tokens.md +++ b/docs/design-tokens.md @@ -83,8 +83,8 @@ Application rules: - `--color-ops-base`: `#0a0f0d` - `--color-ops-surface-base`: `#0e1411` -- `--color-ops-surface-1`: `#121814` -- `--color-ops-surface-2`: `#1a221e` +- `--color-ops-surface-1`: `#101713` +- `--color-ops-surface-2`: `#18221d` - `--color-ops-surface-3`: `#222b27` - `--color-ops-surface-raised`: `#1a221e` - `--color-ops-surface-overlay`: `#222b27` @@ -127,6 +127,7 @@ Application rules: - `--ops-hover-2-bg`: `rgba(255, 255, 255, 0.025)` - `--ops-elevation-1`: standard card elevation - `--ops-elevation-2`: active or hovered panel elevation +- `--ops-elevation-3`: reserved high-emphasis shell elevation - `--ops-motion-fast`: `90ms ease-out` - `--ops-motion-standard`: `160ms ease-out` - `--ops-motion-slow`: `240ms ease-out` @@ -222,11 +223,11 @@ Shared notch sizes live in `src/styles/index.css`. Use the outer value on structural shells and the inner value on the surface inset. Use the chip value for badges, compact controls, and clipped action buttons. -- `--ops-notch-shell-outer`: `16px` -- `--ops-notch-shell-inner`: `15px` -- `--ops-notch-panel-outer`: `12px` +- `--ops-notch-shell-outer`: `18px` +- `--ops-notch-shell-inner`: `16px` +- `--ops-notch-panel-outer`: `13px` - `--ops-notch-panel-inner`: `11px` -- `--ops-notch-chip`: `8px` +- `--ops-notch-chip`: `7px` Shared polygon pattern: From 82e1c2396bd6942c5fe9a9d940164de7472b960c Mon Sep 17 00:00:00 2001 From: Bradley Saucier Date: Wed, 6 May 2026 12:37:53 -0400 Subject: [PATCH 02/11] Refactor DomainCard component styles and structure Signed-off-by: Bradley Saucier --- src/components/DomainCard.tsx | 27 ++++++++++++++------------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/src/components/DomainCard.tsx b/src/components/DomainCard.tsx index 026a60be..74cfc5c8 100644 --- a/src/components/DomainCard.tsx +++ b/src/components/DomainCard.tsx @@ -56,7 +56,7 @@ export function DomainCard({ const shellClassName = busy ? 'clip-notched ops-notch-panel-outer h-full bg-ops-border-strong p-px' - : 'ops-domain-card group clip-notched ops-notch-panel-outer h-full bg-ops-border-strong p-px transition-colors'; + : 'ops-domain-card group clip-notched ops-notch-panel-outer h-full bg-ops-border-strong p-px transition-colors hover:bg-ops-accent/25 focus-within:bg-ops-accent/25'; useEffect(() => { const pendingStatus = pendingKeyboardFocusStatusRef.current; @@ -150,12 +150,12 @@ export function DomainCard({
-
-
+
+ -
+
-

+ + {sector.shortLabel} + +

{sector.label}

-

+

{sector.description}

@@ -201,7 +202,7 @@ export function DomainCard({ role="radiogroup" aria-label={`${sector.label} status`} aria-describedby={describedBy} - className="mt-3 grid grid-cols-3 gap-1.5 xl:gap-2" + className="mt-3 grid grid-cols-3 gap-2" > {STATUS_OPTIONS.map((option, optionIndex) => { const content = getStatusContent(option); @@ -229,7 +230,7 @@ export function DomainCard({ }} onKeyDown={(event) => handleRadioKeyDown(event, optionIndex)} className={[ - 'ops-focus-ring-chip ops-radio-chip tactical-chip-panel ops-tracking-grid min-h-[var(--ops-chip-min-h)] border px-1.5 py-2 text-center text-[11px] font-semibold uppercase xl:px-2', + 'ops-focus-ring-chip ops-radio-chip tactical-chip-panel ops-tracking-grid min-h-[var(--ops-chip-min-h)] border px-2 py-2 text-center text-[11px] font-semibold uppercase', busy ? 'cursor-wait opacity-70' : '', isSelected ? `${content.classes} shadow-[inset_0_1px_0_rgba(255,255,255,0.08)]` From c3fc50f5229f58a9b7f9f7976b190775dcecc533 Mon Sep 17 00:00:00 2001 From: Bradley Saucier Date: Wed, 6 May 2026 12:38:26 -0400 Subject: [PATCH 03/11] Refactor NotchedFrame component class names Signed-off-by: Bradley Saucier --- src/components/NotchedFrame.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/NotchedFrame.tsx b/src/components/NotchedFrame.tsx index 6ef814d9..428edf53 100644 --- a/src/components/NotchedFrame.tsx +++ b/src/components/NotchedFrame.tsx @@ -64,7 +64,7 @@ export const NotchedFrame = forwardRef( >
( >
Date: Wed, 6 May 2026 12:38:55 -0400 Subject: [PATCH 04/11] Refactor StatusLegend component for improved styling Signed-off-by: Bradley Saucier --- src/components/StatusLegend.tsx | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/components/StatusLegend.tsx b/src/components/StatusLegend.tsx index 4c959829..fa79a69c 100644 --- a/src/components/StatusLegend.tsx +++ b/src/components/StatusLegend.tsx @@ -2,20 +2,20 @@ import { StatusBadge } from './StatusBadge'; export function StatusLegend() { return ( -
+

Legend

-
-
+
+
Nominal
-
+
Degraded
-
+
Unmarked
From 8f449485db6380a69fcbbeb9739b29a0980bb816 Mon Sep 17 00:00:00 2001 From: Bradley Saucier Date: Wed, 6 May 2026 12:39:33 -0400 Subject: [PATCH 05/11] Refactor TodayPanel layout and class names Signed-off-by: Bradley Saucier --- src/features/checkin/TodayPanel.tsx | 56 +++++++++++++++-------------- 1 file changed, 29 insertions(+), 27 deletions(-) diff --git a/src/features/checkin/TodayPanel.tsx b/src/features/checkin/TodayPanel.tsx index 970a791f..f0869f65 100644 --- a/src/features/checkin/TodayPanel.tsx +++ b/src/features/checkin/TodayPanel.tsx @@ -70,11 +70,11 @@ function DayCompletionRollup({ return (
-
+

{markedCount} / {totalCount} @@ -83,33 +83,35 @@ function DayCompletionRollup({ {rollupText}

-
- {SECTORS.map((sector, index) => { - const status = statuses[index] ?? 'unmarked'; +
+
+ {SECTORS.map((sector, index) => { + const status = statuses[index] ?? 'unmarked'; - return ( - + return (
); @@ -254,7 +256,7 @@ export function TodayPanel({ totalCount={completion.totalCount} /> -
+
{SECTORS.map((sector, index) => (
Date: Wed, 6 May 2026 12:40:47 -0400 Subject: [PATCH 06/11] Refactor class names for history grid component Signed-off-by: Bradley Saucier --- src/features/history/DesktopHistoryGrid.tsx | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/features/history/DesktopHistoryGrid.tsx b/src/features/history/DesktopHistoryGrid.tsx index ee554cf1..23c7ff30 100644 --- a/src/features/history/DesktopHistoryGrid.tsx +++ b/src/features/history/DesktopHistoryGrid.tsx @@ -177,7 +177,7 @@ export function DesktopHistoryGrid({ model }: DesktopHistoryGridProps) { isToday ? 'ops-history-today-header bg-[var(--ops-tint-1)] text-ops-accent-muted' : isSelectedColumn - ? 'bg-[var(--ops-tint-2)] text-ops-text-primary' + ? 'ops-history-selected-column-header bg-[var(--ops-tint-2)] text-ops-text-primary' : 'bg-ops-surface-1 text-ops-text-secondary', ].join(' ')} scope="col" @@ -280,8 +280,9 @@ export function DesktopHistoryGrid({ model }: DesktopHistoryGridProps) { ? 'ops-history-selected-cell bg-[var(--ops-tint-3)]' : isToday ? 'ops-history-today-cell' - : dateKey === selectedCell.dateKey - ? 'bg-[var(--ops-tint-1)]' + : dateKey === selectedCell.dateKey && + hasInteractedWithHistory + ? 'ops-history-selected-column bg-[var(--ops-tint-1)]' : isOddWeek ? 'ops-history-week-band' : '', From 34de27c7cfbbd080e1ab0a109ecc2e2ff9c7a1cf Mon Sep 17 00:00:00 2001 From: Bradley Saucier Date: Wed, 6 May 2026 12:41:10 -0400 Subject: [PATCH 07/11] Refactor MobileHistoryGrid styles and state handling Signed-off-by: Bradley Saucier --- src/features/history/MobileHistoryGrid.tsx | 33 +++++++++++----------- 1 file changed, 16 insertions(+), 17 deletions(-) diff --git a/src/features/history/MobileHistoryGrid.tsx b/src/features/history/MobileHistoryGrid.tsx index 1917c18f..cddb84d8 100644 --- a/src/features/history/MobileHistoryGrid.tsx +++ b/src/features/history/MobileHistoryGrid.tsx @@ -136,7 +136,7 @@ export function MobileHistoryGrid({ model }: MobileHistoryGridProps) { role="status" aria-live="polite" aria-atomic="true" - className="ops-flat-panel ops-tracking-grid px-3 py-2 text-center text-xs uppercase text-ops-text-secondary max-[360px]:px-2" + className="ops-mobile-week-status ops-flat-panel ops-tracking-grid px-3 py-2 text-center text-xs uppercase text-ops-text-secondary max-[360px]:px-2" > Week {visibleWeekIndex + 1} of {weekGroups.length} @@ -237,22 +237,21 @@ export function MobileHistoryGrid({ model }: MobileHistoryGridProps) { id={weekHeadingId} className="ops-tracking-grid text-xs font-semibold uppercase text-ops-text-secondary" > - W{weekIndex + 1} / {formatDayLabel(weekStart)}- + W{weekIndex + 1}/{formatDayLabel(weekStart)}- {formatDayLabel(weekEnd)}

- {visibleWeekIndex === weekIndex ? ( -
- On deck -
- ) : ( - - Week not active - - )} +
+ {visibleWeekIndex === weekIndex ? 'On deck' : 'Stand by'} +
))} From abd5b578909c8c1f5db2507524736738f27510b3 Mon Sep 17 00:00:00 2001 From: Bradley Saucier Date: Wed, 6 May 2026 12:41:41 -0400 Subject: [PATCH 08/11] Add scrollWeekIntoContainer function for scrolling Signed-off-by: Bradley Saucier --- src/features/history/useHistoryGridModel.ts | 34 +++++++++++++++++---- 1 file changed, 28 insertions(+), 6 deletions(-) diff --git a/src/features/history/useHistoryGridModel.ts b/src/features/history/useHistoryGridModel.ts index d6f19b0c..d2b66d04 100644 --- a/src/features/history/useHistoryGridModel.ts +++ b/src/features/history/useHistoryGridModel.ts @@ -34,6 +34,20 @@ interface HistoryGridDayStatus { status: UiStatus; } +function scrollWeekIntoContainer( + scrollNode: HTMLDivElement | null, + weekNode: HTMLDivElement | null | undefined, +) { + if (!scrollNode || !weekNode) { + return; + } + + scrollNode.scrollTo({ + behavior: 'auto', + left: Math.max(weekNode.offsetLeft - scrollNode.offsetLeft, 0), + }); +} + export interface HistoryGridModel { dateKeys: string[]; todayKey: string; @@ -193,7 +207,7 @@ export function useHistoryGridModel({ const hasAlignedInitialMobileWeekRef = useRef(false); useEffect(() => { - if (isDesktopHistory) { + if (isDesktopHistory || !hasEntries) { hasAlignedInitialMobileWeekRef.current = false; return; } @@ -202,10 +216,16 @@ export function useHistoryGridModel({ return; } - hasAlignedInitialMobileWeekRef.current = true; + const scrollNode = mobileScrollRef.current; const weekNode = weekRefs.current.get(initialSelectedWeekIndex); - weekNode?.scrollIntoView?.({ block: 'nearest', inline: 'nearest' }); - }, [initialSelectedWeekIndex, isDesktopHistory]); + + if (!scrollNode || !weekNode) { + return; + } + + hasAlignedInitialMobileWeekRef.current = true; + scrollWeekIntoContainer(scrollNode, weekNode); + }, [hasEntries, initialSelectedWeekIndex, isDesktopHistory]); useEffect(() => { if ( @@ -355,8 +375,10 @@ export function useHistoryGridModel({ dateKey: targetDateKey, })); - const weekNode = weekRefs.current.get(targetWeekIndex); - weekNode?.scrollIntoView?.({ block: 'nearest', inline: 'nearest' }); + scrollWeekIntoContainer( + mobileScrollRef.current, + weekRefs.current.get(targetWeekIndex), + ); }, [lastWeekIndex, selectedCell.dateKey, weekGroups], ); From 0d8f0641383fc72be237a2038aaaf91ac85b0ec5 Mon Sep 17 00:00:00 2001 From: Bradley Saucier Date: Wed, 6 May 2026 12:42:21 -0400 Subject: [PATCH 09/11] Refactor CSS styles and variable values Updated various CSS variables and styles for better design consistency and responsiveness. Signed-off-by: Bradley Saucier --- src/styles/index.css | 438 ++++++++++++++++++++++--------------------- 1 file changed, 223 insertions(+), 215 deletions(-) diff --git a/src/styles/index.css b/src/styles/index.css index b83a3d5c..de08d2ff 100644 --- a/src/styles/index.css +++ b/src/styles/index.css @@ -63,8 +63,8 @@ @theme { --color-ops-base: #0a0f0d; --color-ops-surface-base: #0e1411; - --color-ops-surface-1: #121814; - --color-ops-surface-2: #181f1b; + --color-ops-surface-1: #101713; + --color-ops-surface-2: #18221d; --color-ops-surface-3: #222b27; --color-ops-surface-raised: #1a221e; --color-ops-surface-overlay: #222b27; @@ -111,11 +111,11 @@ --ops-safe-right: env(safe-area-inset-right, 0px); --ops-safe-bottom: env(safe-area-inset-bottom, 0px); --ops-safe-left: env(safe-area-inset-left, 0px); - --ops-notch-shell-outer: 16px; - --ops-notch-shell-inner: 15px; - --ops-notch-panel-outer: 12px; + --ops-notch-shell-outer: 18px; + --ops-notch-shell-inner: 16px; + --ops-notch-panel-outer: 13px; --ops-notch-panel-inner: 11px; - --ops-notch-chip: 8px; + --ops-notch-chip: 7px; --ops-focus-ring: rgba(229, 255, 242, 0.92); --ops-focus-ring-edge: rgba(10, 15, 13, 0.96); --ops-tracking-caption: 0.04em; @@ -137,6 +137,10 @@ --ops-motion-select: cubic-bezier(0.2, 0, 0, 1); --ops-elevation-1: 0 8px 18px rgba(0, 0, 0, 0.16), 0 2px 8px rgba(0, 0, 0, 0.14); + --ops-elevation-2: + 0 14px 28px rgba(0, 0, 0, 0.2), 0 4px 12px rgba(0, 0, 0, 0.16); + --ops-elevation-3: + 0 20px 38px rgba(0, 0, 0, 0.26), 0 8px 20px rgba(0, 0, 0, 0.18); --ops-focus-ring-solid-edge: rgba(2, 18, 12, 0.96); --ops-focus-ring-solid-outer: rgba(255, 255, 255, 0.92); --ops-focus-ring-shadow-default: @@ -165,9 +169,9 @@ --ops-accent-a-4: 0.4; --ops-accent-a-5: 0.56; --ops-accent-a-6: 0.78; - --ops-tint-1: rgba(255, 255, 255, 0.018); - --ops-tint-2: rgba(255, 255, 255, 0.035); - --ops-tint-3: rgba(255, 255, 255, 0.062); + --ops-tint-1: rgba(255, 255, 255, 0.016); + --ops-tint-2: rgba(255, 255, 255, 0.04); + --ops-tint-3: rgba(255, 255, 255, 0.07); --ops-sector-work-school-tint: rgba(110, 231, 183, 0.06); --ops-sector-household-tint: rgba(110, 231, 183, 0.1); --ops-sector-relationships-tint: color-mix( @@ -230,7 +234,7 @@ body { repeating-linear-gradient( 0deg, transparent 0 47px, - rgba(255, 255, 255, 0.018) 47px 48px + rgba(255, 255, 255, 0.012) 47px 48px ), radial-gradient( ellipse 120% 60% at 50% -10%, @@ -248,7 +252,7 @@ body { repeating-linear-gradient( 0deg, transparent 0 47px, - rgba(255, 255, 255, 0.018) 47px 48px + rgba(255, 255, 255, 0.012) 47px 48px ), radial-gradient( ellipse 120% 60% at 50% -10%, @@ -379,7 +383,7 @@ h3, .ops-headline-h1 { font-size: 2.5rem; line-height: 1.1; - letter-spacing: -0.015em; + letter-spacing: 0; } .ops-headline-h2 { @@ -415,12 +419,6 @@ h3, } } -@media (min-width: 1280px) { - .ops-headline-h1 { - font-size: 3.25rem; - } -} - .ops-tracking-caption { letter-spacing: var(--ops-tracking-caption); } @@ -486,11 +484,6 @@ h3, color: var(--color-ops-accent-muted); } -.ops-hero-coordinate-strip { - padding-bottom: 0.5rem; - border-bottom: 1px solid rgba(110, 231, 183, 0.12); -} - .ops-coordinate-strip > span, .ops-coordinate-chip, .ops-station-chip { @@ -570,27 +563,6 @@ h3, ); } -.ops-hero-rail-mobile { - position: absolute; - top: 0.25rem; - left: -0.5rem; - display: block; - width: 2px; - height: clamp(36px, 6vh, 56px); - background: linear-gradient( - 180deg, - rgba(183, 247, 218, 0.88), - rgba(46, 143, 106, 0.28) - ); - box-shadow: 6px 0 18px rgba(110, 231, 183, 0.16); -} - -@media (min-width: 640px) { - .ops-hero-rail-mobile { - display: none; - } -} - @media (min-width: 640px) { .app-shell { padding-right: max(1.5rem, var(--ops-safe-right)); @@ -625,9 +597,9 @@ h3, overflow: hidden; isolation: isolate; box-shadow: - inset 0 1px 0 rgba(255, 255, 255, 0.05), + inset 0 1px 0 rgba(255, 255, 255, 0.07), inset 0 0 0 1px var(--color-ops-border-soft), - inset 0 -24px 42px rgba(10, 15, 13, 0.14), + inset 0 -28px 48px rgba(4, 8, 7, 0.22), 0 0 0 1px rgba(255, 255, 255, 0.02); } @@ -663,6 +635,17 @@ h3, ); } +.ops-notch-frame-outer { + position: relative; + box-shadow: + inset 0 1px 0 rgba(255, 255, 255, 0.08), + inset 0 -1px 0 rgba(0, 0, 0, 0.36); +} + +.ops-notch-frame-inner { + position: relative; +} + .ops-section-surface { background: var( --ops-section-surface-background, @@ -700,8 +683,7 @@ h3, --ops-section-surface-shadow: inset 0 1px 0 rgba(110, 231, 183, 0.28), inset 0 0 0 1px var(--color-ops-border-soft), - inset 0 -24px 42px rgba(10, 15, 13, 0.14), - 0 0 0 1px rgba(255, 255, 255, 0.02); + inset 0 -30px 54px rgba(4, 8, 7, 0.22), var(--ops-elevation-1); } .ops-section-primary-tick { @@ -717,43 +699,6 @@ h3, pointer-events: none; } -.ops-section-meta { - position: relative; - padding-top: 0.75rem; -} - -.ops-section-meta::before { - content: ''; - display: block; - width: min(100%, 14rem); - height: 1px; - margin: 0 0 0.75rem auto; - background: linear-gradient( - 90deg, - transparent, - var(--color-ops-border-soft) 34%, - rgba(110, 231, 183, 0.24) - ); -} - -@media (min-width: 640px) { - .ops-section-meta { - padding-top: 0; - } -} - -.ops-today-meta { - display: flex; - flex-direction: column; - align-items: flex-start; -} - -@media (min-width: 640px) { - .ops-today-meta { - align-items: flex-end; - } -} - .ops-section-emphasis-standard { --ops-section-frame-background: linear-gradient( 180deg, @@ -766,8 +711,7 @@ h3, --ops-section-surface-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.05), inset 0 0 0 1px var(--color-ops-border-soft), - inset 0 -24px 42px rgba(10, 15, 13, 0.06), - 0 0 0 1px rgba(255, 255, 255, 0.02); + inset 0 -24px 42px rgba(4, 8, 7, 0.16), 0 0 0 1px rgba(255, 255, 255, 0.02); } .ops-section-emphasis-support { @@ -930,7 +874,7 @@ h3, display: grid; place-items: center; width: 100%; - height: 36px; + height: 32px; border: 0; border-radius: 3px; font-size: 11px; @@ -981,11 +925,8 @@ h3, .ops-sector-spine-unmarked { --ops-sector-spine-width: 4px; --ops-sector-spine-color: var(--ops-status-unmarked-border); - --ops-sector-inner-edge: rgba(255, 255, 255, 0.02); box-shadow: inset var(--ops-sector-spine-width) 0 0 var(--ops-sector-spine-color), - inset calc(var(--ops-sector-spine-width) + 1px) 0 0 - var(--ops-sector-inner-edge), inset 0 1px 0 rgba(255, 255, 255, 0.05), inset 0 0 0 1px var(--color-ops-border-soft), inset 0 -24px 42px rgba(10, 15, 13, 0.14), @@ -1044,15 +985,6 @@ h3, color: var(--ops-status-unmarked-text); } -.ops-domain-card:hover, -.ops-domain-card:focus-within { - background: color-mix( - in srgb, - var(--color-ops-border-strong) 74%, - var(--color-ops-accent) 26% - ); -} - .ops-domain-card:hover .ops-sector-spine-nominal, .ops-domain-card:focus-within .ops-sector-spine-nominal, .ops-domain-card:hover .ops-sector-spine-degraded, @@ -1060,7 +992,30 @@ h3, .ops-domain-card:hover .ops-sector-spine-unmarked, .ops-domain-card:focus-within .ops-sector-spine-unmarked { --ops-sector-spine-width: 5px; - --ops-sector-inner-edge: rgba(110, 231, 183, 0.1); +} + +.ops-domain-card { + box-shadow: var(--ops-elevation-1); +} + +.ops-domain-card > div { + background: + linear-gradient(180deg, rgba(255, 255, 255, 0.055), transparent 24%), + radial-gradient( + ellipse 85% 58% at 50% 0, + var(--ops-sector-tint, rgba(110, 231, 183, 0.05)), + transparent 70% + ), + var(--color-ops-surface-raised); +} + +.ops-domain-title { + font-family: var(--font-mono); + letter-spacing: var(--ops-tracking-title); +} + +.ops-domain-description { + color: color-mix(in srgb, var(--color-ops-text-secondary) 88%, transparent); } .ops-section-spine-fault { @@ -1143,7 +1098,7 @@ h3, .ops-surface-raised-card { background: - linear-gradient(180deg, var(--ops-tint-3), var(--ops-tint-1) 28%), + linear-gradient(180deg, var(--ops-tint-3), var(--ops-tint-1) 30%), var(--color-ops-surface-raised); } @@ -1155,10 +1110,12 @@ h3, .ops-surface-hero { background: + linear-gradient(90deg, rgba(110, 231, 183, 0.07), transparent 32%), linear-gradient( 180deg, - rgba(110, 231, 183, 0.1), - rgba(255, 255, 255, 0.02) + rgba(110, 231, 183, 0.11), + rgba(255, 255, 255, 0.025) 34%, + rgba(4, 8, 7, 0.18) ), var(--color-ops-surface-1); } @@ -1174,11 +1131,6 @@ h3, var(--color-ops-surface-overlay); } -.ops-telemetry-horizon-label { - border-left: 2px solid var(--color-ops-accent-deep); - box-shadow: -4px 0 12px rgba(46, 143, 106, 0.18); -} - .ops-surface-fault-card { background: linear-gradient( @@ -1227,7 +1179,7 @@ h3, .ops-alert-frame-info { background: linear-gradient( 180deg, - color-mix(in srgb, var(--color-ops-info) 34%, transparent), + color-mix(in srgb, var(--color-ops-info) 42%, transparent), rgba(255, 255, 255, 0.04) ); } @@ -1235,7 +1187,7 @@ h3, .ops-alert-frame-warning { background: linear-gradient( 180deg, - color-mix(in srgb, var(--color-ops-warn) 32%, transparent), + color-mix(in srgb, var(--color-ops-warn) 42%, transparent), rgba(255, 255, 255, 0.04) ); } @@ -1243,7 +1195,7 @@ h3, .ops-alert-frame-danger { background: linear-gradient( 180deg, - color-mix(in srgb, var(--color-ops-danger) 38%, transparent), + color-mix(in srgb, var(--color-ops-danger) 46%, transparent), rgba(255, 255, 255, 0.04) ); } @@ -1382,45 +1334,48 @@ h3, .ops-telemetry-chip { position: relative; - display: grid; - grid-template-rows: auto minmax(2.75rem, auto) 14px auto; - align-content: start; - row-gap: 0.5rem; overflow: hidden; + background: + linear-gradient(180deg, rgba(255, 255, 255, 0.035), transparent 34%), + linear-gradient(90deg, rgba(110, 231, 183, 0.035), transparent 42%), + rgba(10, 15, 13, 0.16); } .ops-telemetry-chip-primary { - background: linear-gradient(180deg, rgba(110, 231, 183, 0.06), transparent); + background: + linear-gradient(180deg, rgba(110, 231, 183, 0.08), transparent 54%), + linear-gradient(90deg, rgba(110, 231, 183, 0.05), transparent 46%), + rgba(10, 15, 13, 0.18); } -.ops-telemetry-label-row { +.ops-telemetry-label-row, +.ops-telemetry-value-row { display: flex; - align-items: center; + min-width: 0; + align-items: baseline; justify-content: space-between; gap: 0.75rem; } +.ops-telemetry-label-row { + margin-bottom: 0.55rem; +} + .ops-telemetry-register { - display: inline; + flex: 0 0 auto; + border: 1px solid rgba(255, 255, 255, 0.12); + background: rgba(10, 15, 13, 0.34); + padding: 0.125rem 0.35rem; font-family: var(--font-mono); - font-size: 8px; + font-size: 0.625rem; font-weight: 600; - letter-spacing: var(--ops-tracking-grid); line-height: 1; - text-transform: uppercase; - color: var(--color-ops-text-muted); -} - -@media (min-width: 640px) { - .ops-telemetry-register { - font-size: 9px; - } + letter-spacing: var(--ops-tracking-grid); + color: color-mix(in srgb, var(--color-ops-text-secondary) 86%, white 14%); } -.ops-telemetry-value-row { - display: flex; - min-height: 2.75rem; - align-items: end; +.ops-telemetry-value { + display: block; } .ops-telemetry-value-primary { @@ -1429,22 +1384,19 @@ h3, .ops-telemetry-value-secondary { font-size: 1.125rem; - line-height: 1.15; - white-space: normal; } @media (min-width: 640px) { .ops-telemetry-value-secondary { font-size: 1.25rem; - line-height: 1; - white-space: nowrap; } } .ops-telemetry-chip-attention { box-shadow: inset 3px 0 0 var(--ops-status-degraded-border), - inset 0 1px 0 rgba(255, 255, 255, 0.04); + inset 0 1px 0 rgba(255, 255, 255, 0.06), + inset 0 0 0 1px rgba(245, 158, 11, 0.16); } .ops-telemetry-chip-attention-value { @@ -1461,22 +1413,15 @@ h3, .ops-telemetry-spark { display: flex; height: 14px; - margin-top: 0; align-items: end; gap: 2px; - border-bottom: 1px solid rgba(255, 255, 255, 0.08); -} - -.ops-telemetry-rail { - height: 14px; - border-bottom: 1px solid rgba(255, 255, 255, 0.08); + border-bottom: 1px solid rgba(255, 255, 255, 0.1); background: linear-gradient( - 90deg, - transparent, - rgba(255, 255, 255, 0.08), - transparent + 180deg, + transparent 50%, + rgba(255, 255, 255, 0.035) 50% ) - left bottom / 100% 1px no-repeat; + left bottom / 100% 2px no-repeat; } .ops-telemetry-spark-segment { @@ -1511,7 +1456,6 @@ h3, bottom: -1px; width: 2px; height: 16px; - border-radius: 1px; background: var(--color-ops-accent); box-shadow: 0 0 10px rgba(110, 231, 183, 0.2); } @@ -1599,7 +1543,7 @@ h3, } .ops-radio-chip-ghost { - border-color: rgba(255, 255, 255, 0.18); + border-color: rgba(255, 255, 255, 0.16); background: linear-gradient( 180deg, @@ -1625,7 +1569,8 @@ h3, .ops-radio-chip[aria-checked='true'] { box-shadow: - 0 0 0 1px var(--ops-radio-chip-status-border), + 0 0 0 1px + color-mix(in srgb, var(--ops-radio-chip-status-border) 88%, white 12%), inset 0 0 0 1px rgba(255, 255, 255, 0.22), inset 0 1px 0 rgba(255, 255, 255, 0.08), inset 0 -2px 0 rgba(0, 0, 0, 0.32), @@ -1683,15 +1628,11 @@ h3, background: var(--color-ops-surface-3); } -.ops-history-selected-row-header::after { - content: ''; - position: absolute; - top: 0.35rem; - bottom: 0.35rem; - left: 0; - width: 2px; - background: var(--color-ops-accent); - box-shadow: 0 0 10px rgba(110, 231, 183, 0.18); +.ops-history-selected-row-header { + box-shadow: + inset 3px 0 0 var(--ops-sector-border, var(--ops-today-rail)), + inset 0 -1px 0 var(--color-ops-border-soft), + inset 0 1px 0 rgba(255, 255, 255, 0.05); } .ops-history-cell { @@ -1709,8 +1650,7 @@ h3, box-shadow: inset 0 2px 0 var(--ops-status-nominal-border), inset 1px 0 0 var(--ops-today-rail), - inset -1px 0 0 var(--ops-today-rail), - 0 0 10px rgba(110, 231, 183, 0.07); + inset -1px 0 0 var(--ops-today-rail); } .ops-history-today-cell { @@ -1721,7 +1661,10 @@ h3, .ops-history-week-boundary { padding-left: 0.5rem; - box-shadow: inset 1px 0 0 var(--color-ops-border-struct); + box-shadow: + inset 2px 0 0 + color-mix(in srgb, var(--color-ops-accent-deep) 54%, transparent), + inset 3px 0 0 rgba(255, 255, 255, 0.04); } .ops-history-week-band { @@ -1730,26 +1673,48 @@ h3, .ops-history-today-header.ops-history-week-boundary { box-shadow: - inset 2px 0 0 var(--ops-today-rail), + inset 1px 0 0 var(--ops-today-rail), inset -1px 0 0 var(--ops-today-rail), - inset 0 2px 0 var(--ops-status-nominal-border), - 0 0 10px rgba(110, 231, 183, 0.07); + inset 0 2px 0 var(--ops-status-nominal-border); } .ops-history-today-cell.ops-history-week-boundary { box-shadow: - inset 2px 0 0 var(--ops-today-rail), + inset 1px 0 0 var(--ops-today-rail), inset -1px 0 0 rgba(110, 231, 183, 0.22); } +.ops-history-selected-column-header { + box-shadow: + inset 0 2px 0 var(--color-ops-border-strong), + inset 1px 0 0 rgba(255, 255, 255, 0.07), + inset -1px 0 0 rgba(255, 255, 255, 0.05); +} + +.ops-history-selected-column { + box-shadow: + inset 1px 0 0 rgba(255, 255, 255, 0.08), + inset -1px 0 0 rgba(255, 255, 255, 0.05); +} + .ops-mobile-week-pip { height: 4px; width: 1rem; - transition: width var(--ops-motion-fast); + transition: + width var(--ops-motion-fast), + background-color var(--ops-motion-fast), + border-color var(--ops-motion-fast); } -.ops-mobile-week-pip-active { - width: 1.25rem; +.ops-mobile-week-status { + box-shadow: + inset 0 1px 0 rgba(255, 255, 255, 0.06), + inset 0 0 0 1px var(--color-ops-panel-border-strong), + inset 0 -12px 24px rgba(4, 8, 7, 0.18); +} + +.ops-mobile-week-state-chip { + border-color: var(--color-ops-panel-border-strong); } .ops-mobile-day-button { @@ -1766,6 +1731,10 @@ h3, inset 0 1px 0 rgba(255, 255, 255, 0.08); } +.ops-mobile-day-button-selected { + color: var(--ops-status-nominal-text); +} + .ops-history-selected-cell { box-shadow: 0 0 0 1px rgba(110, 231, 183, 0.42), @@ -1783,10 +1752,27 @@ h3, .ops-day-rollup { position: relative; overflow: hidden; + background: + linear-gradient(180deg, rgba(255, 255, 255, 0.06), transparent 30%), + linear-gradient(90deg, rgba(110, 231, 183, 0.08), transparent 36%), + var(--color-ops-surface-raised); +} + +.ops-rollup-count-plate { + border: 1px solid var(--color-ops-panel-border-strong); + background: + linear-gradient(180deg, rgba(255, 255, 255, 0.035), transparent 42%), + rgba(10, 15, 13, 0.3); + padding: 0.75rem 0.875rem; + box-shadow: + inset 0 1px 0 rgba(255, 255, 255, 0.04), + inset 0 -1px 0 rgba(0, 0, 0, 0.28); } -.ops-rollup-pip-grid { - align-items: stretch; +.ops-day-rollup-meter { + box-shadow: + 0 0 0 1px rgba(255, 255, 255, 0.03), + inset 0 1px 0 rgba(255, 255, 255, 0.04); } .ops-rollup-pip-cell { @@ -1798,6 +1784,18 @@ h3, inset 0 0 0 1px rgba(255, 255, 255, 0.03); } +.ops-rollup-pip-cell::before { + content: ''; + position: absolute; + inset: 0; + background: linear-gradient( + 180deg, + rgba(255, 255, 255, 0.08), + transparent 42% + ); + opacity: 0.44; +} + .ops-rollup-pip-glyph { position: relative; z-index: 1; @@ -1872,9 +1870,10 @@ h3, .ops-mobile-active-week { box-shadow: - inset 2px 0 0 var(--ops-today-rail), - inset 0 1px 0 rgba(255, 255, 255, 0.05), - inset 0 0 0 1px var(--color-ops-panel-border-strong); + inset 3px 0 0 var(--ops-today-rail), + inset 0 1px 0 rgba(255, 255, 255, 0.07), + inset 0 0 0 1px var(--color-ops-panel-border-strong), + var(--ops-elevation-1); } .ops-history-brief-spark { @@ -1911,6 +1910,17 @@ h3, color: var(--color-ops-text-secondary); } +.ops-status-legend { + background: + linear-gradient(180deg, rgba(255, 255, 255, 0.035), transparent 34%), + rgba(14, 20, 17, 0.78); +} + +.ops-status-legend-item { + border-top: 1px solid rgba(255, 255, 255, 0.06); + padding-top: 0.375rem; +} + .ops-focus-ring-inset:focus-visible, .ops-focus-ring-inset:focus-within { outline: none; @@ -2236,27 +2246,6 @@ h3, box-shadow: 0 0 10px rgba(110, 231, 183, 0.1); } -.ops-export-accordion-trigger::after { - content: ''; - align-self: center; - flex-shrink: 0; - width: 8px; - height: 8px; - margin-right: 0.25rem; - border-right: 1.5px solid var(--color-ops-text-muted); - border-bottom: 1.5px solid var(--color-ops-text-muted); - opacity: 0.48; - transform: rotate(45deg) translateY(-2px); - transition: - transform var(--ops-motion-fast), - opacity var(--ops-motion-fast); -} - -.ops-export-accordion-trigger-open::after { - opacity: 0.72; - transform: rotate(-135deg) translateY(2px); -} - .ops-export-accordion-trigger-open .ops-export-accordion-cap { width: 6px; background: var(--ops-accordion-cap-color); @@ -2272,10 +2261,6 @@ h3, transition-duration: 0.01ms !important; } - .ops-export-accordion-trigger::after { - transition: none; - } - .ops-action-button:hover, .ops-export-accordion-trigger-collapsed:hover, .ops-history-cell:hover { @@ -2305,12 +2290,22 @@ h3, .ops-domain-glyph, .ops-telemetry-chip, .ops-telemetry-chip-attention, + .ops-telemetry-register, .ops-signal-card, .ops-action-button, .ops-radio-chip, + .ops-rollup-count-plate, + .ops-day-rollup-meter, .ops-rollup-pip-cell, .ops-history-sector-mark, .ops-history-neighbor-row, + .ops-history-selected-row-header, + .ops-history-selected-column-header, + .ops-history-selected-column, + .ops-status-legend, + .ops-status-legend-item, + .ops-mobile-week-status, + .ops-mobile-week-state-chip, .ops-mobile-day-button, .ops-grid-cell, .ops-history-selected-cell, @@ -2322,10 +2317,6 @@ h3, box-shadow: none; } - .ops-telemetry-horizon-label { - box-shadow: none; - } - .ops-section-frame, .ops-frame-emphasis-primary, .ops-frame-emphasis-standard, @@ -2339,14 +2330,11 @@ h3, .ops-hero-rail, .ops-hero-rail::after, .ops-hero-rail-tail, - .ops-hero-rail-mobile, - .ops-section-meta::before, .ops-rule-hairline, + .ops-rollup-pip-cell::before, .ops-rollup-pip-indicator, - .ops-telemetry-rail, .ops-telemetry-spark-today-tick::after, .ops-history-brief-spark-selected::after, - .ops-history-selected-row-header::after, .ops-history-sector-row-header::before, .ops-history-mobile-sector-label::before, .ops-history-edge-left, @@ -2439,8 +2427,7 @@ h3, --notch: var(--ops-notch-chip); min-height: 28px; min-width: 2rem; - width: auto; - flex-shrink: 0; + width: 100%; justify-content: center; padding: 0 0.75rem; gap: 0.5rem; @@ -2448,20 +2435,34 @@ h3, letter-spacing: 0.2em; text-transform: uppercase; color: var(--color-ops-text-secondary); - white-space: nowrap; } @media (min-width: 1024px) { .ops-provenance-facts { grid-auto-flow: column; grid-auto-columns: max-content; - justify-content: start; + justify-content: end; column-gap: 1.5rem; } .ops-provenance-facts > div { grid-template-columns: auto auto; } + + .ops-provenance-facts dd { + text-align: right; + } + + .ops-provenance-source { + justify-self: end; + } +} + +@media (min-width: 1024px) and (max-width: 1535px) { + .ops-provenance-source { + width: 2rem; + padding: 0; + } } @media (max-width: 640px) { @@ -2476,3 +2477,10 @@ h3, .ops-provenance-source:hover { color: var(--color-ops-text-primary); } + +@media (min-width: 640px) { + .ops-provenance-source { + width: auto; + align-self: flex-start; + } +} From e60935eb42e638283041e788b6e210fb803cdb05 Mon Sep 17 00:00:00 2001 From: Bradley Saucier Date: Wed, 6 May 2026 12:43:37 -0400 Subject: [PATCH 10/11] Enhance mobile history test with region checks Add mobile history region locator and scroll check Signed-off-by: Bradley Saucier --- tests/e2e/history-mobile.spec.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tests/e2e/history-mobile.spec.ts b/tests/e2e/history-mobile.spec.ts index e8f20100..1e55a6bc 100644 --- a/tests/e2e/history-mobile.spec.ts +++ b/tests/e2e/history-mobile.spec.ts @@ -30,6 +30,7 @@ test.describe('OpsNormal mobile history', () => { await expect(allDayButtons).toHaveCount(30); const weekStatus = page.getByTestId('mobile-history-week-status'); + const mobileHistoryRegion = page.locator('#history-mobile-region'); const previousWeekButton = page.getByRole('button', { name: /previous week/i, }); @@ -42,6 +43,9 @@ test.describe('OpsNormal mobile history', () => { await expect(nextWeekButton).toBeDisabled(); await expect(weekStatus).toContainText('Week 5 of 5'); + await expect + .poll(async () => mobileHistoryRegion.evaluate((node) => node.scrollLeft)) + .toBeGreaterThan(0); await previousWeekButton.click(); From 33bf96a751c26217645006345c0992639c79cedd Mon Sep 17 00:00:00 2001 From: Bradley Saucier Date: Wed, 6 May 2026 12:57:47 -0400 Subject: [PATCH 11/11] Fix mobile history scroll fallback --- src/features/history/useHistoryGridModel.ts | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/src/features/history/useHistoryGridModel.ts b/src/features/history/useHistoryGridModel.ts index d2b66d04..af15d989 100644 --- a/src/features/history/useHistoryGridModel.ts +++ b/src/features/history/useHistoryGridModel.ts @@ -42,10 +42,20 @@ function scrollWeekIntoContainer( return; } - scrollNode.scrollTo({ - behavior: 'auto', - left: Math.max(weekNode.offsetLeft - scrollNode.offsetLeft, 0), - }); + const nextScrollLeft = Math.max( + weekNode.offsetLeft - scrollNode.offsetLeft, + 0, + ); + + if (typeof scrollNode.scrollTo === 'function') { + scrollNode.scrollTo({ + behavior: 'auto', + left: nextScrollLeft, + }); + return; + } + + scrollNode.scrollLeft = nextScrollLeft; } export interface HistoryGridModel {