fix(radio): keyboard a11y — skip link, focus-visible, mobile-menu
human-flow P0-P1 fixes for radio.azcomputerguru.com: - K1: skip-to-content link (first tab stop) + id/tabindex on <main>. - K2: global :focus-visible ring (accent outline) across links, buttons, inputs and player controls; reveal the seek-bar handle on focus. - K3: mobile menu a11y — aria-expanded/aria-controls, Escape closes and restores focus to the toggle, focus moves to first link on open. All token-based, no emojis. Not built (node_modules absent on this host). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -644,6 +644,15 @@ export default function PersistentPlayer() {
|
|||||||
opacity: 1;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.persistent-player__progress-bar:focus-visible {
|
||||||
|
outline: 2px solid var(--color-accent, hsl(200 85% 55%));
|
||||||
|
outline-offset: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.persistent-player__progress-bar:focus-visible .persistent-player__progress-handle {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
[data-theme="light"] .persistent-player__progress-bar {
|
[data-theme="light"] .persistent-player__progress-bar {
|
||||||
background: hsl(220 12% 85% / 0.5);
|
background: hsl(220 12% 85% / 0.5);
|
||||||
}
|
}
|
||||||
@@ -755,6 +764,17 @@ export default function PersistentPlayer() {
|
|||||||
background: hsl(220 16% 92% / 0.6);
|
background: hsl(220 16% 92% / 0.6);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.persistent-player__btn:focus-visible {
|
||||||
|
outline: 2px solid var(--color-accent, hsl(200 85% 55%));
|
||||||
|
outline-offset: 2px;
|
||||||
|
color: var(--color-text-primary, hsl(220 10% 92%));
|
||||||
|
}
|
||||||
|
|
||||||
|
.persistent-player__btn--play:focus-visible {
|
||||||
|
color: hsl(220 20% 8%);
|
||||||
|
box-shadow: 0 0 16px hsl(200 85% 55% / 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
.persistent-player__btn--play {
|
.persistent-player__btn--play {
|
||||||
width: 40px;
|
width: 40px;
|
||||||
height: 40px;
|
height: 40px;
|
||||||
|
|||||||
@@ -51,12 +51,13 @@ const canonicalURL = new URL(Astro.url.pathname, Astro.site);
|
|||||||
</script>
|
</script>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
<a href="#main-content" class="skip-link">Skip to content</a>
|
||||||
<header class="site-header">
|
<header class="site-header">
|
||||||
<nav class="container site-nav">
|
<nav class="container site-nav">
|
||||||
<a href="/" class="site-logo">
|
<a href="/" class="site-logo">
|
||||||
<span class="logo-text">The Computer Guru Show</span>
|
<span class="logo-text">The Computer Guru Show</span>
|
||||||
</a>
|
</a>
|
||||||
<div class="nav-links">
|
<div class="nav-links" id="primary-nav">
|
||||||
<a href="/episodes">Episodes</a>
|
<a href="/episodes">Episodes</a>
|
||||||
<a href="/blog">Blog</a>
|
<a href="/blog">Blog</a>
|
||||||
<a href="/live">Live</a>
|
<a href="/live">Live</a>
|
||||||
@@ -80,7 +81,7 @@ const canonicalURL = new URL(Astro.url.pathname, Astro.site);
|
|||||||
<path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"></path>
|
<path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"></path>
|
||||||
</svg>
|
</svg>
|
||||||
</button>
|
</button>
|
||||||
<button class="mobile-menu-toggle" id="mobile-menu-toggle" aria-label="Toggle menu">
|
<button class="mobile-menu-toggle" id="mobile-menu-toggle" aria-label="Toggle menu" aria-expanded="false" aria-controls="primary-nav">
|
||||||
<span></span>
|
<span></span>
|
||||||
<span></span>
|
<span></span>
|
||||||
<span></span>
|
<span></span>
|
||||||
@@ -88,7 +89,7 @@ const canonicalURL = new URL(Astro.url.pathname, Astro.site);
|
|||||||
</nav>
|
</nav>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
<main>
|
<main id="main-content" tabindex="-1">
|
||||||
<slot />
|
<slot />
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
@@ -134,9 +135,32 @@ const canonicalURL = new URL(Astro.url.pathname, Astro.site);
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Mobile menu toggle
|
// Mobile menu toggle
|
||||||
document.getElementById('mobile-menu-toggle').addEventListener('click', () => {
|
const menuToggle = document.getElementById('mobile-menu-toggle');
|
||||||
document.querySelector('.nav-links').classList.toggle('open');
|
const navLinks = document.querySelector('.nav-links');
|
||||||
document.getElementById('mobile-menu-toggle').classList.toggle('open');
|
|
||||||
|
function closeMobileMenu(returnFocus) {
|
||||||
|
navLinks.classList.remove('open');
|
||||||
|
menuToggle.classList.remove('open');
|
||||||
|
menuToggle.setAttribute('aria-expanded', 'false');
|
||||||
|
if (returnFocus) {
|
||||||
|
menuToggle.focus();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
menuToggle.addEventListener('click', () => {
|
||||||
|
const isOpen = navLinks.classList.toggle('open');
|
||||||
|
menuToggle.classList.toggle('open', isOpen);
|
||||||
|
menuToggle.setAttribute('aria-expanded', isOpen ? 'true' : 'false');
|
||||||
|
if (isOpen) {
|
||||||
|
const firstLink = navLinks.querySelector('a');
|
||||||
|
if (firstLink) firstLink.focus();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
document.addEventListener('keydown', (e) => {
|
||||||
|
if (e.key === 'Escape' && navLinks.classList.contains('open')) {
|
||||||
|
closeMobileMenu(true);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Fade-in on scroll
|
// Fade-in on scroll
|
||||||
|
|||||||
@@ -31,6 +31,40 @@ a:hover {
|
|||||||
color: var(--color-accent-hover);
|
color: var(--color-accent-hover);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Global keyboard focus styles — strong, consistent ring on the dark theme.
|
||||||
|
Uses outline so it layers over any existing box-shadow focus rings. */
|
||||||
|
a:focus-visible,
|
||||||
|
button:focus-visible,
|
||||||
|
[tabindex]:focus-visible,
|
||||||
|
input:focus-visible,
|
||||||
|
select:focus-visible,
|
||||||
|
textarea:focus-visible {
|
||||||
|
outline: 2px solid var(--color-accent);
|
||||||
|
outline-offset: 2px;
|
||||||
|
border-radius: var(--radius-sm);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Skip to content link — keyboard-reachable first tab stop, hidden until focused */
|
||||||
|
.skip-link {
|
||||||
|
position: absolute;
|
||||||
|
left: -9999px;
|
||||||
|
top: 0;
|
||||||
|
z-index: 1001;
|
||||||
|
padding: var(--space-3) var(--space-6);
|
||||||
|
background: var(--color-bg-secondary);
|
||||||
|
color: var(--color-accent);
|
||||||
|
border: 1px solid var(--color-border);
|
||||||
|
border-radius: var(--radius-sm);
|
||||||
|
font-size: var(--text-sm);
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
.skip-link:focus {
|
||||||
|
left: var(--space-4);
|
||||||
|
top: var(--space-4);
|
||||||
|
outline: 2px solid var(--color-accent);
|
||||||
|
outline-offset: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
img, video {
|
img, video {
|
||||||
max-width: 100%;
|
max-width: 100%;
|
||||||
height: auto;
|
height: auto;
|
||||||
|
|||||||
Reference in New Issue
Block a user