Canvas

Plasma Logo Mark

A fluid plasma fragment shader (raw WebGL, no `three`) CSS-masked to the silhouette of the site's logo image — domain-warped FBM noise flowing through a cyan/orange/purple/blue palette. A plain `<Image>` of the same logo renders underneath at all times as the SSR/no-WebGL fallback; once the canvas mounts it's masked to the identical logo shape and fully occludes the image beneath it. Swaps between light/dark logo assets based on the current theme.

Preview

IZA UI

Code

Code
"use client";

import { useEffect, useRef } from "react";
import Image from "next/image";
import { useReducedMotion } from "framer-motion";
import { useTheme } from "@/lib/theme";
import lefttoplogo from "@/assets/logo/lefttoplogo.png";
import lefttoplogolight from "@/assets/logo/lefttoplogolight.png";

/**
 * The site's left-top brand mark, filled with the fluid plasma shader from
 * docs/webgl-plasma.md (trimmed to drop its mouse-follow term — not
 * meaningful for a small decorative mark) instead of a flat color/gradient.
 * A plain <Image> of the same lockup renders underneath at all times as the
 * SSR/no-WebGL fallback (same convention as AmbientCanvas) — the canvas, once
 * mounted, is CSS-masked to the identical logo silhouette and fully occludes it.
 */

const VERTEX_SHADER = `attribute vec4 p;void main(){gl_Position=p;}`;

const FRAGMENT_SHADER = `
precision highp float;
uniform vec2 u_resolution;
uniform float u_time;

vec3 mod289(vec3 x){return x-floor(x*(1./289.))*289.;}
vec2 mod289(vec2 x){return x-floor(x*(1./289.))*289.;}
vec3 permute(vec3 x){return mod289(((x*34.)+1.)*x);}
float snoise(vec2 v){
    const vec4 C=vec4(.211324865405187,.366025403784439,-.577350269189626,.024390243902439);
    vec2 i=floor(v+dot(v,C.yy));
    vec2 x0=v-i+dot(i,C.xx);
    vec2 i1=(x0.x>x0.y)?vec2(1.,0.):vec2(0.,1.);
    vec4 x12=x0.xyxy+C.xxzz;
    x12.xy-=i1;
    i=mod289(i);
    vec3 pv=permute(permute(i.y+vec3(0.,i1.y,1.))+i.x+vec3(0.,i1.x,1.));
    vec3 m=max(.5-vec3(dot(x0,x0),dot(x12.xy,x12.xy),dot(x12.zw,x12.zw)),0.);
    m=m*m;m=m*m;
    vec3 x=2.*fract(pv*C.www)-1.;
    vec3 h=abs(x)-.5;
    vec3 ox=floor(x+.5);
    vec3 a0=x-ox;
    m*=1.79284291400159-.85373472095314*(a0*a0+h*h);
    vec3 g;
    g.x=a0.x*x0.x+h.x*x0.y;
    g.yz=a0.yz*x12.xz+h.yz*x12.yw;
    return 130.*dot(m,g);
}
float fbm(vec2 q){
    float f=0., a=.5;
    for(int i=0;i<5;i++){ f+=a*snoise(q); q*=2.02; a*=.5; }
    return f;
}
void main(){
    vec2 st=gl_FragCoord.xy/u_resolution.xy;
    vec2 asp=st; asp.x*=u_resolution.x/u_resolution.y;
    float t=u_time*0.0920;

    vec3 cCyan  =vec3(0.1799,0.5344,0.8154);
    vec3 cYellow=vec3(0.9184,0.4978,0.1644);
    vec3 cOrange=vec3(0.3533,0.9185,0.3187);
    vec3 cPurple=vec3(0.9062,0.1289,0.9539);
    vec3 cBlue  =vec3(0.4467,0.3328,0.9258);

    float fq=0.7344;
    float wp=0.3213;

    vec2 q=asp*fq;
    float flow=fbm(q + vec2(t*.3, -t*.2));
    float drape=fbm(q*1.7 + vec2(flow*(1.0+wp*1.2), t*.15));

    float folds=sin((drape*4.0 + flow*2.0)*3.14159);
    float shade=folds*0.5+0.5;
    float highlight=pow(max(folds,0.0),6.0);

    vec3 col=mix(cBlue, cPurple, clamp(flow*.5+.5,0.,1.));
    col=mix(col, cCyan, smoothstep(.2,.9,drape*.5+.5));
    col=mix(col, cOrange, smoothstep(.3,1., shade*(1.0-asp.y)));
    col=mix(col, cYellow, highlight*0.8);

    col*=0.55+0.55*shade;
    col+=highlight*0.35;

    float gr=fract(sin(dot(gl_FragCoord.xy+floor(u_time*24.),vec2(12.9898,78.233)))*43758.5453123);
    col+=(gr-.5)*0.0710;

    gl_FragColor=vec4(col,1.);
}
`;

function compile(gl: WebGLRenderingContext, type: number, src: string) {
  const shader = gl.createShader(type)!;
  gl.shaderSource(shader, src);
  gl.compileShader(shader);
  return shader;
}

export function PlasmaLogoMark({ className = "", alt = "IZA UI" }: { className?: string; alt?: string }) {
  const containerRef = useRef<HTMLDivElement>(null);
  const canvasRef = useRef<HTMLCanvasElement>(null);
  const reduced = useReducedMotion();
  const { theme } = useTheme();
  const logoSrc = theme === "dark" ? lefttoplogo : lefttoplogolight;

  useEffect(() => {
    const canvas = canvasRef.current;
    const container = containerRef.current;
    if (!canvas || !container) return;
    if (typeof window.WebGLRenderingContext === "undefined") return;

    const gl = canvas.getContext("webgl");
    if (!gl) return;

    let disposed = false;
    let raf = 0;

    const program = gl.createProgram()!;
    gl.attachShader(program, compile(gl, gl.VERTEX_SHADER, VERTEX_SHADER));
    gl.attachShader(program, compile(gl, gl.FRAGMENT_SHADER, FRAGMENT_SHADER));
    gl.linkProgram(program);

    const buf = gl.createBuffer();
    gl.bindBuffer(gl.ARRAY_BUFFER, buf);
    gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([1, -1, -1, -1, 1, 1, -1, 1]), gl.STATIC_DRAW);
    const posLoc = gl.getAttribLocation(program, "p");
    const resLoc = gl.getUniformLocation(program, "u_resolution");
    const timeLoc = gl.getUniformLocation(program, "u_time");

    function resize() {
      if (!canvas || !container) return;
      const dpr = Math.min(window.devicePixelRatio || 1, 2);
      canvas.width = Math.max(1, container.clientWidth * dpr);
      canvas.height = Math.max(1, container.clientHeight * dpr);
      gl!.viewport(0, 0, canvas.width, canvas.height);
    }
    resize();
    window.addEventListener("resize", resize);

    const start = performance.now();
    function draw(now: number) {
      if (disposed || !canvas) return;
      gl!.useProgram(program);
      gl!.bindBuffer(gl!.ARRAY_BUFFER, buf);
      gl!.vertexAttribPointer(posLoc, 2, gl!.FLOAT, false, 0, 0);
      gl!.enableVertexAttribArray(posLoc);
      gl!.uniform2f(resLoc, canvas.width, canvas.height);
      gl!.uniform1f(timeLoc, (now - start) / 1000);
      gl!.drawArrays(gl!.TRIANGLE_STRIP, 0, 4);
      if (!reduced) raf = requestAnimationFrame(draw);
    }
    draw(performance.now());

    return () => {
      disposed = true;
      cancelAnimationFrame(raf);
      window.removeEventListener("resize", resize);
      gl.deleteProgram(program);
      gl.deleteBuffer(buf);
    };
  }, [reduced]);

  return (
    <div ref={containerRef} className={`relative ${className}`} style={{ aspectRatio: "94 / 48" }}>
      <Image src={logoSrc} alt={alt} fill sizes="480px" className="object-contain" priority />
      <canvas
        ref={canvasRef}
        aria-hidden="true"
        className="absolute inset-0 h-full w-full"
        style={{
          WebkitMaskImage: `url(${logoSrc.src})`,
          maskImage: `url(${logoSrc.src})`,
          WebkitMaskSize: "contain",
          maskSize: "contain",
          WebkitMaskRepeat: "no-repeat",
          maskRepeat: "no-repeat",
          WebkitMaskPosition: "center",
          maskPosition: "center",
        }}
      />
    </div>
  );
}

Installation

Usage
import { PlasmaLogoMark } from "@/components/marketing/PlasmaLogoMark";

<PlasmaLogoMark className="h-12 w-24" />

Props

PropTypeDefaultDescription
classNamestring""Overrides the wrapper div's classes. The wrapper keeps a fixed 94:48 aspect ratio.
altstring"IZA UI"Alt text passed to the underlying logo `<Image>`.

Accessibility

The visible fallback `<Image>` carries the real `alt` text; the WebGL canvas layered on top is `aria-hidden` and purely decorative.