Skip to content

IrisBackgroundsTrace

Trace

Eight staggered rows of hot magenta circuit trace, each jogging on a true diagonal between two levels, over a near-black warm-maroon ground — a direct build of the reference photo.

trace

Trace

Eight rows of Manhattan-routed trace run left to right across a near-black, warm-maroon ground that blooms faintly toward a soft bloom left of centre. Each row runs flat, jogs on a true 45° diagonal to a second level, holds there, then jogs back and keeps running past the frame edge — never a printed grid, since every row starts, jogs and ends at its own staggered point. A small bright dot marks every joint, a third of the rows carry a soft elongated lens-glow over their shifted stretch, and two rows carry a short run of directional chevron ticks, the way the reference photo marks flow along a couple of its own traces. A scatter of faint, twinkling specks sits in the dark beyond the traces.

A slow signal pulse rides every row on its own phase, born at that row's own left end and fading out at its own right end. The pointer works like a hand held near a live board: whichever traces pass close to the cursor light brighter, right where it's actually pointed, rather than the whole frame flashing at once.

  • warm
  • pointer-driven
  • circuit
  • technical
Family
trace
Status
Available
Licence
Free

Install

No installation needed — self-contained, paste-in code.

Usage

Drop it straight into a page.

example.tsx
import { TraceField } from "./TraceField";

export default function Example() {
  return (
    // Fills its nearest positioned ancestor (it renders itself `absolute
    // inset-0`) — give it a sized, relatively positioned box.
    <div className="relative isolate h-[32rem] w-full overflow-hidden rounded-2xl">
      <TraceField />
    </div>
  );
}

Component

The real source, exactly as it ships — multiple files, kept together.

"use client";

import { useEffect, useRef } from "react";
import { mountShaderSurface } from "@/lib/shader-surface";

/**
 * A live WebGL circuit-board schematic — eight horizontal trace rows, each a
 * staggered polyline that runs flat, jogs on a true 45° diagonal to a second
 * level, holds there, then jogs back and keeps running, exactly the
 * Manhattan-routed look of a PCB trace diagram. Every jog carries a small
 * bright junction dot, a third of the rows carry an elongated lens-glow
 * "pill" over their shifted stretch, and two rows carry a short run of
 * directional chevron ticks — the reference photo this was built from reads
 * as hot magenta-pink neon on a near-black, warm-maroon ground, so the
 * palette stays fixed rather than drawn from the site's own accent ramp,
 * the same call `CoronaField` and `SlipstreamField` make for their own
 * fixed neon palettes.
 *
 * Row breakpoints are procedural, not a literal traced coordinate list: each
 * of the eight rows hashes its own start position, run lengths and jog
 * direction from its index, so the geometry reads as the same kind of
 * staggered, never-quite-aligned trace diagram as the source without
 * hand-carrying forty-odd magic numbers that would only ever fit one
 * viewport's aspect ratio. A slow signal pulse rides every row on its own
 * phase, born at its own left end and fading out at its own right end.
 *
 * The pointer lights whichever traces pass near it — a localised heat term
 * multiplies every wire's core, halo and node brightness, so the effect
 * stays where the cursor actually is rather than a global flash.
 *
 * Drop it into any `position: relative`/`isolate` parent — it fills the box.
 * Built on `lib/shader-surface.ts`, so every degradation path is already
 * handled: no WebGL, a blocked or lost context, a hidden tab, or
 * `prefers-reduced-motion` all leave the CSS `.iris-tracefield__floor`
 * underneath visible — a still composition in the same palette, never a
 * blank box.
 *
 * Reading guard: when `guardSelector` resolves to an element, the field
 * measures that block every frame and clamps its own luminance under a
 * ceiling inside that region (hue and saturation untouched). `null` (the
 * default) turns the guard off — for decorative use where nothing sits on
 * top.
 */

/* Palette, sRGB 0–1. Uniforms, not tokens — the hot magenta-pink-on-maroon
   read lives nowhere in the site's own accent ramp. */
const PALETTE: Record<string, [number, number, number]> = {
  u_ground: [0.02, 0.004, 0.004], // near-black, warm maroon
  u_groundWarm: [0.15, 0.013, 0.011], // the soft centre-left bloom
  u_wireGlow: [0.85, 0.08, 0.34], // the broad magenta-red halo
  u_wireCore: [1.0, 0.5, 0.68], // the hot pink-white core
  u_node: [1.0, 0.72, 0.83], // bright junction dots
  u_pill: [1.0, 0.6, 0.75], // the lens-glow pill on a third of the rows
  u_star: [0.95, 0.55, 0.62], // faint background specks
};

const FRAG = `
uniform vec2  u_res;
uniform float u_time;
uniform float u_scale;
uniform vec3  u_pointer;  /* x, y (0 bottom .. 1 top), presence 0..1 */

uniform vec3 u_ground;
uniform vec3 u_groundWarm;
uniform vec3 u_wireGlow;
uniform vec3 u_wireCore;
uniform vec3 u_node;
uniform vec3 u_pill;
uniform vec3 u_star;

uniform vec4  u_readA;
uniform float u_guard;

float hash1(float n) { return fract(sin(n * 78.233) * 43758.5453123); }

/* Distance to a segment, in the aspect-corrected q-space every field here
   uses — equal deltas in x and y are equal physical pixels, which is what
   makes a jog with matched dx/dy read as a true 45°. */
float segDist(vec2 p, vec2 a, vec2 b) {
  vec2 pa = p - a;
  vec2 ba = b - a;
  float h = clamp(dot(pa, ba) / max(dot(ba, ba), 1e-6), 0.0, 1.0);
  return length(pa - ba * h);
}

void main() {
  vec2 res = u_res / u_scale;
  vec2 uv  = gl_FragCoord.xy / u_scale / res;   /* 0..1, y up */
  vec2 sc  = vec2(res.x / max(res.y, 1.0), 1.0);
  vec2 q   = uv * sc;

  /* the ground: near-black maroon, warming toward a soft bloom left of
     centre, darkening again toward every corner */
  vec2 warmCentre = vec2(sc.x * 0.42, 0.56);
  vec2 wd = (q - warmCentre) / vec2(sc.x * 0.5, 0.5);
  float warm = exp(-dot(wd, wd) * 2.2);
  vec3 col = mix(u_ground, u_groundWarm, warm * 0.55);

  float corner = length((uv - 0.5) * 1.5);
  col *= mix(1.0, 0.4, smoothstep(0.4, 1.1, corner));

  /* a faint, twinkling starfield — a jittered point per lattice cell, only
     a small fraction of cells ever lit */
  float starFreq = 30.0;
  vec4 sd = dotCell(q * starFreq);
  float sh = dotHash(sd.zw);
  vec2 jitter = (vec2(dotHash(sd.zw + 4.0), dotHash(sd.zw + 9.0)) - 0.5) * 0.7;
  vec2 starOff = (sd.xy - jitter) / starFreq;
  float starMask = step(0.965, sh);
  float twinkle = 0.5 + 0.5 * sin(u_time * 1.6 + sh * 40.0);
  col += u_star * exp(-dot(starOff, starOff) * 80000.0) * starMask * twinkle * 0.7;

  /* the pointer's local heat: brightens whichever wire passes near it,
     rather than the whole frame */
  vec2 ptr = u_pointer.xy * sc;
  vec2 pd = q - ptr;
  float hot = exp(-dot(pd, pd) / 0.02) * u_pointer.z;
  float boost = 1.0 + hot * 1.6;

  for (int i = 0; i < 8; i++) {
    float fi = float(i);
    float rowY = 0.80 - fi * 0.082;

    float h1 = hash1(fi * 3.7 + 1.0);
    float h2 = hash1(fi * 3.7 + 2.0);
    float h3 = hash1(fi * 3.7 + 3.0);
    float h4 = hash1(fi * 3.7 + 4.0);
    float dir = mod(fi, 2.0) < 0.5 ? 1.0 : -1.0;
    float jog = 0.034 * dir;
    float absJog = abs(jog);

    /* the polyline: flat, jog on a true diagonal, flat at the shifted
       level, jog back, flat again to (and past) the right edge */
    float xA = mix(-0.08, 0.08, h1) * sc.x;
    float xB = xA + mix(0.10, 0.20, h2) * sc.x;
    vec2 p0 = vec2(xA, rowY);
    vec2 p1 = vec2(xB, rowY);
    vec2 p2 = vec2(xB + absJog, rowY + jog);
    float midLen = mix(0.16, 0.30, h3) * sc.x;
    vec2 p3 = vec2(p2.x + midLen, p2.y);
    vec2 p4 = vec2(p3.x + absJog, rowY);
    float tailLen = mix(0.20, 0.42, h4) * sc.x;
    vec2 p5 = vec2(p4.x + tailLen, rowY);

    float d = segDist(q, p0, p1);
    d = min(d, segDist(q, p1, p2));
    d = min(d, segDist(q, p2, p3));
    d = min(d, segDist(q, p3, p4));
    d = min(d, segDist(q, p4, p5));

    float coreSigma = 0.0026;
    float haloSigma = 0.016;
    float core = exp(-d * d / (coreSigma * coreSigma));
    float halo = exp(-d * d / (haloSigma * haloSigma));

    col += u_wireGlow * halo * 0.55 * boost;
    col += u_wireCore * core * boost;

    /* a small bright dot at every joint */
    float nodeGlow = 0.0;
    nodeGlow += exp(-dot(q - p1, q - p1) / 0.00028);
    nodeGlow += exp(-dot(q - p2, q - p2) / 0.00028);
    nodeGlow += exp(-dot(q - p3, q - p3) / 0.00028);
    nodeGlow += exp(-dot(q - p4, q - p4) / 0.00028);
    col += u_node * nodeGlow * boost;

    /* a slow signal pulse, born at this row's own left end, fading out at
       its own right end, on its own phase */
    float cycle = fract(u_time * 0.05 + h1 * 4.0);
    float travelX = mix(p0.x, p5.x, cycle);
    float pulseFade = smoothstep(0.0, 0.06, cycle) * (1.0 - smoothstep(0.9, 1.0, cycle));
    float pulse = exp(-(q.x - travelX) * (q.x - travelX) / 0.0009) * halo;
    col += u_wireCore * pulse * 0.6 * pulseFade;

    /* a soft lens-glow pill over the shifted stretch, on a third of the
       rows */
    if (mod(fi, 3.0) < 0.5) {
      vec2 pillC = mix(p2, p3, 0.5);
      vec2 dp = q - pillC;
      dp.x /= 0.05;
      dp.y /= 0.014;
      col += u_pill * exp(-dot(dp, dp) * 1.4) * 0.9;
    }

    /* a short run of directional chevron ticks on two of the rows */
    if (i == 1 || i == 4) {
      float xs = p0.x + 0.02 * sc.x;
      float xe = p1.x - 0.01 * sc.x;
      if (q.x > xs && q.x < xe) {
        float cell = 0.03 * sc.x;
        float lx = mod(q.x - xs, cell) - cell * 0.5;
        vec2 cpt = vec2(lx, q.y - rowY);
        float cd = min(
          segDist(cpt, vec2(-cell * 0.28, cell * 0.28), vec2(cell * 0.14, 0.0)),
          segDist(cpt, vec2(cell * 0.14, 0.0), vec2(-cell * 0.28, -cell * 0.28))
        );
        col += u_wireCore * exp(-cd * cd / (0.0016 * 0.0016)) * 0.8;
      }
    }
  }

  col = max(col, 0.0);

  /* ---- the reading guard (see TileField for the full rationale) ---- */
  vec2 rd = abs(uv - u_readA.xy) / max(u_readA.zw, vec2(0.02));
  float m = mix(max(rd.x, rd.y), length(rd), 0.4);
  float band = 1.0 - smoothstep(0.72, 2.1, m);
  col = mix(col, holdUnder(col, 0.09), band * u_guard);

  gl_FragColor = vec4(col, 1.0);
}
`;

export interface TraceFieldProps {
  className?: string;
  /**
   * CSS selector for the block the reading guard should keep readable,
   * resolved against `document`. `null` (the default) turns the guard off.
   */
  guardSelector?: string | null;
}

export function TraceField({
  className,
  guardSelector = null,
}: TraceFieldProps) {
  const canvasRef = useRef<HTMLCanvasElement>(null);

  useEffect(() => {
    const canvas = canvasRef.current;
    if (!canvas) return;

    const guardOn = guardSelector != null;
    let guardEl: Element | null | undefined;
    const readGuardEl = () => {
      if (guardEl === undefined) {
        guardEl = guardOn ? document.querySelector(guardSelector as string) : null;
      }
      return guardEl;
    };

    return mountShaderSurface(canvas, {
      fragment: FRAG,
      uniforms: [...Object.keys(PALETTE), "u_readA", "u_guard"],
      onInit: (gl, u) => {
        for (const name of Object.keys(PALETTE)) {
          if (u[name]) gl.uniform3fv(u[name], PALETTE[name]);
        }
        if (u.u_readA) gl.uniform4f(u.u_readA, 0.5, 0.5, 0.44, 0.32);
        if (u.u_guard) gl.uniform1f(u.u_guard, guardOn ? 1 : 0);
      },
      onFrame: (gl, u, s) => {
        if (!guardOn || !u.u_readA) return;
        let cx = 0.5, cy = 0.5, hw = 0.44, hh = 0.32;
        const el = readGuardEl();
        const { rect } = s;
        if (el && rect.width > 0 && rect.height > 0) {
          const r = el.getBoundingClientRect();
          const padX = rect.width * 0.09;
          const padY = rect.height * 0.11;
          cx = (r.left + r.width / 2 - rect.left) / rect.width;
          cy = 1 - (r.top + r.height / 2 - rect.top) / rect.height;
          hw = (r.width / 2 + padX) / rect.width;
          hh = (r.height / 2 + padY) / rect.height;
        }
        gl.uniform4f(u.u_readA, cx, cy, hw, hh);
      },
      onPainted: () => canvas.setAttribute("data-shader", "on"),
      /* No onIdle — same contract as CoronaField/SpillwayField: once
         painted, the last frame stays on screen while the surface is parked
         off-view, so scrolling away and back does not crossfade to the
         static floor. A lost context still clears it below. */
      onLost: () => canvas.removeAttribute("data-shader"),
      maxPixels: 1_800_000,
      dprCap: 1.5,
      alpha: false,
    });
  }, [guardSelector]);

  return (
    <div
      className={`absolute inset-0 overflow-hidden${className ? ` ${className}` : ""}`}
      aria-hidden="true"
    >
      <div className="absolute inset-0 [background:radial-gradient(_60%_55%_at_42%_56%,oklch(0.24_0.08_20)_0%,oklch(0.12_0.05_20_/_0.7)_35%,transparent_65%_),oklch(0.05_0.02_20)] after:content-[''] after:absolute after:inset-0 after:[background:repeating-linear-gradient(_45deg,transparent_0_26px,oklch(0.62_0.24_350_/_0.22)_26px_27px,transparent_27px_52px_)] after:[-webkit-mask-image:radial-gradient(_65%_60%_at_42%_56%,#000_30%,transparent_82%_)] after:[mask-image:radial-gradient(_65%_60%_at_42%_56%,#000_30%,transparent_82%_)] after:[opacity:0.5]" />
      <canvas ref={canvasRef} className="absolute inset-0 w-full h-full opacity-0 transition-opacity duration-[--duration-slow] ease-[--ease-standard] data-[shader=on]:opacity-100" />
    </div>
  );
}

More backgrounds

Custom work

Need one that isn't in the catalogue?

Describe what you're after and I'll reply by email — no promise on turnaround yet, this is a new channel, not a service with a set price or queue.

Fardin Omor Afnan

Fardin Omor Afnan

Reads and answers every request himself

Or start with a section.

Whole page sections, composed and ready to drop in — take one and build the rest of the page around it.

Browse sections