Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions e2e-tests/home.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,4 +24,20 @@ test.describe('Home Page', () => {
// Check that the welcome message is present using more specific locator
await expect(page.getByText('Find your next game! And maybe even back one! Explore our collection!')).toBeVisible();
});

test('should toggle high contrast mode and persist preference', async ({ page }) => {
const contrastToggle = page.getByTestId('contrast-toggle');

await expect(contrastToggle).toHaveAttribute('aria-pressed', 'false');

await contrastToggle.click();

await expect(contrastToggle).toHaveAttribute('aria-pressed', 'true');
await expect(page.locator('html')).toHaveClass(/high-contrast/);

await page.reload();

await expect(page.locator('html')).toHaveClass(/high-contrast/);
await expect(page.getByTestId('contrast-toggle')).toHaveAttribute('aria-pressed', 'true');
});
});
57 changes: 55 additions & 2 deletions src/components/Header.astro
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@
import Menu from '../assets/icons/menu.svg';
---

<header class="bg-blue-500 dark:bg-blue-700 text-white p-4 shadow-md transition-colors duration-300">
<header class="bg-blue-700 text-white p-4 shadow-md transition-colors duration-300">
<div class="container mx-auto flex justify-between items-center pl-4 pr-4">
<div class="flex items-center">
<div class="relative group">
<button id="menu-toggle" class="focus:outline-none focus:ring-2 focus:ring-white focus:ring-offset-2 focus:ring-offset-blue-500 rounded mr-4" aria-label="Toggle menu" aria-expanded="false" aria-controls="menu">
<button id="menu-toggle" class="focus:outline-none focus:ring-2 focus:ring-white focus:ring-offset-2 focus:ring-offset-blue-700 rounded mr-4" aria-label="Toggle menu" aria-expanded="false" aria-controls="menu" data-testid="menu-toggle">
<Menu class="h-6 w-6" aria-hidden="true" />
</button>
<nav id="menu" aria-label="Main" class="hidden absolute left-0 mt-2 w-48 bg-white dark:bg-slate-800 text-slate-800 dark:text-white rounded shadow-lg z-10 transition-colors duration-300">
Expand All @@ -18,10 +18,63 @@ import Menu from '../assets/icons/menu.svg';
</div>
<div class="text-xl font-bold"><a href="/" class="hover:underline">Tailspin Toys</a></div>
</div>
<button
id="contrast-toggle"
type="button"
class="rounded border border-white px-3 py-2 text-sm font-medium hover:bg-blue-600 focus:outline-none focus:ring-2 focus:ring-white focus:ring-offset-2 focus:ring-offset-blue-700"
aria-label="Enable high contrast mode"
aria-pressed="false"
data-testid="contrast-toggle"
>
<span id="contrast-toggle-label">High Contrast: Off</span>
</button>
</div>
</header>

<script>
const HIGH_CONTRAST_STORAGE_KEY = 'tailspin-high-contrast';
const contrastToggle = document.getElementById('contrast-toggle');
const contrastToggleLabel = document.getElementById('contrast-toggle-label');

const setHighContrastState = (enabled: boolean) => {
document.documentElement.classList.toggle('high-contrast', enabled);

if (contrastToggle) {
contrastToggle.setAttribute('aria-pressed', String(enabled));
contrastToggle.setAttribute(
'aria-label',
enabled ? 'Disable high contrast mode' : 'Enable high contrast mode',
);
}

if (contrastToggleLabel) {
contrastToggleLabel.textContent = enabled ? 'High Contrast: On' : 'High Contrast: Off';
}
};

const getStoredHighContrastPreference = () => {
try {
return window.localStorage.getItem(HIGH_CONTRAST_STORAGE_KEY) === 'true';
} catch {
return false;
}
};

if (contrastToggle) {
setHighContrastState(getStoredHighContrastPreference());

contrastToggle.addEventListener('click', () => {
const isEnabled = !document.documentElement.classList.contains('high-contrast');
setHighContrastState(isEnabled);

try {
window.localStorage.setItem(HIGH_CONTRAST_STORAGE_KEY, String(isEnabled));
} catch {
// Ignore storage access errors.
}
});
}

// Toggle menu visibility when hamburger is clicked
const menuToggle = document.getElementById('menu-toggle');
const menu = document.getElementById('menu');
Expand Down
9 changes: 9 additions & 0 deletions src/layouts/Layout.astro
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,15 @@ const { title = "Tailspin Toys" } = Astro.props;
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<meta name="generator" content={Astro.generator} />
<title>{title}</title>
<script is:inline>
try {
if (window.localStorage.getItem('tailspin-high-contrast') === 'true') {
document.documentElement.classList.add('high-contrast');
}
} catch {
// Ignore storage access errors.
}
</script>
<!-- Preconnect to improve font loading -->
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
Expand Down
48 changes: 48 additions & 0 deletions src/styles/global.css
Original file line number Diff line number Diff line change
@@ -1 +1,49 @@
@import "tailwindcss";

html.high-contrast {
color-scheme: dark;
}

html.high-contrast body,
html.high-contrast .bg-white,
html.high-contrast .bg-slate-100,
html.high-contrast .bg-slate-700,
html.high-contrast .bg-slate-800,
html.high-contrast .bg-slate-900,
html.high-contrast .bg-blue-500,
html.high-contrast .bg-blue-600,
html.high-contrast .bg-blue-700,
html.high-contrast .bg-blue-900,
html.high-contrast .bg-purple-900,
html.high-contrast .dark\:bg-slate-700,
html.high-contrast .dark\:bg-slate-800,
html.high-contrast .dark\:bg-blue-700 {
background-color: #000 !important;
}

html.high-contrast,
html.high-contrast .text-slate-100,
html.high-contrast .text-slate-200,
html.high-contrast .text-slate-300,
html.high-contrast .text-slate-400,
html.high-contrast .text-slate-800,
html.high-contrast .text-white,
html.high-contrast .text-blue-300,
html.high-contrast .text-blue-400,
html.high-contrast .text-purple-300,
html.high-contrast .text-yellow-400,
html.high-contrast .dark\:text-white {
color: #fff !important;
}

html.high-contrast a {
color: #ff0 !important;
text-decoration: underline;
}

html.high-contrast button,
html.high-contrast .border-slate-600,
html.high-contrast .border-slate-700,
html.high-contrast .border-blue-500 {
border-color: #fff !important;
}