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.
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.
Install
No installation needed — self-contained, paste-in code.
Usage
Drop it straight into a page.
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>
);
}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
Reads and answers every request himself