IrisBackgroundsGargantua
Gargantua
A small black horizon embedded in a disk far larger than itself: an actual ray-marched lens, not a drawn ring — icy blue-white near the hole cooling outward on a blackbody curve, a wide near-edge-on band spanning almost the full frame, its far side bent into a broad arc over the top.
Gargantua
Unlike `event-horizon`'s tilted saucer or `singularity`'s hourglass, this one is an actual (heavily simplified) ray marcher rather than a shape drawn in screen space: every pixel casts a 3D ray from a camera sitting almost edge-on to a thin world-space disk, the ray bends toward the hole step by step under a stabilised pull, and each step is tested for an exact crossing of the disk's own plane — a sign change in local height, resolved to the precise crossing point rather than guessed from a density field. The near side shows up because a nearly-straight ray crosses it directly, in front of the horizon, spanning almost the full width; the far side shows up bent above the silhouette because a ray aimed just over the hole curves enough to cross the plane a second time, behind it, before reaching the camera — one loop, alpha-composited front to back over each crossing, not two pasted shapes. A ray that dips inside the horizon is simply captured, flat black; a ray that survives carries its own closest approach, which alone drives the photon ring. Colour runs on an actual temperature curve — hot blue-white near the hole cooling toward orange on a power law, converted through a blackbody approximation, with one sector of the same disk cooling further into the rust-brown patch low on one side — brightened or dimmed by a Doppler-style term reading the local orbital velocity's own alignment with the ray reaching the camera, and textured by turbulence sampled on a seamless cylinder, rotating on two time-shifted phases so the loop never pops.
Every motion here is autonomous — the turbulence's own rotation, the ring's shimmer, the stars' twinkle — the camera and the hole itself never move, and there is no pointer interaction, same reasoning as `event-horizon` and `singularity`.
Install
No installation needed — self-contained, paste-in code.
Usage
Drop it straight into a page.
import { GargantuaField } from "./GargantuaField";
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">
<GargantuaField />
</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 black hole: an actual (heavily simplified) ray marcher, not
* a shape drawn in screen space. Every pixel casts a 3D ray from a camera
* sitting almost edge-on to a thin world-space disk; the ray's own
* direction bends toward the hole step by step under a stabilised
* inverse-square pull; and each step is tested for an exact crossing of
* the disk's own plane (a sign change in local height, resolved by linear
* interpolation to the precise crossing point, not a continuous volumetric
* guess). The near side of the disk is visible because a nearly-straight
* ray crosses it directly, in front of the hole; the far side appears bent
* up and over the silhouette because a ray aimed just above the hole
* curves enough to cross the plane a second time, behind it, before ever
* reaching the camera. One accumulation loop, front-to-back alpha
* composited over each crossing — nothing marks "near band" or "lensed
* arc" as separate shapes.
*
* The shading model at each crossing is adapted from a reference
* implementation built on an actual physical disk model rather than an art
* -directed gradient: temperature runs hot (blue-white) near the inner
* edge and cools (toward orange) outward on a power-law falloff, converted
* to colour through a blackbody approximation; brightness carries a
* relativistic-style Doppler term — the local orbital velocity's own
* alignment with the ray direction reaching the camera, not a flat
* per-side multiplier; and the turbulent texture is a 3D fbm sampled on a
* cylinder (radius, plus the angle's own cosine/sine) so it wraps with no
* seam, blended between two time-shifted phases a full rotation cycle
* apart so the loop is seamless rather than popping.
*
* A ray that dips inside the horizon is captured — flat black, nothing
* further. A ray that survives carries `minR`, its closest approach along
* the whole bent path; that alone drives the photon ring (this shader has
* no separate bloom pass to lean on, unlike the reference), so the ring is
* a consequence of the same integration rather than a second,
* independently drawn circle.
*
* Every motion here is autonomous — the turbulence's own rotation, the
* ring's shimmer, the stars' twinkle — the camera and the hole itself
* never move, and there is no pointer interaction: `u_pointer` isn't read
* by this shader, matching `EventHorizonField` / `SingularityField`.
*
* The composition is exposed as uniforms rather than baked constants —
* `u_blackHoleRadius`, `u_diskTemperature`, `u_lensStrength` and the rest
* below, all in the ray marcher's own world-space units — because this one
* is tuned to a specific photo rather than an open silhouette, and the
* shape most likely to need a nudge later is exactly the shape this file
* can't fully see from code alone.
*
* One of the reusable background fields. 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-gargantuafield__floor` underneath visible — a
* still frame 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 disk's own colour comes
mostly from its blackbody temperature (see PARAMS below), these are
tints and accents layered on top: a mild overall colour-grade on the
disk, the rust patch's own hue, the photon ring / bloom colour, and
deep space. */
const PALETTE: Record<string, [number, number, number]> = {
u_ground: [0.004, 0.005, 0.012], // near-black, cool deep space
u_ring: [0.85, 0.93, 1.0], // the photon ring / bloom, brightest tier
u_disk: [0.92, 0.96, 1.0], // a mild overall tint multiplied onto the blackbody colour
u_warm: [0.55, 0.28, 0.16], // the rust-brown cooling riding one side of the disk
u_star: [0.85, 0.92, 1.0], // the background starfield's cool tier
};
/* Composition, exposed as uniforms rather than baked in — see the doc
comment above for why this one field carries knobs the rest of the
catalogue doesn't. Radii/thickness are world-space units inside the ray
marcher; temperature is in thousands of kelvin. */
const PARAMS: Record<string, number> = {
u_blackHoleRadius: 0.85,
u_diskInclination: 0.12, // camera elevation above the disk plane, radians — small = near edge-on
u_diskThickness: 0.05,
u_lensStrength: 1.4, // gravitational pull driving the ray-bend integration
u_lensFalloff: 0.35, // stabiliser floor in the pull's 1/r^2 denominator
u_diskBrightness: 1.6,
u_diskWidth: 26.0, // the disk's own outer radius
u_diskTemperature: 58.0, // peak (inner-edge) blackbody temperature, thousands of K
u_temperatureFalloff: 0.62, // how fast temperature (and colour) cools outward
u_diskRotationSpeed: -6.0, // sign sets spin direction; magnitude drives Doppler + turbulence phase
u_dopplerStrength: 1.0,
u_diskEdgeSoftnessInner: 0.14,
u_diskEdgeSoftnessOuter: 0.4,
u_diskTurbulence: 1.0,
u_diskStreakScale: 0.34, // radial frequency of the cylinder-mapped turbulence
u_turbulenceStretch: 0.85, // how tight the tangential streaks wrap
u_turbulenceSharpness: 5.5, // how thin/filamentary the turbulence reads
u_turbulenceCycleTime: 6.0, // seconds per seamless turbulence-rotation loop
u_turbulenceLacunarity: 2.4,
u_turbulencePersistence: 0.75,
u_photonRingWidth: 0.045,
u_photonRingBrightness: 1.7,
u_bloomStrength: 0.2,
u_blueIntensity: 1.0,
u_warmDustIntensity: 0.5,
u_starIntensity: 1.0,
u_animationSpeed: 1.0,
};
/* Where the hole sits on screen, as a fraction of the frame — slightly
above vertical centre, per the reference. */
const BH_CENTER: [number, number] = [0.5, 0.54];
const FRAG = `
uniform vec2 u_res;
uniform float u_time;
uniform float u_scale;
uniform vec3 u_ground;
uniform vec3 u_ring;
uniform vec3 u_disk;
uniform vec3 u_warm;
uniform vec3 u_star;
uniform vec2 u_bhCenter;
uniform float u_blackHoleRadius;
uniform float u_diskInclination;
uniform float u_diskThickness;
uniform float u_lensStrength;
uniform float u_lensFalloff;
uniform float u_diskBrightness;
uniform float u_diskWidth;
uniform float u_diskTemperature;
uniform float u_temperatureFalloff;
uniform float u_diskRotationSpeed;
uniform float u_dopplerStrength;
uniform float u_diskEdgeSoftnessInner;
uniform float u_diskEdgeSoftnessOuter;
uniform float u_diskTurbulence;
uniform float u_diskStreakScale;
uniform float u_turbulenceStretch;
uniform float u_turbulenceSharpness;
uniform float u_turbulenceCycleTime;
uniform float u_turbulenceLacunarity;
uniform float u_turbulencePersistence;
uniform float u_photonRingWidth;
uniform float u_photonRingBrightness;
uniform float u_bloomStrength;
uniform float u_blueIntensity;
uniform float u_warmDustIntensity;
uniform float u_starIntensity;
uniform float u_animationSpeed;
uniform vec4 u_readA;
uniform float u_guard;
float wrapAngle(float a) {
return a - 6.2831853 * floor(a / 6.2831853 + 0.5);
}
/* A second, independent hash/noise family in 3D — the shared prelude's
fbm() is 2D, and the disk's turbulence needs to sample a genuine 3D
cylinder (radius, cos angle, sin angle) so it wraps with no seam at
angle = +-pi, which a 2D (angle, radius) flow can't do without a warp. */
float hash3(vec3 p) {
return fract(sin(dot(p, vec3(127.1, 311.7, 74.7))) * 43758.5453);
}
float noise3(vec3 p) {
vec3 i = floor(p);
vec3 f = fract(p);
vec3 u = f * f * (3.0 - 2.0 * f);
float a = hash3(i);
float b = hash3(i + vec3(1.0, 0.0, 0.0));
float c = hash3(i + vec3(0.0, 1.0, 0.0));
float d = hash3(i + vec3(1.0, 1.0, 0.0));
float e = hash3(i + vec3(0.0, 0.0, 1.0));
float f2 = hash3(i + vec3(1.0, 0.0, 1.0));
float g = hash3(i + vec3(0.0, 1.0, 1.0));
float h = hash3(i + vec3(1.0, 1.0, 1.0));
return mix(mix(mix(a, b, u.x), mix(c, d, u.x), u.y),
mix(mix(e, f2, u.x), mix(g, h, u.x), u.y), u.z);
}
float fbm3(vec3 p, float lacunarity, float persistence) {
float value = 0.0;
float amplitude = 0.5;
for (int i = 0; i < 4; i++) {
value += noise3(p) * amplitude;
p *= lacunarity;
amplitude *= persistence;
}
return value;
}
/* A crude but cheap blackbody approximation — plausible enough to carry a
hot blue-white inner edge cooling to orange at the rim, without a real
spectral integral. */
vec3 blackbody(float tempK) {
float t = clamp(tempK / 40000.0, 0.0, 1.0);
vec3 cool = vec3(1.0, 0.36, 0.12); /* ~1500K, deep orange */
vec3 warm = vec3(1.0, 0.86, 0.62); /* ~4500K, warm white */
vec3 neutral = vec3(1.0, 1.0, 1.0); /* ~6500K, white */
vec3 hot = vec3(0.66, 0.8, 1.0); /* ~25000K+, blue-white */
vec3 col = mix(cool, warm, smoothstep(0.0, 0.15, t));
col = mix(col, neutral, smoothstep(0.1, 0.3, t));
col = mix(col, hot, smoothstep(0.15, 0.55, t));
return col;
}
/* A distant starfield indexed by ray direction rather than screen space —
fixed at infinity regardless of how much a given ray bent — with a
per-star size and colour-temperature jitter, plus a slow twinkle for
the same autonomous-motion reasoning the rest of the catalogue holds to. */
vec3 starfield(vec3 dir, float time) {
float theta = atan(dir.z, dir.x);
float phi = asin(clamp(dir.y, -1.0, 1.0));
vec2 sp = vec2(theta, phi) * 60.0;
vec2 cell = floor(sp);
vec2 cellUV = fract(sp);
float ch = dotHash(cell);
vec3 out_ = vec3(0.0);
if (ch > 0.975) {
vec2 starPos = vec2(dotHash(cell + 1.7), dotHash(cell + 5.3)) * 0.8 + 0.1;
float distToStar = length(cellUV - starPos);
float sizeVar = dotHash(cell + 9.1) * 0.12 + 0.03;
float core = smoothstep(sizeVar, 0.0, distToStar);
float glow = smoothstep(sizeVar * 3.0, 0.0, distToStar) * 0.3;
float twinkle = 0.6 + 0.4 * sin(time * (1.0 + ch * 2.0) + ch * 40.0);
float starI = (core + glow) * twinkle * (ch - 0.975) * 40.0;
vec3 starCol = mix(u_star, vec3(1.0, 0.95, 0.85), dotHash(cell + 21.0));
out_ = starCol * starI;
}
return out_ * u_starIntensity;
}
/* The disk's own shading at one exact plane crossing: a physically
-inspired temperature gradient converted through blackbody(), a
Doppler-style brightness split from the local orbital velocity's
alignment with the ray reaching the camera, and turbulence sampled on a
cylinder so it wraps with no seam, cross-faded between two phases one
full rotation apart so the loop never pops. Returns premultiplied
colour plus opacity, for front-to-back compositing in the caller. */
vec4 diskShade(float hitR, float hitAngle, vec3 rayDir, float innerR, float outerR, float time) {
float normR = clamp((hitR - innerR) / max(outerR - innerR, 1e-4), 0.0, 1.0);
float peakTempK = u_diskTemperature * 1000.0;
float outerTempK = 1400.0;
float tempFalloff = pow(innerR / max(hitR, 1e-3), u_temperatureFalloff);
float tempK = mix(outerTempK, peakTempK, tempFalloff);
/* a mild angular cooling bias riding the disk itself, rather than a
patch pinned to the screen — where the reference's rust-brown region
comes from: one sector of the same disk, just cooler */
float dustBias = exp(-pow(wrapAngle(hitAngle + 0.95) / 0.5, 2.0)) * u_warmDustIntensity;
tempK = mix(tempK, tempK * 0.14, dustBias);
vec3 diskCol = blackbody(tempK);
diskCol = mix(diskCol, u_warm, dustBias * 0.75);
diskCol *= mix(vec3(1.0), u_disk, 0.4);
float rotationSign = sign(u_diskRotationSpeed);
vec3 velocityDir = vec3(-sin(hitAngle) * rotationSign, 0.0, cos(hitAngle) * rotationSign);
float velocityMagnitude = 1.0 / sqrt(max(hitR / innerR, 1e-4));
float beta = velocityMagnitude * 0.3;
float cosTheta = dot(velocityDir, rayDir);
float dopplerFactor = 1.0 / max(1.0 - beta * cosTheta, 1e-3);
float dopplerBoost = clamp(pow(dopplerFactor, 3.0 * u_dopplerStrength), 0.1, 5.0);
diskCol *= dopplerBoost;
float edgeFalloff = smoothstep(0.0, u_diskEdgeSoftnessInner, normR)
* smoothstep(1.0, 1.0 - u_diskEdgeSoftnessOuter, normR);
/* two time-shifted turbulence samples, one full rotation cycle apart,
cross-faded — a seamlessly looping spin instead of one that pops at
the cycle boundary */
float cycleLength = max(u_turbulenceCycleTime, 0.5);
float cyclicTime = mod(time, cycleLength);
float blendT = cyclicTime / cycleLength;
float rPow = pow(max(hitR, 1e-3), 1.5);
float phase1 = cyclicTime * u_diskRotationSpeed / rPow;
float phase2 = (cyclicTime + cycleLength) * u_diskRotationSpeed / rPow;
float stretch = max(u_turbulenceStretch, 0.1);
vec3 nc1 = vec3(hitR * u_diskStreakScale, cos(hitAngle + phase1) / stretch, sin(hitAngle + phase1) / stretch);
vec3 nc2 = vec3(hitR * u_diskStreakScale, cos(hitAngle + phase2) / stretch, sin(hitAngle + phase2) / stretch);
float turb1 = fbm3(nc1, u_turbulenceLacunarity, u_turbulencePersistence);
float turb2 = fbm3(nc2, u_turbulenceLacunarity, u_turbulencePersistence);
float turb = mix(turb2, turb1, blendT);
float ringOpacity = pow(clamp(turb, 0.0, 1.0), u_turbulenceSharpness);
ringOpacity = mix(1.0, ringOpacity, clamp(u_diskTurbulence, 0.0, 1.0));
return vec4(diskCol * u_diskBrightness, ringOpacity * edgeFalloff);
}
/* Narkowicz's ACES approximation — a cheap, standard filmic roll-off so
the hottest disk pixels bloom toward white instead of clipping flat. */
vec3 acesFilm(vec3 x) {
float a = 2.51, b = 0.03, c = 2.43, d = 0.59, e = 0.14;
return clamp((x * (a * x + b)) / (x * (c * x + d) + e), 0.0, 1.0);
}
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 - u_bhCenter) * vec2(aspect, 1.0);
float time = u_time * u_animationSpeed;
float bhR = u_blackHoleRadius;
float innerR = bhR * 2.1;
float outerR = max(u_diskWidth, innerR * 1.6);
/* ---- the camera: sitting almost edge-on, a shallow elevation above the
disk's own plane, looking back at the hole. Far enough back, and with
a wide enough field of view, that the disk's own outer radius reaches
toward the frame's edges while the horizon — even amplified by its own
photon-capture cross-section, well past the bare event-horizon radius
— stays a small fraction of that: the ratio between the two is fixed
by world-space geometry alone, not by how far back the camera sits, so
camDist/tanFov are tuned to the disk's own outerR here. ------------ */
float camDist = outerR * 1.55;
float camHeight = camDist * tan(u_diskInclination);
vec3 camPos = vec3(0.0, camHeight, -camDist);
vec3 fwd = normalize(-camPos);
vec3 worldUp = vec3(0.0, 1.0, 0.0);
vec3 right = normalize(cross(fwd, worldUp));
vec3 up = cross(right, fwd);
float tanFov = 0.8;
vec3 dir = normalize(fwd + p.x * tanFov * right + p.y * tanFov * up);
vec3 pos = camPos;
vec3 prevPos = pos;
/* ---- the ray march: bend dir toward the hole every step under a
stabilised inverse-square pull, testing each step for an exact
crossing of the disk's plane and alpha-compositing front to back.
Near-side and far-side (lensed) images fall out of this one loop with
no separate branch for either — see the component doc comment. ---- */
vec3 accum = vec3(0.0);
float alpha = 0.0;
float minR = 1e4;
bool captured = false;
const int STEPS = 40;
for (int i = 0; i < STEPS; i++) {
float r = length(pos);
minR = min(minR, r);
if (r < bhR) { captured = true; break; }
if (alpha > 0.985) break;
float stepLen = clamp(r * 0.22, 0.035, 2.4);
vec3 toCenter = -pos / max(r, 1e-4);
float accel = u_lensStrength / max(r * r, u_lensFalloff);
/* the turn each step is allowed to make is capped outright — without
this, the 1/r^2 pull overshoots badly in the last few steps before
capture and the numerical instability inflates the shadow's own
effective radius far past the horizon itself */
float bend = min(accel * stepLen, 0.3);
dir = normalize(dir + toCenter * bend);
prevPos = pos;
pos += dir * stepLen;
/* an exact crossing: a sign change in local height, resolved to the
precise crossing point by linear interpolation rather than guessed
from a continuous density field */
if (prevPos.y * pos.y < 0.0) {
float t = -prevPos.y / (pos.y - prevPos.y);
vec3 hitPos = mix(prevPos, pos, t);
float hitR = length(hitPos.xz);
if (hitR > innerR && hitR < outerR) {
float hitAngle = atan(hitPos.z, hitPos.x);
vec4 shaded = diskShade(hitR, hitAngle, dir, innerR, outerR, time);
float remaining = 1.0 - alpha;
accum += shaded.rgb * shaded.a * remaining;
alpha += remaining * shaded.a;
}
}
if (r > outerR * 4.0 && dot(dir, pos) > 0.0) break;
}
vec3 col;
if (captured) {
col = u_ground * 0.25;
} else {
vec3 bg = u_ground + starfield(dir, time);
col = bg * (1.0 - alpha) + accum;
/* the photon ring: a consequence of the same integration, not a second
drawn circle — driven by minR, the closest approach the bent ray
actually made (this shader has no separate bloom pass to lean on,
unlike the reference it's adapted from), with a whisper of angular
irregularity and a boost wherever the disk it rode in on is
already bright */
float ringAngNoise = fbm(vec2(cos(atan(p.y, p.x)) * 3.0, sin(atan(p.y, p.x)) * 3.0) + time * 0.02);
float ringSigma = max(u_photonRingWidth * (1.0 + ringAngNoise * 0.35), 0.008);
float ringShape = exp(-pow((minR - bhR * 1.06) / ringSigma, 2.0));
float diskLum = dot(accum, vec3(0.2126, 0.7152, 0.0722));
float ringBoost = 0.45 + 0.9 * clamp(diskLum * 2.2, 0.0, 1.0);
col += u_ring * ringShape * ringBoost * u_photonRingBrightness * u_blueIntensity;
/* a soft bloom, held tight to the hottest pixels only */
float hot = clamp(ringShape * 0.6 + diskLum * 0.8, 0.0, 1.0);
col += mix(u_disk, u_ring, 0.5) * hot * hot * u_bloomStrength * u_blueIntensity * 0.6;
}
/* corner vignette — the ray marcher already darkens the corners on its
own (nothing bends far enough out to reach them), this just firms it
up a little further */
vec2 corner = uv - 0.5;
float cvig = length(corner * vec2(1.0, 1.2));
col *= mix(1.0, 0.55, smoothstep(0.55, 1.15, cvig));
col = max(col, 0.0);
col = acesFilm(col);
/* ---- the reading guard (see TileField for the full rationale) ---- */
vec2 rg = abs(uv - u_readA.xy) / max(u_readA.zw, vec2(0.02));
float md = mix(max(rg.x, rg.y), length(rg), 0.4);
float guardBand = 1.0 - smoothstep(0.72, 2.1, md);
col = mix(col, holdUnder(col, 0.09), guardBand * u_guard);
col += (bayer8(gl_FragCoord.xy) - 0.5) * (2.2 / 255.0);
gl_FragColor = vec4(col, 1.0);
}
`;
export interface GargantuaFieldProps {
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 GargantuaField({ className, guardSelector = null }: GargantuaFieldProps) {
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), ...Object.keys(PARAMS), "u_bhCenter", "u_readA", "u_guard"],
onInit: (gl, u) => {
for (const name of Object.keys(PALETTE)) {
if (u[name]) gl.uniform3fv(u[name], PALETTE[name]);
}
for (const name of Object.keys(PARAMS)) {
if (u[name]) gl.uniform1f(u[name], PARAMS[name]);
}
if (u.u_bhCenter) gl.uniform2f(u.u_bhCenter, BH_CENTER[0], BH_CENTER[1]);
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 EventHorizonField / SingularityField:
once painted, the last frame stays on screen while the surface is
parked off-view. onLost drops back to the CSS floor. */
onLost: () => canvas.removeAttribute("data-shader"),
maxPixels: 1_500_000,
dprCap: 1.4,
});
}, [guardSelector]);
return (
<div
className={`absolute inset-0 overflow-hidden${className ? ` ${className}` : ""}`}
aria-hidden="true"
>
<div className="absolute inset-0 [background:radial-gradient(_4%_6%_at_50%_46%,oklch(0.02_0_0)_0%,oklch(0.02_0_0)_55%,transparent_75%_),radial-gradient(_6%_8%_at_50%_46%,transparent_48%,oklch(0.95_0.02_235_/_0.9)_60%,transparent_78%_),radial-gradient(_46%_9%_at_50%_50%,oklch(0.55_0.14_240_/_0.55)_0%,oklch(0.4_0.13_240_/_0.35)_45%,transparent_82%_),radial-gradient(_38%_20%_at_50%_30%,transparent_40%,oklch(0.62_0.13_240_/_0.5)_60%,transparent_84%_),radial-gradient(_26%_16%_at_24%_53%,oklch(0.32_0.12_40_/_0.4)_0%,transparent_72%_),radial-gradient(_55%_55%_at_50%_46%,oklch(0.06_0.015_255)_0%,transparent_74%_),oklch(0.015_0.008_260)] after:content-[''] after:absolute after:inset-0 after:[background:repeating-radial-gradient(_circle_at_50%_46%,transparent_0_4%,oklch(0.62_0.13_240_/_0.07)_4%_4.5%,transparent_4.5%_9%_)] after:[mix-blend-mode:screen]" />
<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