1/** 2 * Reactive Studio â Hero animation 3 * WebGL2 (preferred) â Canvas2D fallback â static poster (no JS / reduced motion). 4 * 5 * Sémantika: sÃÅ¥ propojených uzlů (grid + linky + pulzujÃcà data flow), 6 * vizuálnà metafora pro "propojujeme platformy a technologie". 7 * 8 * Self-contained: žádné externà knihovny. 9 */ 10(function () { 11 "use strict"; 12 13 const HOST_ID = "hero-anim"; 14 const REDUCED_MOTION = window.matchMedia("(prefers-reduced-motion: reduce)").matches; 15 const MOBILE = window.matchMedia("(max-width: 768px)").matches; 16 17 function init() { 18 const host = document.getElementById(HOST_ID); 19 if (!host) return; 20 21 if (REDUCED_MOTION) { 22 host.classList.add("hero-anim-static"); 23 return; 24 } 25 26 const canvas = document.createElement("canvas"); 27 canvas.className = "hero-anim-canvas"; 28 canvas.setAttribute("aria-hidden", "true"); 29 host.appendChild(canvas); 30 31 const gl = tryWebGL2(canvas); 32 if (gl) { 33 runWebGL(canvas, gl); 34 } else { 35 runCanvas2D(canvas); 36 } 37 } 38 39 function tryWebGL2(canvas) { 40 try { 41 return canvas.getContext("webgl2", { 42 antialias: true, 43 alpha: true, 44 premultipliedAlpha: true, 45 powerPreference: "low-power", 46 }); 47 } catch (e) { 48 return null; 49 } 50 } 51 52 /* =============================================================== 53 WebGL2 cesta â point grid + lines, vÅ¡e v jednom drawcallu (instancing). 54 Shader rendruje uzly jako kruhy s glow + animuje pulzy podél vektoru Äasu. 55 =============================================================== */ 56 57 const VS = `#version 300 es 58 in vec2 a_pos; // base position (-1..1 grid) 59 in float a_seed; // node seed for randomization 60 uniform float u_time; 61 uniform vec2 u_res; 62 out vec2 v_uv; 63 out float v_pulse; 64 out float v_seed; 65 66 void main() { 67 // jemný drift uzlů â sinusový pohyb 68 float dx = sin(u_time * 0.4 + a_seed * 6.28) * 0.012; 69 float dy = cos(u_time * 0.35 + a_seed * 3.14) * 0.012; 70 vec2 p = a_pos + vec2(dx, dy); 71 72 // velikost bodu 73 float pulse = 0.5 + 0.5 * sin(u_time * 1.2 + a_seed * 9.0); 74 v_pulse = pulse; 75 v_seed = a_seed; 76 v_uv = p; 77 78 gl_Position = vec4(p, 0.0, 1.0); 79 gl_PointSize = mix(2.0, 6.5, pulse) * (u_res.y / 800.0); 80 } 81 `; 82 83 const FS = `#version 300 es 84 precision highp float; 85 in float v_pulse; 86 in float v_seed; 87 in vec2 v_uv; 88 uniform float u_time; 89 out vec4 outColor; 90 91 void main() { 92 // kruhový tvar bodu 93 vec2 c = gl_PointCoord - vec2(0.5); 94 float d = length(c); 95 if (d > 0.5) discard; 96 97 // glow gradient 98 float core = smoothstep(0.5, 0.0, d); 99 float halo = smoothstep(0.5, 0.15, d) * 0.4; 100 101 // barevný posun podle seed â modrá/cyan paleta brandu 102 float t = mod(v_seed + u_time * 0.06, 1.0); 103 vec3 colA = vec3(0.10, 0.55, 0.95); // azurová 104 vec3 colB = vec3(0.20, 0.85, 0.95); // cyan 105 vec3 colC = vec3(0.55, 0.30, 0.95); // fialová akcent 106 vec3 base = mix(colA, colB, smoothstep(0.0, 0.6, t)); 107 base = mix(base, colC, smoothstep(0.7, 1.0, t) * 0.6); 108 109 float a = (core + halo) * (0.45 + 0.55 * v_pulse); 110 outColor = vec4(base * a, a); 111 } 112 `; 113 114 const LINE_VS = `#version 300 es 115 in vec2 a_pos; 116 in float a_dist; // 0..1 podél linky pro pulz 117 uniform float u_time; 118 out float v_dist; 119 void main() { 120 v_dist = a_dist; 121 gl_Position = vec4(a_pos, 0.0, 1.0); 122 } 123 `; 124 125 const LINE_FS = `#version 300 es 126 precision highp float; 127 in float v_dist; 128 uniform float u_time; 129 out vec4 outColor; 130 void main() { 131 // pohybujÃcà se svÄtelný pulz po lince 132 float pulse = mod(v_dist - u_time * 0.18, 1.0); 133 float intensity = smoothstep(0.92, 1.0, pulse) + 0.08; 134 vec3 col = vec3(0.20, 0.70, 0.95); 135 outColor = vec4(col * intensity, intensity * 0.55); 136 } 137 `; 138 139 function compile(gl, type, src) { 140 const sh = gl.createShader(type); 141 gl.shaderSource(sh, src); 142 gl.compileShader(sh); 143 if (!gl.getShaderParameter(sh, gl.COMPILE_STATUS)) { 144 console.warn("Shader compile error:", gl.getShaderInfoLog(sh)); 145 return null; 146 } 147 return sh; 148 } 149 150 function program(gl, vsSrc, fsSrc) { 151 const vs = compile(gl, gl.VERTEX_SHADER, vsSrc); 152 const fs = compile(gl, gl.FRAGMENT_SHADER, fsSrc);
153 if (!vs || !fs) return null; 154 const p = gl.createProgram(); 155 gl.attachShader(p, vs); 156 gl.attachShader(p, fs); 157 gl.linkProgram(p); 158 if (!gl.getProgramParameter(p, gl.LINK_STATUS)) { 159 console.warn("Program link error:", gl.getProgramInfoLog(p)); 160 return null; 161 } 162 return p; 163 } 164 165 function runWebGL(canvas, gl) { 166 const dpr = Math.min(window.devicePixelRatio || 1, 2); 167 const COLS = MOBILE ? 14 : 22; 168 const ROWS = MOBILE ? 9 : 13; 169 const NODES = COLS * ROWS; 170 const NEIGHBOR_RADIUS = 1.6; // sousedi v grid jednotkách 171 172 function resize() { 173 const w = Math.floor(canvas.clientWidth * dpr); 174 const h = Math.floor(canvas.clientHeight * dpr); 175 if (canvas.width !== w || canvas.height !== h) { 176 canvas.width = w; 177 canvas.height = h; 178 gl.viewport(0, 0, w, h); 179 } 180 } 181 new ResizeObserver(resize).observe(canvas); 182 resize(); 183 184 // generovánà gridu uzlů s drobným Å¡umem 185 const positions = new Float32Array(NODES * 2); 186 const seeds = new Float32Array(NODES); 187 for (let r = 0; r < ROWS; r++) { 188 for (let c = 0; c < COLS; c++) { 189 const i = r * COLS + c; 190 const jitterX = (Math.random() - 0.5) * 0.04; 191 const jitterY = (Math.random() - 0.5) * 0.04; 192 positions[i * 2] = -1 + (c / (COLS - 1)) * 2 + jitterX; 193 positions[i * 2 + 1] = -1 + (r / (ROWS - 1)) * 2 + jitterY; 194 seeds[i] = Math.random(); 195 } 196 } 197 198 // vytvoÅenà segmentů â soused v rámci NEIGHBOR_RADIUS, omezit duplikáty 199 const lineVerts = []; 200 const lineDists = []; 201 const cellSpacingX = 2 / (COLS - 1); 202 const cellSpacingY = 2 / (ROWS - 1); 203 for (let r = 0; r < ROWS; r++) { 204 for (let c = 0; c < COLS; c++) { 205 const i = r * COLS + c; 206 // pravý soused 207 if (c + 1 < COLS) { 208 const j = i + 1; 209 lineVerts.push(positions[i * 2], positions[i * 2 + 1]); 210 lineVerts.push(positions[j * 2], positions[j * 2 + 1]); 211 lineDists.push(0, 1); 212 } 213 // dolnà soused 214 if (r + 1 < ROWS) { 215 const j = i + COLS; 216 lineVerts.push(positions[i * 2], positions[i * 2 + 1]); 217 lineVerts.push(positions[j * 2], positions[j * 2 + 1]); 218 lineDists.push(0, 1); 219 } 220 // diagonála â ÅidÄeji (jen každá tÅetÃ) 221 if (r + 1 < ROWS && c + 1 < COLS && (r + c) % 3 === 0) { 222 const j = i + COLS + 1; 223 lineVerts.push(positions[i * 2], positions[i * 2 + 1]); 224 lineVerts.push(positions[j * 2], positions[j * 2 + 1]); 225 lineDists.push(0, 1); 226 } 227 } 228 } 229 void cellSpacingX; void cellSpacingY; void NEIGHBOR_RADIUS; 230 231 // body program 232 const pointProg = program(gl, VS, FS); 233 const lineProg = program(gl, LINE_VS, LINE_FS); 234 if (!pointProg || !lineProg) { 235 runCanvas2D(canvas); 236 return; 237 } 238 239 const pointVAO = gl.createVertexArray(); 240 gl.bindVertexArray(pointVAO); 241 const posBuf = gl.createBuffer(); 242 gl.bindBuffer(gl.ARRAY_BUFFER, posBuf); 243 gl.bufferData(gl.ARRAY_BUFFER, positions, gl.STATIC_DRAW); 244 const aPos = gl.getAttribLocation(pointProg, "a_pos"); 245 gl.enableVertexAttribArray(aPos); 246 gl.vertexAttribPointer(aPos, 2, gl.FLOAT, false, 0, 0); 247 248 const seedBuf = gl.createBuffer(); 249 gl.bindBuffer(gl.ARRAY_BUFFER, seedBuf); 250 gl.bufferData(gl.ARRAY_BUFFER, seeds, gl.STATIC_DRAW); 251 const aSeed = gl.getAttribLocation(pointProg, "a_seed"); 252 gl.enableVertexAttribArray(aSeed); 253 gl.vertexAttribPointer(aSeed, 1, gl.FLOAT, false, 0, 0); 254 255 const lineVAO = gl.createVertexArray(); 256 gl.bindVertexArray(lineVAO); 257 const lineBuf = gl.createBuffer(); 258 gl.bindBuffer(gl.ARRAY_BUFFER, lineBuf); 259 gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(lineVerts), gl.STATIC_DRAW); 260 const aLP = gl.getAttribLocation(lineProg, "a_pos"); 261 gl.enableVertexAttribArray(aLP); 262 gl.vertexAttribPointer(aLP, 2, gl.FLOAT, false, 0, 0); 263 264 const distBuf = gl.createBuffer(); 265 gl.bindBuffer(gl.ARRAY_BUFFER, distBuf); 266 gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(lineDists), gl.STATIC_DRAW); 267 const aLD = gl.getAttribLocation(lineProg, "a_dist"); 268 gl.enableVertexAttribArray(aLD); 269 gl.vertexAttribPointer(aLD, 1, gl.FLOAT, false, 0, 0); 270 271 gl.enable(gl.BLEND); 272 gl.blendFunc(gl.SRC_ALPHA, gl.ONE); 273 274 const uTimePoint = gl.getUniformLocation(pointProg, "u_time"); 275 const uResPoint = gl.getUniformLocation(pointProg, "u_res"); 276 const uTimeLine = gl.getUniformLocation(lineProg, "u_time"); 277 278 let running = true; 279 let start = performance.now(); 280 281 // pauza když nenà vidÄt 282 const io = new IntersectionObserver((entries) => { 283 running = entries[0].isIntersecting; 284 }); 285 io.observe(canvas); 286 287 function frame(now) { 288 if (running) { 289 const t = (now - start) / 1000; 290 gl.clearColor(0, 0, 0, 0); 291 gl.clear(gl.COLOR_BUFFER_BIT); 292 293 // linky 294 gl.useProgram(lineProg); 295 gl.bindVertexArray(lineVAO); 296 gl.uniform1f(uTimeLine, t); 297 gl.drawArrays(gl.LINES, 0, lineVerts.length / 2); 298 299 // body 300 gl.useProgram(pointProg); 301 gl.bindVertexArray(pointVAO); 302 gl.uniform1f(uTimePoint, t); 303 gl.uniform2f(uResPoint, canvas.width, canvas.height); 304 gl.drawArrays(gl.POINTS, 0, NODES); 305 } 306 requestAnimationFrame(frame); 307 } 308 requestAnimationFrame(frame); 309 } 310 311 /* =============================================================== 312 Canvas2D fallback â totéž v jednoduššà podobÄ. 313 =============================================================== */ 314 function runCanvas2D(canvas) { 315 const ctx = canvas.getContext("2d"); 316 if (!ctx) return; 317 318 const dpr = Math.min(window.devicePixelRatio || 1, 2); 319 const COLS = MOBILE ? 12 : 18; 320 const ROWS = MOBILE ? 7 : 10; 321 const nodes = []; 322 let W = 0, H = 0; 323 324 function resize() { 325 W = canvas.clientWidth; 326 H = canvas.clientHeight; 327 canvas.width = W * dpr; 328 canvas.height = H * dpr;
329 ctx.setTransform(dpr, 0, 0, dpr, 0, 0); 330 buildNodes(); 331 } 332 new ResizeObserver(resize).observe(canvas); 333 334 function buildNodes() { 335 nodes.length = 0; 336 for (let r = 0; r < ROWS; r++) { 337 for (let c = 0; c < COLS; c++) { 338 nodes.push({ 339 x: (c / (COLS - 1)) * W + (Math.random() - 0.5) * 12, 340 y: (r / (ROWS - 1)) * H + (Math.random() - 0.5) * 12, 341 seed: Math.random() * Math.PI * 2, 342 }); 343 } 344 } 345 } 346 resize(); 347 348 let running = true; 349 new IntersectionObserver((entries) => { 350 running = entries[0].isIntersecting; 351 }).observe(canvas); 352 353 let start = performance.now(); 354 function frame(now) { 355 if (running) { 356 const t = (now - start) / 1000; 357 ctx.clearRect(0, 0, W, H); 358 359 // linky mezi sousedy 360 ctx.lineWidth = 1; 361 for (let r = 0; r < ROWS; r++) { 362 for (let c = 0; c < COLS; c++) { 363 const i = r * COLS + c; 364 const a = nodes[i]; 365 if (c + 1 < COLS) drawLine(ctx, a, nodes[i + 1], t); 366 if (r + 1 < ROWS) drawLine(ctx, a, nodes[i + COLS], t); 367 } 368 } 369 370 // body 371 for (const n of nodes) { 372 const pulse = 0.5 + 0.5 * Math.sin(t * 1.2 + n.seed); 373 const radius = 1.5 + pulse * 3; 374 const grad = ctx.createRadialGradient(n.x, n.y, 0, n.x, n.y, radius * 4); 375 grad.addColorStop(0, `rgba(100, 200, 255, ${0.5 + pulse * 0.5})`); 376 grad.addColorStop(1, "rgba(50, 150, 255, 0)"); 377 ctx.fillStyle = grad; 378 ctx.beginPath(); 379 ctx.arc(n.x, n.y, radius * 4, 0, Math.PI * 2); 380 ctx.fill(); 381 } 382 } 383 requestAnimationFrame(frame); 384 } 385 requestAnimationFrame(frame); 386 } 387 388 function drawLine(ctx, a, b, t) { 389 const grad = ctx.createLinearGradient(a.x, a.y, b.x, b.y); 390 const phase = (Math.sin(t * 0.8 + (a.x + a.y) * 0.005) + 1) * 0.5; 391 grad.addColorStop(0, "rgba(50, 150, 220, 0.06)"); 392 grad.addColorStop(phase, "rgba(100, 200, 255, 0.35)"); 393 grad.addColorStop(1, "rgba(50, 150, 220, 0.06)"); 394 ctx.strokeStyle = grad; 395 ctx.beginPath(); 396 ctx.moveTo(a.x, a.y); 397 ctx.lineTo(b.x, b.y); 398 ctx.stroke(); 399 } 400 401 if (document.readyState === "loading") { 402 document.addEventListener("DOMContentLoaded", init); 403 } else { 404 init(); 405 } 406})();
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.