Skip to content

IrisBackgroundsSignal Grid

Signal Grid

A dark navy-teal lattice of dotted lines, sparse data-block cells, and small red-orange marks at scattered intersections — a direct build of the reference photo.

grid

Signal Grid

An even lattice of dotted lines rules a dark navy-teal ground into a grid the way a schematic marks its space rather than the way a photograph of light does. A sparse, variably opaque subset of the cells reads as data blocks rather than an even wash, and a sparse subset of the intersections carries its own small mark — a red-orange plus, a hollow ring, or a filled square, chosen and sized per intersection so none of it repeats in visible rows. A few stray vertical light streaks and one horizontal one drift through at their own slow shimmer, and the frame's two opposite corners bleed a warm red-orange glow into the dark ground — a direct build of the reference this was taken from, not a variation on the rest of the catalogue's mood.

The pointer works the lattice like a HUD plate: the whole grid drifts a little opposite the cursor, and the intersection nearest it locks a pulsing crosshair-and-ring reticle onto itself, fading back out once the pointer leaves.

  • cool
  • pointer-driven
  • grid
  • technical
Family
grid
Status
Available
Licence
Free

Install

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

Usage

Drop it straight into a page.

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

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">
      <SignalGridField />
    </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 technical grid — a dark navy-teal ground ruled into an even
 * lattice of dotted lines, the way a schematic or a targeting HUD marks its
 * space rather than the way a photograph of light does. A sparse, variably
 * opaque subset of the cells reads as data blocks rather than an even wash,
 * and a sparse subset of the intersections carries its own small mark — a
 * red-orange plus, a hollow ring, or a filled square — chosen and sized per
 * intersection so none of it repeats in visible rows. A few stray vertical
 * light streaks and one horizontal one drift through at their own slow
 * shimmer, and the frame's two opposite corners bleed a warm red-orange
 * glow into the dark ground — a direct build of the reference photo this
 * was taken from, the way `Spillway` and `Cataract` are, not a variation on
 * the rest of the catalogue's mood.
 *
 * Interactive: the whole lattice drifts a little under the pointer, and the
 * intersection nearest the cursor locks a pulsing crosshair-and-ring
 * reticle onto itself — a targeting HUD answering the one thing it can
 * answer, where it's being pointed at. Presence fades the reticle back out
 * once the pointer leaves.
 *
 * One of the reusable background fields (`GauzeField`, `SlipstreamField`,
 * `CoronaField`, …). 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-signalgridfield__floor` underneath visible.
 *
 * 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 — this fixed navy/red-orange
   HUD mood lives nowhere in the accent ramp. */
const PALETTE: Record<string, [number, number, number]> = {
  u_ground: [0.018, 0.05, 0.068], // dark navy-teal ground
  u_grid: [0.42, 0.56, 0.6], // the dotted lattice lines
  u_cell: [0.11, 0.25, 0.29], // the sparse, variably-opaque data blocks
  u_beacon: [1.0, 0.34, 0.14], // the hot red-orange marks, and the reticle
  u_ring: [0.55, 0.62, 0.66], // the cool hollow-ring marks
  u_glow: [0.85, 0.3, 0.13], // the two-corner bleed
  u_streak: [1.0, 0.42, 0.22], // the stray light streaks
};

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_grid;
uniform vec3 u_cell;
uniform vec3 u_beacon;
uniform vec3 u_ring;
uniform vec3 u_glow;
uniform vec3 u_streak;

uniform vec4  u_readA;
uniform float u_guard;

float barMask(vec2 p, float hw, float hl) {
  float ax = smoothstep(hw + 0.006, hw - 0.006, abs(p.x));
  float ay = smoothstep(hl + 0.006, hl - 0.006, abs(p.y));
  return ax * ay;
}
float plusMask(vec2 p, float hw, float hl) {
  return max(barMask(p, hw, hl), barMask(p.yx, hw, hl));
}
float ringMask(vec2 p, float r, float thickness) {
  float d = abs(length(p) - r);
  return smoothstep(thickness, thickness * 0.35, d);
}
float squareMask(vec2 p, float half_) {
  vec2 a = smoothstep(vec2(half_ + 0.006), vec2(half_ - 0.006), abs(p));
  return a.x * a.y;
}

const float CELLS = 17.0;

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 whole lattice drifts a little opposite the pointer, the way a HUD
     plate answers a viewer leaning to one side */
  vec2 drift = (u_pointer.xy - 0.5) * 0.05 * u_pointer.z;
  vec2 P = (q + drift) * CELLS;

  /* the dotted grid: a line only turns "on" at fine steps along its own
     direction, so it reads as a row of dashes rather than a ruled line */
  vec2 frac = fract(P);
  float dGridX = min(frac.x, 1.0 - frac.x);
  float dGridY = min(frac.y, 1.0 - frac.y);
  const float LINE_W = 0.022;
  const float DOTS = 4.0;
  float subX = fract(P.x * DOTS);
  float subY = fract(P.y * DOTS);
  float dDotX = min(subX, 1.0 - subX);
  float dDotY = min(subY, 1.0 - subY);
  float onVert  = smoothstep(LINE_W, 0.0, dGridX) * smoothstep(0.14, 0.0, dDotY);
  float onHoriz = smoothstep(LINE_W, 0.0, dGridY) * smoothstep(0.14, 0.0, dDotX);
  float gridDots = max(onVert, onHoriz);

  vec3 col = u_ground;

  /* the mosaic: a sparse, variably-opaque subset of cells reading as data
     blocks rather than an even wash */
  vec2 cellId = floor(P);
  float ch  = vhash(cellId + 11.0);
  float ch2 = vhash(cellId + 47.0);
  float inside = smoothstep(LINE_W * 1.4, LINE_W * 3.2, dGridX)
               * smoothstep(LINE_W * 1.4, LINE_W * 3.2, dGridY);
  float cellOn = step(0.72, ch);
  col = mix(col, u_cell, inside * cellOn * mix(0.06, 0.26, ch2));

  col += u_grid * gridDots * 0.4;

  /* sparse beacons at a subset of intersections: a plus mark, a hollow
     ring, or a small filled square, chosen and sized per-intersection so
     none of it repeats in visible rows */
  vec2 iid = floor(P + 0.5);
  vec2 ip  = P - iid;
  float hSel  = vhash(iid);
  float hType = vhash(iid + 5.5);
  float hVar  = vhash(iid + 9.25);

  if (hSel < 0.12) {
    float m = 0.0;
    vec3 bc = u_beacon;
    if (hType < 0.45) {
      float armLen = mix(0.075, 0.125, hVar);
      m = plusMask(ip, 0.018, armLen) + exp(-dot(ip, ip) / 0.018) * 0.3;
    } else if (hType < 0.78) {
      float r = mix(0.085, 0.135, hVar);
      m = ringMask(ip, r, 0.013);
      bc = mix(u_ring, u_beacon, step(0.88, hVar));
    } else {
      float half_ = mix(0.03, 0.055, hVar);
      m = squareMask(ip, half_) + exp(-dot(ip, ip) / 0.01) * 0.35;
    }
    col += bc * m;
  }

  /* the pointer's own targeting reticle: locks to the nearest intersection
     under the cursor and pulses gently while the pointer is present */
  vec2 ptrP = u_pointer.xy * sc * CELLS;
  vec2 tIid = floor(ptrP + 0.5);
  vec2 rp = P - tIid;
  float dT = length(rp);
  float pulse = 0.5 + 0.5 * sin(u_time * 2.6);
  float rRing  = ringMask(rp, mix(0.16, 0.21, pulse), 0.02);
  float rCross = plusMask(rp, 0.012, 0.3) * 0.55;
  float rGlow  = exp(-dT * dT / 0.055) * 0.55;
  col += u_beacon * (rRing + rCross + rGlow) * u_pointer.z;

  /* a few stray vertical light streaks, each with its own width,
     brightness and slow shimmer, plus one bounded horizontal one */
  for (int i = 0; i < 3; i++) {
    float fi = float(i);
    float sx = vhash(vec2(fi * 3.1 + 1.0, 4.7)) * sc.x;
    float sw = mix(0.0022, 0.0055, vhash(vec2(fi, 8.2)));
    float sb = mix(0.12, 0.42, vhash(vec2(fi, 12.9)));
    float dx = q.x - sx;
    float core = exp(-dx * dx / (sw * sw));
    float shimmer = 0.65 + 0.35 * sin(u_time * 0.35 + fi * 2.3);
    col += u_streak * core * sb * shimmer;
  }
  float hy = 0.42;
  float hb = 0.5 + 0.5 * sin(u_time * 0.3 + 1.0);
  float dy = q.y - hy;
  float hCore = exp(-dy * dy / (0.0018 * 0.0018));
  float hEnvelope = smoothstep(0.55, 0.98, uv.x) * smoothstep(0.0, 0.35, uv.x);
  col += u_streak * hCore * hEnvelope * mix(0.18, 0.38, hb);

  /* the diagonal red-orange bleed at two opposite corners */
  float glowTL = exp(-6.0 * length(vec2(uv.x, 1.0 - uv.y)));
  float glowBR = exp(-6.0 * length(vec2(1.0 - uv.x, uv.y)));
  col += u_glow * (glowTL + glowBR) * 0.5;

  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 m2 = mix(max(rd.x, rd.y), length(rd), 0.4);
  float band = 1.0 - smoothstep(0.72, 2.1, m2);
  col = mix(col, holdUnder(col, 0.09), band * u_guard);

  col += (bayer8(gl_FragCoord.xy) - 0.5) * (2.4 / 255.0);

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

export interface SignalGridFieldProps {
  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 SignalGridField({
  className,
  guardSelector = null,
}: SignalGridFieldProps) {
  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 GauzeField / CoronaField: 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: 2_000_000,
      dprCap: 1.5,
    });
  }, [guardSelector]);

  return (
    <div
      className={`absolute inset-0 overflow-hidden${className ? ` ${className}` : ""}`}
      aria-hidden="true"
    >
      <div className="absolute inset-0 [background:radial-gradient(_38%_42%_at_4%_4%,oklch(0.5_0.19_35_/_0.55)_0%,transparent_70%_),radial-gradient(_38%_42%_at_96%_96%,oklch(0.5_0.19_35_/_0.5)_0%,transparent_70%_),repeating-linear-gradient(_90deg,oklch(0.6_0.05_200_/_0.16)_0_1.5px,transparent_1.5px_58px_),repeating-linear-gradient(_0deg,oklch(0.6_0.05_200_/_0.16)_0_1.5px,transparent_1.5px_58px_),oklch(0.14_0.03_200)]" />
      <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