Yacine Jernite commited on
Commit
a08c68d
·
1 Parent(s): f07b5d3

sticky_nav_prettier

Browse files
Files changed (3) hide show
  1. js/main.js +7 -1
  2. js/utils/stickyNavigation.js +88 -1
  3. styles.css +39 -2
js/main.js CHANGED
@@ -12,7 +12,13 @@ export function scrollToSection(sectionId) {
12
  if (element) {
13
  const elementRect = element.getBoundingClientRect();
14
  const absoluteElementTop = elementRect.top + window.pageYOffset;
15
- const offset = 120; // Account for fixed header + some padding
 
 
 
 
 
 
16
 
17
  window.scrollTo({
18
  top: absoluteElementTop - offset,
 
12
  if (element) {
13
  const elementRect = element.getBoundingClientRect();
14
  const absoluteElementTop = elementRect.top + window.pageYOffset;
15
+
16
+ // Calculate offset: header height + sticky nav height + padding
17
+ const headerHeight = parseInt(getComputedStyle(document.documentElement)
18
+ .getPropertyValue('--header-height')) || 100;
19
+ const stickyNav = document.querySelector('.page-navigation.sticky-nav');
20
+ const navHeight = stickyNav ? stickyNav.offsetHeight : 0;
21
+ const offset = headerHeight + navHeight + 16; // 16px padding
22
 
23
  window.scrollTo({
24
  top: absoluteElementTop - offset,
js/utils/stickyNavigation.js CHANGED
@@ -1,5 +1,5 @@
1
  // stickyNavigation.js - Sticky navigation behavior and hamburger menu
2
- // Handles: sticky positioning on scroll + collapse/expand toggle
3
 
4
  /**
5
  * Initializes sticky navigation behavior for a page
@@ -36,6 +36,93 @@ export function initializeStickyNavigation(navElementId) {
36
  });
37
 
38
  handleScroll(); // Initial check
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
39
  }
40
 
41
  /**
 
1
  // stickyNavigation.js - Sticky navigation behavior and hamburger menu
2
+ // Handles: sticky positioning on scroll + collapse/expand toggle + active section tracking
3
 
4
  /**
5
  * Initializes sticky navigation behavior for a page
 
36
  });
37
 
38
  handleScroll(); // Initial check
39
+
40
+ // Initialize active section tracking
41
+ initializeActiveSectionTracking();
42
+ }
43
+
44
+ /**
45
+ * Track visible sections and update active navigation item
46
+ * Uses Intersection Observer for efficient scroll tracking
47
+ */
48
+ function initializeActiveSectionTracking() {
49
+ // Get all navigation links
50
+ const navLinks = document.querySelectorAll('.page-navigation a[href^="/#"], .page-navigation a[href*="#"]');
51
+ if (navLinks.length === 0) return;
52
+
53
+ // Extract section IDs from navigation links
54
+ const sectionIds = Array.from(navLinks)
55
+ .map(link => {
56
+ const href = link.getAttribute('href');
57
+ const hashIndex = href.indexOf('#');
58
+ return hashIndex >= 0 ? href.substring(hashIndex + 1) : null;
59
+ })
60
+ .filter(id => id);
61
+
62
+ if (sectionIds.length === 0) return;
63
+
64
+ // Get all section elements
65
+ const sections = sectionIds
66
+ .map(id => document.getElementById(id))
67
+ .filter(section => section);
68
+
69
+ if (sections.length === 0) return;
70
+
71
+ // Track which section is most visible
72
+ const observerOptions = {
73
+ root: null,
74
+ rootMargin: '-20% 0px -60% 0px', // Trigger when section is 20% from top
75
+ threshold: [0, 0.1, 0.2, 0.5, 0.8, 1]
76
+ };
77
+
78
+ let activeSection = null;
79
+
80
+ const observer = new IntersectionObserver((entries) => {
81
+ // Find the most visible section
82
+ let mostVisible = null;
83
+ let maxRatio = 0;
84
+
85
+ entries.forEach(entry => {
86
+ if (entry.isIntersecting && entry.intersectionRatio > maxRatio) {
87
+ maxRatio = entry.intersectionRatio;
88
+ mostVisible = entry.target;
89
+ }
90
+ });
91
+
92
+ // Update active section if changed
93
+ if (mostVisible && mostVisible !== activeSection) {
94
+ activeSection = mostVisible;
95
+ updateActiveNavItem(mostVisible.id);
96
+ }
97
+ }, observerOptions);
98
+
99
+ // Observe all sections
100
+ sections.forEach(section => observer.observe(section));
101
+ }
102
+
103
+ /**
104
+ * Update active navigation item based on current section
105
+ * @param {string} sectionId - ID of the currently visible section
106
+ */
107
+ function updateActiveNavItem(sectionId) {
108
+ const navLinks = document.querySelectorAll('.page-navigation a');
109
+
110
+ navLinks.forEach(link => {
111
+ const href = link.getAttribute('href');
112
+ const linkSectionId = href.substring(href.indexOf('#') + 1);
113
+
114
+ if (linkSectionId === sectionId) {
115
+ // Add active classes
116
+ link.classList.add('text-blue-600', 'font-semibold');
117
+ link.classList.add('underline', 'decoration-2', 'underline-offset-2');
118
+ link.classList.remove('text-gray-700');
119
+ } else {
120
+ // Remove active classes
121
+ link.classList.remove('text-blue-600', 'font-semibold');
122
+ link.classList.remove('underline', 'decoration-2', 'underline-offset-2');
123
+ link.classList.add('text-gray-700');
124
+ }
125
+ });
126
  }
127
 
128
  /**
styles.css CHANGED
@@ -12,6 +12,11 @@ html, body {
12
  width: 100%;
13
  }
14
 
 
 
 
 
 
15
  /* Apply fonts to base elements */
16
  body {
17
  font-family: 'Source Sans Pro', sans-serif;
@@ -164,6 +169,28 @@ p {
164
  transition: all 0.2s ease;
165
  }
166
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
167
  .page-navigation.sticky-nav {
168
  position: fixed;
169
  top: var(--header-height);
@@ -219,7 +246,7 @@ p {
219
  @media (max-width: 767px) {
220
  .page-navigation.sticky-nav {
221
  top: var(--header-height);
222
- padding: 0.25rem 0; /* Less padding on mobile */
223
  }
224
 
225
  .page-navigation.sticky-nav #nav-container {
@@ -228,7 +255,17 @@ p {
228
 
229
  .page-navigation.sticky-nav ~ section:first-of-type,
230
  .page-navigation.sticky-nav ~ div:first-of-type {
231
- margin-top: 0.5rem; /* Less space on mobile */
 
 
 
 
 
 
 
 
 
 
232
  }
233
  }
234
 
 
12
  width: 100%;
13
  }
14
 
15
+ /* Smooth scrolling for the entire page */
16
+ html {
17
+ scroll-behavior: smooth;
18
+ }
19
+
20
  /* Apply fonts to base elements */
21
  body {
22
  font-family: 'Source Sans Pro', sans-serif;
 
169
  transition: all 0.2s ease;
170
  }
171
 
172
+ /* Navigation link active state transitions */
173
+ .page-navigation a {
174
+ transition: all 0.3s ease;
175
+ position: relative;
176
+ }
177
+
178
+ /* Subtle entrance animation for sticky nav */
179
+ @keyframes slideDown {
180
+ from {
181
+ opacity: 0.8;
182
+ transform: translateY(-10px);
183
+ }
184
+ to {
185
+ opacity: 1;
186
+ transform: translateY(0);
187
+ }
188
+ }
189
+
190
+ .page-navigation.sticky-nav #nav-container {
191
+ animation: slideDown 0.3s ease-out;
192
+ }
193
+
194
  .page-navigation.sticky-nav {
195
  position: fixed;
196
  top: var(--header-height);
 
246
  @media (max-width: 767px) {
247
  .page-navigation.sticky-nav {
248
  top: var(--header-height);
249
+ padding: 0.25rem 0;
250
  }
251
 
252
  .page-navigation.sticky-nav #nav-container {
 
255
 
256
  .page-navigation.sticky-nav ~ section:first-of-type,
257
  .page-navigation.sticky-nav ~ div:first-of-type {
258
+ margin-top: 0.5rem;
259
+ }
260
+
261
+ /* Auto-collapse navigation on mobile when sticky for better UX */
262
+ .page-navigation.sticky-nav #nav-items {
263
+ font-size: 0.875rem; /* Slightly smaller text */
264
+ }
265
+
266
+ /* Ensure hamburger is more prominent on mobile */
267
+ .page-navigation.sticky-nav #nav-toggle {
268
+ padding: 0.5rem;
269
  }
270
  }
271