1- import { motion , useScroll , useTransform , AnimatePresence } from "framer-motion"
1+ import { motion , useScroll , useTransform , AnimatePresence , useReducedMotion } from "framer-motion"
22import { Button } from "@/components/ui/button"
33import { Card , CardContent , CardDescription , CardHeader , CardTitle } from "@/components/ui/card"
44import { Badge } from "@/components/ui/badge"
@@ -65,6 +65,7 @@ function App() {
6565
6666 const { scrollYProgress } = useScroll ( )
6767 const heroOpacity = useTransform ( scrollYProgress , [ 0 , 0.15 ] , [ 1 , 0.3 ] )
68+ const prefersReducedMotion = useReducedMotion ( )
6869
6970 useEffect ( ( ) => {
7071 const timer = setTimeout ( ( ) => setShowIntroGuide ( false ) , 5000 )
@@ -100,7 +101,12 @@ function App() {
100101 } , [ isMuted ] )
101102
102103 const scrollToSection = ( id : string ) => {
103- document . getElementById ( id ) ?. scrollIntoView ( { behavior : 'smooth' } )
104+ const el = document . getElementById ( id )
105+ if ( ! el ) return
106+ el . scrollIntoView ( { behavior : prefersReducedMotion ? 'auto' : 'smooth' } )
107+ // Move focus to the section for keyboard/screen-reader users
108+ if ( ! el . hasAttribute ( 'tabindex' ) ) el . setAttribute ( 'tabindex' , '-1' )
109+ el . focus ( { preventScroll : true } )
104110 }
105111
106112 // ── Side navigation sections ──
@@ -389,6 +395,13 @@ function App() {
389395
390396 return (
391397 < div className = "min-h-screen bg-background relative" >
398+ { /* ── WCAG: Skip-to-content ── */ }
399+ < a
400+ href = "#main-content"
401+ className = "sr-only focus:not-sr-only focus:fixed focus:top-4 focus:left-4 focus:z-[9999] focus:px-4 focus:py-2 focus:bg-primary focus:text-primary-foreground focus:rounded-lg focus:shadow-xl focus:outline-2 focus:outline-offset-2 focus:outline-primary-foreground"
402+ >
403+ Zum Hauptinhalt springen
404+ </ a >
392405 < AnimatePresence >
393406 { showIntroGuide && (
394407 < motion . div
@@ -400,10 +413,10 @@ function App() {
400413 < Card className = "shadow-2xl border-2 border-accent" >
401414 < CardContent className = "p-6 flex items-center gap-4" >
402415 < motion . div
403- animate = { { y : [ 0 , 10 , 0 ] } }
416+ animate = { prefersReducedMotion ? { } : { y : [ 0 , 10 , 0 ] } }
404417 transition = { { duration : 1.5 , repeat : Infinity } }
405418 >
406- < ArrowDown className = "h-6 w-6 text-accent" />
419+ < ArrowDown className = "h-6 w-6 text-accent" aria-hidden = "true" />
407420 </ motion . div >
408421 < div >
409422 < p className = "font-semibold text-foreground" > Scrollen Sie, um mehr zu erfahren</ p >
@@ -423,7 +436,7 @@ function App() {
423436 < Separator orientation = "vertical" className = "h-8 hidden md:block" />
424437 < span className = "text-sm font-medium text-muted-foreground hidden md:block" > CASSA · RELIEF</ span >
425438 </ div >
426- < div className = "flex items-center gap-4" >
439+ < nav aria-label = "Hauptnavigation" className = "flex items-center gap-4" >
427440 < Button variant = "ghost" size = "sm" onClick = { ( ) => scrollToSection ( 'fall-becker' ) } className = "hidden md:flex" >
428441 Fall Becker
429442 </ Button >
@@ -445,15 +458,15 @@ function App() {
445458 < Button asChild size = "sm" className = "bg-accent hover:bg-accent/90 text-accent-foreground" >
446459 < a href = "https://www.soprasteria.de/products/cassa" target = "_blank" rel = "noopener noreferrer" >
447460 Mehr erfahren
448- < ArrowRight className = "ml-2 h-4 w-4" />
461+ < ArrowRight className = "ml-2 h-4 w-4" aria-hidden = "true" />
449462 </ a >
450463 </ Button >
451- </ div >
464+ </ nav >
452465 </ div >
453466 </ header >
454467
455468 { /* ── SIDE NAVIGATION (desktop only, top-right below header) ── */ }
456- < nav className = "hidden xl:block fixed right-6 top-20 z-50" >
469+ < nav aria-label = "Seitennavigation" className = "hidden xl:block fixed right-6 top-20 z-50" >
457470 < div className = "bg-card/95 backdrop-blur-md border border-border rounded-xl shadow-lg px-4 py-5 max-h-[calc(100vh-6rem)] overflow-y-auto" >
458471 { ( ( ) => {
459472 let lastGroup = ''
@@ -471,8 +484,9 @@ function App() {
471484 ) }
472485 < button
473486 onClick = { ( ) => scrollToSection ( section . id ) }
487+ aria-current = { activeSection === section . id ? 'location' : undefined }
474488 className = { `
475- flex items-center gap-2.5 w-full text-left text-sm px-2.5 py-2 rounded-lg transition-all duration-200
489+ flex items-center gap-2.5 w-full text-left text-sm px-2.5 py-2 rounded-lg transition-all duration-200 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary
476490 ${ activeSection === section . id
477491 ? 'bg-primary/15 text-primary font-semibold border-l-3 border-primary pl-3'
478492 : 'text-muted-foreground hover:text-foreground hover:bg-muted/50' }
@@ -495,8 +509,15 @@ function App() {
495509 </ div >
496510 </ nav >
497511
512+ < main >
513+
498514 { /* ── HERO ── */ }
499- < motion . section style = { { opacity : heroOpacity } } className = "hero-pattern py-32 md:py-40 relative overflow-hidden" >
515+ < motion . section
516+ id = "main-content"
517+ aria-label = "Einleitung"
518+ style = { { opacity : heroOpacity } }
519+ className = "hero-pattern py-32 md:py-40 relative overflow-hidden"
520+ >
500521 < AnimatedBackground />
501522 < div className = "container mx-auto px-6 max-w-7xl relative z-10" >
502523 < motion . div
@@ -528,7 +549,7 @@ function App() {
528549 className = "bg-accent hover:bg-accent/90 text-accent-foreground text-lg px-10 h-14 shadow-lg hover:shadow-xl transition-shadow"
529550 >
530551 < a href = "https://www.soprasteria.de/products/cassa" target = "_blank" rel = "noopener noreferrer" >
531- < BrainCircuit className = "mr-2 h-5 w-5" />
552+ < BrainCircuit className = "mr-2 h-5 w-5" aria-hidden = "true" />
532553 CASSA entdecken
533554 </ a >
534555 </ Button >
@@ -539,7 +560,7 @@ function App() {
539560 onClick = { ( ) => scrollToSection ( 'challenges' ) }
540561 >
541562 Herausforderungen verstehen
542- < ArrowDown className = "ml-2 h-5 w-5" />
563+ < ArrowDown className = "ml-2 h-5 w-5" aria-hidden = "true" />
543564 </ Button >
544565 </ div >
545566 </ motion . div >
@@ -726,9 +747,11 @@ function App() {
726747 < div className = "flex items-center gap-4" >
727748 < button
728749 onClick = { toggleNarration }
729- className = "flex-shrink-0 w-14 h-14 rounded-full bg-primary text-primary-foreground flex items-center justify-center hover:bg-primary/90 transition-colors shadow-lg"
750+ aria-label = { isPlayingNarration ? 'Narration pausieren' : 'Narration abspielen' }
751+ aria-pressed = { isPlayingNarration }
752+ className = "flex-shrink-0 w-14 h-14 rounded-full bg-primary text-primary-foreground flex items-center justify-center hover:bg-primary/90 transition-colors shadow-lg focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary"
730753 >
731- { isPlayingNarration ? < Pause className = "h-6 w-6" /> : < Play className = "h-6 w-6 ml-0.5" /> }
754+ { isPlayingNarration ? < Pause className = "h-6 w-6" aria-hidden = "true" /> : < Play className = "h-6 w-6 ml-0.5" aria-hidden = "true " /> }
732755 </ button >
733756 < div className = "flex-1 min-w-0" >
734757 < p className = "font-semibold text-foreground" > Fall Becker — Narration</ p >
@@ -738,9 +761,11 @@ function App() {
738761 </ div >
739762 < button
740763 onClick = { toggleMute }
741- className = "flex-shrink-0 p-2 rounded-lg text-muted-foreground hover:text-foreground transition-colors"
764+ aria-label = { isMuted ? 'Ton einschalten' : 'Ton ausschalten' }
765+ aria-pressed = { isMuted }
766+ className = "flex-shrink-0 p-2 rounded-lg text-muted-foreground hover:text-foreground transition-colors focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary"
742767 >
743- { isMuted ? < VolumeX className = "h-5 w-5" /> : < Volume2 className = "h-5 w-5" /> }
768+ { isMuted ? < VolumeX className = "h-5 w-5" aria-hidden = "true" /> : < Volume2 className = "h-5 w-5" aria-hidden = "true " /> }
744769 </ button >
745770 </ div >
746771 </ CardContent >
@@ -1049,17 +1074,21 @@ function App() {
10491074 < div className = "absolute bottom-5 right-5 z-30 flex items-center gap-3" >
10501075 < button
10511076 onClick = { toggleNarration }
1052- className = "flex items-center gap-2 px-4 py-2.5 rounded-full bg-primary text-primary-foreground shadow-lg hover:bg-primary/90 transition-all hover:scale-105 backdrop-blur-sm"
1077+ aria-label = { isPlayingNarration ? 'Narration pausieren' : 'Fall Becker Narration abspielen' }
1078+ aria-pressed = { isPlayingNarration }
1079+ className = "flex items-center gap-2 px-4 py-2.5 rounded-full bg-primary text-primary-foreground shadow-lg hover:bg-primary/90 transition-all hover:scale-105 backdrop-blur-sm focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary-foreground"
10531080 >
1054- { isPlayingNarration ? < Pause className = "h-4 w-4" /> : < Play className = "h-4 w-4 ml-0.5" /> }
1081+ { isPlayingNarration ? < Pause className = "h-4 w-4" aria-hidden = "true" /> : < Play className = "h-4 w-4 ml-0.5" aria-hidden = "true " /> }
10551082 < span className = "text-sm font-medium" > { isPlayingNarration ? 'Pause' : 'Fall Becker anhören' } </ span >
10561083 </ button >
10571084 { isPlayingNarration && (
10581085 < button
10591086 onClick = { toggleMute }
1060- className = "p-2 rounded-full bg-slate-800/80 text-slate-300 hover:bg-slate-700/80 hover:text-white transition-colors backdrop-blur-sm"
1087+ aria-label = { isMuted ? 'Ton einschalten' : 'Ton ausschalten' }
1088+ aria-pressed = { isMuted }
1089+ className = "p-2 rounded-full bg-slate-800/80 text-slate-300 hover:bg-slate-700/80 hover:text-white transition-colors backdrop-blur-sm focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-white"
10611090 >
1062- { isMuted ? < VolumeX className = "h-4 w-4" /> : < Volume2 className = "h-4 w-4" /> }
1091+ { isMuted ? < VolumeX className = "h-4 w-4" aria-hidden = "true" /> : < Volume2 className = "h-4 w-4" aria-hidden = "true " /> }
10631092 </ button >
10641093 ) }
10651094 </ div >
@@ -1231,7 +1260,7 @@ function App() {
12311260 < CardTitle className = "text-base" >
12321261 < a href = { api . url } target = "_blank" rel = "noopener noreferrer" className = "hover:underline inline-flex items-center gap-1" >
12331262 { api . name }
1234- < ExternalLink className = "h-3 w-3 opacity-50" />
1263+ < ExternalLink className = "h-3 w-3 opacity-50" aria-hidden = "true" />
12351264 </ a >
12361265 </ CardTitle >
12371266 </ div >
@@ -1500,7 +1529,7 @@ function App() {
15001529 < CardTitle className = "text-lg leading-tight" >
15011530 < a href = { card . url } target = "_blank" rel = "noopener noreferrer" className = "hover:underline inline-flex items-center gap-1.5" >
15021531 { card . title }
1503- < ExternalLink className = "h-3.5 w-3.5 opacity-40" />
1532+ < ExternalLink className = "h-3.5 w-3.5 opacity-40" aria-hidden = "true" />
15041533 </ a >
15051534 </ CardTitle >
15061535 </ div >
@@ -1629,7 +1658,7 @@ function App() {
16291658 < h4 className = "font-semibold text-sm mb-1" >
16301659 < a href = { tool . url } target = "_blank" rel = "noopener noreferrer" className = "hover:underline inline-flex items-center gap-1" >
16311660 { tool . name }
1632- < ExternalLink className = "h-3 w-3 opacity-40" />
1661+ < ExternalLink className = "h-3 w-3 opacity-40" aria-hidden = "true" />
16331662 </ a >
16341663 </ h4 >
16351664 < p className = "text-xs text-muted-foreground" > { tool . desc } </ p >
@@ -1944,7 +1973,7 @@ function App() {
19441973 < CardTitle className = "text-lg leading-tight" >
19451974 < a href = { model . url } target = "_blank" rel = "noopener noreferrer" className = "hover:underline inline-flex items-center gap-1.5" >
19461975 { model . name }
1947- < ExternalLink className = "h-3.5 w-3.5 opacity-40" />
1976+ < ExternalLink className = "h-3.5 w-3.5 opacity-40" aria-hidden = "true" />
19481977 </ a >
19491978 </ CardTitle >
19501979 < p className = "text-xs text-muted-foreground font-medium mt-1" > { model . role } </ p >
@@ -2078,7 +2107,7 @@ function App() {
20782107 { std . url ? (
20792108 < a href = { std . url } target = "_blank" rel = "noopener noreferrer" className = "hover:underline inline-flex items-center gap-1" >
20802109 { std . name }
2081- < ExternalLink className = "h-3 w-3 opacity-40" />
2110+ < ExternalLink className = "h-3 w-3 opacity-40" aria-hidden = "true" />
20822111 </ a >
20832112 ) : std . name }
20842113 </ div >
@@ -2174,6 +2203,8 @@ function App() {
21742203 </ div >
21752204 </ div >
21762205 </ footer >
2206+
2207+ </ main >
21772208 </ div >
21782209 )
21792210}
0 commit comments