diff --git a/web/views/timeline.ejs b/web/views/timeline.ejs index d33e281..64a5f67 100644 --- a/web/views/timeline.ejs +++ b/web/views/timeline.ejs @@ -62,14 +62,6 @@ overflow: visible; } - .timeline-line .track { - fill: none; - stroke: rgba(144, 238, 144, 0.10); - stroke-width: 4; - stroke-linecap: round; - stroke-linejoin: round; - } - .timeline-line .progress { fill: none; stroke: url(#timelineGradient); @@ -79,6 +71,18 @@ /* no filter, no CSS transition — GSAP owns stroke-dashoffset each frame */ } + .timeline-line .comet-glow { + fill: rgba(144, 238, 144, 0.28); + filter: blur(5px); + } + + .timeline-line .comet { + fill: #F5F5DC; + stroke: #90EE90; + stroke-width: 2; + filter: drop-shadow(0 0 6px rgba(144, 238, 144, 0.95)); + } + .timeline-line .end-marker { fill: #1b1b1b; stroke: rgba(144, 238, 144, 0.35); @@ -270,9 +274,10 @@ - + + <% @@ -350,8 +355,9 @@ (function () { var timeline = document.getElementById('timeline'); var svg = document.getElementById('timelineLine'); - var track = document.getElementById('timelineTrack'); var progress = document.getElementById('timelineProgress'); + var comet = document.getElementById('timelineComet'); + var cometGlow = document.getElementById('timelineCometGlow'); if (!timeline || !svg) return; var nodes = Array.prototype.slice.call(timeline.querySelectorAll('.timeline-node')); @@ -417,7 +423,6 @@ var pts = nodes.map(getMarkerCenter); var d = roundedPath(pts, 36); - track.setAttribute('d', d); progress.setAttribute('d', d); pathLength = progress.getTotalLength(); @@ -432,9 +437,27 @@ endMarker.setAttribute('cx', ep.x.toFixed(1)); endMarker.setAttribute('cy', ep.y.toFixed(1)); } + updateComet(pathLength); return { pts: pts, len: pathLength }; } + function updateComet(len) { + if (!comet || !cometGlow || reduceMotion || drawProgress <= 0 || drawProgress >= 1) { + if (comet) comet.style.opacity = 0; + if (cometGlow) cometGlow.style.opacity = 0; + return; + } + var point = progress.getPointAtLength(len * drawProgress); + var x = point.x.toFixed(1); + var y = point.y.toFixed(1); + comet.setAttribute('cx', x); + comet.setAttribute('cy', y); + cometGlow.setAttribute('cx', x); + cometGlow.setAttribute('cy', y); + comet.style.opacity = 1; + cometGlow.style.opacity = 1; + } + // Find roughly how far along the path a point sits (coarse sampling). var _samples = null; function lengthAtPoint(target, len) { @@ -501,6 +524,7 @@ function renderProgress() { drawProgress = state.progress; progress.style.strokeDashoffset = pathLength * (1 - drawProgress); + updateComet(pathLength); revealReachedNodes(); if (drawProgress >= 1) { nodes.forEach(function (n, index) {