diff --git a/assets/road-path-new-3.svg b/assets/road-path-new-3.svg index dba6bc4..d80e56b 100644 --- a/assets/road-path-new-3.svg +++ b/assets/road-path-new-3.svg @@ -8,7 +8,7 @@ - - + + diff --git a/skiply/path-animation-fade.js b/skiply/path-animation-fade.js index ec56701..5499680 100644 --- a/skiply/path-animation-fade.js +++ b/skiply/path-animation-fade.js @@ -17,7 +17,7 @@ document.addEventListener('DOMContentLoaded', () => { /* ═══════════════════════════════════════════ Reusable Car Animation Setup ═══════════════════════════════════════════ */ - function setupRoadAnimation(svgId, pathId, carId, mainAreaSelector, screenId, speed = 0.00016) { + function setupRoadAnimation(svgId, pathId, carId, mainAreaSelector, screenId, speed = 0.00016, pointNames = null) { const svgEl = document.getElementById(svgId); const pathEl = document.getElementById(pathId); const car = document.getElementById(carId); @@ -45,6 +45,71 @@ document.addEventListener('DOMContentLoaded', () => { return { scale, offsetX, offsetY, rect }; } + /* ── Arrival-triggered "point" reveal ────────── + Each named point is the marker + leader line + label that's + already baked into the road SVG (circle, icon, connector tick and + title/desc text paths tagged with ids/`.road-point` in the HTML) + — we animate that existing artwork in place rather than + positioning a separate HTML copy. It fades in the first time the + car's progress along the road reaches it. */ + let points = []; + if (Array.isArray(pointNames) && pointNames.length) { + points = pointNames.map(name => { + const markerEl = document.getElementById(`point-${name}`); + const circleEl = markerEl ? markerEl.querySelector('circle') : null; + const extraEls = [ + document.getElementById(`text-${name}-1`), + document.getElementById(`text-${name}-2`), + document.getElementById(`tick-${name}`) + ].filter(Boolean); + if (!markerEl || !circleEl) return null; + + const svgX = parseFloat(circleEl.getAttribute('cx')); + const svgY = parseFloat(circleEl.getAttribute('cy')); + + // Find the point on the road path nearest to the marker so + // "arrival" lines up with where the car visually meets it. + let t = 0, bestDist = Infinity; + const SAMPLES = 400; + for (let i = 0; i <= SAMPLES; i++) { + const sampleT = i / SAMPLES; + const p = pathEl.getPointAtLength(sampleT * TOTAL_LEN); + const dist = (p.x - svgX) ** 2 + (p.y - svgY) ** 2; + if (dist < bestDist) { bestDist = dist; t = sampleT; } + } + + return { name, t, markerEl, extraEls, revealed: false }; + }).filter(Boolean).sort((a, b) => a.t - b.t); + + if (typeof gsap !== 'undefined') { + points.forEach(p => { + gsap.set(p.markerEl, { opacity: 0, scale: 0.5, transformOrigin: '50% 50%' }); + if (p.extraEls.length) gsap.set(p.extraEls, { opacity: 0 }); + }); + } + } + + function revealPoint(p) { + if (p.revealed) return; + p.revealed = true; + + if (typeof gsap === 'undefined') { + p.markerEl.style.opacity = '1'; + p.extraEls.forEach(el => { el.style.opacity = '1'; }); + return; + } + gsap.to(p.markerEl, { opacity: 1, scale: 1, duration: 0.4, ease: 'back.out(1.7)' }); + if (p.extraEls.length) { + gsap.to(p.extraEls, { opacity: 1, duration: 0.4, ease: 'power2.out', delay: 0.1 }); + } + } + + function checkPointArrivals(t) { + for (const p of points) { + if (!p.revealed && t >= p.t) revealPoint(p); + } + } + let progress = 0; const SPEED = speed; let lastTime = null; @@ -104,12 +169,14 @@ document.addEventListener('DOMContentLoaded', () => { if (progress >= 1) { progress = 1; placeCar(1); + checkPointArrivals(progress); cancelAnimationFrame(rafId); doLoop(); return; } placeCar(progress); + checkPointArrivals(progress); rafId = requestAnimationFrame(animateCar); } @@ -128,7 +195,9 @@ document.addEventListener('DOMContentLoaded', () => { }, 420); } - const ro = new ResizeObserver(() => {}); + const ro = new ResizeObserver(() => { + placeCar(progress); + }); ro.observe(mainArea); @@ -155,31 +224,36 @@ document.addEventListener('DOMContentLoaded', () => { } ); - // 2. Stagger fade-in for SVG pointers (navy circles and white icons) - const pointers = Array.from(document.querySelectorAll('#' + screenId + ' svg circle, #' + screenId + ' svg path')).filter(el => { - const fill = el.getAttribute('fill'); - if (!fill) return false; - const upper = fill.toUpperCase(); - return upper === '#0A1A37' || upper === 'WHITE' || upper === '#FFFFFF'; - }); - - gsap.fromTo(pointers, - { opacity: 0, scale: 0, transformOrigin: "50% 50%" }, - { - opacity: 1, - scale: 1, - duration: 0.4, - stagger: 0.15, - ease: "back.out(1.5)", - scrollTrigger: { - trigger: screen, - start: "top 50%", - end: "bottom 20%", - toggleActions: "play reverse play reverse" + // 2. Stagger fade-in for SVG pointers (navy circles and white icons). + // Screens with arrival-triggered points (pointNames) reveal each + // marker individually as the car reaches it instead — see + // revealPoint()/checkPointArrivals() above. + if (!points.length) { + const pointers = Array.from(document.querySelectorAll('#' + screenId + ' svg circle, #' + screenId + ' svg path')).filter(el => { + const fill = el.getAttribute('fill'); + if (!fill) return false; + const upper = fill.toUpperCase(); + return upper === '#0A1A37' || upper === 'WHITE' || upper === '#FFFFFF'; + }); + + gsap.fromTo(pointers, + { opacity: 0, scale: 0, transformOrigin: "50% 50%" }, + { + opacity: 1, + scale: 1, + duration: 0.4, + stagger: 0.15, + ease: "back.out(1.5)", + scrollTrigger: { + trigger: screen, + start: "top 50%", + end: "bottom 20%", + toggleActions: "play reverse play reverse" + } } - } - ); - + ); + } + // 3. Start car animation when screen is in view ScrollTrigger.create({ trigger: screen, @@ -191,10 +265,12 @@ document.addEventListener('DOMContentLoaded', () => { } // Initialize Screen 1 - setupRoadAnimation('road-svg', 'road-path', 'road-car', '.main-area--prob1', 'screen-prob1', 0.00008); + setupRoadAnimation('road-svg', 'road-path', 'road-car', '.main-area--prob1', 'screen-prob1', 0.00008, + ['receive', 'sort', 'inspect', 'hold', 'route']); // Initialize Screen 2 - setupRoadAnimation('road-svg-2', 'road-path-2', 'road-car-2', '.main-area--prob2', 'screen-prob2', 0.00016); + setupRoadAnimation('road-svg-2', 'road-path-2', 'road-car-2', '.main-area--prob2', 'screen-prob2', 0.00016, + ['scan', 'decision']); diff --git a/skiply/skiply-fade.css b/skiply/skiply-fade.css index 58a6511..1f7ea06 100644 --- a/skiply/skiply-fade.css +++ b/skiply/skiply-fade.css @@ -260,6 +260,12 @@ body { filter: drop-shadow(0 4px 12px rgba(100, 140, 200, 0.3)); } +/* Baked-in road markers (circle + icon) — hidden until the car + reaches them for the first time; see checkPointArrivals() in JS. */ +.road-point { + opacity: 0; +} + .road-dashes { animation: dashFlow 2s linear infinite; } diff --git a/skiply/skiply-fade.html b/skiply/skiply-fade.html index 8267aaf..62ea6cc 100644 --- a/skiply/skiply-fade.html +++ b/skiply/skiply-fade.html @@ -36,37 +36,37 @@ - - - - - + + + + + - + - + - + - + - + - - - - - - - - - - + + + + + + + + + + @@ -166,18 +166,18 @@
- - + + - + - + - - - - + + + +