1/* 2 Mesh gradient do hero da página de Formações. 3 O shader (setup/render) é o código exportado pelo Figma (efeito "Mesh gradient", 4 CodeComponentId_3f03519cd6fb9dd0902b984c6179893f45bedc85_760), sem alterações. 5 Roda via WebGPU. Em navegadores sem WebGPU, o elemento mantém o gradiente CSS de reserva 6 definido em .hero-f__bg (ver formacoes.html). 7 8 Uso: <canvas data-mesh-gradient></canvas> dentro de um elemento com tamanho definido. 9*/ 10(function () { 11 'use strict'; 12 13 // ---------- Código do Figma (verbatim) ---------- 14 function setup(device, frame) { 15 var meshShader = ` 16diagnostic(off,derivative_uniformity); 17 18struct Uniforms { 19 values: array<vec4f, 32>, 20}; 21 22@group(0) @binding(0) var<uniform> uniforms: Uniforms; 23 24struct VertexInput { 25 @location(0) parameter: vec2f, 26}; 27 28struct VertexOutput { 29 @builtin(position) position: vec4f, 30 @location(0) color: vec4f, 31}; 32 33fn catmull2(a: vec2f, b: vec2f, c: vec2f, d: vec2f, t: f32) -> vec2f { 34 let t2 = t * t; 35 let t3 = t2 * t; 36 return 0.5 * ((2.0 * b) + (-a + c) * t + (2.0 * a - 5.0 * b + 4.0 * c - d) * t2 + (-a + 3.0 * b - 3.0 * c + d) * t3); 37} 38 39fn catmull4(a: vec4f, b: vec4f, c: vec4f, d: vec4f, t: f32) -> vec4f { 40 let t2 = t * t; 41 let t3 = t2 * t; 42 return 0.5 * ((2.0 * b) + (-a + c) * t + (2.0 * a - 5.0 * b + 4.0 * c - d) * t2 + (-a + 3.0 * b - 3.0 * c + d) * t3); 43} 44 45fn srgbToLinearChannel(value: f32) -> f32 { 46 let low = value / 12.92; 47 let high = pow((value + 0.055) / 1.055, 2.4); 48 return select(high, low, value <= 0.04045); 49} 50 51fn srgbToLinear(color: vec4f) -> vec4f { 52 return vec4f( 53 srgbToLinearChannel(clamp(color.r, 0.0, 1.0)), 54 srgbToLinearChannel(clamp(color.g, 0.0, 1.0)), 55 srgbToLinearChannel(clamp(color.b, 0.0, 1.0)), 56 clamp(color.a, 0.0, 1.0) 57 ); 58} 59 60fn segment(value: f32) -> vec2f { 61 let scaled = clamp(value, 0.0, 1.0) * 3.0; 62 let index = min(floor(scaled), 2.0); 63 return vec2f(index, scaled - index); 64} 65 66fn pointIndex(column: i32, row: i32) -> i32 { 67 return row * 4 + column; 68} 69 70fn meshPosition(parameter: vec2f) -> vec2f { 71 let sx = segment(parameter.x); 72 let sy = segment(parameter.y); 73 let ix = i32(sx.x); 74 let iy = i32(sy.x); 75 let x0 = max(ix - 1, 0); 76 let x1 = ix; 77 let x2 = ix + 1; 78 let x3 = min(ix + 2, 3); 79 let y0 = max(iy - 1, 0); 80 let y1 = iy; 81 let y2 = iy + 1; 82 let y3 = min(iy + 2, 3); 83 84 let row0 = catmull2(uniforms.values[pointIndex(x0, y0)].xy, uniforms.values[pointIndex(x1, y0)].xy, uniforms.values[pointIndex(x2, y0)].xy, uniforms.values[pointIndex(x3, y0)].xy, sx.y); 85 let row1 = catmull2(uniforms.values[pointIndex(x0, y1)].xy, uniforms.values[pointIndex(x1, y1)].xy, uniforms.values[pointIndex(x2, y1)].xy, uniforms.values[pointIndex(x3, y1)].xy, sx.y); 86 let row2 = catmull2(uniforms.values[pointIndex(x0, y2)].xy, uniforms.values[pointIndex(x1, y2)].xy, uniforms.values[pointIndex(x2, y2)].xy, uniforms.values[pointIndex(x3, y2)].xy, sx.y); 87 let row3 = catmull2(uniforms.values[pointIndex(x0, y3)].xy, uniforms.values[pointIndex(x1, y3)].xy, uniforms.values[pointIndex(x2, y3)].xy, uniforms.values[pointIndex(x3, y3)].xy, sx.y); 88 return catmull2(row0, row1, row2, row3, sy.y); 89} 90 91fn meshColor(parameter: vec2f) -> vec4f { 92 let sx = segment(parameter.x); 93 let sy = segment(parameter.y); 94 let ix = i32(sx.x); 95 let iy = i32(sy.x); 96 let x0 = max(ix - 1, 0); 97 let x1 = ix; 98 let x2 = ix + 1; 99 let x3 = min(ix + 2, 3); 100 let y0 = max(iy - 1, 0); 101 let y1 = iy; 102 let y2 = iy + 1; 103 let y3 = min(iy + 2, 3); 104 let offset = 16; 105 106 let row0 = catmull4(srgbToLinear(uniforms.values[offset + pointIndex(x0, y0)]), srgbToLinear(uniforms.values[offset + pointIndex(x1, y0)]), srgbToLinear(uniforms.values[offset + pointIndex(x2, y0)]), srgbToLinear(uniforms.values[offset + pointIndex(x3, y0)]), sx.y); 107 let row1 = catmull4(srgbToLinear(uniforms.values[offset + pointIndex(x0, y1)]), srgbToLinear(uniforms.values[offset + pointIndex(x1, y1)]), srgbToLinear(uniforms.values[offset + pointIndex(x2, y1)]), srgbToLinear(uniforms.values[offset + pointIndex(x3, y1)]), sx.y); 108 let row2 = catmull4(srgbToLinear(uniforms.values[offset + pointIndex(x0, y2)]), srgbToLinear(uniforms.values[offset + pointIndex(x1, y2)]), srgbToLinear(uniforms.values[offset + pointIndex(x2, y2)]), srgbToLinear(uniforms.values[offset + pointIndex(x3, y2)]), sx.y); 109 let row3 = catmull4(srgbToLinear(uniforms.values[offset + pointIndex(x0, y3)]), srgbToLinear(uniforms.values[offset + pointIndex(x1, y3)]), srgbToLinear(uniforms.values[offset + pointIndex(x2, y3)]), srgbToLinear(uniforms.values[offset + pointIndex(x3, y3)]), sx.y); 110 return clamp(catmull4(row0, row1, row2, row3, sy.y), vec4f(0.0), vec4f(1.0)); 111} 112 113@vertex 114fn vs_main(input: VertexInput) -> VertexOutput { 115 let position = meshPosition(input.parameter); 116 var output: VertexOutput; 117 output.position = vec4f(position.x * 2.0 - 1.0, 1.0 - position.y * 2.0, 0.0, 1.0); 118 output.color = meshColor(input.parameter); 119 return output; 120} 121 122@fragment 123fn fs_main(input: VertexOutput) -> @location(0) vec4f { 124 return input.color; 125} 126`; 127 var resolveShader = ` 128diagnostic(off,derivative_uniformity); 129 130@group(0) @binding(0) var source: texture_multisampled_2d<f32>; 131 132struct VertexInput { 133 @location(0) position: vec2f, 134 @location(1) uv: vec2f, 135}; 136 137struct VertexOutput { 138 @builtin(position) position: vec4f, 139 @location(0) uv: vec2f, 140}; 141 142@vertex 143fn vs_main(input: VertexInput) -> VertexOutput { 144 var output: VertexOutput; 145 output.position = vec4f(input.position, 0.0, 1.0); 146 output.uv = input.uv; 147 return output; 148} 149 150fn linearToSrgbChannel(value: f32) -> f32 { 151 let safeValue = max(value, 0.0); 152 let low = safeValue * 12.92;
153 let high = 1.055 * pow(safeValue, 1.0 / 2.4) - 0.055; 154 return select(high, low, safeValue <= 0.0031308); 155} 156 157fn linearToSrgb(color: vec3f) -> vec3f { 158 return vec3f( 159 linearToSrgbChannel(color.r), 160 linearToSrgbChannel(color.g), 161 linearToSrgbChannel(color.b) 162 ); 163} 164 165@fragment 166fn fs_main(input: VertexOutput) -> @location(0) vec4f { 167 let dimensions = textureDimensions(source); 168 let pixel = clamp(vec2i(input.position.xy), vec2i(0), vec2i(dimensions) - vec2i(1)); 169 let c0 = textureLoad(source, pixel, 0); 170 let c1 = textureLoad(source, pixel, 1); 171 let c2 = textureLoad(source, pixel, 2); 172 let c3 = textureLoad(source, pixel, 3); 173 let alphaSum = c0.a + c1.a + c2.a + c3.a; 174 let weighted = c0.rgb * c0.a + c1.rgb * c1.a + c2.rgb * c2.a + c3.rgb * c3.a; 175 let linearRgb = weighted / max(alphaSum, 0.000001); 176 let covered = alphaSum > 0.0; 177 let rgb = select(vec3f(0.0), clamp(linearToSrgb(linearRgb), vec3f(0.0), vec3f(1.0)), covered); 178 return vec4f(rgb, alphaSum * 0.25); 179} 180`; 181 frame.state.meshModule = device.createShaderModule({ code: meshShader }); 182 frame.state.resolveModule = device.createShaderModule({ code: resolveShader }); 183 frame.state.uniformBuffer = device.createBuffer({ 184 size: 512, 185 usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST, 186 }); 187 frame.state.quad = device.createBuffer({ 188 size: 6 * 4 * 4, 189 usage: GPUBufferUsage.VERTEX, 190 mappedAtCreation: true, 191 }); 192 new Float32Array(frame.state.quad.getMappedRange()).set([ 193 -1, -1, 0, 1, 194 1, -1, 1, 1, 195 -1, 1, 0, 0, 196 -1, 1, 0, 0, 197 1, -1, 1, 1, 198 1, 1, 1, 0, 199 ]); 200 frame.state.quad.unmap(); 201 } 202 203 function render(device, frame) { 204 var clampNumber = function (value, low, high) { 205 return Math.min(Math.max(value, low), high); 206 }; 207 var pointNames = [ 208 "p00", "p10", "p20", "p30", 209 "p01", "p11", "p21", "p31", 210 "p02", "p12", "p22", "p32", 211 "p03", "p13", "p23", "p33", 212 ]; 213 var uniformValues = new Float32Array(128); 214 var index = 0; 215 for (index = 0; index < 16; index += 1) { 216 var point = frame.params[pointNames[index]]; 217 uniformValues[index * 4] = clampNumber(point.x, 0, 100) / 100; 218 uniformValues[index * 4 + 1] = clampNumber(point.y, 0, 100) / 100; 219 uniformValues[index * 4 + 2] = 0; 220 uniformValues[index * 4 + 3] = 0; 221 uniformValues[(16 + index) * 4] = clampNumber(point.color.r, 0, 1); 222 uniformValues[(16 + index) * 4 + 1] = clampNumber(point.color.g, 0, 1); 223 uniformValues[(16 + index) * 4 + 2] = clampNumber(point.color.b, 0, 1); 224 uniformValues[(16 + index) * 4 + 3] = clampNumber(point.color.a, 0, 1); 225 } 226 device.queue.writeBuffer(frame.state.uniformBuffer, 0, uniformValues); 227 var tessellation = clampNumber(Math.round(frame.params.tessellation), 8, 256); 228 if (frame.state.tessellation !== tessellation) { 229 if (frame.state.gridVertices) { 230 frame.state.gridVertices.destroy(); 231 } 232 if (frame.state.gridIndices) { 233 frame.state.gridIndices.destroy(); 234 } 235 var side = tessellation + 1; 236 var vertices = new Float32Array(side * side * 2); 237 var vertexOffset = 0; 238 var row = 0; 239 var column = 0; 240 for (row = 0; row < side; row += 1) { 241 for (column = 0; column < side; column += 1) { 242 vertices[vertexOffset] = column / tessellation; 243 vertices[vertexOffset + 1] = row / tessellation; 244 vertexOffset += 2; 245 } 246 } 247 var indices = new Uint32Array(tessellation * tessellation * 6); 248 var indexOffset = 0; 249 for (row = 0; row < tessellation; row += 1) { 250 for (column = 0; column < tessellation; column += 1) { 251 var topLeft = row * side + column; 252 var topRight = topLeft + 1; 253 var bottomLeft = topLeft + side; 254 var bottomRight = bottomLeft + 1; 255 indices[indexOffset] = topLeft; 256 indices[indexOffset + 1] = bottomLeft; 257 indices[indexOffset + 2] = topRight; 258 indices[indexOffset + 3] = topRight; 259 indices[indexOffset + 4] = bottomLeft; 260 indices[indexOffset + 5] = bottomRight; 261 indexOffset += 6; 262 } 263 } 264 frame.state.gridVertices = device.createBuffer({ 265 size: vertices.byteLength, 266 usage: GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_DST, 267 }); 268 frame.state.gridIndices = device.createBuffer({ 269 size: indices.byteLength, 270 usage: GPUBufferUsage.INDEX | GPUBufferUsage.COPY_DST, 271 }); 272 device.queue.writeBuffer(frame.state.gridVertices, 0, vertices); 273 device.queue.writeBuffer(frame.state.gridIndices, 0, indices); 274 frame.state.indexCount = indices.length; 275 frame.state.tessellation = tessellation; 276 }
277 if (frame.state.pipelineFormat !== frame.output.format) { 278 frame.state.meshPipeline = device.createRenderPipeline({ 279 layout: "auto", 280 vertex: { 281 module: frame.state.meshModule, 282 entryPoint: "vs_main", 283 buffers: [{ 284 arrayStride: 8, 285 attributes: [{ shaderLocation: 0, format: "float32x2", offset: 0 }], 286 }], 287 }, 288 fragment: { 289 module: frame.state.meshModule, 290 entryPoint: "fs_main", 291 targets: [{ format: frame.output.format }], 292 }, 293 primitive: { topology: "triangle-list" }, 294 multisample: { count: 4 }, 295 }); 296 frame.state.resolvePipeline = device.createRenderPipeline({ 297 layout: "auto", 298 vertex: { 299 module: frame.state.resolveModule, 300 entryPoint: "vs_main", 301 buffers: [{ 302 arrayStride: 16, 303 attributes: [ 304 { shaderLocation: 0, format: "float32x2", offset: 0 }, 305 { shaderLocation: 1, format: "float32x2", offset: 8 }, 306 ], 307 }], 308 }, 309 fragment: { 310 module: frame.state.resolveModule, 311 entryPoint: "fs_main", 312 targets: [{ format: frame.output.format }], 313 }, 314 primitive: { topology: "triangle-list" }, 315 }); 316 frame.state.pipelineFormat = frame.output.format; 317 } 318 if (frame.state.msaaWidth !== frame.output.width || 319 frame.state.msaaHeight !== frame.output.height || 320 frame.state.msaaFormat !== frame.output.format) { 321 if (frame.state.msaaTexture) { 322 frame.state.msaaTexture.destroy(); 323 } 324 frame.state.msaaTexture = device.createTexture({ 325 size: [frame.output.width, frame.output.height, 1], 326 format: frame.output.format, 327 sampleCount: 4, 328 usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.TEXTURE_BINDING, 329 }); 330 frame.state.msaaWidth = frame.output.width; 331 frame.state.msaaHeight = frame.output.height; 332 frame.state.msaaFormat = frame.output.format; 333 } 334 var meshBindGroup = device.createBindGroup({ 335 layout: frame.state.meshPipeline.getBindGroupLayout(0), 336 entries: [{ binding: 0, resource: { buffer: frame.state.uniformBuffer } }], 337 }); 338 var resolveBindGroup = device.createBindGroup({ 339 layout: frame.state.resolvePipeline.getBindGroupLayout(0), 340 entries: [{ binding: 0, resource: frame.state.msaaTexture.createView() }], 341 }); 342 var encoder = device.createCommandEncoder(); 343 var meshPass = encoder.beginRenderPass({ 344 colorAttachments: [{ 345 view: frame.state.msaaTexture.createView(), 346 loadOp: "clear", 347 clearValue: { r: 0, g: 0, b: 0, a: 0 }, 348 storeOp: "store", 349 }], 350 }); 351 meshPass.setPipeline(frame.state.meshPipeline); 352 meshPass.setBindGroup(0, meshBindGroup); 353 meshPass.setVertexBuffer(0, frame.state.gridVertices); 354 meshPass.setIndexBuffer(frame.state.gridIndices, "uint32"); 355 meshPass.drawIndexed(frame.state.indexCount); 356 meshPass.end(); 357 var resolvePass = encoder.beginRenderPass({ 358 colorAttachments: [{ 359 view: frame.output.createView(), 360 loadOp: "clear", 361 clearValue: { r: 0, g: 0, b: 0, a: 0 }, 362 storeOp: "store", 363 }], 364 }); 365 resolvePass.setPipeline(frame.state.resolvePipeline); 366 resolvePass.setBindGroup(0, resolveBindGroup); 367 resolvePass.setVertexBuffer(0, frame.state.quad); 368 resolvePass.draw(6); 369 resolvePass.end(); 370 device.queue.submit([encoder.finish()]); 371 } 372 373 // ---------- Parâmetros do Figma (node 4978:1413) ---------- 374 var NAVY = { r: 0.007843137718737125, g: 0.07450980693101883, b: 0.12156862765550613, a: 1 }; 375 var ORANGE = { r: 0.9137254953384399, g: 0.40392157435417175, b: 0.1921568661928177, a: 1 }; 376 var LIME = { r: 0.8352941274642944, g: 0.9607843160629272, b: 0.47843137383461, a: 1 }; 377 var PARAMS = { 378 p00: { x: 0, y: 0, color: NAVY }, 379 p10: { x: 33, y: 0, color: NAVY }, 380 p20: { x: 69.08238983154297, y: -2.6197876930236816, color: NAVY }, 381 p30: { x: 106.66366577148438, y: -14.106548309326172, color: NAVY }, 382 p01: { x: 0, y: 33, color: NAVY }, 383 p11: { x: 40.080142974853516, y: 38.441097259521484, color: ORANGE }, 384 p21: { x: 60.891639709472656, y: 31.79086685180664, color: LIME }, 385 p31: { x: 100, y: 33, color: LIME }, 386 p02: { x: 0, y: 67, color: NAVY }, 387 p12: { x: 29.717578887939453, y: 54.05290985107422, color: NAVY }, 388 p22: { x: 67.97178649902344, y: 53.69953918457031, color: NAVY }, 389 p32: { x: 100, y: 67, color: NAVY }, 390 p03: { x: 0, y: 100, color: NAVY }, 391 p13: { x: 33, y: 100, color: NAVY }, 392 p23: { x: 67, y: 100, color: NAVY }, 393 p33: { x: 100, y: 100, color: NAVY }, 394 tessellation: 168 395 }; 396 397 // ---------- Host mÃnimo: desenha num <canvas> com WebGPU ---------- 398 async function mount(canvas) { 399 if (!navigator.gpu) return false;
400 var adapter = await navigator.gpu.requestAdapter(); 401 if (!adapter) return false; 402 var device = await adapter.requestDevice(); 403 var context = canvas.getContext('webgpu'); 404 if (!context) return false; 405 var format = navigator.gpu.getPreferredCanvasFormat(); 406 context.configure({ device: device, format: format, alphaMode: 'premultiplied' }); 407 408 var frame = { state: {}, params: PARAMS, output: null }; 409 setup(device, frame); 410 411 var draw = function () { 412 var dpr = Math.min(window.devicePixelRatio || 1, 2); 413 var w = Math.max(1, Math.round(canvas.clientWidth * dpr)); 414 var h = Math.max(1, Math.round(canvas.clientHeight * dpr)); 415 if (canvas.width !== w) canvas.width = w; 416 if (canvas.height !== h) canvas.height = h; 417 frame.output = context.getCurrentTexture(); 418 render(device, frame); 419 }; 420 draw(); 421 var t; 422 new ResizeObserver(function () { clearTimeout(t); t = setTimeout(draw, 60); }).observe(canvas); 423 return true; 424 } 425 426 document.querySelectorAll('canvas[data-mesh-gradient]').forEach(function (canvas) { 427 mount(canvas).then(function (ok) { 428 if (ok) canvas.classList.add('is-ready'); 429 }).catch(function () { /* fica o gradiente CSS de reserva */ }); 430 }); 431})();
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.