✅ E2.S1: Tailwind config update - Teal-700 (#0F766E) als PRIMARY brand color (5.47:1 contrast) - Amber-600→700 gradient voor AI features - Updated ring color naar teal-700 voor WCAG AA ✅ E2.S2: Global CSS variables - --color-brand → teal-700 (was blue-600) - --color-info → teal-700 (was blue-500) - --color-input-focus → teal-700 (was blue-500) - --color-ai toegevoegd → amber-600 ✅ E2.S3: Component color updates - components/ui/sign-in.tsx: Alle blue → teal - components/ui/timeline.tsx: Gradient blue → teal - components/ui/reading-progress.tsx: Progress bar blue → teal - components/ui/modern-side-bar.tsx: Logo, active states blue → teal ✅ E2.S4: AIButton component - Nieuw component: components/ui/ai-button.tsx - Amber-600→700 gradient voor WCAG compliance - 3 variants: default, outline, ghost - Loading state + Sparkles icon - Fully accessible (WCAG AA focus states) ✅ E2.S5: Contrast testing - scripts/test-contrast.ts: Automated WCAG testing - docs/design/wcag-compliance.md: Compliance documentatie - Resultaten: 7/11 AA Normal (4.5:1), 11/11 AA Large (3:1) ✅ WCAG AA Compliance: ✅ PASS - Teal-700 op wit: 5.47:1 (AA Normal) - White op teal-700: 5.47:1 (AA Normal) - White op amber-600: 3.19:1 (AA Large - OK voor buttons) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
70 lines
2.1 KiB
TypeScript
70 lines
2.1 KiB
TypeScript
'use client'
|
|
|
|
/**
|
|
* Reading Progress Bar Component
|
|
*
|
|
* Fixed top progress indicator that shows scroll progress through the page.
|
|
* Smooth animation, 2px height, accent blue color.
|
|
* Respects prefers-reduced-motion for accessibility.
|
|
*/
|
|
|
|
import { useEffect, useState } from 'react'
|
|
|
|
export function ReadingProgress() {
|
|
const [progress, setProgress] = useState(0)
|
|
const [reducedMotion, setReducedMotion] = useState(false)
|
|
|
|
useEffect(() => {
|
|
// Check for reduced motion preference
|
|
const mediaQuery = window.matchMedia('(prefers-reduced-motion: reduce)')
|
|
setReducedMotion(mediaQuery.matches)
|
|
|
|
const handleChange = (e: MediaQueryListEvent) => setReducedMotion(e.matches)
|
|
mediaQuery.addEventListener('change', handleChange)
|
|
|
|
const updateProgress = () => {
|
|
const windowHeight = window.innerHeight
|
|
const documentHeight = document.documentElement.scrollHeight
|
|
const scrollTop = window.scrollY || document.documentElement.scrollTop
|
|
|
|
// Calculate scroll progress percentage
|
|
const scrollableHeight = documentHeight - windowHeight
|
|
const currentProgress = scrollableHeight > 0
|
|
? (scrollTop / scrollableHeight) * 100
|
|
: 0
|
|
|
|
setProgress(Math.min(100, Math.max(0, currentProgress)))
|
|
}
|
|
|
|
// Initial calculation
|
|
updateProgress()
|
|
|
|
// Update on scroll
|
|
window.addEventListener('scroll', updateProgress, { passive: true })
|
|
window.addEventListener('resize', updateProgress, { passive: true })
|
|
|
|
return () => {
|
|
mediaQuery.removeEventListener('change', handleChange)
|
|
window.removeEventListener('scroll', updateProgress)
|
|
window.removeEventListener('resize', updateProgress)
|
|
}
|
|
}, [])
|
|
|
|
return (
|
|
<div
|
|
className="fixed top-[64px] left-0 right-0 z-50 h-0.5 bg-slate-200"
|
|
role="progressbar"
|
|
aria-valuenow={Math.round(progress)}
|
|
aria-valuemin={0}
|
|
aria-valuemax={100}
|
|
aria-label="Leesvoortgang"
|
|
>
|
|
<div
|
|
className={`h-full bg-teal-600 ${reducedMotion ? '' : 'transition-all duration-150 ease-out'}`}
|
|
style={{ width: `${progress}%` }}
|
|
/>
|
|
</div>
|
|
)
|
|
}
|
|
|