-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathscript.js
More file actions
90 lines (81 loc) · 3.56 KB
/
Copy pathscript.js
File metadata and controls
90 lines (81 loc) · 3.56 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
// =========================================================
// Reveal on scroll — with explicit and stagger delays
// =========================================================
const STAGGER_MS = 90;
const revealObserver = new IntersectionObserver((entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
entry.target.classList.add('is-visible');
revealObserver.unobserve(entry.target);
}
});
}, { threshold: 0.12, rootMargin: '0px 0px -6% 0px' });
// Apply explicit data-delay attributes, then observe
document.querySelectorAll('.reveal').forEach((el) => {
const explicit = el.dataset.delay;
if (explicit) el.style.setProperty('--delay', `${explicit}ms`);
revealObserver.observe(el);
});
// Apply per-group stagger to siblings tagged with data-stagger
const staggerGroups = new Map();
document.querySelectorAll('[data-stagger]').forEach((el) => {
const parent = el.parentElement;
if (!staggerGroups.has(parent)) staggerGroups.set(parent, 0);
const i = staggerGroups.get(parent);
el.style.setProperty('--delay', `${i * STAGGER_MS}ms`);
staggerGroups.set(parent, i + 1);
});
// =========================================================
// Scroll progress bar
// =========================================================
const progress = document.getElementById('scrollProgress');
const updateProgress = () => {
const doc = document.documentElement;
const scrolled = doc.scrollTop;
const max = doc.scrollHeight - doc.clientHeight;
const pct = max > 0 ? (scrolled / max) * 100 : 0;
if (progress) progress.style.width = `${pct}%`;
};
document.addEventListener('scroll', updateProgress, { passive: true });
updateProgress();
// =========================================================
// Active section indicator in nav
// =========================================================
const navLinks = Array.from(document.querySelectorAll('.masthead__nav a[data-target]'));
const targetSections = navLinks
.map((a) => document.getElementById(a.dataset.target))
.filter(Boolean);
const navObserver = new IntersectionObserver((entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
navLinks.forEach((a) => a.classList.remove('is-active'));
const link = navLinks.find((a) => a.dataset.target === entry.target.id);
if (link) link.classList.add('is-active');
}
});
}, { rootMargin: '-40% 0px -55% 0px', threshold: 0 });
targetSections.forEach((sec) => navObserver.observe(sec));
// =========================================================
// Theme toggle (light / dark) — event delegation, attaches to document
// so timing or DOM-ready issues can't break it.
// =========================================================
document.addEventListener('click', (e) => {
const btn = e.target.closest('#themeToggle');
if (!btn) return;
const root = document.documentElement;
const next = root.getAttribute('data-theme') === 'dark' ? 'light' : 'dark';
root.setAttribute('data-theme', next);
try { localStorage.setItem('theme', next); } catch (err) {}
});
// Follow system preference live, unless user has explicitly chosen
try {
const mq = window.matchMedia('(prefers-color-scheme: dark)');
const onChange = (e) => {
let saved = null;
try { saved = localStorage.getItem('theme'); } catch (err) {}
if (saved) return;
document.documentElement.setAttribute('data-theme', e.matches ? 'dark' : 'light');
};
if (mq.addEventListener) mq.addEventListener('change', onChange);
else if (mq.addListener) mq.addListener(onChange); // Safari < 14 fallback
} catch (err) { /* matchMedia unavailable, ignore */ }