diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 295c607..b399948 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -17,16 +17,16 @@ "esbenp.prettier-vscode", "msjsdiag.vscode-react-native" ], - "mcpServers": { - "playwright": { - "command": "npx", - "args": ["@playwright/mcp@latest"] - }, - "lighthouse": { - "command": "npx", - "args": ["lighthouse-mcp"], - "disabled": false, - "autoApprove": [] + "mcp": { + "servers": { + "playwright": { + "command": "npx", + "args": ["@playwright/mcp@latest"] + }, + "lighthouse": { + "command": "npx", + "args": ["lighthouse-mcp"] + } } } } diff --git a/ACCESSIBILITY.md b/ACCESSIBILITY.md index 9312f7c..6d3dbd2 100644 --- a/ACCESSIBILITY.md +++ b/ACCESSIBILITY.md @@ -23,6 +23,14 @@ All colors tested on white background (#ffffff): - **Warning** (#8a6b00): 5.02:1 contrast ratio ✓ - **Danger** (#d1242a): 5.25:1 contrast ratio ✓ +### Button Contrast + +- **Primary button gradient** (linear-gradient(135deg, #0073cc 0%, #cc4400 100%) on white text): + - Start color: 4.86:1 contrast ratio ✓ + - End color: 4.78:1 contrast ratio ✓ + - Minimum: 4.78:1 (exceeds WCAG AA) +- **Outline buttons**: Use solid primary color borders (4.86:1) for proper contrast + ### Dark Mode Color Palette All colors tested on dark background (#1d2026): @@ -34,6 +42,12 @@ All colors tested on dark background (#1d2026): - **Primary Light** (#66b3ff): 7.35:1 contrast ratio ✓ - **Primary Lighter** (#73baff): 7.92:1 contrast ratio ✓ - **Primary Lightest** (#99ccff): 9.66:1 contrast ratio ✓ +- **Secondary text** (#d3d7e2): 11.34:1 contrast ratio ✓ +- **White text** (#ffffff): 16.32:1 contrast ratio ✓ + +### Button Contrast in Dark Mode + +- **Outline buttons**: Use primary-light color (#66b3ff) for borders (7.35:1 contrast ratio) ✓ ### Requirements @@ -57,7 +71,10 @@ All interactive elements are fully accessible via keyboard: - Focus indicators use 3px solid outlines with 3px offset - `:focus-visible` pseudo-class for keyboard-only focus states - Mouse clicks don't show focus rings (better UX) -- Enhanced visibility in both light and dark modes +- Enhanced visibility in both light and dark modes: + - Light mode: Uses primary color (#0073cc) for focus outlines + - Dark mode: Uses primary-light color (#66b3ff) for better visibility +- Focus outlines are visible on all interactive elements ## Screen Reader Support @@ -96,8 +113,10 @@ The theme respects `prefers-reduced-motion` settings: The theme adapts to high contrast preferences: -- Darker primary colors in high contrast mode +- Enhanced primary colors in high contrast mode (#005299 for primary, #993300 for secondary) +- Solid backgrounds for gradient buttons to ensure maximum contrast - Thicker focus outlines (4px instead of 3px) +- Thicker borders on outline buttons (3px instead of 2px) - Enhanced borders for better element distinction - Maintains usability for users with low vision diff --git a/packages/docs/src/pages/index.module.css b/packages/docs/src/pages/index.module.css index fdd46f5..f3c4785 100644 --- a/packages/docs/src/pages/index.module.css +++ b/packages/docs/src/pages/index.module.css @@ -39,6 +39,11 @@ .swatch code { font-family: var(--ifm-font-family-monospace); font-size: 0.85rem; + color: var(--hk-color-dark); +} + +[data-theme="dark"] .swatch code { + color: #fff; } .callout { diff --git a/packages/theme/src/styles/hoverkraft.css b/packages/theme/src/styles/hoverkraft.css index c4071f5..449a282 100644 --- a/packages/theme/src/styles/hoverkraft.css +++ b/packages/theme/src/styles/hoverkraft.css @@ -40,7 +40,7 @@ --ifm-navbar-shadow: 0 1px 3px rgba(29, 32, 38, 0.1); --ifm-button-border-radius: 8px; - --docusaurus-highlighted-code-line-bg: rgba(25, 152, 255, 0.1); + --docusaurus-highlighted-code-line-bg: rgba(0, 115, 204, 0.1); } [data-theme="dark"] { @@ -58,7 +58,7 @@ --ifm-font-color-secondary: #d3d7e2; --ifm-navbar-background-color: rgba(37, 40, 50, 0.95); --ifm-navbar-shadow: 0 1px 3px rgba(0, 0, 0, 0.3); - --docusaurus-highlighted-code-line-bg: rgba(25, 152, 255, 0.2); + --docusaurus-highlighted-code-line-bg: rgba(77, 166, 255, 0.2); } html, @@ -82,7 +82,8 @@ body { } [data-theme="dark"] :focus-visible { - outline: 3px solid var(--ifm-color-primary); + outline: 3px solid var(--ifm-color-primary-light); + outline-offset: 3px; } .navbar { @@ -180,7 +181,7 @@ body { .button.button--primary, .button--primary { - background: linear-gradient(135deg, #1998ff 0%, #ff5a02 100%); + background: linear-gradient(135deg, #0073cc 0%, #cc4400 100%); border: none; border-radius: 999px; color: #ffffff !important; @@ -191,7 +192,7 @@ body { padding: 0.875rem 2.25rem; font-weight: 600; text-decoration: none !important; - box-shadow: 0 8px 24px rgba(25, 152, 255, 0.35); + box-shadow: 0 8px 24px rgba(0, 115, 204, 0.35); transition: transform 0.2s ease, box-shadow 0.2s ease, @@ -201,21 +202,21 @@ body { .button.button--primary:hover, .button--primary:hover { transform: translateY(-2px); - box-shadow: 0 14px 32px rgba(25, 152, 255, 0.45); - filter: brightness(1.03); + box-shadow: 0 14px 32px rgba(0, 115, 204, 0.45); + filter: brightness(1.1); } .button.button--primary:active, .button--primary:active { transform: translateY(0); - box-shadow: 0 6px 18px rgba(25, 152, 255, 0.4); + box-shadow: 0 6px 18px rgba(0, 115, 204, 0.4); } .button.button--primary:focus-visible, .button--primary:focus-visible { box-shadow: - 0 0 0 4px rgba(25, 152, 255, 0.25), - 0 12px 28px rgba(25, 152, 255, 0.45); + 0 0 0 4px rgba(0, 115, 204, 0.25), + 0 12px 28px rgba(0, 115, 204, 0.45); } .button.button--secondary, @@ -224,7 +225,7 @@ body { .button--outline { background: transparent; border-radius: 999px; - border: 2px solid rgba(25, 152, 255, 0.35); + border: 2px solid var(--hk-color-primary); color: var(--hk-color-dark); display: inline-flex; align-items: center; @@ -245,7 +246,7 @@ body { [data-theme="dark"] .button.button--outline, [data-theme="dark"] .button--outline { color: #ffffff; - border-color: rgba(255, 255, 255, 0.4); + border-color: var(--ifm-color-primary); } .button.button--secondary:hover, @@ -253,23 +254,23 @@ body { .button.button--outline:hover, .button--outline:hover { transform: translateY(-2px); - background: rgba(25, 152, 255, 0.08); - border-color: rgba(25, 152, 255, 0.5); + background: rgba(0, 115, 204, 0.08); + border-color: var(--hk-color-primary); } [data-theme="dark"] .button.button--secondary:hover, [data-theme="dark"] .button--secondary:hover, [data-theme="dark"] .button.button--outline:hover, [data-theme="dark"] .button--outline:hover { - background: rgba(255, 255, 255, 0.12); - border-color: rgba(255, 255, 255, 0.6); + background: rgba(77, 166, 255, 0.12); + border-color: var(--ifm-color-primary-light); } .button.button--secondary:focus-visible, .button--secondary:focus-visible, .button.button--outline:focus-visible, .button--outline:focus-visible { - box-shadow: 0 0 0 4px rgba(25, 152, 255, 0.25); + box-shadow: 0 0 0 4px rgba(0, 115, 204, 0.25); } a { @@ -350,18 +351,18 @@ a:hover { .hero .button.button--primary, .hero .button--primary { - box-shadow: 0 12px 30px rgba(25, 152, 255, 0.45); + box-shadow: 0 12px 30px rgba(0, 115, 204, 0.45); } .hero .button.button--primary:hover, .hero .button--primary:hover { - box-shadow: 0 18px 40px rgba(25, 152, 255, 0.5); + box-shadow: 0 18px 40px rgba(0, 115, 204, 0.5); } @media (prefers-contrast: high) { :root { - --hk-color-primary: #0056b3; - --hk-color-secondary: #b33d00; + --hk-color-primary: #005299; + --hk-color-secondary: #993300; } :focus-visible { @@ -371,6 +372,19 @@ a:hover { .navbar { border-bottom: 2px solid currentColor; } + + .button.button--primary, + .button--primary { + background: #005299; + border: 2px solid #003d73; + } + + .button.button--secondary, + .button--secondary, + .button.button--outline, + .button--outline { + border-width: 3px; + } } @media (prefers-reduced-motion: reduce) { diff --git a/packages/theme/src/theme/hoverscape/HoverkraftBrandHighlight.module.css b/packages/theme/src/theme/hoverscape/HoverkraftBrandHighlight.module.css index 8f76f91..a3571f6 100644 --- a/packages/theme/src/theme/hoverscape/HoverkraftBrandHighlight.module.css +++ b/packages/theme/src/theme/hoverscape/HoverkraftBrandHighlight.module.css @@ -1,5 +1,9 @@ .highlight { - background: linear-gradient(45deg, #1998ff, #ff5a02); + background: linear-gradient( + 45deg, + var(--hk-color-primary, #0073cc), + var(--hk-color-secondary, #cc4400) + ); background-clip: text; -webkit-background-clip: text; -webkit-text-fill-color: transparent; diff --git a/packages/theme/src/theme/hoverscape/HoverkraftButton.module.css b/packages/theme/src/theme/hoverscape/HoverkraftButton.module.css index 82935d4..92a553a 100644 --- a/packages/theme/src/theme/hoverscape/HoverkraftButton.module.css +++ b/packages/theme/src/theme/hoverscape/HoverkraftButton.module.css @@ -18,7 +18,12 @@ } .button:focus-visible { - outline: 3px solid rgba(25, 152, 255, 0.4); + outline: 3px solid var(--hk-color-primary, #0073cc); + outline-offset: 2px; +} + +[data-theme="dark"] .button:focus-visible { + outline: 3px solid var(--ifm-color-primary-light, #66b3ff); outline-offset: 2px; } @@ -43,70 +48,70 @@ } .primary { - background: linear-gradient(135deg, #1998ff 0%, #ff5a02 100%); - box-shadow: 0 8px 24px rgba(25, 152, 255, 0.35); + background: linear-gradient(135deg, #0073cc 0%, #cc4400 100%); + box-shadow: 0 8px 24px rgba(0, 115, 204, 0.35); } .primary:hover { transform: translateY(-2px); - box-shadow: 0 14px 32px rgba(25, 152, 255, 0.45); - filter: brightness(1.03); + box-shadow: 0 14px 32px rgba(0, 115, 204, 0.45); + filter: brightness(1.1); color: #ffffff; - text-shadow: 0 3px 12px rgba(8, 43, 80, 0.35); + text-shadow: 0 3px 12px rgba(0, 0, 0, 0.35); } .primary:active { transform: translateY(0); - box-shadow: 0 6px 18px rgba(25, 152, 255, 0.35); + box-shadow: 0 6px 18px rgba(0, 115, 204, 0.35); } .secondary { - background: rgba(25, 152, 255, 0.15); - border: 2px solid rgba(25, 152, 255, 0.5); + background: rgba(0, 115, 204, 0.15); + border: 2px solid var(--hk-color-primary, #0073cc); color: var(--hk-color-dark, #1d2026); - box-shadow: 0 2px 8px rgba(25, 152, 255, 0.15); + box-shadow: 0 2px 8px rgba(0, 115, 204, 0.15); } .secondary:hover { transform: translateY(-2px); - background: rgba(25, 152, 255, 0.22); - border-color: rgba(25, 152, 255, 0.65); - box-shadow: 0 4px 12px rgba(25, 152, 255, 0.25); - text-shadow: 0 3px 12px rgba(8, 43, 80, 0.35); + background: rgba(0, 115, 204, 0.22); + border-color: var(--hk-color-primary-dark, #0066b8); + box-shadow: 0 4px 12px rgba(0, 115, 204, 0.25); + text-shadow: 0 3px 12px rgba(0, 0, 0, 0.35); } .outline { background: transparent; - border: 2px solid rgba(25, 152, 255, 0.35); + border: 2px solid var(--hk-color-primary, #0073cc); color: var(--hk-color-dark, #1d2026); } .outline:hover { transform: translateY(-2px); - background: rgba(25, 152, 255, 0.08); - border-color: rgba(25, 152, 255, 0.5); - text-shadow: 0 3px 12px rgba(8, 43, 80, 0.35); + background: rgba(0, 115, 204, 0.08); + border-color: var(--hk-color-primary, #0073cc); + text-shadow: 0 3px 12px rgba(0, 0, 0, 0.35); } [data-theme="dark"] .secondary { - background: rgba(25, 152, 255, 0.25); - border-color: rgba(25, 152, 255, 0.6); + background: rgba(77, 166, 255, 0.25); + border-color: var(--ifm-color-primary, #4da6ff); color: #ffffff; - box-shadow: 0 2px 8px rgba(25, 152, 255, 0.2); + box-shadow: 0 2px 8px rgba(77, 166, 255, 0.2); } [data-theme="dark"] .secondary:hover { - background: rgba(25, 152, 255, 0.35); - border-color: rgba(25, 152, 255, 0.75); - box-shadow: 0 4px 12px rgba(25, 152, 255, 0.3); + background: rgba(77, 166, 255, 0.35); + border-color: var(--ifm-color-primary-light, #66b3ff); + box-shadow: 0 4px 12px rgba(77, 166, 255, 0.3); } [data-theme="dark"] .outline { color: #ffffff; - border-color: rgba(255, 255, 255, 0.4); + border-color: var(--ifm-color-primary-light, #66b3ff); } [data-theme="dark"] .outline:hover { - background: rgba(255, 255, 255, 0.12); - border-color: rgba(255, 255, 255, 0.6); + background: rgba(77, 166, 255, 0.12); + border-color: var(--ifm-color-primary-light, #66b3ff); } diff --git a/packages/theme/src/theme/hoverscape/HoverkraftFeatureList.module.css b/packages/theme/src/theme/hoverscape/HoverkraftFeatureList.module.css index 6482e21..e651a70 100644 --- a/packages/theme/src/theme/hoverscape/HoverkraftFeatureList.module.css +++ b/packages/theme/src/theme/hoverscape/HoverkraftFeatureList.module.css @@ -22,8 +22,8 @@ .featureCard:hover { transform: translateY(-4px); - box-shadow: 0 16px 30px rgba(25, 152, 255, 0.2); - border-color: #1998ff; + box-shadow: 0 16px 30px rgba(0, 115, 204, 0.2); + border-color: var(--hk-color-primary, #0073cc); } [data-theme="dark"] .featureCard { diff --git a/packages/theme/src/theme/hoverscape/HoverkraftHero.module.css b/packages/theme/src/theme/hoverscape/HoverkraftHero.module.css index 236ef44..4b17422 100644 --- a/packages/theme/src/theme/hoverscape/HoverkraftHero.module.css +++ b/packages/theme/src/theme/hoverscape/HoverkraftHero.module.css @@ -11,7 +11,7 @@ 135deg, var(--hk-color-dark, #1d2026) 0%, var(--hk-color-gray, #506690) 50%, - var(--ifm-color-primary, #1998ff) 100% + var(--ifm-color-primary, #0073cc) 100% ); } @@ -20,7 +20,7 @@ } .daylight .heroBackground { - background: linear-gradient(135deg, rgba(25, 152, 255, 0.12) 0%, rgba(255, 90, 2, 0.18) 100%); + background: linear-gradient(135deg, rgba(0, 115, 204, 0.12) 0%, rgba(204, 68, 0, 0.18) 100%); } .daylight .heroContent { @@ -38,7 +38,7 @@ 135deg, var(--hk-color-dark, #1d2026) 0%, var(--hk-color-gray, #506690) 50%, - var(--ifm-color-primary, #1998ff) 100% + var(--ifm-color-primary, #0073cc) 100% ); opacity: 0.95; filter: saturate(1.02); diff --git a/packages/theme/src/theme/hoverscape/HoverkraftProjectCard.module.css b/packages/theme/src/theme/hoverscape/HoverkraftProjectCard.module.css index 405d894..d3b1df9 100644 --- a/packages/theme/src/theme/hoverscape/HoverkraftProjectCard.module.css +++ b/packages/theme/src/theme/hoverscape/HoverkraftProjectCard.module.css @@ -14,14 +14,14 @@ } .primary { - background: linear-gradient(135deg, rgba(25, 152, 255, 0.08) 0%, rgba(255, 90, 2, 0.06) 100%); - border-color: rgba(25, 152, 255, 0.3); - box-shadow: 0 4px 12px rgba(25, 152, 255, 0.15); + background: linear-gradient(135deg, rgba(0, 115, 204, 0.08) 0%, rgba(204, 68, 0, 0.06) 100%); + border-color: rgba(0, 115, 204, 0.3); + box-shadow: 0 4px 12px rgba(0, 115, 204, 0.15); } .primary:hover { - border-color: rgba(25, 152, 255, 0.5); - box-shadow: 0 12px 24px rgba(25, 152, 255, 0.2); + border-color: rgba(0, 115, 204, 0.5); + box-shadow: 0 12px 24px rgba(0, 115, 204, 0.2); } .neutral { @@ -39,14 +39,14 @@ } [data-theme="dark"] .primary { - background: linear-gradient(135deg, rgba(25, 152, 255, 0.15) 0%, rgba(255, 90, 2, 0.1) 100%); - border-color: rgba(25, 152, 255, 0.4); - box-shadow: 0 4px 12px rgba(25, 152, 255, 0.2); + background: linear-gradient(135deg, rgba(77, 166, 255, 0.15) 0%, rgba(204, 68, 0, 0.1) 100%); + border-color: rgba(77, 166, 255, 0.4); + box-shadow: 0 4px 12px rgba(77, 166, 255, 0.2); } [data-theme="dark"] .primary:hover { - border-color: rgba(25, 152, 255, 0.6); - box-shadow: 0 12px 24px rgba(25, 152, 255, 0.3); + border-color: rgba(77, 166, 255, 0.6); + box-shadow: 0 12px 24px rgba(77, 166, 255, 0.3); } [data-theme="dark"] .neutral { @@ -96,7 +96,7 @@ .projectMeta { font-size: 0.9rem; - color: #1998ff; + color: var(--hk-color-primary, #0073cc); margin: 0; font-weight: 500; } @@ -128,8 +128,8 @@ font-size: 0.85rem; padding: 0.35rem 0.75rem; border-radius: 999px; - background: rgba(25, 152, 255, 0.12); - color: #1998ff; + background: rgba(0, 115, 204, 0.12); + color: var(--hk-color-primary, #0073cc); } [data-theme="dark"] .projectTag {