|
1 | | -// assets/js/main.js |
2 | | -// main script |
3 | 1 | (function () { |
4 | 2 | "use strict"; |
5 | 3 |
|
6 | | - // --- Mobile Nav Toggle --- |
7 | 4 | const body = document.body; |
8 | 5 | const navToggler = document.getElementById("nav-toggler"); |
9 | 6 | const navOverlay = document.getElementById("mobile-nav-overlay"); |
|
23 | 20 | navCloseBtn.addEventListener("click", toggleNav); |
24 | 21 | } |
25 | 22 |
|
26 | | - // Close on Escape key |
27 | 23 | document.addEventListener('keydown', (e) => { |
28 | 24 | if (e.key === 'Escape' && body.classList.contains('is-mobile-nav-open')) { |
29 | 25 | toggleNav(); |
30 | 26 | } |
31 | 27 | }); |
32 | 28 |
|
33 | | - // --- Contribution Bubbles for Community Partners --- |
34 | 29 | const initContributionBubbles = () => { |
35 | 30 | const section = document.querySelector('.community-partners-section'); |
36 | 31 | const bubbleContainer = document.querySelector('.bubbles-container'); |
|
39 | 34 | const partnersWithContributions = Array.from(section.querySelectorAll('.partner-logo-link[data-contributions]')); |
40 | 35 | if (partnersWithContributions.length === 0) return; |
41 | 36 |
|
42 | | - // --- Logic for randomly appearing bubbles --- |
43 | 37 | const MAX_BUBBLES = 3; |
44 | 38 | const BUBBLE_LIFETIME = 4000; |
45 | 39 | const BUBBLE_INTERVAL = 2000; |
|
102 | 96 | setTimeout(() => { recentlyHidden.delete(partner); }, COOLDOWN_PERIOD); |
103 | 97 | }; |
104 | 98 |
|
105 | | - // --- NEW: Hover-triggered bubble logic --- |
106 | 99 | let hoverBubble = document.createElement('div'); |
107 | 100 | hoverBubble.className = 'contribution-bubble'; |
108 | 101 | bubbleContainer.appendChild(hoverBubble); |
|
111 | 104 | partnersWithContributions.forEach(partner => { |
112 | 105 | partner.addEventListener('mouseenter', () => { |
113 | 106 | isHovering = true; |
114 | | - // Hide any random bubbles that might be showing |
115 | 107 | activeBubbles.forEach((bubble, p) => hideBubble(p, bubble)); |
116 | 108 |
|
117 | 109 | hoverBubble.innerHTML = setBubbleContent(partner); |
118 | 110 |
|
119 | | - // Position calculation must happen *after* content is set |
120 | 111 | const partnerRect = partner.getBoundingClientRect(); |
121 | 112 | const containerRect = bubbleContainer.getBoundingClientRect(); |
122 | 113 | const left = partnerRect.left - containerRect.left + (partnerRect.width / 2) - (hoverBubble.offsetWidth / 2); |
|
132 | 123 | }); |
133 | 124 | }); |
134 | 125 |
|
135 | | - // --- Modified interval for random bubbles --- |
136 | 126 | setInterval(() => { |
137 | | - // Pause random bubbles if user is hovering or max bubbles are already shown |
138 | 127 | if (isHovering || activeBubbles.size >= MAX_BUBBLES) return; |
139 | 128 |
|
140 | 129 | const rect = section.getBoundingClientRect(); |
|
155 | 144 | }, BUBBLE_INTERVAL); |
156 | 145 | }; |
157 | 146 |
|
158 | | - |
159 | | - // --- Copy Code Block --- |
160 | 147 | const initCopyCodeButtons = () => { |
161 | 148 | const codeBlocks = document.querySelectorAll('.code-block-wrapper'); |
162 | 149 | codeBlocks.forEach(wrapper => { |
|
174 | 161 | setTimeout(() => { |
175 | 162 | button.classList.remove('copied'); |
176 | 163 | button.querySelector('.copy-text').classList.remove('hidden'); |
177 | | - button.querySelector('.copied-text').add('hidden'); |
| 164 | + button.querySelector('.copied-text').classList.add('hidden'); |
178 | 165 | }, 2000); |
179 | 166 | }).catch(err => { |
180 | 167 | console.error('Failed to copy text: ', err); |
|
185 | 172 | }); |
186 | 173 | }; |
187 | 174 |
|
188 | | - // --- OG Image Assets Modal --- |
189 | 175 | const initOgImageModal = () => { |
190 | 176 | const modal = document.getElementById('og-image-modal'); |
191 | 177 | const modalContent = document.getElementById('og-modal-content'); |
|
215 | 201 | }); |
216 | 202 |
|
217 | 203 | window.openOgModal = (imageUrls) => { |
218 | | - modalContent.innerHTML = ''; // Clear previous content |
219 | | - |
| 204 | + modalContent.innerHTML = ''; |
220 | 205 | if (!imageUrls || imageUrls.length === 0) { |
221 | 206 | modalContent.innerHTML = '<p class="text-center col-span-full">No social media assets found for this page.</p>'; |
222 | 207 | } else { |
|
255 | 240 | }); |
256 | 241 | }; |
257 | 242 |
|
258 | | - // Wait for the DOM to be fully loaded to initialize |
259 | | - if (document.readyState === 'loading') { |
260 | | - document.addEventListener('DOMContentLoaded', () => { |
261 | | - initCopyCodeButtons(); |
262 | | - initOgImageModal(); |
263 | | - if (window.innerWidth > 768) { // Only run on larger screens |
264 | | - initContributionBubbles(); |
| 243 | + const initMissionBoardFilters = () => { |
| 244 | + const filterTags = document.querySelectorAll('.filter-tag'); |
| 245 | + const clearButton = document.getElementById('clear-filter-btn'); |
| 246 | + const projectContainers = document.querySelectorAll('.project-container'); |
| 247 | + const pastContributionsSection = document.getElementById('past-contributions-section'); |
| 248 | + const projectLinks = document.querySelectorAll('.project-filter-link'); |
| 249 | + const stickyFilterPanel = document.querySelector('.filter-panel-glow'); |
| 250 | + const toc = document.getElementById('TableOfContents'); |
| 251 | + |
| 252 | + if (filterTags.length === 0 || projectContainers.length === 0) return; |
| 253 | + let activeTag = null; |
| 254 | + |
| 255 | + function scrollToElementWithOffset(element) { |
| 256 | + if (!element || !stickyFilterPanel) return; |
| 257 | + const stickyHeaderBottom = stickyFilterPanel.getBoundingClientRect().bottom; |
| 258 | + const elementPosition = element.getBoundingClientRect().top; |
| 259 | + const offsetPosition = elementPosition + window.scrollY - stickyHeaderBottom - 20; |
| 260 | + |
| 261 | + window.scrollTo({ |
| 262 | + top: offsetPosition, |
| 263 | + behavior: 'smooth' |
| 264 | + }); |
| 265 | + } |
| 266 | + |
| 267 | + function handleAnchorClick(e) { |
| 268 | + const link = e.currentTarget; |
| 269 | + const targetId = link.getAttribute('href'); |
| 270 | + if (targetId && targetId.startsWith('#')) { |
| 271 | + e.preventDefault(); |
| 272 | + const targetElement = document.querySelector(targetId); |
| 273 | + if (targetElement) { |
| 274 | + scrollToElementWithOffset(targetElement); |
| 275 | + history.pushState(null, null, targetId); |
| 276 | + } |
265 | 277 | } |
| 278 | + } |
| 279 | + |
| 280 | + projectLinks.forEach(link => { |
| 281 | + link.addEventListener('click', (e) => { |
| 282 | + if (activeTag) clearFilter(); |
| 283 | + handleAnchorClick(e); |
| 284 | + }); |
266 | 285 | }); |
267 | | - } else { |
| 286 | + |
| 287 | + if (toc) { |
| 288 | + toc.addEventListener('click', (e) => { |
| 289 | + const link = e.target.closest('a'); |
| 290 | + if (link) handleAnchorClick({ currentTarget: link, preventDefault: () => e.preventDefault() }); |
| 291 | + }); |
| 292 | + } |
| 293 | + |
| 294 | + function applyFilter(selectedTag) { |
| 295 | + let firstVisibleProject = null; |
| 296 | + if (pastContributionsSection) pastContributionsSection.style.display = 'none'; |
| 297 | + |
| 298 | + projectContainers.forEach(project => { |
| 299 | + const issues = project.querySelectorAll('.issue-item'); |
| 300 | + let projectHasVisibleIssue = false; |
| 301 | + issues.forEach(issue => { |
| 302 | + const hasTag = issue.querySelector(`[data-issue-tag="${selectedTag}"]`); |
| 303 | + issue.style.display = hasTag ? 'flex' : 'none'; |
| 304 | + if (hasTag) { |
| 305 | + projectHasVisibleIssue = true; |
| 306 | + } |
| 307 | + }); |
| 308 | + |
| 309 | + project.style.display = projectHasVisibleIssue ? 'block' : 'none'; |
| 310 | + |
| 311 | + if (projectHasVisibleIssue && !firstVisibleProject) { |
| 312 | + firstVisibleProject = project; |
| 313 | + } |
| 314 | + }); |
| 315 | + |
| 316 | + return firstVisibleProject; |
| 317 | + } |
| 318 | + |
| 319 | + function clearFilter() { |
| 320 | + activeTag = null; |
| 321 | + updateTagStyles(null); |
| 322 | + if(clearButton) clearButton.classList.add('hidden'); |
| 323 | + if (pastContributionsSection) pastContributionsSection.style.display = 'block'; |
| 324 | + projectContainers.forEach(project => { |
| 325 | + project.style.display = 'block'; |
| 326 | + project.querySelectorAll('.issue-item').forEach(issue => { |
| 327 | + issue.style.display = 'flex'; |
| 328 | + }); |
| 329 | + }); |
| 330 | + } |
| 331 | + |
| 332 | + function updateTagStyles(selectedTag) { |
| 333 | + filterTags.forEach(tag => { |
| 334 | + tag.classList.toggle('is-active', tag.dataset.tag === selectedTag); |
| 335 | + }); |
| 336 | + } |
| 337 | + |
| 338 | + filterTags.forEach(tag => { |
| 339 | + tag.addEventListener('click', () => { |
| 340 | + const selectedTag = tag.dataset.tag; |
| 341 | + if (activeTag === selectedTag) { |
| 342 | + clearFilter(); |
| 343 | + return; |
| 344 | + } |
| 345 | + activeTag = selectedTag; |
| 346 | + updateTagStyles(selectedTag); |
| 347 | + if(clearButton) clearButton.classList.remove('hidden'); |
| 348 | + const firstVisibleElement = applyFilter(selectedTag); |
| 349 | + if (firstVisibleElement) { |
| 350 | + scrollToElementWithOffset(firstVisibleElement); |
| 351 | + } |
| 352 | + }); |
| 353 | + }); |
| 354 | + |
| 355 | + if(clearButton) { |
| 356 | + clearButton.addEventListener('click', clearFilter); |
| 357 | + } |
| 358 | + |
| 359 | + const initialHash = window.location.hash; |
| 360 | + if (initialHash) { |
| 361 | + setTimeout(() => { |
| 362 | + const targetElement = document.querySelector(initialHash); |
| 363 | + if (targetElement) { |
| 364 | + scrollToElementWithOffset(targetElement); |
| 365 | + } |
| 366 | + }, 100); |
| 367 | + } |
| 368 | + }; |
| 369 | + |
| 370 | + const initAll = () => { |
268 | 371 | initCopyCodeButtons(); |
269 | 372 | initOgImageModal(); |
270 | | - if (window.innerWidth > 768) { // Only run on larger screens |
| 373 | + initMissionBoardFilters(); |
| 374 | + if (window.innerWidth > 768) { |
271 | 375 | initContributionBubbles(); |
272 | 376 | } |
| 377 | + }; |
| 378 | + |
| 379 | + if (document.readyState === 'loading') { |
| 380 | + document.addEventListener('DOMContentLoaded', initAll); |
| 381 | + } else { |
| 382 | + initAll(); |
273 | 383 | } |
274 | 384 |
|
275 | 385 | })(); |
0 commit comments