Achieve 100% Lighthouse accessibility and SEO on all pages

- Fix color contrast: emerald-600 → emerald-700 for WCAG AA compliance
- Fix heading hierarchy across all pages (h1 → h2 → h3 → h4)
- Lazy load NebulaField with next/dynamic and requestIdleCallback
- Remove debug console.log statements
- Add .env.example for Turnstile configuration
- Update documentation with audit results
This commit is contained in:
jwinterm
2026-01-17 11:15:51 -05:00
parent f2940ad956
commit 3c1e0b659c
10 changed files with 200 additions and 225 deletions
+7
View File
@@ -0,0 +1,7 @@
# Cloudflare Turnstile (bot protection for contact form)
# Get keys from: https://dash.cloudflare.com/?to=/:account/turnstile
NEXT_PUBLIC_TURNSTILE_SITE_KEY=your_site_key_here
TURNSTILE_SECRET_KEY=your_secret_key_here
# Optional: Email for contact form notifications
# CONTACT_EMAIL=your@email.com
+5 -5
View File
@@ -33,10 +33,10 @@ export default function AboutPage() {
<div className="grid md:grid-cols-2 gap-10 mb-12">
<div>
<h3 className="text-xl font-bold mb-4 flex items-center gap-2">
<FaBuilding className="text-primary" />
<h2 className="text-xl font-bold mb-4 flex items-center gap-2">
<FaBuilding className="text-primary" />
Headquarters
</h3>
</h2>
<address className="not-italic text-muted-foreground space-y-1 pl-7 border-l-2 border-border ml-2">
<p>Such Software LLC</p>
<p>110 E State St</p>
@@ -65,7 +65,7 @@ export default function AboutPage() {
<div className="flex flex-col justify-center items-center bg-muted/30 rounded-xl p-6">
<div className="text-center">
<h3 className="text-lg font-semibold mb-2">Connect</h3>
<h2 className="text-lg font-semibold mb-2">Connect</h2>
<p className="text-sm text-muted-foreground mb-4">Find us on your favorite platforms</p>
<div className="flex gap-6 justify-center">
<a href="https://x.com/such_software" target="_blank" rel="noopener noreferrer" className="text-muted-foreground hover:text-primary transition-colors">
@@ -86,7 +86,7 @@ export default function AboutPage() {
</div>
<div className="border-t border-border/50 pt-10">
<h3 className="text-2xl font-bold mb-6 text-center">Leadership</h3>
<h2 className="text-2xl font-bold mb-6 text-center">Leadership</h2>
<FounderSignature />
</div>
+3 -3
View File
@@ -38,7 +38,7 @@ export default function Home() {
<div className="w-full max-w-6xl mx-auto mb-16 px-4">
<div className="text-left mb-8 section-container border-l-4 border-l-blue-500">
<h3 className="text-3xl font-bold mb-3 tracking-tight text-foreground">Selected Works</h3>
<h2 className="text-3xl font-bold mb-3 tracking-tight text-foreground">Selected Works</h2>
<p className="text-muted-foreground text-lg max-w-3xl leading-relaxed">
A curated gallery of recent projects where we've pushed the boundaries of
performance, accessibility, and user experience.
@@ -49,7 +49,7 @@ export default function Home() {
<div className="w-full max-w-6xl mx-auto mb-20 px-4">
<div className="text-left mb-12 section-container border-l-4 border-l-purple-500">
<h3 className="text-3xl font-bold mb-3 tracking-tight text-foreground">Inclusive by Design</h3>
<h2 className="text-3xl font-bold mb-3 tracking-tight text-foreground">Inclusive by Design</h2>
<p className="text-muted-foreground text-lg max-w-3xl leading-relaxed">
We believe the web is for everyone. Every line of code we write is
audited for WCAG 2.2 compliance and high-performance across all devices.
@@ -60,7 +60,7 @@ export default function Home() {
<div id="contact" className="w-full mt-20 mb-20 flex flex-col items-center">
<div className="w-full max-w-6xl text-left mb-12 section-container border-l-4 border-l-cyan-500">
<h3 className="text-3xl font-bold mb-3 tracking-tight text-foreground">Start a Project</h3>
<h2 className="text-3xl font-bold mb-3 tracking-tight text-foreground">Start a Project</h2>
<p className="text-muted-foreground text-lg max-w-3xl leading-relaxed">
Ready to build something exceptional? Drop us a line and let's discuss
how we can help you scale your digital presence.
@@ -25,7 +25,7 @@ export const FounderSignature = () => {
</div>
<div>
<p className="font-mono text-xs uppercase tracking-[0.4em] text-primary/80 mb-1">Founder & Principal Engineer</p>
<h2 className="text-3xl font-bold tracking-tight">John Winter Murphy</h2>
<h3 className="text-3xl font-bold tracking-tight">John Winter Murphy</h3>
</div>
</div>
@@ -46,7 +46,7 @@ export const FounderSignature = () => {
<div className="space-y-6">
<Card className="glass-card hover:border-purple-500/30">
<h4 className="text-base font-bold mb-3 flex items-center gap-2">
<span className="w-6 h-6 rounded-full bg-purple-500/10 flex items-center justify-center text-purple-500 text-[10px]">01</span>
<span className="w-6 h-6 rounded-full bg-purple-600 flex items-center justify-center text-white text-[10px] font-bold" aria-hidden="true">01</span>
A11y First Architecture
</h4>
<p className="text-muted-foreground text-sm leading-relaxed font-medium">
@@ -56,7 +56,7 @@ export const FounderSignature = () => {
<Card className="glass-card hover:border-blue-500/30">
<h4 className="text-base font-bold mb-3 flex items-center gap-2">
<span className="w-6 h-6 rounded-full bg-blue-500/10 flex items-center justify-center text-blue-500 text-[10px]">02</span>
<span className="w-6 h-6 rounded-full bg-blue-600 flex items-center justify-center text-white text-[10px] font-bold" aria-hidden="true">02</span>
Edge Performance
</h4>
<p className="text-muted-foreground text-sm leading-relaxed font-medium">
@@ -66,21 +66,21 @@ export const FounderSignature = () => {
<Card className="glass-card hover:border-emerald-500/30 border-dashed">
<h4 className="text-base font-bold mb-4 flex items-center gap-2">
<span className="text-emerald-500 text-[10px]">03</span>
<span className="w-6 h-6 rounded-full bg-emerald-700 flex items-center justify-center text-white text-[10px] font-bold" aria-hidden="true">03</span>
Core Competencies
</h4>
<ul className="grid grid-cols-1 gap-2 font-mono text-xs uppercase tracking-wider text-muted-foreground">
<li className="flex items-center gap-2">
<span className="text-emerald-500">✓</span> WCAG 2.2 AA Compliance
<span className="text-emerald-600 dark:text-emerald-400" aria-hidden="true">✓</span> WCAG 2.2 AA Compliance
</li>
<li className="flex items-center gap-2">
<span className="text-emerald-500">✓</span> Section 508 & ADA
<span className="text-emerald-600 dark:text-emerald-400" aria-hidden="true">✓</span> Section 508 & ADA
</li>
<li className="flex items-center gap-2">
<span className="text-emerald-500">✓</span> Semantic HTML5 Architecture
<span className="text-emerald-600 dark:text-emerald-400" aria-hidden="true">✓</span> Semantic HTML5 Architecture
</li>
<li className="flex items-center gap-2">
<span className="text-emerald-500">✓</span> Playwright A11y Automation
<span className="text-emerald-600 dark:text-emerald-400" aria-hidden="true">✓</span> Playwright A11y Automation
</li>
</ul>
</Card>
@@ -1,35 +1,56 @@
'use client';
import { useTheme } from "next-themes";
import { NebulaField } from "@repo/ui/procedural/nebula-field";
import dynamic from "next/dynamic";
import { useEffect, useState } from "react";
// Lazy load the heavy NebulaField component
const NebulaField = dynamic(
() => import("@repo/ui/procedural/nebula-field").then((mod) => mod.NebulaField),
{
ssr: false,
loading: () => <div className="fixed inset-0 z-0 h-screen w-screen bg-background" />,
}
);
export function DynamicBackground() {
const { resolvedTheme, theme, systemTheme } = useTheme();
const { resolvedTheme, systemTheme } = useTheme();
const [mounted, setMounted] = useState(false);
const [showNebula, setShowNebula] = useState(false);
useEffect(() => {
setMounted(true);
}, []);
// Delay nebula render to prioritize main content (LCP)
const timer = requestIdleCallback
? requestIdleCallback(() => setShowNebula(true))
: setTimeout(() => setShowNebula(true), 100);
// Debug on first load
useEffect(() => {
console.log('[DynamicBackground] mounted:', mounted, 'theme:', theme, 'resolvedTheme:', resolvedTheme, 'systemTheme:', systemTheme);
}, [mounted, theme, resolvedTheme, systemTheme]);
return () => {
if (typeof timer === 'number') {
clearTimeout(timer);
} else if (cancelIdleCallback) {
cancelIdleCallback(timer as unknown as number);
}
};
}, []);
// Don't render until mounted (standard hydration fix)
if (!mounted) {
return <div className="fixed inset-0 z-0 h-screen w-screen" />;
return <div className="fixed inset-0 z-0 h-screen w-screen bg-background" />;
}
// After mount, determine the theme - fall back to system preference or 'dark'
const effectiveTheme = resolvedTheme || systemTheme || 'dark';
const themeMode = effectiveTheme === 'dark' ? 'dark' : 'light';
if (!showNebula) {
return <div className="fixed inset-0 z-0 h-screen w-screen bg-background" />;
}
return (
<NebulaField
className="fixed inset-0 z-0 h-screen w-screen"
themeMode={themeMode}
/>
);
}
}
@@ -4,7 +4,7 @@ export function ServicesPreview() {
return (
<div className="w-full max-w-6xl mx-auto px-4 mt-8 mb-16 relative z-10">
<div className="text-left mb-8 section-container border-l-4 border-l-emerald-500">
<h3 className="text-3xl font-bold mb-3 tracking-tight">High-Performance Webshops</h3>
<h2 className="text-3xl font-bold mb-3 tracking-tight">High-Performance Webshops</h2>
<p className="text-muted-foreground text-lg max-w-3xl leading-relaxed">
We build commerce infrastructure that scales with you. From complex supply chains
to global payments, we handle the technical heavy lifting so you can focus on your brand.
+1 -6
View File
@@ -6,18 +6,13 @@ import { Button } from "@repo/ui/components/button";
import { useEffect, useState } from "react";
export function ThemeToggle() {
const { setTheme, resolvedTheme, theme, systemTheme } = useTheme();
const { setTheme, resolvedTheme } = useTheme();
const [mounted, setMounted] = useState(false);
useEffect(() => {
setMounted(true);
}, []);
// Debug
useEffect(() => {
console.log('[ThemeToggle] mounted:', mounted, 'theme:', theme, 'resolvedTheme:', resolvedTheme);
}, [mounted, theme, resolvedTheme]);
const toggleTheme = (e: React.MouseEvent<HTMLButtonElement>) => {
const isDark = resolvedTheme === "dark";
const prefersReducedMotion = window.matchMedia("(prefers-reduced-motion: reduce)").matches;