{"id":1234,"date":"2026-08-31T11:35:19","date_gmt":"2026-08-31T09:35:19","guid":{"rendered":"https:\/\/onode.es\/projects\/"},"modified":"2026-09-14T15:33:45","modified_gmt":"2026-09-14T13:33:45","slug":"projects","status":"publish","type":"page","link":"https:\/\/onode.es\/en\/projects\/","title":{"rendered":"Projects"},"content":{"rendered":"<div class=\"et_pb_section_0 et_pb_section et_section_regular et_block_section et_animated hero-manchas onode-fluid-section\">\n<div class=\"et_pb_row_0 et_pb_row et_block_row\">\n<div class=\"et_pb_column_0 et_pb_column et_pb_column_4_4 et-last-child et_block_column et_pb_css_mix_blend_mode_passthrough\">\n<div class=\"et_pb_code_0 et_pb_code et_pb_module\"><div class=\"et_pb_code_inner\"><!-- ============================================================\n  EFECTO MANCHAS ANIMADAS \u2014 onode.thelab.es\n  Pegar en un m\u00f3dulo \"C\u00f3digo\" de Divi dentro de la secci\u00f3n hero.\n  La secci\u00f3n debe tener la clase CSS personalizada: hero-manchas\n  ============================================================ --><style id=\"blob-style\">.hero-manchas {\n    position: fixed !important;\n    overflow: hidden !important;\n    top: 0px!important;\n  }\n  #blob-canvas-wrapper {\n    position: absolute;\n    inset: 0;\n    pointer-events: none;\n    z-index: 0;\n  }\n  \/* Degradado inferior hacia blanco *\/\n  #blob-canvas-wrapper::after {\n    content: '';\n    position: absolute;\n    bottom: 0; left: 0; right: 0;\n    height: 45%;\n    \/*background: linear-gradient(to bottom, transparent, #91C0DB);*\/\n    pointer-events: none;\n    z-index: 2;\n  }\n  #blob-canvas {\n    width: 100%;\n    height: 100%;\n    display: block;\n    filter: blur(72px) saturate(1.4);\n  }\n  .hero-manchas > *:not(#blob-canvas-wrapper) {\n    position: relative;\n    z-index: 1;\n  }\n<\/style><script>(function () {\n  function initBlobs() {\n    var hero = document.querySelector('.hero-manchas');\n    if (!hero || document.getElementById('blob-canvas-wrapper')) return;\n \n    \/* --- Canvas --- *\/\n    var wrapper = document.createElement('div');\n    wrapper.id = 'blob-canvas-wrapper';\n    var canvas = document.createElement('canvas');\n    canvas.id = 'blob-canvas';\n    wrapper.appendChild(canvas);\n    hero.insertBefore(wrapper, hero.firstChild);\n \n    var ctx = canvas.getContext('2d');\n    var W, H;\n    function resize() { W = canvas.width = hero.offsetWidth; H = canvas.height = hero.offsetHeight; }\n    resize();\n    window.addEventListener('resize', resize);\n \n    \/* --- Rat\u00f3n: solo activo mientras se mueve --- *\/\n    var mouse = { x: 0, y: 0, active: false };\n    var mouseTimer = null;\n    document.addEventListener('mousemove', function (e) {\n      var rect = hero.getBoundingClientRect();\n      mouse.x = e.clientX - rect.left;\n      mouse.y = e.clientY - rect.top;\n      mouse.active = true;\n      clearTimeout(mouseTimer);\n      mouseTimer = setTimeout(function () { mouse.active = false; }, 300);\n    });\n \n    \/* --- Manchas --- *\/\n    var blobs = [\n      {\n        x: W * 0.28, y: H * 0.45,\n        vx: 1.1, vy: 0.75,\n        r: 680,\n        color: [50, 85, 114], alpha: 0.82,\n        ox: [{ a: 1.4, f: 0.009 }, { a: 0.7, f: 0.017 }],\n        oy: [{ a: 1.0, f: 0.007 }, { a: 0.8, f: 0.013 }],\n        phase: 0,\n        attract: 0.008\n      },\n      {\n        x: W * 0.68, y: H * 0.52,\n        vx: -0.9, vy: 0.85,\n        r: 600,\n        color: [145, 192, 219], alpha: 0.78,\n        ox: [{ a: 1.2, f: 0.008 }, { a: 0.7, f: 0.015 }],\n        oy: [{ a: 1.1, f: 0.011 }, { a: 0.6, f: 0.006 }],\n        phase: 2.5,\n        attract: 0.007\n      }\n    ];\n \n    function drawBlob(b) {\n      var r = b.color[0], g = b.color[1], bl = b.color[2];\n      var grad = ctx.createRadialGradient(b.x, b.y, 0, b.x, b.y, b.r);\n      grad.addColorStop(0,   'rgba('+r+','+g+','+bl+','+b.alpha+')');\n      grad.addColorStop(0.5, 'rgba('+r+','+g+','+bl+','+(b.alpha*0.45)+')');\n      grad.addColorStop(1,   'rgba('+r+','+g+','+bl+',0)');\n      ctx.beginPath();\n      ctx.arc(b.x, b.y, b.r, 0, Math.PI * 2);\n      ctx.fillStyle = grad;\n      ctx.fill();\n    }\n \n    var t = 0;\n    function animate() {\n      t++;\n      ctx.clearRect(0, 0, W, H);\n \n      blobs.forEach(function (b) {\n        \/* Movimiento aut\u00f3nomo: velocidad base + oscilaciones en capas *\/\n        var driftX = b.ox.reduce(function (s, o) { return s + Math.sin(t * o.f + b.phase) * o.a; }, 0);\n        var driftY = b.oy.reduce(function (s, o) { return s + Math.cos(t * o.f + b.phase) * o.a; }, 0);\n        b.x += b.vx + driftX;\n        b.y += b.vy + driftY;\n \n        \/* Atracci\u00f3n solo cuando el rat\u00f3n se est\u00e1 moviendo *\/\n        if (mouse.active) {\n          b.x += (mouse.x - b.x) * b.attract;\n          b.y += (mouse.y - b.y) * b.attract;\n        }\n \n        \/* Rebote suave en bordes *\/\n        var m = b.r * 0.2;\n        if (b.x < m)     { b.x = m;     b.vx =  Math.abs(b.vx); }\n        if (b.x > W - m) { b.x = W - m; b.vx = -Math.abs(b.vx); }\n        if (b.y < m)     { b.y = m;     b.vy =  Math.abs(b.vy); }\n        if (b.y > H - m) { b.y = H - m; b.vy = -Math.abs(b.vy); }\n \n        drawBlob(b);\n      });\n \n      requestAnimationFrame(animate);\n    }\n    animate();\n  }\n \n  if (document.readyState === 'loading') {\n    document.addEventListener('DOMContentLoaded', initBlobs);\n  } else {\n    initBlobs();\n  }\n})();\n<\/script><\/div><\/div>\n<\/div>\n<\/div>\n<\/div>\n\n<div class=\"et_pb_section_1 et_pb_section et_section_regular et_flex_section et_animated\">\n<div class=\"et_pb_row_1 et_pb_row et_flex_row\">\n<div class=\"et_pb_column_1 et_pb_column et-last-child et_flex_column et_pb_css_mix_blend_mode_passthrough et_flex_column_24_24 et_flex_column_24_24_tablet et_flex_column_24_24_phone\">\n<div class=\"et_pb_text_0 et_pb_text et_pb_bg_layout_light et_pb_module et_flex_module text-typing\"><div class=\"et_pb_text_inner\"><h1>Projects<\/h1>\n<\/div><\/div>\n<\/div>\n<\/div>\n<\/div>","protected":false},"excerpt":{"rendered":"","protected":false},"author":1,"featured_media":0,"parent":0,"menu_order":0,"comment_status":"closed","ping_status":"closed","template":"","meta":{"_acf_changed":false,"footnotes":""},"class_list":["post-1234","page","type-page","status-publish","hentry"],"acf":[],"_links":{"self":[{"href":"https:\/\/onode.es\/en\/wp-json\/wp\/v2\/pages\/1234","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/onode.es\/en\/wp-json\/wp\/v2\/pages"}],"about":[{"href":"https:\/\/onode.es\/en\/wp-json\/wp\/v2\/types\/page"}],"author":[{"embeddable":true,"href":"https:\/\/onode.es\/en\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/onode.es\/en\/wp-json\/wp\/v2\/comments?post=1234"}],"version-history":[{"count":1,"href":"https:\/\/onode.es\/en\/wp-json\/wp\/v2\/pages\/1234\/revisions"}],"predecessor-version":[{"id":1235,"href":"https:\/\/onode.es\/en\/wp-json\/wp\/v2\/pages\/1234\/revisions\/1235"}],"wp:attachment":[{"href":"https:\/\/onode.es\/en\/wp-json\/wp\/v2\/media?parent=1234"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}