@@ -31,10 +31,13 @@ const languageLabel = computed(() => {
3131
3232const scrollProgress = ref (0 );
3333const navbarBg = ref (" rgba(5, 7, 18, 0.72)" );
34+ const showBackToTop = ref (false );
35+ const activeSection = ref (" " );
3436
3537let scrollRaf: number | null = null ;
3638let pointerRaf: number | null = null ;
3739let sectionObserver: IntersectionObserver | null = null ;
40+ let navHighlightObserver: IntersectionObserver | null = null ;
3841
3942function ensureBusuanziScriptLoaded() {
4043 const scriptId = " busuanzi-script" ;
@@ -106,6 +109,7 @@ function switchLanguage(lang: Lang) {
106109
107110function updateScrollEffects() {
108111 navbarBg .value = window .scrollY > 100 ? " rgba(5, 7, 18, 0.82)" : " rgba(5, 7, 18, 0.72)" ;
112+ showBackToTop .value = window .scrollY > 300 ;
109113
110114 const maxScroll = document .documentElement .scrollHeight - window .innerHeight ;
111115 scrollProgress .value = maxScroll > 0 ? window .scrollY / maxScroll : 0 ;
@@ -195,6 +199,29 @@ function setupMemberCardHoverZIndex() {
195199 });
196200}
197201
202+ function setupNavHighlightObserver() {
203+ const sections = [" about" , " achievements" , " projects" , " members" , " news" , " contact" ];
204+
205+ navHighlightObserver = new IntersectionObserver (
206+ (entries ) => {
207+ entries .forEach ((entry ) => {
208+ if (entry .isIntersecting ) {
209+ activeSection .value = entry .target .id ;
210+ }
211+ });
212+ },
213+ {
214+ threshold: 0.3 ,
215+ rootMargin: " -80px 0px -50% 0px" ,
216+ }
217+ );
218+
219+ sections .forEach ((id ) => {
220+ const el = document .getElementById (id );
221+ if (el ) navHighlightObserver ?.observe (el );
222+ });
223+ }
224+
198225onMounted (async () => {
199226 document .documentElement .classList .add (" js" );
200227
@@ -210,6 +237,7 @@ onMounted(async () => {
210237
211238 setupSectionObserver ();
212239 setupMemberCardHoverZIndex ();
240+ setupNavHighlightObserver ();
213241 ensureBusuanziScriptLoaded ();
214242});
215243
@@ -228,12 +256,15 @@ onUnmounted(() => {
228256 document .removeEventListener (" click" , onDocumentClick );
229257 sectionObserver ?.disconnect ();
230258 sectionObserver = null ;
259+ navHighlightObserver ?.disconnect ();
260+ navHighlightObserver = null ;
231261});
232262 </script >
233263
234264<template >
235- <div class =" scroll-progress" :style =" { transform: `scaleX(${scrollProgress})` }" ></div >
236- <nav class =" navbar" :class =" { 'menu-open': isMenuOpen }" :style =" { background: navbarBg }" >
265+ <a href =" #main-content" class =" skip-link" >{{ t('a11y.skipToContent') }}</a >
266+ <div class =" scroll-progress" :style =" { transform: `scaleX(${scrollProgress})` }" role =" progressbar" :aria-valuenow =" Math.round(scrollProgress * 100)" aria-valuemin =" 0" aria-valuemax =" 100" :aria-label =" t('a11y.scrollProgress')" ></div >
267+ <nav class =" navbar" :class =" { 'menu-open': isMenuOpen }" :style =" { background: navbarBg }" role =" navigation" :aria-label =" t('a11y.mainNavigation')" >
237268 <div class =" nav-content" >
238269 <a href =" #" class =" nav-logo" @click.prevent =" scrollToTop" >
239270 <img src =" /RushDB.png" alt =" RushDB Logo" class =" nav-logo-img" />
@@ -245,22 +276,22 @@ onUnmounted(() => {
245276 </button >
246277 <ul class =" nav-links" >
247278 <li >
248- <a href =" #about" >{{ t("nav.about") }}</a >
279+ <a href =" #about" :class = " { active: activeSection === 'about' } " >{{ t("nav.about") }}</a >
249280 </li >
250281 <li >
251- <a href =" #achievements" >{{ t("nav.achievements") }}</a >
282+ <a href =" #achievements" :class = " { active: activeSection === 'achievements' } " >{{ t("nav.achievements") }}</a >
252283 </li >
253284 <li >
254- <a href =" #projects" >{{ t("nav.projects") }}</a >
285+ <a href =" #projects" :class = " { active: activeSection === 'projects' } " >{{ t("nav.projects") }}</a >
255286 </li >
256287 <li >
257- <a href =" #members" >{{ t("nav.members") }}</a >
288+ <a href =" #members" :class = " { active: activeSection === 'members' } " >{{ t("nav.members") }}</a >
258289 </li >
259290 <li >
260- <a href =" #news" >{{ t("nav.news") }}</a >
291+ <a href =" #news" :class = " { active: activeSection === 'news' } " >{{ t("nav.news") }}</a >
261292 </li >
262293 <li >
263- <a href =" #contact" >{{ t("nav.contact") }}</a >
294+ <a href =" #contact" :class = " { active: activeSection === 'contact' } " >{{ t("nav.contact") }}</a >
264295 </li >
265296 </ul >
266297
@@ -288,7 +319,7 @@ onUnmounted(() => {
288319 <HeroSection />
289320
290321 <div class =" container" >
291- <main class =" main-content" >
322+ <main id = " main-content " class =" main-content" role = " main " >
292323 <div class =" content-inner" >
293324 <AboutSection />
294325 <AchievementsSection />
@@ -349,4 +380,17 @@ onUnmounted(() => {
349380 </div >
350381 </footer >
351382 </div >
383+
384+ <!-- Back to Top Button -->
385+ <button
386+ v-show =" showBackToTop"
387+ class =" back-to-top"
388+ type =" button"
389+ :aria-label =" t('a11y.backToTop')"
390+ @click =" scrollToTop"
391+ >
392+ <svg width =" 20" height =" 20" viewBox =" 0 0 24 24" fill =" none" stroke =" currentColor" stroke-width =" 2.5" stroke-linecap =" round" stroke-linejoin =" round" aria-hidden =" true" >
393+ <polyline points =" 18 15 12 9 6 15" ></polyline >
394+ </svg >
395+ </button >
352396</template >
0 commit comments