diff --git a/packages/web/src/features/auth/CaregiverPortalPage.tsx b/packages/web/src/features/auth/CaregiverPortalPage.tsx index 43374c88..3aa9fed3 100644 --- a/packages/web/src/features/auth/CaregiverPortalPage.tsx +++ b/packages/web/src/features/auth/CaregiverPortalPage.tsx @@ -98,7 +98,8 @@ export function CaregiverPortalPage() { type="button" onClick={logout} style={{ - backgroundColor: 'transparent', + background: 'transparent', + boxShadow: 'none', border: '1px solid rgba(255,255,255,0.1)', color: 'var(--color-text-subtle)', borderRadius: '9px', diff --git a/packages/web/src/features/compliance-engine/ClaimMatchingPage.tsx b/packages/web/src/features/compliance-engine/ClaimMatchingPage.tsx index dba52393..b004b91c 100644 --- a/packages/web/src/features/compliance-engine/ClaimMatchingPage.tsx +++ b/packages/web/src/features/compliance-engine/ClaimMatchingPage.tsx @@ -80,7 +80,8 @@ const primaryButtonStyle: React.CSSProperties = { }; const ghostButtonStyle: React.CSSProperties = { - backgroundColor: 'transparent', + background: 'transparent', + boxShadow: 'none', border: '1px solid var(--color-border)', borderRadius: 8, color: 'var(--color-text)', diff --git a/packages/web/src/features/learning/CopilotChatPage.tsx b/packages/web/src/features/learning/CopilotChatPage.tsx index 464211d1..685a05bd 100644 --- a/packages/web/src/features/learning/CopilotChatPage.tsx +++ b/packages/web/src/features/learning/CopilotChatPage.tsx @@ -476,7 +476,8 @@ const confirmBtnStyle: React.CSSProperties = { }; const declineBtnStyle: React.CSSProperties = { - backgroundColor: 'transparent', + background: 'transparent', + boxShadow: 'none', color: 'var(--color-primary-dark)', border: '1px solid var(--color-primary-light)', padding: '0.45rem 0.9rem', diff --git a/packages/web/src/features/learning/EnrollCaregiverModal.tsx b/packages/web/src/features/learning/EnrollCaregiverModal.tsx index 57426ad9..e72fc733 100644 --- a/packages/web/src/features/learning/EnrollCaregiverModal.tsx +++ b/packages/web/src/features/learning/EnrollCaregiverModal.tsx @@ -407,7 +407,8 @@ const primaryButtonStyle: React.CSSProperties = { }; const secondaryButtonStyle: React.CSSProperties = { - backgroundColor: 'transparent', + background: 'transparent', + boxShadow: 'none', color: 'var(--color-text-secondary)', border: '1px solid var(--color-border-strong)', padding: '0.55rem 1.1rem', diff --git a/packages/web/src/features/onboarding/ApplicantDetailPage.tsx b/packages/web/src/features/onboarding/ApplicantDetailPage.tsx index 9c2ea319..184eaf0d 100644 --- a/packages/web/src/features/onboarding/ApplicantDetailPage.tsx +++ b/packages/web/src/features/onboarding/ApplicantDetailPage.tsx @@ -269,7 +269,8 @@ export function ApplicantDetailPage() { alignItems: 'center', gap: '0.4rem', padding: '0.4rem 0.75rem', - backgroundColor: 'transparent', + background: 'transparent', + boxShadow: 'none', border: '1px solid var(--color-border)', borderRadius: '6px', fontSize: '0.875rem', diff --git a/packages/web/src/features/onboarding/OnboardingHubPage.tsx b/packages/web/src/features/onboarding/OnboardingHubPage.tsx index 57041bc4..d02b0a40 100644 --- a/packages/web/src/features/onboarding/OnboardingHubPage.tsx +++ b/packages/web/src/features/onboarding/OnboardingHubPage.tsx @@ -249,7 +249,8 @@ export function OnboardingHubPage() { border: 'none', borderBottom: activeTab === tab.value ? '2px solid var(--color-primary-dark)' : '2px solid transparent', - backgroundColor: 'transparent', + background: 'transparent', + boxShadow: 'none', color: activeTab === tab.value ? 'var(--color-primary-dark)' : 'var(--color-text-muted)', fontWeight: activeTab === tab.value ? 600 : 400, fontSize: '0.875rem', diff --git a/packages/web/src/index.css b/packages/web/src/index.css index bb9122ac..af41eba3 100644 --- a/packages/web/src/index.css +++ b/packages/web/src/index.css @@ -27,19 +27,31 @@ --color-surface-soft: #F1F5F9; --color-sidebar: #0F172A; /* slate-900, dark sidebar (kept) */ --color-sidebar-hover: rgba(255, 255, 255, 0.06); - --color-sidebar-active: rgba(16, 116, 128, 0.22); /* purple-tinted active */ - --color-sidebar-text: #94A3B8; + --color-sidebar-active: rgba(16, 116, 128, 0.22); + --color-brand-badge-bg: rgba(16, 116, 128, 0.2); + --color-brand-badge-text: #7FC7CF; + --color-sidebar-text: var(--color-text-muted-on-dark); --color-sidebar-text-active: #FFFFFF; /* Borders */ --color-border: #E2E8F0; /* slate-200 */ --color-border-strong: #CBD5E1; /* slate-300 */ + --color-border-hover: #94A3B8; /* slate-400, border under a hovered control */ /* Text */ --color-text: #0F172A; /* slate-900 */ --color-text-secondary: #475569; /* slate-600 */ --color-text-muted: #64748B; /* slate-500, kept richer than design spec for legibility */ - --color-text-subtle: #94A3B8; + /* Was #94A3B8 (slate-400), which is 2.56:1 on white. It is used almost + exclusively on 11-13px text, where WCAG AA allows no large-text + exception, so it is now the lightest slate that still clears 4.5:1. + --color-sidebar-text keeps the original value: on the dark rail the + same hex measures 6.96:1 and is perfectly readable. */ + --color-text-subtle: #637895; + /* De-emphasized text on a DARK surface (sidebar, command panel). Kept light + on purpose: contrast runs the other way there, and --color-text-subtle + would be nearly unreadable. */ + --color-text-muted-on-dark: #94A3B8; /* Semantic, kept independent of brand colors. Danger uses red-600 (not the accent red-700) so a true error state reads distinct from brand chrome. */ @@ -51,6 +63,7 @@ --color-danger-bg: #FEF2F2; --color-danger-border: #FECACA; --color-danger-text: #BE123C; /* readable rose-700 for text on -bg */ + --color-danger-hover: #E11D48; /* rose-600, hover fill for .btn-danger */ --color-warning: #B45309; /* amber-700, safe with white labels */ --color-warning-bg: #FFFBEB; --color-warning-border: #FCD34D; @@ -213,7 +226,7 @@ textarea { input:hover:not(:disabled), select:hover:not(:disabled), textarea:hover:not(:disabled) { - border-color: #94A3B8; + border-color: var(--color-border-hover); } input:focus, @@ -324,48 +337,61 @@ button:disabled { cursor: not-allowed; } +/* Every variant below MUST use the `background` shorthand, never the + `background-color` longhand. The base `button` rule above paints + `--gradient-brand` via the shorthand, which sets background-image, and a + longhand `background-color` cannot clear a background-image , the gradient + would keep painting on top of the fill the variant asked for. That is how + .btn-ghost ended up rendering brand-on-brand (invisible) text. The same + reasoning applies to box-shadow: the base rule sets the brand glow, so any + variant that is not a brand-filled button has to turn it off. + scripts/css-contract-scan.ts enforces this. */ + .btn-primary { - background-color: var(--color-primary); - color: white; + background: var(--color-primary); + color: var(--color-on-brand); border-color: var(--color-primary); } .btn-primary:hover:not(:disabled) { - background-color: var(--color-primary-dark); + background: var(--color-primary-dark); border-color: var(--color-primary-dark); } .btn-secondary { - background-color: var(--color-surface); + background: var(--color-surface); color: var(--color-text); border: 1px solid var(--color-border-strong); + box-shadow: var(--shadow-sm); } .btn-secondary:hover:not(:disabled) { - background-color: #F1F5F9; - border-color: #94A3B8; + background: var(--color-surface-soft); + border-color: var(--color-border-hover); } .btn-ghost { - background-color: transparent; + background: transparent; color: var(--color-primary); border: 1px solid transparent; + box-shadow: none; } .btn-ghost:hover:not(:disabled) { - background-color: var(--color-primary-bg); + background: var(--color-primary-bg); color: var(--color-primary-dark); } .btn-danger { - background-color: var(--color-danger); - color: white; + background: var(--color-danger); + color: var(--color-on-brand); border-color: var(--color-danger); + box-shadow: none; } .btn-danger:hover:not(:disabled) { - background-color: #E11D48; - border-color: #E11D48; + background: var(--color-danger-hover); + border-color: var(--color-danger-hover); } .btn-sm {