1/* ========================================================================== 2 Predictvia - Main JavaScript 3 ========================================================================== */ 4 5document.addEventListener('DOMContentLoaded', () => { 6 initNavigation(); 7 initScrollAnimations(); 8 // Only init canvas network if element exists (on How We Work page) 9 if (document.getElementById('network-canvas')) { 10 initCanvasNetwork(); 11 } 12}); 13 14/* -------------------------------------------------------------------------- 15 Navigation 16 -------------------------------------------------------------------------- */ 17function initNavigation() { 18 const nav = document.querySelector('.nav'); 19 const topBar = document.querySelector('.top-bar'); 20 const toggle = document.querySelector('.nav__toggle'); 21 const links = document.querySelector('.nav__links'); 22 23 // Scroll effect - hide top bar and adjust nav on scroll 24 let lastScroll = 0; 25 window.addEventListener('scroll', () => { 26 const currentScroll = window.pageYOffset; 27 28 if (currentScroll > 50) { 29 nav.classList.add('scrolled'); 30 if (topBar) topBar.classList.add('hidden'); 31 } else { 32 nav.classList.remove('scrolled'); 33 if (topBar) topBar.classList.remove('hidden'); 34 } 35 36 lastScroll = currentScroll; 37 }); 38 39 // Mobile menu toggle 40 if (toggle && links) { 41 toggle.addEventListener('click', () => { 42 links.classList.toggle('open'); 43 toggle.classList.toggle('active'); 44 }); 45 46 // Close menu when clicking a link 47 links.querySelectorAll('a').forEach(link => { 48 link.addEventListener('click', () => { 49 links.classList.remove('open'); 50 toggle.classList.remove('active'); 51 }); 52 }); 53 } 54} 55 56/* -------------------------------------------------------------------------- 57 Scroll Animations 58 -------------------------------------------------------------------------- */ 59function initScrollAnimations() { 60 const observerOptions = { 61 root: null, 62 rootMargin: '0px', 63 threshold: 0.1 64 }; 65 66 const observer = new IntersectionObserver((entries) => { 67 entries.forEach(entry => { 68 if (entry.isIntersecting) { 69 entry.target.classList.add('visible'); 70 } 71 }); 72 }, observerOptions); 73 74 // Observe fade-in elements 75 document.querySelectorAll('.fade-in, .stagger-children').forEach(el => { 76 observer.observe(el); 77 }); 78} 79 80/* -------------------------------------------------------------------------- 81 Canvas Network Animation (Background) 82 -------------------------------------------------------------------------- */ 83function initCanvasNetwork() { 84 const canvas = document.getElementById('network-canvas'); 85 if (!canvas) return; 86 87 const ctx = canvas.getContext('2d'); 88 let animationId; 89 let nodes = []; 90 let edges = []; 91 let pulses = []; 92 93 // Colors 94 const colors = { 95 node: '#3B82F6', 96 nodeDim: 'rgba(59, 130, 246, 0.3)', 97 edge: 'rgba(255, 255, 255, 0.1)', 98 pulse: '#3B82F6', 99 text: 'rgba(255, 255, 255, 0.6)' 100 }; 101 102 // Node labels 103 const labels = [ 104 'Ops Agent', 105 'Data Pipeline', 106 'Compliance', 107 'Analytics', 108 'Monitoring', 109 'Orchestrator' 110 ]; 111 112 function resize() { 113 const rect = canvas.parentElement.getBoundingClientRect(); 114 canvas.width = rect.width * window.devicePixelRatio; 115 canvas.height = rect.height * window.devicePixelRatio; 116 canvas.style.width = rect.width + 'px'; 117 canvas.style.height = rect.height + 'px'; 118 ctx.scale(window.devicePixelRatio, window.devicePixelRatio); 119 120 initNodes(rect.width, rect.height); 121 } 122 123 function initNodes(width, height) { 124 nodes = []; 125 edges = []; 126 127 const centerX = width / 2; 128 const centerY = height / 2; 129 const radius = Math.min(width, height) * 0.35; 130 131 // Create nodes in a circular pattern 132 const nodeCount = 6; 133 for (let i = 0; i < nodeCount; i++) { 134 const angle = (i / nodeCount) * Math.PI * 2 - Math.PI / 2; 135 const x = centerX + Math.cos(angle) * radius; 136 const y = centerY + Math.sin(angle) * radius; 137 138 nodes.push({ 139 x, 140 y, 141 radius: i === 0 ? 12 : 8, 142 label: labels[i], 143 isMain: i === 0, 144 pulsePhase: Math.random() * Math.PI * 2, 145 hover: false 146 }); 147 } 148 149 // Add center node (orchestrator) 150 nodes.push({ 151 x: centerX, 152 y: centerY, 153 radius: 14, 154 label: 'Core System', 155 isMain: true, 156 pulsePhase: 0, 157 hover: false 158 }); 159 160 // Create edges from center to all nodes 161 const centerNode = nodes[nodes.length - 1]; 162 for (let i = 0; i < nodes.length - 1; i++) { 163 edges.push({ 164 from: centerNode, 165 to: nodes[i], 166 pulseOffset: i * 0.3 167 }); 168 } 169 170 // Add some connections between adjacent nodes 171 for (let i = 0; i < nodes.length - 2; i++) { 172 edges.push({ 173 from: nodes[i], 174 to: nodes[(i + 1) % (nodes.length - 1)], 175 pulseOffset: i * 0.5 + 2 176 }); 177 } 178 } 179 180 function drawNode(node, time) { 181 // Glow effect 182 const glowIntensity = 0.3 + 0.2 * Math.sin(time * 0.002 + node.pulsePhase); 183 const gradient = ctx.createRadialGradient( 184 node.x, node.y, 0,
185 node.x, node.y, node.radius * 3 186 ); 187 gradient.addColorStop(0, `rgba(59, 130, 246, ${glowIntensity})`); 188 gradient.addColorStop(1, 'rgba(59, 130, 246, 0)'); 189 190 ctx.beginPath(); 191 ctx.arc(node.x, node.y, node.radius * 3, 0, Math.PI * 2); 192 ctx.fillStyle = gradient; 193 ctx.fill(); 194 195 // Node circle 196 ctx.beginPath(); 197 ctx.arc(node.x, node.y, node.radius, 0, Math.PI * 2); 198 ctx.fillStyle = node.isMain ? colors.node : colors.nodeDim; 199 ctx.fill(); 200 201 // Inner highlight 202 ctx.beginPath(); 203 ctx.arc(node.x, node.y, node.radius * 0.5, 0, Math.PI * 2); 204 ctx.fillStyle = 'rgba(255, 255, 255, 0.3)'; 205 ctx.fill(); 206 } 207 208 function drawEdge(edge, time) { 209 ctx.beginPath(); 210 ctx.moveTo(edge.from.x, edge.from.y); 211 ctx.lineTo(edge.to.x, edge.to.y); 212 ctx.strokeStyle = colors.edge; 213 ctx.lineWidth = 1; 214 ctx.stroke(); 215 216 // Draw pulse 217 const pulseProgress = ((time * 0.0005 + edge.pulseOffset) % 1); 218 const pulseX = edge.from.x + (edge.to.x - edge.from.x) * pulseProgress; 219 const pulseY = edge.from.y + (edge.to.y - edge.from.y) * pulseProgress; 220 221 const pulseGradient = ctx.createRadialGradient( 222 pulseX, pulseY, 0, 223 pulseX, pulseY, 8 224 ); 225 pulseGradient.addColorStop(0, 'rgba(59, 130, 246, 0.8)'); 226 pulseGradient.addColorStop(1, 'rgba(59, 130, 246, 0)'); 227 228 ctx.beginPath(); 229 ctx.arc(pulseX, pulseY, 8, 0, Math.PI * 2); 230 ctx.fillStyle = pulseGradient; 231 ctx.fill(); 232 } 233 234 function draw(time) { 235 const rect = canvas.getBoundingClientRect(); 236 ctx.clearRect(0, 0, rect.width, rect.height); 237 238 // Draw edges first
239 edges.forEach(edge => drawEdge(edge, time)); 240 241 // Draw nodes on top 242 nodes.forEach(node => drawNode(node, time)); 243 244 animationId = requestAnimationFrame(draw); 245 } 246 247 // Handle hover 248 canvas.addEventListener('mousemove', (e) => { 249 const rect = canvas.getBoundingClientRect(); 250 const x = e.clientX - rect.left; 251 const y = e.clientY - rect.top; 252 253 nodes.forEach(node => { 254 const dist = Math.sqrt((x - node.x) ** 2 + (y - node.y) ** 2); 255 node.hover = dist < node.radius * 2; 256 }); 257 }); 258 259 // Initialize 260 resize(); 261 draw(0); 262 263 // Handle resize 264 window.addEventListener('resize', () => { 265 cancelAnimationFrame(animationId); 266 resize(); 267 draw(0); 268 }); 269} 270
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.