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) {