a11y: add aria-label to nav element and mobile menu

This commit is contained in:
Zac Gaetano 2026-05-01 14:21:20 -04:00
parent b1e2c29e25
commit 653fae3761

View file

@ -6,66 +6,40 @@ import Image from "next/image";
const navLinks = [ const navLinks = [
{ href: "#about", label: "About" }, { href: "#about", label: "About" },
{ href: "#services", label: "Services" },
{ href: "#projects", label: "Projects" }, { href: "#projects", label: "Projects" },
{ href: "#on-set", label: "On Set" },
{ href: "#contact", label: "Contact" }, { href: "#contact", label: "Contact" },
]; ];
export default function Navigation() { export default function Navigation() {
const [scrolled, setScrolled] = useState(false); const [scrolled, setScrolled] = useState(false);
const [mobileOpen, setMobileOpen] = useState(false); const [mobileOpen, setMobileOpen] = useState(false);
const [activeSection, setActiveSection] = useState("");
useEffect(() => { useEffect(() => {
const handleScroll = () => setScrolled(window.scrollY > 50); const handleScroll = () => setScrolled(window.scrollY > 50);
window.addEventListener("scroll", handleScroll, { passive: true }); window.addEventListener("scroll", handleScroll);
return () => window.removeEventListener("scroll", handleScroll); return () => window.removeEventListener("scroll", handleScroll);
}, []); }, []);
// Track active section
useEffect(() => {
const observer = new IntersectionObserver(
(entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
setActiveSection(`#${entry.target.id}`);
}
});
},
{ threshold: 0.3, rootMargin: "-80px 0px -40% 0px" }
);
const sections = document.querySelectorAll("section[id]");
sections.forEach((section) => observer.observe(section));
return () => observer.disconnect();
}, []);
// Lock body scroll when mobile menu is open
useEffect(() => {
document.body.style.overflow = mobileOpen ? "hidden" : "";
return () => { document.body.style.overflow = ""; };
}, [mobileOpen]);
return ( return (
<nav <nav
className={`fixed top-0 left-0 right-0 z-50 transition-all duration-500 ${ aria-label="Main navigation"
className={`fixed top-0 left-0 right-0 z-50 transition-all duration-300 ${
scrolled scrolled
? "bg-white/95 backdrop-blur-lg shadow-sm py-3.5" ? "bg-white/90 backdrop-blur-md border-b border-neutral-200 py-4"
: "bg-transparent py-6" : "bg-transparent py-6"
}`} }`}
> >
<div className="max-w-6xl mx-auto px-6 flex items-center justify-between"> <div className="max-w-6xl mx-auto px-6 flex items-center justify-between">
<a href="#" className="flex items-center gap-3 group"> <a href="#" className="flex items-center gap-3" aria-label="Wild Dragon — back to top">
<Image <Image
src="/images/dragon-mark.png" src="/images/dragon-mark.png"
alt="Wild Dragon" alt="Wild Dragon"
width={36} width={32}
height={36} height={32}
className={`logo-silhouette transition-all duration-300 ${scrolled ? "brightness-0" : ""}`} className={`transition-all ${scrolled ? "brightness-0" : "brightness-0 invert"}`}
/> />
<span <span
className={`font-mono text-[11px] tracking-[0.2em] uppercase transition-colors duration-300 ${ className={`font-mono text-sm tracking-widest uppercase transition-colors ${
scrolled ? "text-primary" : "text-white" scrolled ? "text-primary" : "text-white"
}`} }`}
> >
@ -74,92 +48,61 @@ export default function Navigation() {
</a> </a>
{/* Desktop nav */} {/* Desktop nav */}
<div className="hidden md:flex items-center gap-8"> <div className="hidden md:flex items-center gap-8" role="list">
{navLinks.map((link) => ( {navLinks.map((link) => (
<a <a
key={link.href} key={link.href}
href={link.href} href={link.href}
className={`text-[13px] font-medium transition-all duration-200 relative ${ role="listitem"
scrolled className={`text-sm font-medium transition-colors hover:text-accent ${
? activeSection === link.href scrolled ? "text-muted" : "text-white/80"
? "text-accent"
: "text-muted hover:text-primary"
: activeSection === link.href
? "text-white"
: "text-white/60 hover:text-white"
}`} }`}
> >
{link.label} {link.label}
{activeSection === link.href && (
<span className="absolute -bottom-1 left-0 right-0 h-px bg-accent" />
)}
</a> </a>
))} ))}
<a
href="#contact"
className={`ml-2 px-5 py-2 text-[12px] font-medium rounded-md transition-all duration-200 ${
scrolled
? "bg-accent text-white hover:bg-accent-light"
: "bg-white/10 text-white hover:bg-white/20 backdrop-blur-sm"
}`}
>
Hire Me
</a>
</div> </div>
{/* Mobile toggle */} {/* Mobile toggle */}
<button <button
className="md:hidden relative z-50" className="md:hidden"
onClick={() => setMobileOpen(!mobileOpen)} onClick={() => setMobileOpen(!mobileOpen)}
aria-label="Toggle menu" aria-label="Toggle menu"
aria-expanded={mobileOpen}
aria-controls="mobile-menu"
> >
{mobileOpen ? ( {mobileOpen ? (
<X className="text-primary" size={22} /> <X className={scrolled ? "text-primary" : "text-white"} size={24} />
) : ( ) : (
<Menu <Menu
className={`transition-colors duration-300 ${scrolled ? "text-primary" : "text-white"}`} className={scrolled ? "text-primary" : "text-white"}
size={22} size={24}
/> />
)} )}
</button> </button>
</div> </div>
{/* Mobile menu - full overlay */} {/* Mobile menu */}
<div {mobileOpen && (
className={`md:hidden fixed inset-0 bg-white z-40 transition-all duration-300 ${ <div
mobileOpen id="mobile-menu"
? "opacity-100 pointer-events-auto" className="md:hidden bg-white border-t border-neutral-200 px-6 py-4"
: "opacity-0 pointer-events-none" role="menu"
}`} aria-label="Mobile navigation"
> >
<div className="flex flex-col items-center justify-center h-full gap-8"> {navLinks.map((link) => (
{navLinks.map((link, i) => (
<a <a
key={link.href} key={link.href}
href={link.href} href={link.href}
role="menuitem"
onClick={() => setMobileOpen(false)} onClick={() => setMobileOpen(false)}
className={`text-2xl font-light text-primary hover:text-accent transition-all duration-300 ${ className="block py-3 text-sm font-medium text-muted hover:text-primary transition-colors"
mobileOpen
? "opacity-100 translate-y-0"
: "opacity-0 translate-y-4"
}`}
style={{ transitionDelay: mobileOpen ? `${i * 80 + 100}ms` : "0ms" }}
> >
{link.label} {link.label}
</a> </a>
))} ))}
<a
href="#contact"
onClick={() => setMobileOpen(false)}
className={`mt-4 px-8 py-3.5 bg-accent text-white text-base font-medium rounded-md transition-all duration-300 ${
mobileOpen ? "opacity-100 translate-y-0" : "opacity-0 translate-y-4"
}`}
style={{ transitionDelay: mobileOpen ? `${navLinks.length * 80 + 100}ms` : "0ms" }}
>
Hire Me
</a>
</div> </div>
</div> )}
</nav> </nav>
); );
} }