-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathscript.js
More file actions
95 lines (84 loc) · 2.95 KB
/
Copy pathscript.js
File metadata and controls
95 lines (84 loc) · 2.95 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
// ---------- Year ----------
document.getElementById("year").textContent = new Date().getFullYear();
// ---------- Nav scroll state + progress bar ----------
const nav = document.getElementById("nav");
const progress = document.getElementById("scrollProgress");
const onScroll = () => {
const y = window.scrollY;
nav.classList.toggle("scrolled", y > 40);
const docH = document.documentElement.scrollHeight - window.innerHeight;
progress.style.width = `${(y / docH) * 100}%`;
};
window.addEventListener("scroll", onScroll, { passive: true });
onScroll();
// ---------- Mobile menu ----------
const toggle = document.getElementById("navToggle");
const links = document.getElementById("navLinks");
toggle.addEventListener("click", () => {
toggle.classList.toggle("open");
links.classList.toggle("open");
});
links.querySelectorAll("a").forEach((a) =>
a.addEventListener("click", () => {
toggle.classList.remove("open");
links.classList.remove("open");
})
);
// ---------- Reveal on scroll ----------
const observer = new IntersectionObserver(
(entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
entry.target.classList.add("visible");
observer.unobserve(entry.target);
}
});
},
{ threshold: 0.12, rootMargin: "0px 0px -60px 0px" }
);
document.querySelectorAll(".reveal").forEach((el) => observer.observe(el));
// ---------- Animated stat counters ----------
const formatNum = (val, decimals) =>
decimals ? val.toFixed(decimals) : Math.round(val).toLocaleString();
const animateCount = (el) => {
const target = parseFloat(el.dataset.count);
const decimals = parseInt(el.dataset.decimals || "0", 10);
const suffix = el.dataset.suffix || "";
const duration = 1600;
const start = performance.now();
const tick = (now) => {
const t = Math.min((now - start) / duration, 1);
const eased = 1 - Math.pow(1 - t, 3); // easeOutCubic
el.textContent = formatNum(target * eased, decimals) + suffix;
if (t < 1) requestAnimationFrame(tick);
else el.textContent = formatNum(target, decimals) + suffix;
};
requestAnimationFrame(tick);
};
const statObserver = new IntersectionObserver(
(entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
animateCount(entry.target);
statObserver.unobserve(entry.target);
}
});
},
{ threshold: 0.5 }
);
document.querySelectorAll(".stat__num").forEach((el) => statObserver.observe(el));
// ---------- Active nav link on scroll ----------
const sections = document.querySelectorAll("section[id]");
const navAnchors = links.querySelectorAll("a[href^='#']");
const spy = () => {
const scrollPos = window.scrollY + 140;
let current = "";
sections.forEach((sec) => {
if (scrollPos >= sec.offsetTop) current = sec.id;
});
navAnchors.forEach((a) => {
a.style.color = a.getAttribute("href") === `#${current}` ? "var(--text)" : "";
});
};
window.addEventListener("scroll", spy, { passive: true });
spy();