This commit is contained in:
NotSoToothless
2026-06-04 13:55:14 -07:00
committed by GitHub
parent 62d25125cc
commit 732730829d
+150 -40
View File
@@ -417,6 +417,8 @@
};
</script>
<script src="/js/main.js?v=3"></script>
<script src="https://cdn.jsdelivr.net/npm/gsap@3.12.5/dist/gsap.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/gsap@3.12.5/dist/ScrollTrigger.min.js"></script>
<!-- Serpentine timeline renderer -->
<script>
@@ -430,9 +432,16 @@
var nodes = Array.prototype.slice.call(timeline.querySelectorAll('.timeline-node'));
var reduceMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
var useGsap = !!(window.gsap && window.ScrollTrigger);
var drawProgress = 0; // 0 to 1, how much of the line is drawn
var maxDrawProgress = 0;
var ticking = false;
var targetDrawProgress = 0;
var pathLength = 0;
var nodeProgresses = [];
var nodeRevealed = [];
var state = { progress: 0 };
var lineTrigger = null;
var nodeTriggers = [];
var scrollNormalized = false;
// Build a rounded-corner path that threads through a list of points.
function roundedPath(pts, radius) {
@@ -473,11 +482,14 @@
track.setAttribute('d', d);
progress.setAttribute('d', d);
var len = progress.getTotalLength();
progress.style.strokeDasharray = len;
progress.style.strokeDashoffset = len * (1 - drawProgress);
updateComet(len);
return { pts: pts, len: len };
pathLength = progress.getTotalLength();
progress.style.strokeDasharray = pathLength;
progress.style.strokeDashoffset = pathLength * (1 - drawProgress);
nodeProgresses = nodes.map(function (node, index) {
return nodePathProgress(node, index, pathLength);
});
updateComet(pathLength);
return { pts: pts, len: pathLength };
}
function updateComet(len) {
@@ -511,56 +523,148 @@
return best;
}
function updateFromScroll() {
ticking = false;
var len = progress.getTotalLength();
var viewport = window.innerHeight || document.documentElement.clientHeight;
var revealLine = viewport * 0.9;
var targetProgress = maxDrawProgress;
function nodePathProgress(node, index, len) {
if (index === nodes.length - 1) return 1;
var marker = node.querySelector('.timeline-marker');
var markerRect = (marker || node).getBoundingClientRect();
var box = timeline.getBoundingClientRect();
var pt = {
x: markerRect.left - box.left + markerRect.width / 2,
y: markerRect.top - box.top + markerRect.height / 2
};
return lengthAtPoint(pt, len) / len;
}
function revealReachedNodes(len) {
nodes.forEach(function (node, index) {
var cardRect = node.getBoundingClientRect();
if (cardRect.top > revealLine) return;
node.classList.add('is-visible');
var marker = node.querySelector('.timeline-marker');
var markerRect = (marker || node).getBoundingClientRect();
var box = timeline.getBoundingClientRect();
var pt = {
x: markerRect.left - box.left + markerRect.width / 2,
y: markerRect.top - box.top + markerRect.height / 2
};
var nodeProgress = index === nodes.length - 1 ? 1 : lengthAtPoint(pt, len) / len;
targetProgress = Math.max(targetProgress, nodeProgress);
if (nodeRevealed[index]) return;
if (drawProgress >= (nodeProgresses[index] || 0) - 0.012) {
nodeRevealed[index] = true;
node.classList.add('is-visible');
if (useGsap) {
gsap.to(node, {
autoAlpha: 1,
y: 0,
scale: 1,
duration: 0.7,
ease: 'power3.out',
overwrite: true
});
}
}
});
}
maxDrawProgress = Math.max(maxDrawProgress, targetProgress);
drawProgress = maxDrawProgress;
progress.style.strokeDashoffset = len * (1 - drawProgress);
updateComet(len);
function renderProgress() {
drawProgress = state.progress;
progress.style.strokeDashoffset = pathLength * (1 - drawProgress);
updateComet(pathLength);
revealReachedNodes(pathLength);
if (drawProgress >= 1) {
comet.style.opacity = 0;
nodes.forEach(function (n) { n.classList.add('is-visible'); });
nodes.forEach(function (n, index) {
nodeRevealed[index] = true;
n.classList.add('is-visible');
if (useGsap) gsap.set(n, { autoAlpha: 1, y: 0, scale: 1 });
});
}
}
function requestScrollUpdate() {
if (ticking) return;
ticking = true;
requestAnimationFrame(updateFromScroll);
function advanceTo(value, duration) {
var next = Math.max(targetDrawProgress, value);
targetDrawProgress = Math.min(1, Math.max(0, next));
if (targetDrawProgress <= state.progress + 0.001) return;
if (!useGsap) {
state.progress = targetDrawProgress;
renderProgress();
return;
}
gsap.to(state, {
progress: targetDrawProgress,
duration: duration || 0.85,
ease: 'power3.out',
overwrite: true,
onUpdate: renderProgress
});
}
function destroyScrollTriggers() {
if (lineTrigger) {
lineTrigger.kill();
lineTrigger = null;
}
nodeTriggers.forEach(function (trigger) { trigger.kill(); });
nodeTriggers = [];
}
function createScrollTriggers() {
if (!useGsap) return;
gsap.registerPlugin(ScrollTrigger);
if (!scrollNormalized) {
try {
ScrollTrigger.normalizeScroll({
allowNestedScroll: true,
lockAxis: false,
momentum: function (self) {
return Math.min(2.4, Math.abs(self.velocityY) / 1100);
}
});
} catch (e) {
ScrollTrigger.normalizeScroll(true);
}
scrollNormalized = true;
}
destroyScrollTriggers();
nodes.forEach(function (node, index) {
gsap.set(node, nodeRevealed[index]
? { autoAlpha: 1, y: 0, scale: 1, transformOrigin: '50% 50%' }
: { autoAlpha: 0, y: 34, scale: 0.985, transformOrigin: '50% 50%' });
});
lineTrigger = ScrollTrigger.create({
trigger: timeline,
start: 'top 82%',
endTrigger: nodes[nodes.length - 1] || timeline,
end: 'top 82%',
scrub: 0.75,
invalidateOnRefresh: true,
onUpdate: function (self) {
advanceTo(self.progress, 0.45);
}
});
nodes.forEach(function (node, index) {
nodeTriggers.push(ScrollTrigger.create({
trigger: node,
start: 'top 82%',
once: true,
onEnter: function () {
advanceTo(nodeProgresses[index] || 0, 0.85);
}
}));
});
}
function init() {
build();
if (reduceMotion) {
drawProgress = 1;
state.progress = 1;
progress.style.strokeDashoffset = 0;
nodes.forEach(function (n) { n.classList.add('is-visible'); });
return;
}
updateFromScroll();
window.addEventListener('scroll', requestScrollUpdate, { passive: true });
if (useGsap) {
createScrollTriggers();
renderProgress();
ScrollTrigger.refresh();
} else {
advanceTo(1, 0);
}
}
// Recompute geometry on resize (layout flips between 1 and 3 columns).
@@ -570,7 +674,10 @@
resizeTimer = setTimeout(function () {
_samples = null;
build();
if (!reduceMotion) updateFromScroll();
if (!reduceMotion && useGsap) {
createScrollTriggers();
ScrollTrigger.refresh();
}
}, 120);
});
@@ -583,7 +690,10 @@
window.addEventListener('load', function () {
_samples = null;
build();
if (!reduceMotion) updateFromScroll();
if (!reduceMotion && useGsap) {
createScrollTriggers();
ScrollTrigger.refresh();
}
});
})();
</script>