PageSourceSearch

https://reactive.cz/js/hero-animation.js

js reactive.cz collected 2026-09-27 10:05:41 UTC 12,876 bytes, 406 lines download raw bytes

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.