From fd125959bf21c6278aba8aa726c78664cf7180b0 Mon Sep 17 00:00:00 2001 From: Devanshu Rajesh Chicholikar Date: Tue, 21 Apr 2026 20:30:40 -0400 Subject: [PATCH 1/2] fix(overlay): WCAG token fixes, semantic palette, health colors, nudge info [GET-15] --- ui/overlay-styles.ts | 71 ++++++++++++++++++++++++++++++-------------- 1 file changed, 48 insertions(+), 23 deletions(-) diff --git a/ui/overlay-styles.ts b/ui/overlay-styles.ts index f8e429c..a8a8c24 100644 --- a/ui/overlay-styles.ts +++ b/ui/overlay-styles.ts @@ -18,12 +18,12 @@ export const OVERLAY_CSS = ` --lco-warn-bg: rgba(245, 158, 11, 0.09); /* Dark mode (default on claude.ai) */ - --lco-bg: rgba(30, 30, 32, 0.82); - --lco-bg-hover: rgba(38, 38, 42, 0.88); + --lco-bg: rgba(30, 30, 28, 0.92); /* was .82 — prevents muted text failing on light page content bleedthrough */ + --lco-bg-hover: rgba(38, 38, 36, 0.95); --lco-text: #d4d4d8; - --lco-muted: #71717a; - --lco-border: rgba(255, 255, 255, 0.06); - --lco-border-hover: rgba(255, 255, 255, 0.12); + --lco-muted: #8a8a93; /* was #71717a — bumped for WCAG AA headroom (~5.8:1 on dark surface) */ + --lco-border: rgba(255, 255, 255, 0.12); /* was .06 — invisible on claude.ai dark panels */ + --lco-border-hover: rgba(255, 255, 255, 0.18); -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; @@ -31,10 +31,10 @@ export const OVERLAY_CSS = ` @media (prefers-color-scheme: light) { :host { - --lco-bg: rgba(244, 243, 238, 0.85); - --lco-bg-hover: rgba(238, 236, 230, 0.90); + --lco-bg: rgba(244, 243, 238, 0.92); /* matched to dark mode .92 floor */ + --lco-bg-hover: rgba(238, 236, 230, 0.95); --lco-text: #27272a; - --lco-muted: #a1a1aa; + --lco-muted: #6b7280; /* was #a1a1aa (~3.2:1 fail on warm cream) — gray-500 gives ~4.8:1 AA */ --lco-accent: #b35a34; --lco-bar-fill: #b35a34; --lco-bar-glow: rgba(179, 90, 52, 0.20); @@ -42,8 +42,8 @@ export const OVERLAY_CSS = ` --lco-warn-fill: #d97706; --lco-warn-glow: rgba(217, 119, 6, 0.20); --lco-warn-bg: rgba(217, 119, 6, 0.08); - --lco-border: rgba(0, 0, 0, 0.06); - --lco-border-hover: rgba(0, 0, 0, 0.12); + --lco-border: rgba(0, 0, 0, 0.08); /* was .06 — widget edge was missing in light mode */ + --lco-border-hover: rgba(0, 0, 0, 0.14); } } @@ -60,8 +60,8 @@ export const OVERLAY_CSS = ` } @keyframes lco-dot-pulse { - 0%, 100% { box-shadow: 0 0 4px rgba(248, 113, 113, 0.4); } - 50% { box-shadow: 0 0 10px rgba(248, 113, 113, 0.7); } + 0%, 100% { box-shadow: 0 0 4px rgba(239, 68, 68, 0.4); } + 50% { box-shadow: 0 0 10px rgba(239, 68, 68, 0.7); } } @keyframes lco-nudge-in { @@ -237,9 +237,9 @@ export const OVERLAY_CSS = ` Instant swap avoids a paint-layer transition on the main thread. */ } -.lco-health-dot--healthy { background: #4ade80; box-shadow: 0 0 4px rgba(74, 222, 128, 0.4); } -.lco-health-dot--degrading { background: #fbbf24; box-shadow: 0 0 4px rgba(251, 191, 36, 0.4); } -.lco-health-dot--critical { background: #f87171; animation: lco-dot-pulse 2s ease-in-out infinite; } +.lco-health-dot--healthy { background: #86efac; box-shadow: 0 0 4px rgba(134, 239, 172, 0.4); } +.lco-health-dot--degrading { background: #f59e0b; box-shadow: 0 0 4px rgba(245, 158, 11, 0.4); } +.lco-health-dot--critical { background: #ef4444; animation: lco-dot-pulse 2s ease-in-out infinite; } .lco-health-label { font-size: 10px; @@ -248,9 +248,9 @@ export const OVERLAY_CSS = ` /* No transition: color is a paint property; health state changes snap instantly. */ } -.lco-health-label--healthy { color: #4ade80; } -.lco-health-label--degrading { color: #fbbf24; } -.lco-health-label--critical { color: #f87171; } +.lco-health-label--healthy { color: #86efac; } +.lco-health-label--degrading { color: #f59e0b; } +.lco-health-label--critical { color: #ef4444; } .lco-coaching { font-size: 9px; @@ -292,6 +292,30 @@ export const OVERLAY_CSS = ` outline-offset: 2px; } +/* Critical state: filled button — more urgent than the outline used at degrading */ +.lco-start-fresh--critical { + background: #c15f3c; + color: rgba(255, 255, 255, 0.92); + border-color: transparent; + box-shadow: 0 2px 10px rgba(193, 95, 60, 0.35); +} + +.lco-start-fresh--critical:hover { + background: #a8522f; + box-shadow: 0 4px 14px rgba(193, 95, 60, 0.45); +} + +.lco-start-fresh--critical:active { + background: #944829; + transform: scale(0.97); + box-shadow: 0 1px 6px rgba(193, 95, 60, 0.3); +} + +.lco-start-fresh--critical:focus-visible { + outline: 2px solid #c15f3c; + outline-offset: 2px; +} + /* ── Progress bars ── */ .lco-bar-row { @@ -331,9 +355,9 @@ export const OVERLAY_CSS = ` box-shadow: 0 0 6px var(--lco-warn-glow); } -.lco-bar-fill--healthy { background: #4ade80; box-shadow: 0 0 6px rgba(74, 222, 128, 0.3); } -.lco-bar-fill--degrading { background: #fbbf24; box-shadow: 0 0 6px rgba(251, 191, 36, 0.3); } -.lco-bar-fill--critical { background: #f87171; box-shadow: 0 0 6px rgba(248, 113, 113, 0.3); } +.lco-bar-fill--healthy { background: #86efac; box-shadow: 0 0 6px rgba(134, 239, 172, 0.3); } +.lco-bar-fill--degrading { background: #f59e0b; box-shadow: 0 0 6px rgba(245, 158, 11, 0.3); } +.lco-bar-fill--critical { background: #ef4444; box-shadow: 0 0 6px rgba(239, 68, 68, 0.3); } .lco-bar-fill.lco-streaming { animation: lco-bar-pulse 1.2s ease-in-out infinite; @@ -364,7 +388,7 @@ export const OVERLAY_CSS = ` animation: lco-nudge-in 0.2s cubic-bezier(0.16, 1, 0.3, 1) forwards; } -.lco-nudge--info { background: rgba(99, 179, 237, 0.09); border-left: 2px solid #63b3ed; } +.lco-nudge--info { background: rgba(107, 140, 174, 0.09); border-left: 2px solid #6b8cae; } /* desaturated steel from terracotta undertones — no pure blue in palette */ .lco-nudge--warning { background: rgba(245, 158, 11, 0.11); border-left: 2px solid #f59e0b; } .lco-nudge--critical { background: rgba(239, 68, 68, 0.11); border-left: 2px solid #ef4444; } @@ -415,7 +439,8 @@ export const OVERLAY_CSS = ` .lco-bar-fill { transition: none; } .lco-bar-fill.lco-streaming { animation: none; } .lco-health-dot--critical { animation: none; } - .lco-start-fresh { transition: none; } + .lco-start-fresh, + .lco-start-fresh--critical { transition: none; } .lco-nudge, .lco-nudge--exiting { animation: none; } .lco-nudge-dismiss { transition: none; } From c683860d581ad978cff40be653749a3260359d70 Mon Sep 17 00:00:00 2001 From: Devanshu Rajesh Chicholikar Date: Tue, 21 Apr 2026 20:30:43 -0400 Subject: [PATCH 2/2] fix(overlay): collapsed pill shows session total and health dot, critical button filled [GET-15] --- ui/overlay.ts | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/ui/overlay.ts b/ui/overlay.ts index a13bba1..37eaee0 100644 --- a/ui/overlay.ts +++ b/ui/overlay.ts @@ -51,6 +51,7 @@ export function createOverlay(): OverlayHandle { let elNudgeDismiss: HTMLButtonElement | null = null; let elHealth: HTMLElement | null = null; let elCostMini: HTMLElement | null = null; + let elHealthDotMini: HTMLElement | null = null; let nudgeHideTimer: ReturnType | null = null; let elDraftRow: HTMLElement | null = null; let elDraftValue: HTMLElement | null = null; @@ -80,8 +81,15 @@ export function createOverlay(): OverlayHandle { costMini.style.display = 'none'; // shown only when collapsed elCostMini = costMini; + // Health dot shown in collapsed pill — sole health signal when minimized. + const healthDotMini = document.createElement('span'); + healthDotMini.className = 'lco-health-dot'; + healthDotMini.style.display = 'none'; + elHealthDotMini = healthDotMini; + header.appendChild(title); header.appendChild(costMini); + header.appendChild(healthDotMini); widget.appendChild(header); // Body — collapsible @@ -259,6 +267,7 @@ export function createOverlay(): OverlayHandle { collapsed = !collapsed; body.classList.toggle('lco-body--collapsed', collapsed); costMini.style.display = collapsed ? '' : 'none'; + healthDotMini.style.display = collapsed ? '' : 'none'; widget.classList.toggle('lco-collapsed', collapsed); }); } @@ -359,9 +368,11 @@ export function createOverlay(): OverlayHandle { } // "Start fresh" button: visible when Degrading or Critical. + // Critical gets a filled variant; degrading keeps the outline. if (elStartFresh) { const showFresh = state.health !== null && state.health.level !== 'healthy'; elStartFresh.style.display = showFresh ? '' : 'none'; + elStartFresh.classList.toggle('lco-start-fresh--critical', state.health?.level === 'critical'); } if (elLimitRow && elLimitFill && elLimitLabel) { @@ -393,8 +404,16 @@ export function createOverlay(): OverlayHandle { } } - if (elCostMini && state.lastRequest) { - elCostMini.textContent = fmtCost(state.lastRequest.cost); + // Collapsed pill: show session total (not last reply cost). + // Cost color stays terra cotta regardless of health state — dot is the sole health signal. + if (elCostMini && state.session.requestCount > 0) { + elCostMini.textContent = fmtCost(state.session.totalCost); + } + + // Collapsed health dot: mirrors the expanded dot color. + if (elHealthDotMini) { + const level = state.health?.level ?? 'healthy'; + elHealthDotMini.className = `lco-health-dot lco-health-dot--${level}`; } }