PageSourceSearch

https://www.johnbward.com/scripts/about.js

js johnbward.com collected 2026-10-07 22:48:04 UTC 2,961 bytes, 114 lines download raw bytes

1/** About Us — scroll reveal + timeline progress line + active markers. */
2(function () {
3	function initReveal() {
4		document.documentElement.classList.add('jbw-anim');
5
6		var targets = document.querySelectorAll('[data-reveal]');
7		if (!targets.length) return;
8
9		function markInView(el) {
10			var rect = el.getBoundingClientRect();
11			return rect.top < window.innerHeight * 0.92 && rect.bottom > 0;
12		}
13
14		if (!('IntersectionObserver' in window)) {
15			targets.forEach(function (el) {
16				el.classList.add('is-inview');
17			});
18			return;
19		}
20
21		var observer = new IntersectionObserver(
22			function (entries) {
23				entries.forEach(function (entry) {
24					if (entry.isIntersecting) {
25						entry.target.classList.add('is-inview');
26						observer.unobserve(entry.target);
27					}
28				});
29			},
30			{ rootMargin: '0px 0px -8% 0px', threshold: 0.08 }
31		);
32
33		targets.forEach(function (el) {
34			if (markInView(el)) {
35				el.classList.add('is-inview');
36			} else {
37				observer.observe(el);
38			}
39		});
40	}
41
42	function initTimeline() {
43		var timeline = document.querySelector('[data-jbw-timeline]');
44		var progress = document.querySelector('[data-jbw-timeline-progress]');
45		if (!timeline || !progress) return;
46
47		var items = Array.prototype.slice.call(
48			timeline.querySelectorAll('.jbw-about-timeline__item')
49		);
50		var ticking = false;
51		var lastActive = -1;
52		var lastRatio = -1;
53
54		function update() {
55			ticking = false;
56
57			var rect = timeline.getBoundingClientRect();
58			var top = window.scrollY + rect.top;
59			var height = timeline.offsetHeight;
60			var viewportMid = window.scrollY + window.innerHeight * 0.45;
61			var start = top;
62			var end = top + height;
63			var ratio = (viewportMid - start) / Math.max(end - start, 1);
64			ratio = Math.min(1, Math.max(0, ratio));
65
66			if (Math.abs(ratio - lastRatio) > 0.001) {
67				lastRatio = ratio;
68				progress.style.transform = 'scaleY(' + ratio + ')';
69			}
70
71			var focusY = window.innerHeight * 0.42;
72			var best = -1;
73			var bestDist = Infinity;
74
75			for (var i = 0; i < items.length; i++) {
76				var itemRect = items[i].getBoundingClientRect();
77				if (itemRect.bottom < 0 || itemRect.top > window.innerHeight) continue;
78				var mid = itemRect.top + itemRect.height * 0.35;
79				var dist = Math.abs(mid - focusY);
80				if (dist < bestDist) {
81					bestDist = dist;
82					best = i;
83				}
84			}
85
86			if (best !== lastActive) {
87				if (lastActive >= 0) items[lastActive].classList.remove('is-active');
88				if (best >= 0) items[best].classList.add('is-active');
89				lastActive = best;
90			}
91		}
92
93		function onScroll() {
94			if (!ticking) {
95				ticking = true;
96				requestAnimationFrame(update);
97			}
98		}
99
100		window.addEventListener('scroll', onScroll, { passive: true });
101		window.addEventListener('resize', onScroll);
102		update();
103	}
104
105	if (document.readyState === 'loading') {
106		document.addEventListener('DOMContentLoaded', function () {
107			initReveal();
108			initTimeline();
109		});
110	} else {
111		initReveal();
112		initTimeline();
113	}
114})();

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.