IrisBackgroundsContraflow
Contraflow
One curving road, two streams of long-exposure light — white headlights up one side of a dashed divider, red taillights down the other; the pointer steers the road and drags a slipstream through it.
Contraflow
An elevated slow-shutter view of a road that S-bends across the frame, the two directions of traffic separated into two ribbons of light: a bundle of cool white-to-gold headlight trails runs up one side of a dashed lane divider, a bundle of red-to-amber taillight trails runs down the other, the two travelling in genuinely opposite directions over open black. The lanes and the divider foreshorten together as the road narrows toward the top, and a faint asphalt sheen shows between the trails near the camera.
The pointer's x leans the whole road sideways under you, the way easing into a bend swings the lane across. Anywhere the cursor rests it opens a slipstream — the nearby trails quicken along their own direction of travel and widen — and drops a warm headlight bloom with a near-white core.
Install
No installation needed — self-contained, paste-in code.
Usage
Drop it straight into a page.
import { ContraflowField } from "./ContraflowField";
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">
<ContraflowField />
</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 long-exposure of a road that S-bends across the frame, seen
* from an elevated angle — the reference is a slow-shutter motorway shot
* where the two directions of traffic have separated into two ribbons of
* light. A bundle of cool white-to-gold headlight trails runs up one side of
* a dashed lane divider; a bundle of red-to-amber taillight trails runs down
* the other. The two travel in genuinely opposite directions along the same
* curve, over open black, with a faint asphalt sheen showing between them
* near the camera and everything compressing and dimming toward the top as
* the road recedes.
*
* The curve is one shared centreline `roadCenter(y)` — a lean from upper-left
* to lower-right plus one sine bend — so both bundles ride the same road
* rather than wobbling independently. Cross position is normalised to
* `n ∈ [-1, 1]` against a half-width that narrows with height, so the lanes
* and the divider foreshorten together instead of staying parallel in screen
* space.
*
* Two pointer interactions, both driving metaphors rather than a hover glow:
* - steering — the pointer's x leans the whole road sideways under you, the
* way easing into a bend swings the lane across.
* - slipstream — wherever the cursor rests it drags the nearby trails along
* their own direction of travel: they quicken (a bounded phase nudge, not
* a speed multiplier — see `kick`) and widen, the wake of something moving
* through the frame.
* A third, smaller touch: the cursor drops a warm headlight bloom with a
* near-white core, the same "your touch joined the light" move `RoadField`
* and `ArcLightsField` make.
*
* One of the reusable background fields (`TileField`, `SilkField`,
* `RoadField`, `CurrentField`, `FilamentField`). 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-contraflowfield__floor` underneath visible (a
* still composed frame in the same palette, never a blank box).
*
* Like `RoadField` / `CurrentField` (and unlike `SilkField`), the palette is
* a fixed set passed as uniforms rather than read from the token ramp — the
* near-white headlight core and the cool/warm split live nowhere in the
* accent ramp, so the CSS floor carries the colour rather than a live token
* read.
*
* 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), so copy laid
* over the field holds WCAG AA with no overlay layer. `null` (the default)
* turns the guard off — for decorative use where nothing sits on top.
*/
/* Palette, sRGB 0–1. Uniforms, not tokens — see the note above. */
const PALETTE: Record<string, [number, number, number]> = {
u_black: [0.011, 0.01, 0.012], // the near-black ground and sky
u_asphalt: [0.05, 0.052, 0.062], // the cool road sheen between the trails
u_headDim: [0.32, 0.28, 0.2], // the dim, barely-lit headlight lanes
u_headWarm: [0.99, 0.86, 0.62], // the warm white of a lit headlight trail
u_headCore: [1.0, 0.98, 0.93], // the near-white core riding the hot ones
u_tailDim: [0.26, 0.035, 0.028], // the dim inner taillight lane and road haze
u_tail: [0.86, 0.11, 0.06], // the mid-warm red trail
u_tailHot: [1.0, 0.46, 0.16], // the hotter amber outer taillight
u_line: [0.82, 0.79, 0.7], // the dashed lane divider
};
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_black;
uniform vec3 u_asphalt;
uniform vec3 u_headDim;
uniform vec3 u_headWarm;
uniform vec3 u_headCore;
uniform vec3 u_tailDim;
uniform vec3 u_tail;
uniform vec3 u_tailHot;
uniform vec3 u_line;
/* Reading region for the contrast guard: xy = centre (uv, y up),
zw = half-extent. Measured from the live copy block every frame.
u_guard is 0 when the guard is off. */
uniform vec4 u_readA;
uniform float u_guard;
/* The one shared centreline (the dashed divider) every lane rides — a lean
from upper-left to lower-right with one long S-bend, so both bundles ride
the same curving road. 'steer' is the pointer's lean, added in so the
whole road swings sideways under the cursor. */
float roadCenter(float y, float steer) {
float s = y - 0.5;
return 0.46 - 0.20 * s + 0.10 * sin(y * 2.2 + 0.7) + steer;
}
/* Half-width of the road in uv.x units — wide, so the band spans the frame;
a little narrower as it recedes so the lanes still foreshorten together
rather than staying ruler-parallel. */
float roadHalf(float y) {
return mix(0.62, 0.40, smoothstep(0.0, 1.0, y));
}
/* One bundle of lane trails. 'n' is the pixel's normalised cross position
(-1..1 across the road); 's' is -1 for the headlight bundle, +1 for the
taillight bundle. 'along' is the travel coordinate down the road (screen
height plus a little world-depth compression) — it drives the run of lit
gaps and the drift along each trail, so the light reads as travelling
rather than painted on. 'kick' is a bounded local phase nudge from the
cursor — see the note in main() on why it is additive, not a multiplier. */
float lanes(float n, float along, float t, float s, float kick, out float core) {
float side = n * s; /* >0 within this bundle's half */
float u = (side - 0.03) / 0.97; /* 0 at the divider .. 1 at the kerb */
float count = 12.0;
float lane = u * count;
float id = floor(lane);
float f = fract(lane) - 0.5;
/* each lane wanders a little of its own, so the bundle reads as separate
ribbons rather than one ruled grid — small, so lanes never cross */
float wob = fbm(vec2(along * 0.3 + id * 3.1, id * 1.7 + s * 5.0)) * 0.14;
float dx = f + wob;
float body = exp(-dx * dx * 130.0);
core = exp(-dx * dx * 900.0);
/* travel — opposite sign per bundle; the cursor's kick pushes with the
local direction of flow, so the slipstream drags the light forward */
float dir = -s;
float ph = along * dir - t * 1.5 * dir - kick * dir;
/* long continuous streaks with a slow swell along them, not dashes; some
lanes run dim and a few are unlit */
float m = fbm(vec2(ph * 0.85 + id * 4.0, id * 2.3 + s * 9.0));
float bright = smoothstep(0.04, 0.6, m + 0.06);
bright *= step(0.16, dotHash(vec2(id, s * 13.0 + 2.0)));
/* a gentle drift along each streak so it reads as moving light */
bright *= 0.82 + 0.18 * sin(ph * 1.8 + id * 2.0);
bright = max(bright, 0.0);
core *= bright;
return body * bright;
}
void main() {
vec2 res = u_res / u_scale;
vec2 uv = gl_FragCoord.xy / u_scale / res; /* 0..1, y up */
float aspect = res.x / res.y;
vec2 P = (uv - 0.5) * vec2(aspect, 1.0);
vec2 ptr = (u_pointer.xy - 0.5) * vec2(aspect, 1.0);
vec2 toPtr = P - ptr;
float dPtr = dot(toPtr, toPtr);
/* steering: the pointer's x leans the whole centreline sideways. u_pointer
is already eased upstream (shader-surface's shared pointer), so this
needs no smoothing of its own. */
float steer = (u_pointer.x - 0.5) * u_pointer.z * 0.14;
/* slipstream: a bounded pull that quickens and widens the local trails.
Kept as a phase nudge rather than a per-pixel speed multiplier — a
spatially-varying multiplier compounds against the absolute run time and
fractures the lanes near the cursor after the surface has been animating
for a few seconds (see CurrentField's 'kick' note). */
float pull = u_pointer.z * exp(-dPtr * 22.0);
float kick = pull * 1.7;
float cx = roadCenter(uv.y, steer);
float hw = roadHalf(uv.y);
float n = (uv.x - cx) / hw; /* -1..1, + is right */
float inRoad = 1.0 - smoothstep(0.9, 1.18, abs(n));
float depth = uv.y; /* 0 near .. 1 far */
float wz = uv.y / max(1.0 - uv.y * 0.55, 0.2); /* world depth */
float sky = 1.0 - smoothstep(0.9, 1.0, uv.y); /* black above the road */
/* travel coordinate down the road — mostly screen height so the streaks
advance at a readable rate everywhere in the frame, with a little
world-depth compression mixed in so they still bunch up as they recede */
float along = uv.y * 6.0 + wz * 1.4;
vec3 col = u_black;
/* the asphalt itself — a faint cool sheen inside the road, only near the
camera; nothing here is pure black, the discipline the other trail
fields keep */
col += u_asphalt * inRoad * (1.0 - depth) * 0.5 * sky;
/* a warm haze pooling under each bundle near the camera */
float haze = smoothstep(0.6, 0.0, depth) * exp(-n * n * 1.4) * inRoad;
col += mix(u_headDim, u_tailDim, smoothstep(-0.4, 0.4, n)) * haze * 0.6;
float widen = 1.0 + pull * 1.4;
/* headlights — the bundle left of the divider (s = -1) */
float hCore;
float hBody = lanes(n, along, u_time, -1.0, kick, hCore) * widen;
float halfL = smoothstep(0.0, 0.12, -n);
float gateL = halfL * inRoad * sky;
col += mix(u_headDim, u_headWarm, smoothstep(0.0, 0.5, hBody)) * hBody * gateL * 1.35;
col += u_headCore * hCore * gateL * 0.55;
/* taillights — the bundle right of the divider (s = +1) */
float tCore;
float tBody = lanes(n, along, u_time, 1.0, kick, tCore) * widen;
float halfR = smoothstep(0.0, 0.12, n);
float gateR = halfR * inRoad * sky;
col += mix(u_tailDim, u_tail, smoothstep(0.0, 0.5, tBody)) * tBody * gateR * 1.6;
col += u_tailHot * tCore * gateR * 0.6;
/* the dashed lane divider — spaced and animated down the road, so it
compresses toward the top exactly the way the lanes converge */
float dSig = 0.009 + 0.005 * depth;
float dGauss = exp(-n * n / (dSig * dSig + 1e-6));
float dPhase = fract(along * 1.4 - u_time * 0.9);
float dOn = clamp(smoothstep(0.0, 0.03, dPhase) - smoothstep(0.2, 0.28, dPhase), 0.0, 1.0);
/* break the mechanical regularity — some dashes dim, some missing */
float dVar = 0.35 + 0.65 * fbm(vec2(floor(along * 1.4) * 1.7, 3.0));
col += u_line * dGauss * dOn * dVar * inRoad * sky * (1.0 - depth * 0.45) * 0.7;
/* a soft bloom bleeding off the road onto the black */
float glow = exp(-pow(abs(n), 1.4) * 1.15) * (1.0 - depth * 0.7);
col += mix(u_headWarm, u_tail, smoothstep(-0.3, 0.3, n)) * glow * 0.05 * sky;
/* the touch: a warm headlight bloom with a near-white core */
col += u_headWarm * u_pointer.z * exp(-dPtr * 10.0) * 0.4;
col += u_headCore * u_pointer.z * exp(-dPtr * 44.0) * 0.35;
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 md = mix(max(rd.x, rd.y), length(rd), 0.4);
float band = 1.0 - smoothstep(0.72, 2.1, md);
col = mix(col, holdUnder(col, 0.09), band * u_guard);
col += (bayer8(gl_FragCoord.xy) - 0.5) * (2.2 / 255.0);
gl_FragColor = vec4(col, 1.0);
}
`;
export interface ContraflowFieldProps {
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 ContraflowField({
className,
guardSelector = null,
}: ContraflowFieldProps) {
const canvasRef = useRef<HTMLCanvasElement>(null);
useEffect(() => {
const canvas = canvasRef.current;
if (!canvas) return;
const guardOn = guardSelector != null;
/* The block the reading guard protects. Looked up once, lazily. */
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 RoadField / CurrentField: 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_200_000,
dprCap: 1.5,
});
}, [guardSelector]);
return (
<div
className={`absolute inset-0 overflow-hidden${className ? ` ${className}` : ""}`}
aria-hidden="true"
data-print="hide"
>
<div className="absolute inset-0 [background:radial-gradient(_46%_40%_at_33%_24%,oklch(0.93_0.06_92_/_0.9)_0%,transparent_60%_),radial-gradient(_44%_46%_at_44%_46%,oklch(0.8_0.08_82_/_0.5)_0%,transparent_62%_),radial-gradient(_58%_54%_at_73%_80%,oklch(0.6_0.2_26_/_0.92)_0%,transparent_60%_),radial-gradient(_72%_62%_at_90%_98%,oklch(0.48_0.2_22_/_0.72)_0%,transparent_58%_),radial-gradient(_120%_120%_at_50%_60%,oklch(0.05_0.01_30)_0%,transparent_70%_),oklch(0.02_0.006_30)] after:content-[''] after:absolute after:inset-0 after:[background:repeating-linear-gradient(_-58deg,transparent_0_5px,oklch(0.96_0.03_82_/_0.14)_5px_7px_)] after:[-webkit-mask-image:radial-gradient(_78%_82%_at_54%_54%,#000_0_42%,transparent_82%_)] after:[mask-image:radial-gradient(_78%_82%_at_54%_54%,#000_0_42%,transparent_82%_)]" />
<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