update line (#1297)
This commit is contained in:
+44
-37
@@ -174,6 +174,8 @@
|
||||
.timeline-node {
|
||||
opacity: 0;
|
||||
transform: translateY(28px);
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
transition: opacity 0.6s ease, transform 0.6s cubic-bezier(0.2, 0.9, 0.25, 1);
|
||||
}
|
||||
.timeline-node.is-visible {
|
||||
@@ -196,8 +198,8 @@
|
||||
<p class="text-muted text-sm font-semibold tracking-[0.25em] uppercase mb-4">Our Story</p>
|
||||
<h1 class="text-4xl lg:text-6xl font-extrabold mb-5 gradient-text">The Timeline</h1>
|
||||
<p class="text-lg text-white/70 max-w-2xl mx-auto">
|
||||
How <%= botName %> grew from a late-night Discord experiment into a
|
||||
dedicated companion for War Thunder Squadron Battles.
|
||||
Our history, the milestones we passed along the way, and the giants whose
|
||||
shoulders helped us climb higher.
|
||||
</p>
|
||||
</div>
|
||||
</section>
|
||||
@@ -428,8 +430,8 @@
|
||||
|
||||
var nodes = Array.prototype.slice.call(timeline.querySelectorAll('.timeline-node'));
|
||||
var reduceMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
|
||||
var drawProgress = 0; // 0 → 1, how much of the line is drawn
|
||||
var animating = false;
|
||||
var drawProgress = 0; // 0 to 1, how much of the line is drawn
|
||||
var ticking = false;
|
||||
|
||||
// Build a rounded-corner path that threads through a list of points.
|
||||
function roundedPath(pts, radius) {
|
||||
@@ -524,30 +526,33 @@
|
||||
return best;
|
||||
}
|
||||
|
||||
function animateDraw() {
|
||||
if (animating) return;
|
||||
animating = true;
|
||||
var start = null;
|
||||
var duration = 7200; // ms for the line to travel the whole serpentine
|
||||
function step(ts) {
|
||||
if (start === null) start = ts;
|
||||
var p = Math.min((ts - start) / duration, 1);
|
||||
// ease-in-out
|
||||
drawProgress = p < 0.5 ? 2 * p * p : 1 - Math.pow(-2 * p + 2, 2) / 2;
|
||||
var len = progress.getTotalLength();
|
||||
progress.style.strokeDashoffset = len * (1 - drawProgress);
|
||||
updateComet(len);
|
||||
revealNodes();
|
||||
if (p < 1) {
|
||||
requestAnimationFrame(step);
|
||||
} else {
|
||||
drawProgress = 1;
|
||||
progress.style.strokeDashoffset = 0;
|
||||
comet.style.opacity = 0;
|
||||
nodes.forEach(function (n) { n.classList.add('is-visible'); });
|
||||
}
|
||||
function clamp(value, min, max) {
|
||||
return Math.max(min, Math.min(max, value));
|
||||
}
|
||||
|
||||
function updateFromScroll() {
|
||||
ticking = false;
|
||||
var len = progress.getTotalLength();
|
||||
var rect = timeline.getBoundingClientRect();
|
||||
var viewport = window.innerHeight || document.documentElement.clientHeight;
|
||||
var start = viewport * 0.35;
|
||||
var end = -rect.height + viewport * 0.45;
|
||||
var raw = (start - rect.top) / (start - end);
|
||||
|
||||
drawProgress = clamp(raw, 0, 1);
|
||||
progress.style.strokeDashoffset = len * (1 - drawProgress);
|
||||
updateComet(len);
|
||||
revealNodes();
|
||||
if (drawProgress >= 1) {
|
||||
comet.style.opacity = 0;
|
||||
nodes.forEach(function (n) { n.classList.add('is-visible'); });
|
||||
}
|
||||
requestAnimationFrame(step);
|
||||
}
|
||||
|
||||
function requestScrollUpdate() {
|
||||
if (ticking) return;
|
||||
ticking = true;
|
||||
requestAnimationFrame(updateFromScroll);
|
||||
}
|
||||
|
||||
function init() {
|
||||
@@ -558,21 +563,19 @@
|
||||
nodes.forEach(function (n) { n.classList.add('is-visible'); });
|
||||
return;
|
||||
}
|
||||
// Start drawing only after the user has scrolled into the timeline.
|
||||
var trigger = nodes[0] || timeline;
|
||||
var io = new IntersectionObserver(function (entries) {
|
||||
entries.forEach(function (e) {
|
||||
if (e.isIntersecting) { animateDraw(); io.disconnect(); }
|
||||
});
|
||||
}, { threshold: 0.65, rootMargin: '0px 0px -16% 0px' });
|
||||
io.observe(trigger);
|
||||
updateFromScroll();
|
||||
window.addEventListener('scroll', requestScrollUpdate, { passive: true });
|
||||
}
|
||||
|
||||
// Recompute geometry on resize (layout flips between 1 and 3 columns).
|
||||
var resizeTimer;
|
||||
window.addEventListener('resize', function () {
|
||||
clearTimeout(resizeTimer);
|
||||
resizeTimer = setTimeout(function () { _samples = null; build(); }, 120);
|
||||
resizeTimer = setTimeout(function () {
|
||||
_samples = null;
|
||||
build();
|
||||
if (!reduceMotion) updateFromScroll();
|
||||
}, 120);
|
||||
});
|
||||
|
||||
if (document.readyState === 'loading') {
|
||||
@@ -581,7 +584,11 @@
|
||||
init();
|
||||
}
|
||||
// Re-measure after fonts/icons settle so markers are positioned correctly.
|
||||
window.addEventListener('load', function () { _samples = null; build(); });
|
||||
window.addEventListener('load', function () {
|
||||
_samples = null;
|
||||
build();
|
||||
if (!reduceMotion) updateFromScroll();
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
|
||||
Reference in New Issue
Block a user