|
1 | | -$(document).ready(function() { |
2 | | - |
3 | | - // Scroll to top button ---------------------------------------------------------- |
4 | | - // When the user scrolls down 20px from the top of the document, show the button |
5 | | - window.onscroll = function() { |
6 | | - scrollFunction() |
7 | | - }; |
8 | | - |
9 | | - function scrollFunction() { |
10 | | - if (document.body.scrollTop > 350 || document.documentElement.scrollTop > 350) { |
11 | | - document.getElementById("topper").style.display = "block"; |
12 | | - } else { |
13 | | - document.getElementById("topper").style.display = "none"; |
14 | | - } |
| 1 | +/* ── Apply saved theme immediately to prevent flash ── */ |
| 2 | +(function () { |
| 3 | + var saved = localStorage.getItem('theme'); |
| 4 | + if (saved === 'dark' || (!saved && window.matchMedia('(prefers-color-scheme: dark)').matches)) { |
| 5 | + document.documentElement.setAttribute('data-theme', 'dark'); |
| 6 | + } |
| 7 | +})(); |
| 8 | + |
| 9 | +document.addEventListener('DOMContentLoaded', function () { |
| 10 | + |
| 11 | + /* ── Scroll-to-top button (debounced via rAF) ── */ |
| 12 | + var topper = document.getElementById('topper'); |
| 13 | + if (topper) { |
| 14 | + var ticking = false; |
| 15 | + window.addEventListener('scroll', function () { |
| 16 | + if (!ticking) { |
| 17 | + window.requestAnimationFrame(function () { |
| 18 | + topper.style.display = |
| 19 | + document.documentElement.scrollTop > 350 ? 'block' : 'none'; |
| 20 | + ticking = false; |
| 21 | + }); |
| 22 | + ticking = true; |
| 23 | + } |
| 24 | + }, { passive: true }); |
| 25 | + |
| 26 | + topper.addEventListener('click', function (e) { |
| 27 | + e.preventDefault(); |
| 28 | + window.scrollTo({ top: 0, behavior: 'smooth' }); |
| 29 | + }); |
15 | 30 | } |
16 | 31 |
|
17 | | - // When the user clicks on the button, scroll to the top of the document |
18 | | - function topFunction() { |
19 | | - document.body.scrollTop = 0; // For Safari |
20 | | - document.documentElement.scrollTop = 0; // For Chrome, Firefox, IE and Opera |
| 32 | + /* ── Read-more toggle ── */ |
| 33 | + var readMore = document.getElementById('read-more'); |
| 34 | + var badgeMore = document.getElementById('badge-more'); |
| 35 | + if (readMore && badgeMore) { |
| 36 | + readMore.style.display = 'none'; |
| 37 | + badgeMore.addEventListener('click', function () { |
| 38 | + var hidden = readMore.style.display === 'none'; |
| 39 | + readMore.style.display = hidden ? 'block' : 'none'; |
| 40 | + badgeMore.textContent = hidden ? 'less' : 'more'; |
| 41 | + }); |
21 | 42 | } |
22 | 43 |
|
23 | | - $("#topper").on("click", function() { |
24 | | - $("html").animate({ |
25 | | - scrollTop: 0 |
26 | | - }, 400); |
| 44 | + /* ── Accordion show-all / hide-all ── */ |
| 45 | + document.querySelectorAll('.expander').forEach(function (btn) { |
| 46 | + btn.addEventListener('click', function () { |
| 47 | + var showAll = btn.textContent.trim() === 'show all'; |
| 48 | + document.querySelectorAll('.panel-collapse').forEach(function (panel) { |
| 49 | + var inst = bootstrap.Collapse.getOrCreateInstance(panel, { toggle: false }); |
| 50 | + showAll ? inst.show() : inst.hide(); |
| 51 | + }); |
| 52 | + document.querySelectorAll('.accordion-plus-toggle').forEach(function (a) { |
| 53 | + a.setAttribute('aria-expanded', showAll ? 'true' : 'false'); |
| 54 | + showAll ? a.classList.remove('collapsed') : a.classList.add('collapsed'); |
| 55 | + }); |
| 56 | + btn.textContent = showAll ? 'hide all' : 'show all'; |
| 57 | + }); |
27 | 58 | }); |
28 | 59 |
|
29 | | - // tooltips function |
30 | | - $(function() { |
31 | | - $('[data-toggle="tooltip"]').tooltip() |
32 | | - }) |
33 | | - |
34 | | - // Initally hide the read more div |
35 | | - $("#read-more").css("display", "none"); |
36 | | - |
37 | | - // Show more on click |
38 | | - $("#badge-more").on("click", function() { |
39 | | - |
40 | | - // Show/hide the div |
41 | | - $("#read-more").fadeToggle("fast"); |
42 | | - |
43 | | - // Change the button |
44 | | - if ($("#badge-more").text() == "more") { |
45 | | - $("#badge-more").text("less"); |
46 | | - } else { |
47 | | - $("#badge-more").text("more"); |
48 | | - } |
49 | | - |
50 | | - }); |
51 | | - |
52 | | - // popover function |
53 | | - $('[data-toggle="popover"]').popover(); |
54 | | - |
55 | | - // open all accordion panels for possible rinting |
56 | | - $(".expander").on("click", function() { |
57 | | - |
58 | | - // Change the button |
59 | | - if ($(".expander").text() == "show all") { |
60 | | - $(".expander").text("hide all"); |
61 | | - $(".panel-collapse").addClass("in"); |
62 | | - $(".panel-default a").attr("aria-expanded", "true").removeClass("collapsed"); |
63 | | - } else { |
64 | | - $(".expander").text("show all"); |
65 | | - $(".panel-collapse").removeClass("in"); |
66 | | - $(".panel-default a").attr("aria-expanded", "false").addClass("collapsed"); |
| 60 | + /* ── Fade-in on scroll (IntersectionObserver) ── */ |
| 61 | + var fadeEls = document.querySelectorAll('.fade-in'); |
| 62 | + if (fadeEls.length && 'IntersectionObserver' in window) { |
| 63 | + var observer = new IntersectionObserver(function (entries) { |
| 64 | + entries.forEach(function (entry) { |
| 65 | + if (entry.isIntersecting) { |
| 66 | + entry.target.classList.add('visible'); |
| 67 | + observer.unobserve(entry.target); |
67 | 68 | } |
| 69 | + }); |
| 70 | + }, { threshold: 0.12 }); |
| 71 | + fadeEls.forEach(function (el) { observer.observe(el); }); |
| 72 | + } |
68 | 73 |
|
69 | | - }); |
70 | | - |
71 | | - $(".accordion-toggle").on("click", function() { |
| 74 | + /* ── Navbar shadow on scroll ── */ |
| 75 | + var nav = document.getElementById('main-nav'); |
| 76 | + if (nav) { |
| 77 | + window.addEventListener('scroll', function () { |
| 78 | + nav.classList.toggle('navbar-scrolled', document.documentElement.scrollTop > 10); |
| 79 | + }, { passive: true }); |
| 80 | + } |
72 | 81 |
|
73 | | - $(".panel-collapse").removeClass("in"); |
74 | | - $(".panel-default a").attr("aria-expanded", "false").addClass("collapsed"); |
75 | | - $(".expander").text("show all"); |
| 82 | + /* ── News gallery click-to-open ── */ |
| 83 | + document.querySelectorAll('.news-gallery img').forEach(function (img) { |
| 84 | + img.style.cursor = 'pointer'; |
| 85 | + img.addEventListener('click', function () { |
| 86 | + window.open(img.src, '_blank'); |
| 87 | + }); |
| 88 | + }); |
76 | 89 |
|
77 | | - }); |
| 90 | +}); |
78 | 91 |
|
79 | | - // Project +/- toggle for descriptions |
80 | | - $(document).on("click", ".project-toggle", function() { |
81 | | - var $item = $(this).closest(".news-item"); |
82 | | - var $details = $item.find(".project-details"); |
83 | | - $details.slideToggle(200); |
84 | | - $(this).text($(this).text() === "+" ? "\u2212" : "+"); |
85 | | - }); |
| 92 | +/* ── Called by w3IncludeHTML callback after navbar/footer are loaded ── */ |
| 93 | +function initPage() { |
| 94 | + |
| 95 | + /* Active nav link based on current page */ |
| 96 | + var page = location.pathname.split('/').pop() || 'index.html'; |
| 97 | + var map = { |
| 98 | + 'index.html': 'about', |
| 99 | + 'news.html': 'news', |
| 100 | + 'projects.html': 'projects', |
| 101 | + 'publication.html': 'publication', |
| 102 | + 'vitae.html': 'vitae' |
| 103 | + }; |
| 104 | + var id = map[page]; |
| 105 | + if (id) { |
| 106 | + var li = document.getElementById(id); |
| 107 | + if (li) { |
| 108 | + var a = li.querySelector('a'); |
| 109 | + if (a) { |
| 110 | + a.classList.add('active'); |
| 111 | + a.classList.add('hvr-bubble-bottom'); |
| 112 | + } |
| 113 | + } |
| 114 | + } |
86 | 115 |
|
| 116 | + /* Dark-mode toggle button (lives inside included navbar) */ |
| 117 | + var toggle = document.getElementById('theme-toggle'); |
| 118 | + if (toggle) { |
| 119 | + var html = document.documentElement; |
| 120 | + function updateIcon() { |
| 121 | + var icon = toggle.querySelector('i'); |
| 122 | + if (icon) { |
| 123 | + icon.className = html.getAttribute('data-theme') === 'dark' |
| 124 | + ? 'fas fa-sun' : 'fas fa-moon'; |
| 125 | + } |
| 126 | + } |
| 127 | + updateIcon(); |
| 128 | + toggle.addEventListener('click', function () { |
| 129 | + var dark = html.getAttribute('data-theme') !== 'dark'; |
| 130 | + html.setAttribute('data-theme', dark ? 'dark' : 'light'); |
| 131 | + localStorage.setItem('theme', dark ? 'dark' : 'light'); |
| 132 | + updateIcon(); |
| 133 | + }); |
| 134 | + } |
87 | 135 |
|
88 | | -}); |
| 136 | + /* Bootstrap 5 tooltips */ |
| 137 | + document.querySelectorAll('[data-bs-toggle="tooltip"]').forEach(function (el) { |
| 138 | + new bootstrap.Tooltip(el); |
| 139 | + }); |
| 140 | +} |
0 commit comments