Manual setup

Not using the shadcn CLI? Copy the runtime plus the orbs you want into your project. They import nothing but React.

1. Check your alias

The orb imports the runtime from @/components/ui/orbkit-core. If your project uses a different alias, adjust the import at the top of the orb file — that is the only path either file references.

// tsconfig.json
{
  "compilerOptions": {
    "paths": { "@/*": ["./*"] }
  }
}

2. components/ui/orbkit-core.tsx

"use client";

import { useEffect, useRef, useState, type CSSProperties, type ReactNode } from "react";

/* ----------------------------------------------------------------------------
   Orbkit core — raw WebGL shader orb runtime. No dependencies.

   An orb is a full-screen triangle rendered into a transparent canvas by a
   fragment shader. Every orb declares a parameter schema (sliders + colors);
   the values are uploaded as uniforms each frame from a ref, so a controls
   panel can tune them live without ever remounting the canvas (a remount would
   drop the WebGL context).

   Animation model: every state synthesizes two volume signals — input (user
   speech energy) and output (agent speech energy) — smooths them, and the
   shaders react to those. The flow clock's speed itself follows the output
   volume, so orbs visibly quicken when the agent is talking.
---------------------------------------------------------------------------- */

export type OrbState = "idle" | "thinking" | "speaking";

export const ORB_STATES = ["idle", "thinking", "speaking"] as const;

function clamp01(n: number) {
  return Math.min(1, Math.max(0, n));
}

/** Per-state [input, output] volume synthesis. */
/**
 * Transition rate shared by params, colours and the flow-speed multiplier.
 * They must move together: if the rate multiplier eases faster than the look,
 * a state change spins the orb up before it has finished cross-fading, which
 * reads as a lurch.
 *
 * This drives a critically damped spring rather than the exponential ease it
 * used to. An exponential's velocity is highest at the instant the target
 * changes, so every state change began with a jolt — and for params that are
 * spatial frequencies (Corona's warpFreq travels 5.25 -> 19.5 between states)
 * that jolt sweeps the field through its intermediate frequencies at maximum
 * rate, which is what read as the transition "scrambling".
 *
 * A spring starts at rest and accelerates, so the sweep is spread across the
 * transition instead of front-loaded. Measured on that warpFreq move it is a
 * 20% lower peak rate of change (20.3/s vs 25.3/s) AND it arrives sooner —
 * 1.50s to within 2% against the exponential's 2.18s, since an exponential
 * only ever asymptotes toward its target.
 */
const PARAM_EASE = 4;

/*
  One step of a critically damped spring, implicit (semi-implicit Euler would
  blow up at the frame times a backgrounded tab produces). Returns nothing and
  writes through the scratch pair so the hot loop allocates nothing.
*/
const springOut = { x: 0, v: 0 };
function springStep(x: number, v: number, target: number, dt: number, omega: number) {
  const f = 1 + 2 * dt * omega;
  const oo = omega * omega;
  const hoo = dt * oo;
  const hhoo = dt * hoo;
  const detInv = 1 / (f + hhoo);
  springOut.x = (f * x + dt * v + hhoo * target) * detInv;
  springOut.v = (v + hoo * (target - x)) * detInv;
}

function targetVolumes(state: OrbState, t: number): [number, number] {
  switch (state) {
    case "idle":
      return [0, 0.3];
    case "speaking":
      return [
        clamp01(0.65 + Math.sin(t * 4.8) * 0.22),
        clamp01(0.75 + Math.sin(t * 3.6) * 0.22)
      ];
    case "thinking": {
      const base = 0.38 + 0.07 * Math.sin(t * 0.7);
      const wander = 0.05 * Math.sin(t * 2.1) * Math.sin(t * 0.37 + 1.2);
      return [clamp01(base + wander), clamp01(0.48 + 0.12 * Math.sin(t * 1.05 + 0.6))];
    }
  }
}

/* ------------------------------ param schema ------------------------------- */

export interface OrbParamDef {
  key: string;
  label: string;
  min: number;
  max: number;
  step: number;
  default: number;
  /**
   * Rate params. The engine integrates them into a clock
   * (`clock += dt * value * volumeSpeed`) and uploads the clock instead of the
   * raw value, so changing the rate never jumps the phase — the motion speeds
   * up or slows down rather than snapping to a new position.
   */
  integrate?: boolean;
}

export interface OrbColorDef {
  key: string;
  label: string;
  /** hex, e.g. `#ff8b73` */
  default: string;
}

export interface OrbVariant {
  key: string;
  label: string;
  note: string;
  /** GLSL fragment shader body. Uniform declarations are generated for you. */
  frag: string;
  params: OrbParamDef[];
  colors: OrbColorDef[];
  /**
   * Per-state parameter targets. The engine glides each param toward the
   * active state's preset. Params passed explicitly via the `params` prop
   * always win over the preset.
   */
  statePresets?: Partial<Record<OrbState, Record<string, number>>>;
  /**
   * Per-state colour targets, the colour counterpart of `statePresets`.
   * Kept a separate map because presets are numeric and colours are hex
   * strings — a union would lose type safety on both. Colours glide in RGB
   * on the same easing as params, so a state change cross-fades rather
   * than cutting. Colours passed explicitly via the `colors` prop always
   * win, exactly as with params.
   */
  stateColors?: Partial<Record<OrbState, Record<string, string>>>;
}

export type OrbParamValues = Partial<Record<string, number>>;
export type OrbColorValues = Partial<Record<string, string>>;

/** Every param and color at its schema default. */
export function defaultValuesFor(variant: OrbVariant): {
  params: Record<string, number>;
  colors: Record<string, string>;
} {
  return {
    params: Object.fromEntries(variant.params.map((p) => [p.key, p.default])),
    colors: Object.fromEntries(variant.colors.map((c) => [c.key, c.default]))
  };
}

export function hexToRgb(hex: string): [number, number, number] {
  let h = hex.replace("#", "").trim();
  if (h.length === 3) h = h[0] + h[0] + h[1] + h[1] + h[2] + h[2];
  const n = parseInt(h, 16);
  if (h.length !== 6 || Number.isNaN(n)) return [1, 1, 1];
  return [((n >> 16) & 255) / 255, ((n >> 8) & 255) / 255, (n & 255) / 255];
}

/* ------------------------------- GLSL shared ------------------------------- */

const VERT = `
attribute vec2 aPos;
void main() { gl_Position = vec4(aPos, 0.0, 1.0); }
`;

/**
 * Prelude prepended to every orb fragment shader: uniforms, value noise, fbm,
 * and the centered aspect-corrected UV helper.
 */
export const ORB_GLSL_HELPERS = `
precision highp float;
uniform vec2 uRes;
uniform float uTime;   // slow ambient clock (half real-time)
uniform float uAnim;   // flow clock — its speed follows the output volume
uniform float uInput;  // input volume 0..1: user speech energy
uniform float uOutput; // output volume 0..1: agent speech energy

float hash(vec2 p) { return fract(sin(dot(p, vec2(127.1, 311.7))) * 43758.5453123); }
float noise(vec2 p) {
  vec2 i = floor(p);
  vec2 f = fract(p);
  f = f * f * (3.0 - 2.0 * f);
  return mix(
    mix(hash(i), hash(i + vec2(1.0, 0.0)), f.x),
    mix(hash(i + vec2(0.0, 1.0)), hash(i + vec2(1.0, 1.0)), f.x),
    f.y
  );
}
float fbm(vec2 p) {
  float v = 0.0;
  float a = 0.5;
  for (int i = 0; i < 5; i++) {
    v += a * noise(p);
    p = p * 2.03 + vec2(11.7, 7.3);
    a *= 0.5;
  }
  return v;
}
vec2 orbUV() { return (2.0 * gl_FragCoord.xy - uRes) / min(uRes.x, uRes.y); }

// GLSL ES 1.0 has no tanh() — it arrived in ES 3.0. Shader-golf listings lean
// on it as a tone-mapper, so it ships here. Clamped against exp() overflow;
// accurate for the non-negative accumulators those shaders produce.
vec3 tanh3(vec3 x) {
  x = clamp(x, -10.0, 10.0);
  vec3 e = exp(2.0 * x);
  return (e - 1.0) / (e + 1.0);
}

`;

function paramUniformDecls(variant: OrbVariant): string {
  return [
    ...variant.params.map((p) => `uniform float uP_${p.key};`),
    ...variant.colors.map((c) => `uniform vec3 uC_${c.key};`)
  ].join("\n");
}

/* ------------------------------- engine ------------------------------------ */

/**
 * Per-canvas context-lifecycle controller. Created on first mount of a canvas
 * and kept for the element's whole life — the router can hide a page and show
 * the same DOM again, and React re-runs effects on the same canvas, so the
 * lost/restored listeners must outlive any single effect run: an uncanceled
 * webglcontextlost event marks the context permanently unrestorable.
 */
interface CanvasContextController {
  /** Whether a mounted orb currently wants this context alive. */
  desired: boolean;
  /** Builds a render generation; returns its teardown. Rebound per effect run. */
  start: (() => () => void) | null;
  /** Teardown of the live generation, if one is running. */
  stopGen: (() => void) | null;
}

const canvasControllers = new WeakMap<HTMLCanvasElement, CanvasContextController>();

function compile(gl: WebGLRenderingContext, type: number, src: string): WebGLShader | null {
  const shader = gl.createShader(type);
  if (!shader) return null;
  gl.shaderSource(shader, src);
  gl.compileShader(shader);
  if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
    console.error("[orbkit] shader compile error:", gl.getShaderInfoLog(shader));
    gl.deleteShader(shader);
    return null;
  }
  return shader;
}

/* ----------------------------------------------------------------------------
   Wrappers — optional decoration drawn around, under and over the orb.

   A wrapper is pure CSS/SVG, never a shader: the orb keeps its own canvas and
   the decoration is composited on top of it by the browser. That keeps the
   whole set free for any orb (no per-variant shader work), costs nothing on
   the GPU budget the shaders are already spending, and means swapping one
   wrapper for another at runtime never touches the WebGL context. Turning a
   wrapper on or off does, since that is what moves the canvas into or out of
   the wrapper element — see the effect's `wrapped` dependency.

   Layout: a wrapped orb becomes a square box, and the canvas is absolutely
   positioned inside it with the spec's `inset`. The FOOTPRINT is unchanged —
   `size` is still the outer diameter — so dropping a wrapper onto an existing
   orb reflows nothing; the orb itself just shrinks to leave the ring room.

   Colour: every layer that isn't physically light or shadow paints in
   `currentColor`, so a wrapper picks up the surrounding text colour and reads
   correctly on light and dark pages with no configuration. `wrapperColor`
   sets that colour when you want something other than the inherited one.
---------------------------------------------------------------------------- */

export const ORB_WRAPPERS = [
  "none",
  "glass",
  "ring",
  "dotted",
  "ticks",
  "reticle",
  "grid",
  "halftone",
  "scanlines"
] as const;

export type OrbWrapper = (typeof ORB_WRAPPERS)[number];

/*
  Keyframes for the animated wrappers, shipped inside the component so an orb
  stays a single self-contained file with nothing to add to a global
  stylesheet. React 19 hoists a <style href precedence> into <head> and
  de-duplicates it, so a page full of wrapped orbs emits this exactly once;
  older React renders it inline, which is redundant but harmless.

  Reduced motion parks all of it. The shader runtime already honours the same
  preference for the orb itself (see the reduce-motion branch in the render
  loop), and a ring that keeps spinning around a frozen orb would be the worse
  half of the two still moving.
*/
const WRAPPER_STYLE_HREF = "orbkit-wrapper";
const WRAPPER_CSS = `
@keyframes orbkit-w-spin { to { transform: rotate(360deg); } }
@keyframes orbkit-w-roll { from { transform: translateY(-110%); } to { transform: translateY(360%); } }
@media (prefers-reduced-motion: reduce) {
  .orbkit-w-anim { animation: none !important; }
}
`;

interface WrapperSpec {
  /**
   * How far the canvas sits inside the box, in percent, leaving the
   * decoration room. Applied as explicit width/height rather than as `inset`:
   * a canvas is a REPLACED element, so an absolutely positioned one with
   * `left` and `right` both set does not stretch between them — it keeps its
   * intrinsic size and the over-constrained edge is dropped. The orb would
   * then be drawn into a canvas the size of the page.
   */
  inset: number;
  /**
   * Soft circular mask on the canvas. Only the wrappers that read as a
   * CONTAINER set one — a bubble has to hold the orb, whereas a bezel sits
   * beside it and clipping the halo there would just amputate the glow.
   */
  mask?: string;
  /** Cast by the assembly as a whole, on the outer box. */
  shadow?: string;

  /** True when the spec uses one of the keyframes above. */
  animated?: boolean;
  /** Painted beneath the canvas. */
  under?: ReactNode;
  /** Painted over it. */
  over?: ReactNode;
}

const DISC: CSSProperties = { borderRadius: "50%" };

/** One absolutely-positioned decoration layer, filling the wrapper box. */
function Layer({
  inset = 0,
  style,
  className
}: {
  inset?: number | string;
  style: CSSProperties;
  className?: string;
}) {
  return (
    <span
      aria-hidden="true"
      className={className}
      style={{ position: "absolute", inset, pointerEvents: "none", ...style }}
    />
  );
}

/**
 * A free-floating highlight — glass's specular and its bounce. These do not go
 * through `Layer` because they are placed with `left`/`top`/`width`, and a
 * style object that sets those on top of `Layer`'s `inset` shorthand is mixing
 * shorthand and longhand for the same property: React warns about it, and the
 * result depends on key order rather than on anything you would want to rely
 * on.
 */
function Highlight({ style }: { style: CSSProperties }) {
  return (
    <span
      aria-hidden="true"
      style={{ position: "absolute", borderRadius: "50%", pointerEvents: "none", ...style }}
    />
  );
}

/**
 * The mask that makes a wrapper HOLD the orb: cuts the canvas back to the
 * bubble and stops a hairline short of the rim, so the orb never quite touches
 * the glass.
 *
 * The percentage has to be derived rather than written down. `closest-side`
 * measures the CANVAS, and a wrapper with a negative inset draws its canvas
 * LARGER than the bubble — at -7 the canvas is 114% of the box, so the rim
 * sits at 100/1.14 = 87.7% of the canvas's own radius, not at 100%.
 *
 * The gap is real pixels rather than a share of the size: it reads as the same
 * band at 120px and at 900px, which a percentage would not.
 */
const RIM_GAP_PX = 4;

function rimMask(inset: number): string {
  const rim = (100 / (1 - (2 * inset) / 100)).toFixed(2);
  // Feathered over the final pixel, so the cut is not a razor edge.
  const solid = RIM_GAP_PX + 0.5;
  const clear = RIM_GAP_PX - 0.5;
  return `radial-gradient(circle closest-side, #000 calc(${rim}% - ${solid}px), rgba(0,0,0,0) calc(${rim}% - ${clear}px))`;
}

/** Both spellings, since Safari still wants the prefix for mask-image. */
function masked(image: string): CSSProperties {
  return { WebkitMaskImage: image, maskImage: image };
}

const svgLayer: CSSProperties = {
  position: "absolute",
  inset: 0,
  width: "100%",
  height: "100%",
  pointerEvents: "none",
  overflow: "visible"
};

/** Glass's overfill, shared by its inset and the mask derived from it. */
const GLASS_INSET = -4;

const WRAPPER_SPECS: Record<Exclude<OrbWrapper, "none">, WrapperSpec> = {
  /*
    glass — a blown bubble with the orb suspended inside it.

    Five layers in the order light actually arrives: the body brightening
    toward the key light, the Fresnel ring where a sphere's edge turns almost
    edge-on and reflects nearly everything, the rim itself, the window
    reflection, and the bounce coming back up off whatever the bubble is
    sitting on. All of it is white and black rather than `currentColor` —
    glass has no colour of its own, only the light it moves around.
  */
  glass: {
    /*
      Negative on purpose. An orb's shader does not necessarily paint to the
      edge of its canvas — most draw a sphere with transparent margin around
      it — so a canvas sized to the bubble leaves a dead ring between the orb
      and the rim, which is not what a thing suspended in glass looks like.
      Oversizing the canvas by 14% pushes the sphere out to the rim, and
      `rimMask` cuts whatever overflows — a few pixels short of the glass, so
      the orb sits just inside it rather than welded to it. Orbs that already fill
      their canvas lose a few percent off the limb, which is the same crop the
      reference bubble makes.
    */
    inset: GLASS_INSET,
    mask: rimMask(GLASS_INSET),
    shadow: "0 24px 48px -26px rgba(0,0,0,0.55)",
    over: (
      <>
        <Layer
          style={{
            ...DISC,
            background:
              "radial-gradient(ellipse 80% 70% at 28% 20%, rgba(255,255,255,0.16), rgba(255,255,255,0.03) 45%, rgba(255,255,255,0) 72%)"
          }}
        />
        <Layer
          style={{
            ...DISC,
            background:
              "radial-gradient(circle closest-side, rgba(255,255,255,0) 0%, rgba(255,255,255,0.0) 55%, rgba(255,255,255,0.05) 99.5%, rgba(255,255,255,0) 100%)",
              overflow: 'hidden'
          }}
        />
        <Layer
          style={{
            ...DISC,
           boxShadow: '3px 6px 10px #ffffff20 inset'
          }}
        />
        {/*
          The shell's own darkening, just inside the rim. Invisible on a dark
          page — it is black over black — and doing all the work on a light
          one, where the white highlights below have nothing to stand out
          against and the bubble would otherwise read as a bare drop shadow.
        */}
        <Layer
          style={{
            ...DISC,
            background:
              "radial-gradient(circle closest-side, rgba(0,0,0,0) 78%, rgba(0,0,0,0.05) 93%, rgba(0,0,0,0.02) 100%)"
          }}
        />
        <Layer
          style={{
            ...DISC,
            boxShadow:
              "inset 0 6px 12px -7px rgba(255,255,255,0.05), inset 0 -9px 16px -9px rgba(255,255,255,0.1), 0 0 0 1px rgba(0,0,0,0.07)"
          }}
        />
        <Highlight
          style={{
            left: "15%",
            top: "10%",
            width: "38%",
            height: "22%",
            transform: "rotate(-25deg)",
            background:
              "radial-gradient(closest-side, rgba(255,255,255,0.9), rgba(255,255,255,0.3) 55%, rgba(255,255,255,0) 100%)",
              filter: 'blur(10px)'
          }}
        />
       
      </>
    )
  },

  /* ring — two hairlines and nothing else. The restrained one. */
  ring: {
    inset: 9,
    over: (
      <>
        <Layer style={{ ...DISC, border: "1px solid currentColor", opacity: 0.22 }} />
        <Layer inset="5%" style={{ ...DISC, border: "1px solid currentColor", opacity: 0.1 }} />
      </>
    )
  },

  /*
    dotted — evenly spaced dots around the circumference, turning slowly.

    Drawn as one dashed circle with round caps and a near-zero dash length, so
    each dash collapses to a dot. `pathLength="64"` renormalizes the path to 64
    units first, which is what makes the count exact: the dash period is
    literally 1/64th of the circle, so the pattern closes on itself with no
    seam where the last gap would otherwise be short.
  */
  dotted: {
    inset: 10,
    animated: true,
    over: (
      <svg
        aria-hidden="true"
        viewBox="0 0 100 100"
        className="orbkit-w-anim"
        style={{ ...svgLayer, animation: "orbkit-w-spin 48s linear infinite" }}
      >
        <circle
          cx="50"
          cy="50"
          r="47"
          fill="none"
          stroke="currentColor"
          strokeWidth="1.7"
          strokeLinecap="round"
          pathLength={64}
          strokeDasharray="0.0001 0.9999"
          opacity={0.45}
        />
      </svg>
    )
  },

  /*
    ticks — an instrument bezel: a fine minor scale every 6 degrees with a
    longer major tick every 30. Both are one repeating conic gradient masked
    down to an annulus, so the tick count is set by the gradient's period and
    the tick LENGTH by how far in the mask reaches.
  */
  ticks: {
    inset: 12,
    over: (
      <>
        <Layer
          style={{
            ...DISC,
            opacity: 0.32,
            background:
              "repeating-conic-gradient(from -0.5deg, transparent 0deg 0.2deg, currentColor 0.4deg 0.6deg, transparent 0.8deg 6deg)",
            ...masked(
              "radial-gradient(circle closest-side, transparent 88%, #000 90%, #000 97%, transparent 99%)"
            )
          }}
        />
        <Layer
          style={{
            ...DISC,
            opacity: 0.6,
            background:
              "repeating-conic-gradient(from -0.75deg, transparent 0deg 0.25deg, currentColor 0.5deg 1deg, transparent 1.25deg 30deg)",
            ...masked(
              "radial-gradient(circle closest-side, transparent 80%, #000 82%, #000 97%, transparent 99%)"
            )
          }}
        />
        <Layer style={{ ...DISC, border: "1px solid currentColor", opacity: 0.12 }} />
      </>
    )
  },

  /* reticle — viewfinder furniture: corner brackets, cardinal ticks, a track. */
  reticle: {
    inset: 13,
    over: (
      <svg aria-hidden="true" viewBox="0 0 100 100" style={svgLayer}>
        <g fill="none" stroke="currentColor" strokeWidth="1.2" opacity="0.5">
          <path d="M1 13 L1 1 L13 1" />
          <path d="M87 1 L99 1 L99 13" />
          <path d="M99 87 L99 99 L87 99" />
          <path d="M13 99 L1 99 L1 87" />
        </g>
        <g fill="none" stroke="currentColor" strokeWidth="1" opacity="0.38">
          <path d="M50 1 L50 9" />
          <path d="M50 91 L50 99" />
          <path d="M1 50 L9 50" />
          <path d="M91 50 L99 50" />
        </g>
        <circle
          cx="50"
          cy="50"
          r="46"
          fill="none"
          stroke="currentColor"
          strokeWidth="0.7"
          opacity="0.22"
        />
      </svg>
    )
  },

  /*
    grid — a graticule laid over the orb and masked back to the disc, so the
    mesh appears to be etched on the glass in front of it rather than drawn on
    the page behind. The lines are 1px whatever the size; the SPACING is a
    percentage, so the cell count holds from a gallery thumbnail to a
    full-bleed hero.
  */
  grid: {
    inset: 8,
    /*
      The ruling sits UNDER the canvas: graph paper the orb rests on, not a
      mesh laid over its face. Drawn on top it crosshatched the shader — the
      one thing the wrapper is meant to frame. The ring stays over, since it
      only ever meets the transparent margin at the canvas edge.
    */
    under: (
      <Layer
        style={{
          ...DISC,
          opacity: 0.18,
          backgroundImage:
            "repeating-linear-gradient(to right, currentColor 0 1px, transparent 1px 12.5%), repeating-linear-gradient(to bottom, currentColor 0 1px, transparent 1px 12.5%)",
          ...masked("radial-gradient(circle closest-side, #000 86%, rgba(0,0,0,0) 99%)")
        }}
      />
    ),
    over: <Layer style={{ ...DISC, border: "1px solid currentColor", opacity: 0.2 }} />
  },

  /*
    halftone — a print screen over the outer band of the orb. The mask keeps
    the middle clear, so the dots read as the image breaking up toward its
    edge instead of a texture pasted across the whole face.
  */
  halftone: {
    inset: 6,
    over: (
      <Layer
        style={{
          ...DISC,
          opacity: 0.5,
          backgroundImage: "radial-gradient(currentColor 22%, transparent 24%)",
          backgroundSize: "7px 7px",
          ...masked(
            "radial-gradient(circle closest-side, transparent 40%, #000 80%, #000 94%, rgba(0,0,0,0) 100%)"
          )
        }}
      />
    )
  },

  /*
    scanlines — a phosphor tube. Black lines rather than `currentColor`,
    because scanlines are the UNLIT gaps between rows and stay dark whatever
    the page is; the slow bright band rolling down is the vertical hold
    drifting, which is the part that reads as a CRT rather than as stripes.
  */
  scanlines: {
    inset: 0,
    animated: true,
    over: (
      <>
        <Layer
          style={{
            ...DISC,
            backgroundImage:
              "repeating-linear-gradient(to bottom, rgba(0,0,0,0.45) 0 1px, rgba(0,0,0,0) 1px 3px)",
            ...masked("radial-gradient(circle closest-side, #000 84%, rgba(0,0,0,0) 100%)")
          }}
        />
        <span
          aria-hidden="true"
          style={{
            position: "absolute",
            inset: 0,
            borderRadius: "50%",
            overflow: "hidden",
            pointerEvents: "none"
          }}
        >
          <span
            className="orbkit-w-anim"
            style={{
              position: "absolute",
              left: 0,
              right: 0,
              top: 0,
              height: "30%",
              background:
                "linear-gradient(to bottom, rgba(255,255,255,0) 0%, rgba(255,255,255,0.07) 50%, rgba(255,255,255,0) 100%)",
              animation: "orbkit-w-roll 7s linear infinite"
            }}
          />
        </span>
        <Layer style={{ ...DISC, boxShadow: "inset 0 0 40px -8px rgba(0,0,0,0.5)" }} />
      </>
    )
  }
};

export interface ShaderOrbProps {
  /** The orb definition: shader + param schema + state presets. */
  variant: OrbVariant;
  /** Drives the synthesized volume signals. Defaults to `"idle"`. */
  state?: OrbState;
  /** Rendered size in CSS pixels. Ignored when `className` sizes the canvas. */
  size?: number;
  /** Explicit param overrides. Any key present here wins over the state preset. */
  params?: OrbParamValues;
  /** Explicit color overrides, as hex strings. */
  colors?: OrbColorValues;
  /**
   * Per-state parameter targets, overriding the variant's own. Merged KEY BY
   * KEY over what the orb already defines, so `{ thinking: { churn: 1.62 } }`
   * retouches one param of one state and leaves every other param — and the
   * other two states — exactly as the orb ships them.
   *
   * This is the prop form of the variant's `statePresets`, so you can retune
   * an orb's states from the outside without forking its file. Values still
   * glide, so switching states cross-fades into your targets. An explicit
   * `params` value outranks this, the same way it outranks the variant.
   */
  statePresets?: Partial<Record<OrbState, Record<string, number>>>;
  /** The colour counterpart of `statePresets`, merged the same key-by-key way. */
  stateColors?: Partial<Record<OrbState, Record<string, string>>>;
  /**
   * Per-state volume drive, the third member of the same family. Use it to
   * give each state its own energy; use `volumes` below instead when you have
   * a real signal to feed in, such as live mic level.
   */
  stateVolumes?: Partial<Record<OrbState, { input?: number; output?: number }>>;
  /**
   * Overrides the synthesized volume signals for the active state. The engine
   * normally derives these from `state` — a slow breath at idle, a restless
   * wander while thinking, speech-shaped peaks while speaking — and most
   * shaders read them as their reactivity. Setting either channel here pins
   * it instead, which is how the playground lets you dial each state's drive
   * independently. Omit a channel to keep its synthesized motion.
   */
  volumes?: { input?: number; output?: number };
  /** Freeze the animation on the current frame. */
  paused?: boolean;
  /**
   * Stop rendering while the orb is scrolled out of view. Defaults to `true` —
   * a page full of orbs would otherwise run a WebGL loop per card.
   */
  pauseOffscreen?: boolean;
  /** Device-pixel-ratio ceiling. Defaults to `2`. */
  maxDpr?: number;
  /**
   * Decoration drawn around the orb — a glass bubble, a dotted bezel, a
   * viewfinder. Defaults to `"none"`, which renders the bare canvas exactly as
   * it always has, with no extra element in the tree.
   *
   * A wrapper never changes the orb's footprint: `size` stays the outer
   * diameter and the canvas is inset inside it, so switching one on reflows
   * nothing around it.
   */
  wrapper?: OrbWrapper;
  /**
   * The colour a wrapper draws its lines and dots in. Defaults to
   * `currentColor` — the inherited text colour — which is what makes the
   * bezels legible on a light and a dark page without being told which one
   * they are on. `glass` ignores it: glass has no colour of its own.
   */
  wrapperColor?: string;
  /** Applied to the outermost element — the wrapper when there is one. */
  className?: string;
  /** Merged onto the outermost element's style. */
  style?: CSSProperties;
  /** Accessible label. When omitted the orb is hidden from assistive tech. */
  ariaLabel?: string;
}

export function ShaderOrb({
  variant,
  state = "idle",
  size,
  params,
  colors,
  statePresets,
  stateColors,
  stateVolumes,
  volumes,
  paused = false,
  pauseOffscreen = true,
  maxDpr = 2,
  wrapper = "none",
  wrapperColor,
  className,
  style,
  ariaLabel
}: ShaderOrbProps) {
  const canvasRef = useRef<HTMLCanvasElement>(null);
  const spec = wrapper === "none" ? undefined : WRAPPER_SPECS[wrapper];
  const wrapped = spec !== undefined;

  // Live refs: the render loop reads these every frame, so changing a param
  // never re-runs the GL setup effect (which would drop the context). Synced in
  // an effect rather than during render — a ref write during render is unsafe
  // under concurrent rendering, and the loop picks the new value up on the very
  // next frame anyway.
  /*
    Whether the canvas has drawn a frame yet, tracked per variant because a
    variant swap mounts a brand new canvas (see the `key` below).

    A mounted-but-never-drawn canvas is at the browser's mercy: rather than
    the transparent rectangle you would expect, a page that mounts a dozen at
    once gets white boxes — and in some browsers a broken-image placeholder —
    for as long as the compositor has nothing to raster. That window is not
    small here: every orb compiles a full fragment shader synchronously in its
    own mount effect, so on the gallery grid the first canvases sit empty
    while the last ones are still compiling. Holding each canvas invisible
    until its own first frame lands is what makes the grid fade in cleanly
    instead of flashing. One state change per orb, once, on mount.
  */
  const [paintedKey, setPaintedKey] = useState<string | null>(null);
  const painted = paintedKey === variant.key;

  const stateRef = useRef<OrbState>(state);
  const paramsRef = useRef<OrbParamValues | undefined>(params);
  const colorsRef = useRef<OrbColorValues | undefined>(colors);
  const statePresetsRef = useRef(statePresets);
  const stateColorsRef = useRef(stateColors);
  const stateVolumesRef = useRef(stateVolumes);
  const volumesRef = useRef(volumes);
  const pausedRef = useRef(paused);

  useEffect(() => {
    stateRef.current = state;
    paramsRef.current = params;
    colorsRef.current = colors;
    statePresetsRef.current = statePresets;
    stateColorsRef.current = stateColors;
    stateVolumesRef.current = stateVolumes;
    volumesRef.current = volumes;
    pausedRef.current = paused;
  }, [state, params, colors, statePresets, stateColors, stateVolumes, volumes, paused]);

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

    const gl = canvas.getContext("webgl", {
      alpha: true,
      // No MSAA: the geometry is a single full-screen triangle, so there are no
      // primitive edges to antialias — softness comes from the shaders. Leaving
      // it on costs the multisample buffers plus a resolve every frame.
      antialias: false,
      premultipliedAlpha: true
    });
    if (!gl) return;

    const loseExt = gl.getExtension("WEBGL_lose_context");

    /*
      A "generation" is everything tied to a live context: program, buffers,
      observers, render loop. Browsers cap live WebGL contexts per page and
      evict the oldest past the cap, and an evicted orb's canvas stays blank
      forever unless the app rebuilds — so generations tear down and rebuild on
      the lost/restored events instead of assuming the context is immortal.
    */
    let announcedPaint = false;

    const startGeneration = (): (() => void) => {
      if (gl.isContextLost()) return () => {};
      // Every generation announces its own first frame: a context that was
      // lost and restored has an empty drawing buffer and is hidden again
      // (below), so it has to earn its reveal back.
      announcedPaint = false;

      const vs = compile(gl, gl.VERTEX_SHADER, VERT);
      const fs = compile(
        gl,
        gl.FRAGMENT_SHADER,
        ORB_GLSL_HELPERS + paramUniformDecls(variant) + variant.frag
      );
      const releaseShaders = () => {
        if (vs) gl.deleteShader(vs);
        if (fs) gl.deleteShader(fs);
      };
      if (!vs || !fs) return releaseShaders;

      const prog = gl.createProgram();
      if (!prog) return releaseShaders;
      gl.attachShader(prog, vs);
      gl.attachShader(prog, fs);
      gl.linkProgram(prog);
      if (!gl.getProgramParameter(prog, gl.LINK_STATUS)) {
        console.error("[orbkit] program link error:", gl.getProgramInfoLog(prog));
        gl.deleteProgram(prog);
        return releaseShaders;
      }
      gl.useProgram(prog);

      const buf = gl.createBuffer();
      gl.bindBuffer(gl.ARRAY_BUFFER, buf);
      gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([-1, -1, 3, -1, -1, 3]), gl.STATIC_DRAW);
      const aPos = gl.getAttribLocation(prog, "aPos");
      gl.enableVertexAttribArray(aPos);
      gl.vertexAttribPointer(aPos, 2, gl.FLOAT, false, 0, 0);

      gl.enable(gl.BLEND);
      gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA);

      const uRes = gl.getUniformLocation(prog, "uRes");
      const uTime = gl.getUniformLocation(prog, "uTime");
      const uAnim = gl.getUniformLocation(prog, "uAnim");
      const uInput = gl.getUniformLocation(prog, "uInput");
      const uOutput = gl.getUniformLocation(prog, "uOutput");

      const paramLocs = variant.params.map((p) => ({
        def: p,
        loc: gl.getUniformLocation(prog, `uP_${p.key}`)
      }));
      const colorLocs = variant.colors.map((c) => ({
        def: c,
        loc: gl.getUniformLocation(prog, `uC_${c.key}`)
      }));

      /* --- sizing: track the element box, not a one-shot measurement ------- */
      // Backing-store scale, stepped down by the adaptive-resolution logic in
      // the loop when the GPU can't hold frame rate. CSS size never changes —
      // the browser upscales, which these soft shaders absorb gracefully.
      let resScale = 1;
      const resize = () => {
        const dpr = Math.min(window.devicePixelRatio || 1, maxDpr) * resScale;
        const w = Math.max(1, Math.round(canvas.clientWidth * dpr));
        const h = Math.max(1, Math.round(canvas.clientHeight * dpr));
        if (canvas.width !== w || canvas.height !== h) {
          canvas.width = w;
          canvas.height = h;
          gl.viewport(0, 0, w, h);
        }
        /*
          Uploaded UNCONDITIONALLY, outside the size guard. A rebuilt
          generation (React strict-mode remount, a restored context) links a
          fresh program whose uRes starts at zero — and the canvas usually
          already holds the right backing size, so an upload gated behind
          the resize never ran. With uRes = 0, orbUV() divides by zero and
          every fragment lands transparent: a healthy context, a bound
          program, and a permanently blank orb.
        */
        gl.uniform2f(uRes, w, h);
      };
      resize();

      const resizeObserver =
        typeof ResizeObserver !== "undefined" ? new ResizeObserver(resize) : null;
      resizeObserver?.observe(canvas);

      /* --- visibility: don't burn a render loop on an offscreen orb -------- */
      let visible = !pauseOffscreen;
      const intersectionObserver =
        pauseOffscreen && typeof IntersectionObserver !== "undefined"
          ? new IntersectionObserver(
              (entries) => {
                visible = Boolean(entries[0]?.isIntersecting);
                if (visible) {
                  last = performance.now() / 1000;
                }
              },
              { rootMargin: "150px 0px", threshold: 0 }
            )
          : null;
      if (intersectionObserver) {
        intersectionObserver.observe(canvas);
      } else {
        visible = true;
      }

      const reduceMotion =
        typeof window.matchMedia === "function" &&
        window.matchMedia("(prefers-reduced-motion: reduce)").matches;

      /* --- driver state ---------------------------------------------------- */
      let tSec = 0;
      // random phase so two orbs on the same page never look synchronized
      let anim = Math.random() * 100;
      let speed = 0.1;
      const cur = { in: 0, out: 0.3 };
      const presets = variant.statePresets;
      const paramCur: Record<string, number> = {};
      const paramVel: Record<string, number> = {};
      const paramClocks: Record<string, number> = {};
      const colorCur: Record<string, [number, number, number]> = {};
      const colorVel: Record<string, [number, number, number]> = {};
      let speedVel = 0;
      const [initialIn, initialOut] = targetVolumes(stateRef.current, 0);
      cur.in = initialIn;
      cur.out = initialOut;
      let last = performance.now() / 1000;
      let raf = 0;
      // smoothed frame time for the adaptive-resolution check
      let frameEma = 1 / 60;

      const uploadAndDraw = (dt: number, snap = false) => {
        // Synthesized from the state, unless a channel is pinned via
        // `volumes`. Pinned values still glide on the same easing, so dialing
        // one in the playground cross-fades rather than jumping.
        const [tin, tout] = targetVolumes(stateRef.current, tSec);
        // Same order as params and colours: direct prop, then the per-state
        // map, then the engine's own synthesis.
        const liveVolumes = volumesRef.current;
        const stateVolume = stateVolumesRef.current?.[stateRef.current];
        const targetIn = liveVolumes?.input ?? stateVolume?.input ?? tin;
        const targetOut = liveVolumes?.output ?? stateVolume?.output ?? tout;
        const kVol = 1 - Math.exp(-dt * 12);
        cur.in += (targetIn - cur.in) * kVol;
        cur.out += (targetOut - cur.out) * kVol;

        /*
          Flow speed follows the output volume. It multiplies every integrated
          clock's increment, so it is a RATE: easing it quickly makes the orb
          visibly lurch — a state change would spin the orb up hard before the
          params had finished gliding.

          It is therefore eased on the same constant as the params below, so a
          state change ramps its motion over the same half second that its look
          takes to cross-fade. The steady-state values are unchanged, so a
          speaking orb still flows faster than an idle one; only the transition
          into that rate is gradual.
        */
        const targetSpeed = 0.1 + (1 - Math.pow(cur.out - 1, 2)) * 0.9;
        if (snap) {
          speed = targetSpeed;
          speedVel = 0;
        } else {
          springStep(speed, speedVel, targetSpeed, dt, PARAM_EASE);
          speed = springOut.x;
          speedVel = springOut.v;
        }
        anim += dt * speed;

        gl.uniform1f(uTime, tSec * 0.5);
        gl.uniform1f(uAnim, anim);
        gl.uniform1f(uInput, cur.in);
        gl.uniform1f(uOutput, cur.out);

        // Resolution order per param: explicit `params` → `statePresets`
        // prop → the variant's own preset → schema default. The two middle
        // steps are per-key, so overriding one param of one state leaves the
        // rest of that state alone. Values glide rather than snap.
        const liveParams = paramsRef.current;
        const statePreset = presets?.[stateRef.current];
        const overridePreset = statePresetsRef.current?.[stateRef.current];

        for (const { def, loc } of paramLocs) {
          const explicit = liveParams?.[def.key];
          const target =
            typeof explicit === "number"
              ? explicit
              : (overridePreset?.[def.key] ?? statePreset?.[def.key] ?? def.default);
          const curVal = paramCur[def.key] ?? target;
          let next: number;
          if (snap) {
            next = target;
            paramVel[def.key] = 0;
          } else {
            springStep(curVal, paramVel[def.key] ?? 0, target, dt, PARAM_EASE);
            next = springOut.x;
            paramVel[def.key] = springOut.v;
          }
          paramCur[def.key] = next;

          if (def.integrate) {
            const clock =
              (paramClocks[def.key] ?? (paramClocks[def.key] = Math.random() * 100)) +
              dt * speed * next;
            paramClocks[def.key] = clock;
            gl.uniform1f(loc, clock);
          } else {
            gl.uniform1f(loc, next);
          }
        }

        // Same resolution order and same easing as params, so a state change
        // cross-fades the palette instead of cutting to it.
        const liveColors = colorsRef.current;
        const stateColor = variant.stateColors?.[stateRef.current];
        const overrideColor = stateColorsRef.current?.[stateRef.current];
        for (const { def, loc } of colorLocs) {
          const target = hexToRgb(
            liveColors?.[def.key] ??
              overrideColor?.[def.key] ??
              stateColor?.[def.key] ??
              def.default
          );
          const curCol = (colorCur[def.key] ??= [...target] as [number, number, number]);
          const velCol = (colorVel[def.key] ??= [0, 0, 0]);
          for (let i = 0; i < 3; i++) {
            if (snap) {
              curCol[i] = target[i];
              velCol[i] = 0;
            } else {
              springStep(curCol[i], velCol[i], target[i], dt, PARAM_EASE);
              curCol[i] = springOut.x;
              velCol[i] = springOut.v;
            }
          }
          gl.uniform3f(loc, curCol[0], curCol[1], curCol[2]);
        }

        gl.clearColor(0, 0, 0, 0);
        gl.clear(gl.COLOR_BUFFER_BIT);
        gl.drawArrays(gl.TRIANGLES, 0, 3);
        // Deferred a microtask: the first of these draws runs synchronously
        // inside this effect, and a sync setState there trips the compiler
        // lint. A microtask still resolves before the browser paints, so the
        // reveal is not delayed by a frame.
        if (!announcedPaint) {
          announcedPaint = true;
          queueMicrotask(() => {
            /*
              Re-check: the context can be evicted between this draw and the
              microtask, and mounting one more orb anywhere on the page is
              enough to do it — opening the details drawer over a full gallery
              is exactly that. Revealing on the strength of a frame that has
              already been thrown away puts a dead canvas on screen, which is
              what the browser draws its broken-canvas placeholder over. The
              generation that follows the restore announces again.
            */
            if (gl.isContextLost()) return;
            setPaintedKey(variant.key);
          });
        }
      };

      const releaseGL = () => {
        resizeObserver?.disconnect();
        intersectionObserver?.disconnect();
        gl.deleteProgram(prog);
        gl.deleteShader(vs);
        gl.deleteShader(fs);
        gl.deleteBuffer(buf);
      };

      if (reduceMotion) {
        // One representative frame, then stop — snapped straight onto the
        // state's targets, since a spring would only be part-way there.
        tSec = 1;
        uploadAndDraw(1, true);
        return releaseGL;
      }

      const loop = () => {
        raf = requestAnimationFrame(loop);
        const now = performance.now() / 1000;
        const dt = Math.min(now - last, 0.05);
        last = now;
        if (!visible || pausedRef.current) return;
        tSec += dt;

        /*
          Adaptive resolution. When the smoothed frame time sits above ~30fps,
          the GPU is drowning in fragment work (these shaders are pure fill
          cost), so step the backing store down 20% and re-measure. Steps only
          go down — never back up — so the resolution can't oscillate. The
          warm-up guard keeps page-load jank (hydration, first compiles) from
          triggering a downgrade the GPU never asked for.
        */
        /*
          Only unstalled frames are evidence about GPU fill cost. `dt` above is
          clamped at 0.05, so a frame that hits the clamp is the main thread
          having been blocked — a slider drag re-rendering React, a GC pause, a
          tab regaining focus — and feeding those in made UI jank look
          identical to a drowning GPU.
        */
        if (dt < 0.05) frameEma += (dt - frameEma) * 0.08;

        if (tSec > 1.5 && frameEma > 1 / 34 && resScale > 0.5) {
          resScale = Math.max(0.5, resScale * 0.8);
          frameEma = 1 / 60; // require fresh evidence before the next step
          resize();
        } else if (tSec > 1.5 && frameEma < 1 / 55 && resScale < 1) {
          /*
            And step back up once frames are comfortably fast again. This used
            to be one-way, on the reasoning that it could not then oscillate —
            but that also meant one transient stall permanently halved the
            orb's resolution, and at half resolution a high-frequency shader
            aliases into shimmer that reads as the shader itself misbehaving.
            The gap between the two thresholds (29ms down, 18ms up) is the
            hysteresis that stops it hunting.
          */
          resScale = Math.min(1, resScale / 0.8);
          frameEma = 1 / 60;
          resize();
        }

        uploadAndDraw(dt);
      };
      /*
        First frame synchronously, before entering the rAF loop. rAF does not
        run at all in hidden documents (background tabs, embedded previews),
        so a freshly mounted orb would otherwise sit fully transparent until
        the page next becomes visible — a grid of mounted, healthy, blank
        canvases. The sync frame guarantees every mount paints: background
        documents get a static frame, visible ones start animating over it.
        dt = 1 lands the param glide on its targets, as in the reduce-motion
        frame above.
      */
      uploadAndDraw(1);
      loop();

      return () => {
        cancelAnimationFrame(raf);
        releaseGL();
      };
    };

    /*
      Wire the canvas's lifecycle controller. The listeners are attached ONCE
      per canvas element and never removed, deliberately: the lost event must
      be canceled even while no orb is mounted on the canvas — an uncanceled
      webglcontextlost marks the context permanently unrestorable, and the
      router can show this exact canvas again later. Whether a loss leads to
      a revival is decided by `desired`, not by listener presence.
    */
    /*
      A canvas whose context has gone is not simply blank: Chrome paints its
      broken-image placeholder over the element's whole box — the white square
      you see on a reloaded grid. Hide the canvas the moment the context is
      lost, and let the generation that follows a restore reveal it again.
    */
    const hideNow = () => {
      /*
        Both, deliberately. The style write lands in this tick — the browser
        paints its placeholder over a dead canvas immediately, and a busy main
        thread can hold a React update for several frames. The state change is
        what keeps React's own view in sync, so the reveal that follows a
        restore clears the inline value again rather than fighting it.
      */
      canvas.style.opacity = "0";
      setPaintedKey(null);
    };

    const onContextLostHide = () => hideNow();
    canvas.addEventListener("webglcontextlost", onContextLostHide);

    /*
      Hand the context back before the next document asks for one.

      A hard reload never runs this effect's cleanup — the document is
      discarded whole — so the outgoing page's contexts are still alive while
      the incoming page allocates its own. On a grid of orbs that puts the
      live count past the browser's ~16 cap, and the ones it evicts are
      exactly the canvases that come back as placeholders until the restore
      path catches them. `persisted` is a bfcache suspend, where the page is
      shown again untouched and must keep everything it holds.
    */
    const onPageHide = (event: PageTransitionEvent) => {
      if (event.persisted) return;
      try {
        loseExt?.loseContext();
      } catch {
        // Already released — nothing to hand back.
      }
    };
    window.addEventListener("pagehide", onPageHide);

    let ctl = canvasControllers.get(canvas);
    if (!ctl) {
      const created: CanvasContextController = { desired: false, start: null, stopGen: null };
      canvas.addEventListener("webglcontextlost", (event) => {
        event.preventDefault(); // always cancel — keeps the context restorable
        created.stopGen?.();
        created.stopGen = null;
        if (created.desired) {
          /*
            Ask for the context back — but in a LATER task. The browser only
            marks a loss as restorable once the lost event's dispatch has
            completed and it has seen the canceled flag, so a restoreContext()
            issued during dispatch (or before it, as the mount path may) is
            silently refused. This is the path a synchronous cleanup+setup
            pair hits — React re-running the effect on the same canvas loses
            the context and wants it right back. For losses we didn't cause
            (eviction, GPU reset) the call may refuse; the canceled event then
            lets the browser restore on its own schedule.
          */
          setTimeout(() => {
            if (!created.desired) return;
            try {
              loseExt?.restoreContext();
            } catch {
              // Natural loss — restoration is the browser's call now.
            }
          }, 0);
        }
      });
      canvas.addEventListener("webglcontextrestored", () => {
        if (created.desired && created.start) {
          created.stopGen = created.start();
        }
      });
      canvasControllers.set(canvas, created);
      ctl = created;
    }
    const controller = ctl;

    controller.desired = true;
    controller.start = startGeneration;
    if (gl.isContextLost()) {
      // A previous run on this canvas released the context (effect re-run, or
      // the router re-showing a kept-alive page). If the lost event already
      // dispatched this request is honored now; if it is still queued, the
      // lost handler above re-requests it on dispatch.
      try {
        loseExt?.restoreContext();
      } catch {
        // No restore path — the orb stays blank rather than throwing.
      }
    } else {
      controller.stopGen = startGeneration();
    }

    return () => {
      /*
        Hide BEFORE tearing the context down.

        This cleanup releases the context deliberately, and the lost event it
        provokes is dispatched asynchronously — by which point the listener
        below is unhooked and the replacement effect has not drawn yet. That
        leaves a revealed canvas with a dead context, which is precisely what
        the browser paints its broken-canvas placeholder over. The window is
        not rare: any prop change in this effect's deps re-runs it, so it hits
        every time the drawer switches preview example (maxDpr differs on the
        layout one) or a wrapper is toggled.
      */
      hideNow();
      canvas.removeEventListener("webglcontextlost", onContextLostHide);
      window.removeEventListener("pagehide", onPageHide);
      controller.desired = false;
      controller.start = null;
      controller.stopGen?.();
      controller.stopGen = null;
      /*
        Release the context NOW instead of when the canvas is garbage
        collected. Browsers cap live WebGL contexts per page (~8–16) and evict
        the oldest when the cap is hit — client-side navigation that unmounts
        and remounts a page of orbs otherwise piles up zombie contexts until
        freshly mounted orbs get evicted and render blank.
      */
      try {
        loseExt?.loseContext();
      } catch {
        // Context already lost — nothing to release.
      }
    };
    /*
      `wrapped` is in here because turning a wrapper on or off moves the canvas
      from being this component's root element to being a child of the wrapper
      div — React drops the old element and mounts a new one, and the GL
      context, its observers and its render loop all belong to the old one. Any
      other prop leaves the element alone, INCLUDING a swap between two
      wrappers: the canvas keeps its slot among the decoration layers, so
      glass -> ring reuses the context instead of rebuilding it.
    */
  }, [variant, pauseOffscreen, maxDpr, wrapped]);

  const sizeStyle: CSSProperties =
    size === undefined ? {} : { width: size, height: size };

  /*
    Spread ahead of the caller's `style`, so an orb that wants to own its own
    opacity still can — it simply opts out of the reveal.

    A hard flip, deliberately: no transition, no fade. A hidden document
    (background tab, embedded preview) does not advance CSS transitions, so a
    faded reveal left orbs pinned at zero in exactly the case the synchronous
    first frame above exists to serve — mounted, healthy, and invisible. The
    cut is not a pop either way, since it happens on the frame the orb first
    has something to show.
  */
  const revealStyle: CSSProperties = painted ? {} : { opacity: 0 };

  const canvas = (
    <canvas
      // A lost WebGL context can't be reused, so each variant gets a fresh canvas.
      key={variant.key}
      ref={canvasRef}
      className={spec ? undefined : className}
      style={
        spec
          ? {
              display: "block",
              position: "absolute",
              left: `${spec.inset}%`,
              top: `${spec.inset}%`,
              width: `${100 - 2 * spec.inset}%`,
              height: `${100 - 2 * spec.inset}%`,
              ...revealStyle,
              ...(spec.mask ? masked(spec.mask) : {})
            }
          : { display: "block", ...sizeStyle, ...revealStyle, ...style }
      }
      role={!spec && ariaLabel ? "img" : undefined}
      aria-label={spec ? undefined : ariaLabel}
      aria-hidden={!spec && ariaLabel ? undefined : true}
    />
  );

  if (!spec) return canvas;

  /*
    Wrapped: the box becomes the orb's footprint and the canvas is absolutely
    positioned inside it. `under`, the canvas and `over` are all positioned
    with an auto z-index, so they paint in DOM order — decoration behind the
    orb, then the orb, then decoration in front of it.

    `aspectRatio` is the fallback for the sizeless case: `size` is optional
    (callers may size the orb with a class instead), and an absolutely
    positioned canvas contributes nothing to its parent's height, so without
    it a class that only sets a width would collapse the box to zero.
  */
  return (
    <div
      className={className}
      style={{
        position: "relative",
        aspectRatio: "1 / 1",
        ...(spec.shadow ? { borderRadius: "50%", boxShadow: spec.shadow } : {}),
        ...sizeStyle,
        ...(wrapperColor ? { color: wrapperColor } : {}),
        ...style
      }}
      role={ariaLabel ? "img" : undefined}
      aria-label={ariaLabel}
      aria-hidden={ariaLabel ? undefined : true}
    >
      {spec.animated ? (
        <style
          href={WRAPPER_STYLE_HREF}
          precedence="default"
          dangerouslySetInnerHTML={{ __html: WRAPPER_CSS }}
        />
      ) : null}
      {spec.under}
      {canvas}
      {spec.over}
    </div>
  );
}

3. components/ui/shdr-01.tsx

/*
 * Deliberately not a `"use client"` module — see the note in `shdr-11.tsx`.
 *
 * Note for editors: the shader lives in a template literal, so its comments
 * must not contain backticks.
 */
import { ShaderOrb, type OrbVariant, type ShaderOrbProps } from "@/components/ui/orbkit-core";

/* ----------------------------------------------------------------------------
   SHDR-01 — a cut-glass orb whose own shell does the dispersing.

   Ported from a golfed twigl listing:

     for(float i,z,d,s;i++<2e1;o+=(cos(s-z+vec4(0,1,8,0))+1.)/d){
       vec3 p=z*normalize(FC.rgb*2.-r.xyy),a=p;
       for(d=2.;d++<7.;)a-=sin(a*d+t+i).yzx/d;
       z+=d=abs(2.-max(p=abs(p),p.y).x)+abs(cos(s=a.z+a.y-t))/7.;}
     o=tanh(o/2e2);

   What it actually is, decoded:

   - abs(2. - max(|x|,|y|)) is an infinite SQUARE TUBE along z, half-width 2,
     and the camera sits at the origin inside it — an endless glowing tunnel.
   - abs(cos(a.z + a.y - t))/7. adds translucent SHEETS wherever the warped
     field crosses cos zero; the march slows there, and since each step is
     weighted 1/d, slow means bright.
   - cos(s - z + vec4(0,1,8,0)) reads one palette phase per channel — that
     per-channel offset is the whole "dispersion": the sheets split into
     rainbow striations, coupled to depth through the -z term.

   Port decisions, each one a documented trap in the README:

   - A tunnel is an open shape and reads as a portal, not an orb — and a
     prism bounded inside a ball reads as an object in a jar. So the ORB IS
     THE OBJECT: the square tube becomes a sphere-radius shell evaluated on
     the turbulence-warped point, which makes the ball's own surface the
     thing that disperses. Rays grazing the limb ride the shell for a long
     arc — many small-d, high-weight steps — so the rim glows the way glass
     does, for free. The README's closed-shell trap (accumulation on a shell
     is uniform) does not bite because the detail comes from the warped
     sheet field, not from the accumulation itself.
   - The silhouette is cut ANALYTICALLY in main() from each ray's closest
     approach to the sphere, so the edge is exact and tunably sharp rather
     than a fuzzy envelope fade.
   - The golfed listing relies on i, z, d, s starting at zero. Uninitialised
     locals are UNDEFINED in GLSL ES 1.0 — everything is explicit below.
   - The clock only ever enters as an ADDITIVE PHASE (inside sin/cos), never
     scaling a per-step quantity, so the unbounded integrated clock is safe
     here — no per-step degeneration to fix, unlike the sweep-angle bug this
     repo hit before.
   - The prism is oriented with real orthonormal rotations: a static tilt and
     a spin driven by its own integrated clock, so changing the spin rate
     never jumps the phase. No golfed cos-phase "rotation" matrices — those
     breathe scale and squash the silhouette.
---------------------------------------------------------------------------- */

/*
 * Step counts are `#define`s: ES 1.0 requires constant loop bounds.
 * TURB is 5 to match the original's five octaves (d = 3..7).
 */
const DISPERSION_FRAG = `
#define STEPS 60
#define TURB 5
#define AA 1

// Volume-reactive values, resolved once per fragment in main().
float dispersionTurb;
float dispersionExposure;

mat2 rot2(float a) {
  float c = cos(a);
  float s = sin(a);
  return mat2(c, -s, s, c);
}

vec3 dispersionRender(vec2 fragCoord) {
  float animTime = uP_speed; // integrated clock: turbulence + sheet drift
  float spinAng = uP_spin;   // integrated clock: prism precession

  vec2 uv = (2.0 * fragCoord - uRes) / min(uRes.x, uRes.y);
  vec3 ro = vec3(0.0, 0.0, uP_camDist);
  vec3 rd = normalize(vec3(uv, -uP_focal));

  vec3 acc = vec3(0.0);

  // transmittance carried front-to-back, as in shdr-21 and the README's
  // diffusion note — near sheets veil far ones, which is where the depth
  // read comes from
  float T = 1.0;

  /*
    March only the span that can contribute. The tube is infinite, so a ray
    grazing its wall far in FRONT of the ball would otherwise stall there —
    small d, step after step — and exhaust STEPS before ever reaching the
    envelope, leaving a dark notch across the orb. Start at the envelope's
    near edge and break past its far edge; all 60 steps land where the
    envelope is non-zero.
  */
  float z = max(uP_camDist - uP_envRadius * 1.3, 0.0);
  float zEnd = uP_camDist + uP_envRadius * 1.3;

  for (int it = 0; it < STEPS; it++) {
    vec3 p = ro + rd * z;

    // orient the prism: static tilt about x, then precession about y from
    // the spin clock. Real rotations — see the header note.
    vec3 q = p;
    q.xz = rot2(spinAng) * q.xz;
    q.yz = rot2(uP_tilt) * q.yz;

    // turbulence in the prism's rotating frame; the +float(it) offset is the
    // original's +i, decorrelating octaves per step for a smoky depth
    vec3 a = q;
    for (int j = 0; j < TURB; j++) {
      float dj = float(j) + 3.0;
      a -= dispersionTurb * sin(a * dj + animTime + float(it)).yzx / dj;
    }

    /*
      The orb's own shell, in place of the original's square tube
      (golfed there as max(p=abs(p),p.y).x — just max(|x|,|y|)). Evaluated
      on the WARPED point, so the turbulence shimmers the surface itself
      like an oil film; at turb 0 it is a perfect glass shell. The cos
      sheets fill the interior with the dispersive volume.
    */
    float wall = abs(length(a) - uP_envRadius);
    float s = a.z + a.y - animTime;
    float d = max(wall + abs(cos(s)) / uP_sheets, 1e-4);

    /*
      Per-channel palette: one cosine phase per channel, scaled by uP_disperse
      (0 collapses to monochrome breathing, 1 is the original rainbow). The
      -z term couples depth into the phase, which is what turns the sheets
      into striations; uP_stria scales it.

      The CLAMP is load-bearing, same as the other accumulators here: 1/d
      spikes where a ray grazes the wall exactly where a sheet sits, and one
      unclamped sample would own the whole 60-step sum at some phases.
    */
    vec3 w = (cos(s - z * uP_stria + vec3(0.0, 1.0, 8.0) * uP_disperse) + 1.0) / d;
    w = min(w, vec3(uP_stepClamp));

    /*
      Envelope: bounds the sheet glow (the cos field lives EVERYWHERE in
      space, not just inside the ball) and adds the uP_fill floor that
      guarantees a body. The outer bound sits 12% PAST the radius on
      purpose: the shell IS the radius now, the silhouette is cut
      analytically in main(), and a hard cut only reads as a sharp edge if
      there is still emission left at the boundary to cut. envCore is where
      the plateau saturates — 1 keeps the shell at full strength, lower
      values pull the brightness into the core.
    */
    float env = smoothstep(uP_envRadius * 1.12, uP_envRadius * uP_envCore, length(p));
    w = (w + uP_fill) * env;

    acc += T * w;
    T *= exp(-dot(w, vec3(0.299, 0.587, 0.114)) * uP_scatter);

    z += d;
    if (T < 0.004 || z > zEnd) break;
  }

  return acc;
}

void main() {
  dispersionTurb = uP_turb * (1.0 + 0.5 * uInput);
  dispersionExposure = uP_exposure * (1.0 - 0.35 * uOutput);

  vec3 acc = vec3(0.0);
#if AA > 1
  for (int mx = 0; mx < AA; mx++) {
    for (int my = 0; my < AA; my++) {
      vec2 offset = vec2(float(mx), float(my)) / float(AA) - 0.5;
      acc += dispersionRender(gl_FragCoord.xy + offset);
    }
  }
  acc /= float(AA * AA);
#else
  acc = dispersionRender(gl_FragCoord.xy);
#endif

  // tanh tone map, as in the original but per channel and with a tunable
  // knee — the envelope and transmittance change the accumulator's scale
  // completely, so the golfed /2e2 constant means nothing here
  vec3 col = tanh3(acc / max(dispersionExposure, 1.0));
  col = pow(clamp(col, 0.0, 1.0), vec3(uP_contrast));

  // saturation about luminance, then the tint
  float lum = dot(col, vec3(0.299, 0.587, 0.114));
  col = mix(vec3(lum), col, uP_saturation);
  col *= uC_tint;

  // alpha from the brightest channel, not luminance — a saturated violet
  // fringe has low luminance but must not go transparent
  float peak = max(col.r, max(col.g, col.b));
  float a = clamp(peak * uP_alphaGain, 0.0, 1.0);

  /*
    Analytic silhouette: the perpendicular distance from the sphere's centre
    to this pixel's ray, against the shell radius. Exact — not a fade of the
    accumulated glow — which is what makes the edge read as cut glass.
    uP_edge trades the transition band: 1 is a couple of pixels, 0 falls
    back to a soft feather. Colour AND alpha, as always.
  */
  vec3 mrd = normalize(vec3(orbUV(), -uP_focal));
  float closest = length(cross(vec3(0.0, 0.0, uP_camDist), mrd));
  float band = mix(0.35, 0.012, clamp(uP_edge, 0.0, 1.0));
  float mask = 1.0 - smoothstep(uP_envRadius * (1.0 - band), uP_envRadius * 1.005, closest);
  col *= mask;
  a *= mask;

  // Fade colour as well as alpha — with premultiplied output, fading only
  // alpha leaves the pixel emitting at full brightness up to the cutoff,
  // which reads as a hard rim. With the analytic mask doing the real work
  // this is only a safety taper at the frame boundary.
  float r2d = length(orbUV());
  float fade = 1.0 - smoothstep(uP_edgeFade, 1.0, r2d);
  col *= fade;
  a *= fade;

  // Emitted light, so rgb is already premultiplied — do NOT scale by alpha
  // again (see the same note in shdr-31).
  gl_FragColor = vec4(col, a);
}
`;

export const shdr01Orb: OrbVariant = {
  key: "shdr-01",
  label: "SHDR-01",
  note: "cut-glass orb with a dispersive, turbulent interior",
  frag: DISPERSION_FRAG,
  params: [
    { key: "speed", label: "Anim speed", min: 0.015, max: 10, step: 0.05, default: 0.5, integrate: true },
    { key: "spin", label: "Spin rate", min: 0, max: 5, step: 0.03, default: 0.25, integrate: true },
    { key: "camDist", label: "Camera distance", min: 1, max: 50, step: 0.3, default: 7 },
    { key: "focal", label: "Lens", min: 0.15, max: 15, step: 0.1, default: 2.25 },
    { key: "tilt", label: "Field tilt", min: 0, max: 4, step: 0.02, default: 0.5 },
    { key: "turb", label: "Turbulence", min: 0, max: 5, step: 0.03, default: 0.3 },
    { key: "sheets", label: "Sheet density", min: 1, max: 60, step: 0.5, default: 7 },
    { key: "disperse", label: "Dispersion", min: 0, max: 5, step: 0.03, default: 1 },
    { key: "stria", label: "Striation depth", min: 0, max: 10, step: 0.05, default: 1 },
    { key: "envRadius", label: "Envelope radius", min: 0.15, max: 15, step: 0.1, default: 2.6 },
    { key: "envCore", label: "Envelope core", min: 0.3, max: 1.02, step: 0.01, default: 1 },
    { key: "fill", label: "Body fill", min: 0, max: 100, step: 0.3, default: 1.5 },
    { key: "stepClamp", label: "Step clamp", min: 0.3, max: 300, step: 1.5, default: 20 },
    { key: "scatter", label: "Diffusion", min: 0, max: 0.5, step: 0.003, default: 0.02 },
    { key: "exposure", label: "Exposure", min: 1.5, max: 1500, step: 10, default: 60 },
    { key: "contrast", label: "Contrast", min: 0.15, max: 15, step: 0.1, default: 1 },
    { key: "saturation", label: "Saturation", min: 0, max: 4, step: 0.02, default: 1 },
    { key: "alphaGain", label: "Alpha gain", min: 0.05, max: 15, step: 0.1, default: 2 },
    { key: "edge", label: "Edge sharpness", min: 0, max: 1, step: 0.01, default: 1 },
    { key: "edgeFade", label: "Halo falloff", min: 0.1, max: 3, step: 0.015, default: 0.98 }
  ],
  colors: [{ key: "tint", label: "Tint", default: "#ffffff" }],
  statePresets: {
    idle: {
      speed: 0.5,
      spin: 0.25,
      turb: 0.3,
      disperse: 1,
      sheets: 7,
      exposure: 60,
      scatter: 0.02,
      alphaGain: 2
    },
    thinking: {
      speed: 0.6,
      spin: 0.5,
      turb: 0.35,
      disperse: 1.1,
      sheets: 7,
      exposure: 57,
      scatter: 0.019,
      alphaGain: 2.1
    },
    // loudest: fast drift, dense sheets, wide rainbow
    speaking: {
      speed: 1.2,
      spin: 0.7,
      turb: 0.55,
      disperse: 1.5,
      sheets: 5.5,
      exposure: 45,
      scatter: 0.015,
      alphaGain: 2.5
    }
  }
};

export type Shdr01Props = Omit<ShaderOrbProps, "variant">;

export function Shdr01({ size = 280, ...rest }: Shdr01Props) {
  return <ShaderOrb variant={shdr01Orb} size={size} {...rest} />;
}

export default Shdr01;

4. components/ui/shdr-02.tsx

/*
 * Deliberately not a `"use client"` module — see the note in `shdr-11.tsx`.
 */
import { ShaderOrb, type OrbVariant, type ShaderOrbProps } from "@/components/ui/orbkit-core";

/* ----------------------------------------------------------------------------
   SHDR-02 — ornate scrollwork wrapped onto a sphere.

   Ten layers, each running the same nine-step iterative warp on a 2D point:

     v = p;  for f in 1..9:  v += sin(v.yx * f + i + t) / f

   The `v.yx` swizzle is what makes it ornate rather than noisy — each step
   feeds a coordinate back into the other axis, so the field folds into scrolls
   and shells instead of blurring. Brightness is `1 / length(v)`: wherever the
   warp happens to land a point near the origin, that layer flares. The layer
   index `i` both offsets the warp phase and picks the colour, so the ten sheets
   are differently coloured and never coincide.

   The original is a full-screen 2D field. Rather than mask a disc out of it —
   which just reads as a flat coin — the pattern is sampled through a
   STEREOGRAPHIC projection of the orb's dome, so it compresses toward the rim
   the way a texture on a real sphere does, and rolls as the dome rotates.
---------------------------------------------------------------------------- */

/*
 * Layer and warp counts are `#define`s: ES 1.0 requires constant loop bounds.
 * 10x9 is ~90 sin() per pixel — light next to the raymarched orbs.
 */
const ROCAILLE_FRAG = `
#define LAYERS 10
#define WARP 9

void main() {
  vec2 uv = orbUV();
  float R = uP_radius + uP_swell * uInput;
  float r2d = length(uv);
  float mask = smoothstep(0.012, -0.012, r2d - R);
  float nr = clamp(r2d / max(R, 0.001), 0.0, 1.0);
  float z = sqrt(max(1.0 - nr * nr, 0.0));

  float animTime = uP_speed; // integrated clock

  vec3 sp = vec3(uv / max(R, 0.001), z);

  /*
    Stereographic projection: sphere → plane. Equal steps in screen space map to
    ever-larger steps in pattern space as the rim is approached, which is exactly
    the foreshortening that sells a flat field as wrapped geometry. uP_bulge
    softens the divisor — higher flattens it back toward a disc.

    DO NOT rotate sp in 3D before this. Spinning the dome about Y mixes sp.x
    into sp.z, so near the rim the divisor collapses toward zero, p explodes,
    length(v) goes huge, and 1/length(v) leaves most of the sphere black. That
    is what hollowed the orb out. The projection needs sp.z to stay the
    view-facing component.

    Motion comes from animTime inside the warp below instead, which changes the
    scrollwork without ever touching the projection. If you want the pattern to
    travel, rotate or translate p here in 2D — that is projection-safe.
  */
  vec2 p = sp.xy / (sp.z + 1.0 + uP_bulge) * uP_zoom;

  // projection-safe 2D drift, in place of a dome spin
  float sw = animTime * uP_swirl;
  p = mat2(cos(sw), -sin(sw), sin(sw), cos(sw)) * p;

  // input volume tightens the warp; output volume brightens the layers
  float warpFreq = uP_warpFreq * (1.0 + 0.35 * uInput);
  float gain = uP_gain * (0.75 + 0.7 * uOutput);

  vec4 acc = vec4(0.0);
  for (int i = 1; i <= LAYERS; i++) {
    float fi = float(i);
    vec2 v = p;
    for (int j = 1; j <= WARP; j++) {
      float f = float(j);
      v += sin(v.yx * f * warpFreq + fi + animTime) / f;
    }
    // uP_coreClamp guards the divide and doubles as the flare size — the
    // original has no guard and relies on length(v) never hitting zero.
    //
    // uP_falloff is the FILL control. The original's plain 1/length(v) decays
    // fast, so only the knots where the warp lands near the origin light up and
    // the rest of the sphere stays near black. An exponent below 1 flattens the
    // tail — at length(v)=10 a 0.6 power is ~4x brighter than 1/x — which lifts
    // the filigree between the knots without blowing the knots themselves out.
    float rad = pow(max(length(v), uP_coreClamp), uP_falloff);
    acc += (cos(fi + vec4(0.0, 1.0, 2.0, 3.0) + uP_hueShift) + 1.0) / 6.0 / rad;
  }

  // the original squares before tone-mapping, which is what crushes the dim
  // filigree and leaves the bright scrollwork
  vec3 col = tanh3(acc.rgb * acc.rgb * gain);

  // rim light, so the silhouette reads as a ball rather than a cut-out
  float fresnel = pow(1.0 - z, uP_rimPow);
  col += vec3(fresnel) * uP_rim;

  float lum = dot(col, vec3(0.2126, 0.7152, 0.0722));
  float visibility = clamp(lum * uP_alphaGain + uP_baseVis + fresnel * 0.25, 0.0, 1.0);

  // Surface-lit and mask-bounded, so alpha is coverage: premultiply normally.
  // (Unlike Corona and Nimbus, which are emissive and must not be.)
  float a = mask * visibility;
  gl_FragColor = vec4(col * a, a);
}
`;

export const shdr02Orb: OrbVariant = {
  key: "shdr-02",
  label: "SHDR-02",
  note: "ornate scrollwork on a rolling dome",
  frag: ROCAILLE_FRAG,
  params: [
    { key: "speed", label: "Anim speed", min: 0.015, max: 10, step: 0.05, default: 0.5, integrate: true },
    { key: "swirl", label: "Swirl", min: 0, max: 3, step: 0.015, default: 0.06 },
    { key: "radius", label: "Radius", min: 0.15, max: 3, step: 0.015, default: 0.9 },
    { key: "swell", label: "Input swell", min: 0, max: 1, step: 0.01, default: 0.06 },
    { key: "zoom", label: "Pattern zoom", min: 0.15, max: 40, step: 0.2, default: 4.4 },
    { key: "bulge", label: "Sphere bulge", min: 0, max: 10, step: 0.05, default: 0.35 },
    { key: "warpFreq", label: "Warp frequency", min: 0.05, max: 10, step: 0.05, default: 1.5 },
    { key: "hueShift", label: "Hue shift", min: 0, max: 6.283, step: 0.05, default: 0 },
    { key: "coreClamp", label: "Flare size", min: 0.003, max: 3, step: 0.015, default: 0.12 },
    { key: "falloff", label: "Fill", min: 0.05, max: 4, step: 0.05, default: 1 },
    { key: "gain", label: "Exposure", min: 0.015, max: 10, step: 0.05, default: 0.55 },
    { key: "rim", label: "Rim light", min: 0, max: 3, step: 0.015, default: 0.12 },
    { key: "rimPow", label: "Rim tightness", min: 0.15, max: 15, step: 0.1, default: 2.2 },
    { key: "alphaGain", label: "Alpha gain", min: 0.05, max: 15, step: 0.1, default: 2.4 },
    { key: "baseVis", label: "Base visibility", min: 0, max: 1.5, step: 0.01, default: 0.08 }
  ],
  colors: [],
  /*
   * No dome rotation in any state — see the projection note in the shader. The
   * states differ by how fast the scrollwork evolves and how dense it is.
   *
   * `swirl` is deliberately absent from every preset: the rotation angle is
   * animTime * swirl, so a per-state swirl value makes a state change sweep
   * the angle by (accumulated clock) x (delta) — the whole dome visibly spins
   * while the preset glides. Held constant, the angle stays continuous and a
   * state change only retimes the scrollwork.
   */
  statePresets: {
    // calm: slow evolution, open scrollwork
    idle: {
      speed: 0.5,
      zoom: 4.4,
      warpFreq: 1.5,
      coreClamp: 0.12,
      falloff: 1,
      gain: 0.55,
      rim: 0.12,
      alphaGain: 2.4
    },
    thinking: {
      speed: 0.65,
      zoom: 4.6,
      warpFreq: 1.6,
      coreClamp: 0.11,
      falloff: 0.96,
      gain: 0.6,
      rim: 0.13,
      alphaGain: 2.5
    },
    /*
      speaking is SPEED-led, like hydrogen's: the scrollwork keeps the idle
      structure but reforms itself several times faster, with hotter knots and
      MORE contrast (falloff above 1), so it reads as the same orb answering
      at speed. Densifying the pattern here (higher zoom/warpFreq) is what
      used to make speaking look like a blurry mesh: more detail per pixel and
      a flatter falloff wash the filigree into fog.

      warpFreq 1.2 is deliberate: the shader scales it by the input volume,
      and speaking synthesizes input around 0.65, so the EFFECTIVE frequency
      lands back at the crisp ~1.5 that idle shows at zero input.
    */
    speaking: {
      speed: 3,
      zoom: 4.4,
      warpFreq: 1.2,
      coreClamp: 0.07,
      falloff: 1.1,
      gain: 0.85,
      rim: 0.2,
      alphaGain: 3
    }
  }
};

export type Shdr02Props = Omit<ShaderOrbProps, "variant">;

export function Shdr02({ size = 280, ...rest }: Shdr02Props) {
  return <ShaderOrb variant={shdr02Orb} size={size} {...rest} />;
}

export default Shdr02;

5. components/ui/shdr-03.tsx

/*
 * Deliberately not a `"use client"` module — see the note in `shdr-11.tsx`.
 *
 * Note for editors: the shader lives in a template literal, so its comments
 * must not contain backticks.
 */
import { ShaderOrb, type OrbVariant, type ShaderOrbProps } from "@/components/ui/orbkit-core";

/* ----------------------------------------------------------------------------
   SHDR-03 — a turbulent belt of light girdling the ball, coloured by its
   own distance field.

   Ported from a golfed twigl listing:

     for(float i,z,d;i++<8e1;o+=(cos(d/.1+vec4(0,2,4,0))+1.)/d*z)
     {vec3 p=z*normalize(FC.rgb*2.-r.xyy),
           a=normalize(cos(vec3(4,2,0)+t-d*8.));
      p.z+=5.,a=a*dot(a,p)-cross(a,p);
      for(d=1.;d++<9.;)a+=sin(a*d+t).yzx/d;
      z+=d=.05*abs(length(p)-3.)+.04*abs(a.y);}
     o=tanh(o/1e4);

   What it actually is, decoded:

   - THE FIELD IS A SPHERE PLUS A PLANE, added. abs(length(p) - 3) is the
     distance to a shell of radius three; abs(a.y) is the distance to the
     plane y = 0 of the TURBULENT frame. A sum is small only where both
     terms are, so what lights up is the intersection — a great circle
     round the ball, dragged out of true by eight octaves of warp. A belt,
     not a shell.
   - THE SHELL TERM READS THE RAW POINT AND THE PLANE TERM READS THE
     WARPED ONE. That asymmetry is the whole composition: the ball stays a
     clean sphere while the belt writhes across it. Warp both and the
     sphere goes with it.
   - THE ROTATION AXIS IS FED BY THE PREVIOUS STEP'S DENSITY. The listing
     writes cos(vec3(4,2,0) + t - d*8) where d is left over from the last
     iteration, so the axis — and with it the belt's tilt — settles as the
     ray closes on the surface and swings away from it out in the open. It
     is a one-line feedback loop hiding inside a constant.
   - HUE COMES FROM THE DENSITY, NOT FROM DEPTH. cos(d/.1 + ...) bands the
     colour along the distance field itself, so the belt is contoured in
     rainbow like a topographic map of its own edge — where shdr-18
     colours by depth along the ray and shdr-22 by step index.
   - The weight is z/d, not 1/d: it brightens what is FAR as well as what
     is close to the surface, so the far limb of the belt burns hotter
     than the near one.

   Port decisions, each one a documented trap or rule in the README:

   - THE ORB IS THE OBJECT, and this listing hands it over: the shell it
     traces is the ball. All that was needed was the family envelope and
     analytic silhouette to bound the residue, sized past the shell so the
     belt is not shaved at the limb.
   - The golfed rotation, a*dot(a,p) - cross(a,p) with unit a, is an exact
     minus-90-degree Rodrigues — orthonormal, against the README's usual
     warning about golfed rotations. Same construction as shdr-18.
   - The listing relies on d being ZERO on the first iteration, where the
     axis reads it before anything has written it. Uninitialised locals are
     UNDEFINED in GLSL ES 1.0 — explicit here, and it matters more than
     usual because that value steers the geometry rather than a phase.
   - No step-length weighting: 1/d is the density, as in shdr-22 and
     shdr-18, and multiplying by the step would cancel it exactly. The
     clamp bounds it instead.
   - Both singular divisors need floors. The step floor is the BELT WIDTH,
     which is the only material control here.
   - Emitted light, so rgb is already premultiplied and alpha comes from
     the peak channel (see shdr-31).
---------------------------------------------------------------------------- */

/*
 * Step counts are `#define`s: ES 1.0 requires constant loop bounds. STEPS and
 * TURB are the listing's i++ < 8e1 and d++ < 9 (which runs d = 2..9).
 */
const ECLIPTIC_FRAG = `
#define STEPS 80
#define TURB 8
#define AA 1

// Volume-reactive values, resolved once per fragment in main().
float eclipticTurb;
float eclipticPlane;
float eclipticExposure;
float eclipticWidth;

vec3 eclipticRender(vec2 fragCoord) {
  vec2 uv = (2.0 * fragCoord - uRes) / min(uRes.x, uRes.y);
  vec3 ro = vec3(0.0, 0.0, uP_camDist);
  vec3 rd = normalize(vec3(uv, -uP_focal));

  float animTime = uP_speed; // integrated clock: the warp
  float wander = uP_wander;  // integrated clock: the belt's tilt

  vec3 acc = vec3(0.0);

  // transmittance carried front-to-back — the near belt veils the far one
  float T = 1.0;

  // march only the span the envelope can light, as in shdr-01
  float z = max(uP_camDist - uP_envRadius * 1.3, 0.0);
  float zEnd = uP_camDist + uP_envRadius * 1.3;

  /*
    The listing's feedback variable, explicit. On the first iteration the
    axis below reads this before anything has written it — zero is what the
    golfed version gets, so zero is what it gets here.
  */
  float d = 0.0;

  for (int it = 0; it < STEPS; it++) {
    vec3 p = ro + rd * z;

    /*
      The axis, steered by the PREVIOUS step's density: the belt's tilt
      settles as the ray closes on the surface and swings away from it out
      in the open. The three phases are far enough apart that the cosines
      can never null together, so the normalize is safe without a guard.
    */
    vec3 axis = normalize(cos(wander + vec3(4.0, 2.0, 0.0) - d * uP_feedback));

    // the exact minus-90-degree rotation about that axis
    vec3 a = dot(axis, p) * axis - cross(axis, p);

    // eight octaves of plain feedback warp — no lattice quantizer here,
    // unlike its cousins shdr-22 and shdr-04
    for (int j = 0; j < TURB; j++) {
      float f = float(j) + 2.0;
      a += eclipticTurb * sin(a * f + animTime).yzx / f;
    }

    /*
      Sphere plus plane. The shell reads the RAW point so the ball stays a
      ball; the plane reads the WARPED one so the belt writhes across it.
      uP_plane at zero drops the belt and lights the whole shell, which is
      worth being able to see once.
    */
    d = uP_shellW * abs(length(p) - uP_shellR) + eclipticPlane * abs(a.y);
    d = max(d, eclipticWidth);

    /*
      Hue from the DENSITY — the belt contoured in rainbow along its own
      distance field — and the listing's z in the numerator, which burns
      the far limb hotter than the near one.
    */
    vec3 w = cos(d * uP_hue + vec3(0.0, 2.0, 4.0) * uP_spread) + 1.0;
    w *= z / d;
    w = min(w, vec3(uP_stepClamp));

    // envelope: plateau through the ball, cut 12% past the radius so the
    // analytic silhouette in main() still has emission left to cut
    float env = smoothstep(uP_envRadius * 1.12, uP_envRadius * uP_envCore, length(p));
    w = (w + uP_fill) * env;

    acc += T * w;
    T *= exp(-dot(w, vec3(0.299, 0.587, 0.114)) * uP_scatter);

    z += d;
    if (T < 0.004 || z > zEnd) break;
  }

  return acc;
}

void main() {
  /*
    The SURGE: tightness and warp swept together on one phase, so the belt
    gathers into a hard warped girdle and then opens back into a smooth
    shell. Two params, one gesture — swept apart they read as two unrelated
    things happening at once.

    Absolute bounds rather than a swing around what was dialled, so the range
    is exactly the range: tightness 0.1 to 0.3, warp 0.5 to 1.6. That is why
    the mix sits OUTSIDE the volume terms below — folding the surge under
    them would shave the top of both ranges by whatever the agent happened to
    be doing. At surge zero those terms are all that is left, so a state that
    does not ask for this is untouched.
  */
  float surge = 0.5 - 0.5 * cos(uAnim * 4.0);

  eclipticTurb = mix(uP_turb * (1.0 + 0.4 * uInput), mix(0.5, 1.6, surge), uP_surge);
  // the belt broadens toward a full shell while the agent speaks
  eclipticPlane = mix(uP_plane * (1.0 - 0.35 * uOutput), mix(0.1, 0.3, surge), uP_surge);
  eclipticExposure = uP_exposure * (1.0 - 0.3 * uOutput);

  /*
    The belt BREATHES. At pulse zero the width is exactly what was dialled,
    so a state that does not ask for this is untouched; at one it sweeps the
    whole way from nothing to that width and back, once every few seconds.

    It cannot truly reach zero. Belt width is the march's step floor — see
    the port note above — and a zero step stalls the ray on one point, which
    with no scatter to close the transmittance accumulates the clamp eighty
    times into a white flare. The floor is the param's own minimum, four
    times finer than what the resting belt uses, so it reads as gone.

    Off uAnim rather than a raw clock, so the breath quickens with the agent
    like every other motion in the engine.
  */
  eclipticWidth = max(uP_width * (1.0 - uP_pulse * (0.5 + 0.5 * cos(uAnim * 3.0))), 5e-4);

  vec3 acc = vec3(0.0);
#if AA > 1
  for (int mx = 0; mx < AA; mx++) {
    for (int my = 0; my < AA; my++) {
      vec2 offset = (vec2(float(mx), float(my)) + 0.5) / float(AA) - 0.5;
      acc += eclipticRender(gl_FragCoord.xy + offset);
    }
  }
  acc /= float(AA * AA);
#else
  acc = eclipticRender(gl_FragCoord.xy);
#endif

  // tanh tone map per channel — the envelope and transmittance change the
  // accumulator's scale, so the golfed /1e4 knee is a tunable here
  vec3 col = tanh3(acc / max(eclipticExposure, 1.0));
  col = pow(clamp(col, 0.0, 1.0), vec3(uP_contrast));

  // saturation about luminance, then the tint
  float lum = dot(col, vec3(0.299, 0.587, 0.114));
  col = mix(vec3(lum), col, uP_saturation);
  col *= uC_tint;

  // alpha from the brightest channel, not luminance — a deep blue contour
  // has low luminance but must not go transparent
  float peak = max(col.r, max(col.g, col.b));
  float a = clamp(peak * uP_alphaGain, 0.0, 1.0);

  // Analytic silhouette — identical construction to shdr-01: exact
  // ray-to-centre distance against the radius, colour AND alpha.
  vec3 mrd = normalize(vec3(orbUV(), -uP_focal));
  float closest = length(cross(vec3(0.0, 0.0, uP_camDist), mrd));
  float band = mix(0.35, 0.012, clamp(uP_edge, 0.0, 1.0));
  float mask = 1.0 - smoothstep(uP_envRadius * (1.0 - band), uP_envRadius * 1.005, closest);
  col *= mask;
  a *= mask;

  // safety taper at the frame boundary — colour as well as alpha
  float r2d = length(orbUV());
  float fade = 1.0 - smoothstep(uP_edgeFade, 1.0, r2d);
  col *= fade;
  a *= fade;

  // Emitted light, so rgb is already premultiplied — do NOT scale by alpha
  // again (see the same note in shdr-31).
  gl_FragColor = vec4(col, a);
}
`;

export const shdr03Orb: OrbVariant = {
  key: "shdr-03",
  label: "SHDR-03",
  note: "a turbulent belt of light girdling the ball, contoured in rainbow",
  frag: ECLIPTIC_FRAG,
  params: [
    { key: "speed", label: "Anim speed", min: 0.015, max: 10, step: 0.05, default: 0.6, integrate: true },
    { key: "wander", label: "Belt tilt drift", min: 0, max: 5, step: 0.02, default: 0.3, integrate: true },
    { key: "feedback", label: "Tilt feedback", min: 0, max: 40, step: 0.1, default: 1.5 },
    { key: "camDist", label: "Camera distance", min: 1, max: 50, step: 0.3, default: 5 },
    { key: "focal", label: "Lens", min: 0.15, max: 15, step: 0.05, default: 1.05 },
    { key: "shellR", label: "Shell radius", min: 0.2, max: 20, step: 0.1, default: 3 },
    { key: "shellW", label: "Shell weight", min: 0.005, max: 1, step: 0.005, default: 0.12 },
    { key: "plane", label: "Belt tightness", min: 0, max: 1, step: 0.005, default: 0.15 },
    { key: "turb", label: "Warp", min: 0, max: 4, step: 0.02, default: 0.55 },
    { key: "width", label: "Belt width", min: 0.0005, max: 0.4, step: 0.0005, default: 0.004 },
    { key: "pulse", label: "Belt breathing", min: 0, max: 1, step: 0.01, default: 0 },
    { key: "surge", label: "Belt surge", min: 0, max: 1, step: 0.01, default: 0 },
    { key: "hue", label: "Contour hue", min: 0, max: 60, step: 0.1, default: 25 },
    { key: "spread", label: "Colour spread", min: 0, max: 3, step: 0.02, default: 1 },
    { key: "envRadius", label: "Envelope radius", min: 0.15, max: 20, step: 0.1, default: 3.3 },
    { key: "envCore", label: "Envelope core", min: 0.3, max: 1.02, step: 0.01, default: 0.92 },
    { key: "fill", label: "Body fill", min: 0, max: 40, step: 0.05, default: 0.1 },
    { key: "stepClamp", label: "Step clamp", min: 5, max: 20000, step: 25, default: 1500 },
    { key: "scatter", label: "Diffusion", min: 0, max: 0.1, step: 0.0002, default: 0.0006 },
    { key: "exposure", label: "Exposure", min: 20, max: 100000, step: 50, default: 1500 },
    { key: "contrast", label: "Contrast", min: 0.15, max: 15, step: 0.05, default: 1.2 },
    { key: "saturation", label: "Saturation", min: 0, max: 4, step: 0.02, default: 1.25 },
    { key: "alphaGain", label: "Alpha gain", min: 0.05, max: 15, step: 0.1, default: 2 },
    { key: "edge", label: "Edge sharpness", min: 0, max: 1, step: 0.01, default: 1 },
    { key: "edgeFade", label: "Halo falloff", min: 0.1, max: 3, step: 0.015, default: 0.98 }
  ],
  colors: [{ key: "tint", label: "Tint", default: "#ffffff" }],
  /*
    Staged on BELT TIGHTNESS, which is the only control that changes what
    the object is: high and the light is a single girdle, low and it opens
    out into the whole shell. Warp and belt width carry the rest.
  */
  statePresets: {
    /*
      at rest: a hairline thread on a small, thin shell. The belt is left
      loose — a twentieth of the way to thinking's wire — so it is the WARP,
      more than double what searching carries and the highest steady value of
      the three, that gives the light its shape rather than the plane
      confining it. Hue is pushed warm and the
      envelope opened to its ceiling, which is what lets so fine a line still
      read as a body.
    */
    idle: {
      speed: 0.6,
      wander: 0.3,
      feedback: 2,
      shellR: 2,
      shellW: 0.085,
      plane: 0.085,
      turb: 1.54,
      width: 0.001,
      hue: 39.8,
      spread: 0.94,
      envCore: 1.02,
      stepClamp: 1475,
      exposure: 1450,
      scatter: 0.0006,
      alphaGain: 2
    },
    /*
      searching: the belt BREATHES. Width is on `pulse` at full depth, so the
      band swells from nothing to fifty times the resting belt and closes
      again every few seconds — the state's whole tell, and the reason warp
      drops to under half of idle's: the shape comes from the breathing now,
      not from the noise.

      Under it the belt is also four times tighter than at rest and hunting
      hard — the tilt three times as fast, the axis feedback near quadrupled
      — on a shell pulled small and thin inside a much wider envelope, so
      what pulses is a broad band on a small ball rather than a girdle.
    */
    thinking: {
      speed: 2,
      wander: 1.1,
      feedback: 7.5,
      focal: 1.9,
      shellR: 1.9,
      shellW: 0.005,
      plane: 0.345,
      turb: 0.7,
      width: 0.048,
      pulse: 1,
      hue: 28.3,
      spread: 1.02,
      envRadius: 7.4,
      envCore: 0.84,
      exposure: 2300,
      scatter: 0,
      alphaGain: 2
    },
    /*
      answering: the belt SURGES. Tightness and warp are both on the surge at
      full depth, sweeping 0.1 to 0.3 and 0.5 to 1.6 together about every
      second and a half — from a loose, lightly warped band to a tight warped
      girdle and back. Where thinking pulses one control, this one swings the
      two that decide what the object is, which is why it reads as the
      loudest of the three.

      Tightness starts the sweep at exactly what is dialled here, so the
      preset value is the loose end of the swing; warp does not, and its 0.14
      is only what you would see with the surge turned off. What the preset
      carries either way is the body under them — a broad shell, tilt
      drifting near three times idle's, the axis feedback almost off — plus
      eighteen times the resting belt width to keep the girdle solid at the
      tight end.
    */
    speaking: {
      speed: 0.9,
      wander: 0.84,
      feedback: 0.3,
      shellR: 1.8,
      shellW: 0.19,
      plane: 0.1,
      turb: 0.14,
      width: 0.018,
      surge: 1,
      spread: 1.04,
      envRadius: 3.4,
      exposure: 650,
      scatter: 0.0003,
      alphaGain: 2.7
    }
  },
  // the contour ramp supplies the colour, so the tint only shifts its
  // temperature: neutral at rest, cooled while searching, warmed while
  // answering
  stateColors: {
    idle: { tint: "#ffffff" },
    thinking: { tint: "#9db8ff" },
    speaking: { tint: "#ffc492" }
  }
};

export type Shdr03Props = Omit<ShaderOrbProps, "variant">;

export function Shdr03({ size = 280, ...rest }: Shdr03Props) {
  return <ShaderOrb variant={shdr03Orb} size={size} {...rest} />;
}

export default Shdr03;

6. components/ui/shdr-04.tsx

/*
 * Deliberately not a `"use client"` module — see the note in `shdr-11.tsx`.
 *
 * Note for editors: the shader lives in a template literal, so its comments
 * must not contain backticks.
 */
import { ShaderOrb, type OrbVariant, type ShaderOrbProps } from "@/components/ui/orbkit-core";

/* ----------------------------------------------------------------------------
   SHDR-04 — a hollow shell of light, faceted by a voxel lattice, approached
   but never reached.

   Ported from a golfed twigl listing:

     vec3 p;
     for(float i,z,f;i++<5e1;z+=f=.003+.1*abs(length(p)-5.),o.rgb+=(p/z+.8)/f)
       for(p=z*(FC.rgb*2.-r.xyy)/r.y,p.z+=9.,f=1.;f++<7.;
           p+=sin(round(p.zxy/.1)*.1*f-t)/f);
     o=tanh(o/2e3);

   What it actually is, decoded:

   - THE SUBJECT IS A SPHERE, written down as one. abs(length(p) - 5) is
     the distance to a shell of radius five, and the march sphere-traces
     it. Of all the listings in this library this is the only one that did
     not have to be argued onto a ball — it was already one.
   - THE MARCH NEVER ARRIVES. The step is a tenth of the distance to the
     shell, so the remaining gap falls by 10% per step and after fifty
     steps is half a percent of where it started: an asymptotic approach
     from outside that never crosses. Every step accumulates 1/step, so
     the sum is dominated by the last few and the shell reads as a glowing
     surface rather than as anything the ray passes through. The .003 floor
     is the only thing bounding that sum, which makes it the SURFACE
     WIDTH — the closest this shader has to a material.
   - THE LATTICE PITCH IS FIXED ACROSS OCTAVES. round(p.zxy/.1)*.1 snaps
     to a tenth-unit grid, and the octave index f multiplies the PHASE,
     never the lattice. So all six octaves quantize on the SAME grid and
     the displacement is piecewise constant on it — the shell comes out
     faceted at one crisp scale instead of fractally rough. That is the
     opposite of how shdr-22 and shdr-07 use the same trick, where
     each octave gets its own lattice.
   - THE COLOUR IS THE POSITION. p/z is the warped point over the distance
     travelled, so x, y and z paint red, green and blue and the whole thing
     washes toward the .8 white floor as the ray goes deeper. Near facets
     are strongly coloured; far ones are white.

   Port decisions, each one a documented trap or rule in the README:

   - round() is ES 3.0 and does not exist in GLSL ES 1.0 — hand-written as
     floor(x + 0.5).
   - The march starts at the camera rather than at an envelope bound, which
     is the reverse of the choice shdr-22 and orb-nova make. Here the
     APPROACH IS THE IMAGE: skipping ahead to the shell would discard the
     geometric ramp that builds the glow, and the first samples' large p/z
     are what tint the near side.
   - The listing leaves its ray direction unnormalized, so its z is not a
     distance and its .003 and .1 are in a units system that depends on the
     lens. Normalized here, which makes those two numbers mean what they
     say and lets camera distance and lens move independently.
   - The golfed listing relies on i, z and p starting at zero;
     uninitialised locals are UNDEFINED in GLSL ES 1.0, explicit here. p in
     particular is read by the step expression BEFORE the inner loop has
     ever written it, on the first iteration only.
   - Emitted light, so rgb is already premultiplied and alpha comes from
     the peak channel (see shdr-31).
---------------------------------------------------------------------------- */

/*
 * Step counts are `#define`s: ES 1.0 requires constant loop bounds. STEPS and
 * TURB are the listing's i++ < 5e1 and f++ < 7 (which runs f = 2..7).
 */
const GEODE_FRAG = `
#define STEPS 50
#define TURB 6
#define AA 1

// Volume-reactive values, resolved once per fragment in main().
float geodeTurb;
float geodeWidth;
float geodeExposure;

// GLSL ES 1.0 has no round() — it arrived in ES 3.0. The listing quantizes
// with it, so it ships here.
vec3 roundv(vec3 x) { return floor(x + 0.5); }

vec3 geodeRender(vec2 fragCoord) {
  vec2 uv = (2.0 * fragCoord - uRes) / min(uRes.x, uRes.y);
  vec3 ro = vec3(0.0, 0.0, uP_camDist);
  vec3 rd = normalize(vec3(uv, -uP_focal));

  float animTime = uP_speed; // integrated clock
  float shellR = uP_shellR;
  float pitch = max(uP_pitch, 0.002);

  // the run-away bound for rays that miss the shell entirely
  float zEnd = uP_camDist + shellR * 2.5;

  vec3 acc = vec3(0.0);

  // The listing starts its march at the camera and lets the trace do the
  // travelling — see the header. z is the distance already walked.
  float z = 0.0;

  for (int it = 0; it < STEPS; it++) {
    vec3 p = ro + rd * z;

    /*
      Six octaves on ONE lattice. The pitch never changes; only the phase
      multiplier does, so the displacement is piecewise constant on a
      single grid and the shell facets at one scale.
    */
    for (int j = 0; j < TURB; j++) {
      float f = float(j) + 2.0;
      p += geodeTurb * sin(roundv(p.zxy / pitch) * pitch * f - animTime) / f;
    }

    /*
      Sphere trace toward the shell, on the WARPED point — so the facets
      are what the ray is chasing, not a smooth ball underneath them. The
      slack is the listing's tenth, and the floor is the surface width.
    */
    float d = geodeWidth + uP_slack * abs(length(p) - shellR);

    z += d;

    /*
      Position as colour, washing toward white with depth. Guarded on z:
      the listing gets away with reading p/z here because its comma
      operator advances z first, which is worth knowing before anyone
      reorders these two lines.
    */
    acc += (p * uP_hueGain / max(z, 1e-3) + uP_floorLevel) / d;

    if (z > zEnd) break;
  }

  return acc;
}

void main() {
  geodeTurb = uP_turb * (1.0 + 0.5 * uInput);
  geodeWidth = max(uP_width * (1.0 - 0.4 * uOutput), 0.0002);
  geodeExposure = uP_exposure * (1.0 - 0.3 * uOutput);

  vec3 acc = vec3(0.0);
#if AA > 1
  for (int mx = 0; mx < AA; mx++) {
    for (int my = 0; my < AA; my++) {
      vec2 offset = (vec2(float(mx), float(my)) + 0.5) / float(AA) - 0.5;
      acc += geodeRender(gl_FragCoord.xy + offset);
    }
  }
  acc /= float(AA * AA);
#else
  acc = geodeRender(gl_FragCoord.xy);
#endif

  // tanh tone map per channel — the golfed /2e3 knee is a tunable here
  vec3 col = tanh3(acc / max(geodeExposure, 1.0));
  col = pow(clamp(col, 0.0, 1.0), vec3(uP_contrast));

  // saturation about luminance, then the tint
  float lum = dot(col, vec3(0.299, 0.587, 0.114));
  col = mix(vec3(lum), col, uP_saturation);
  col *= uC_tint;

  // alpha from the brightest channel, not luminance — a deep blue facet
  // has low luminance but must not go transparent
  float peak = max(col.r, max(col.g, col.b));
  float a = clamp(peak * uP_alphaGain, 0.0, 1.0);

  /*
    Analytic silhouette against the shell, widened by uP_envScale because
    the turbulence pushes the visible surface OUT past the nominal radius —
    cut at the bare radius and the facets would be shaved flat all round
    the limb.
  */
  vec3 mrd = normalize(vec3(orbUV(), -uP_focal));
  float closest = length(cross(vec3(0.0, 0.0, uP_camDist), mrd));
  float sil = uP_shellR * uP_envScale;
  float band = mix(0.35, 0.012, clamp(uP_edge, 0.0, 1.0));
  float mask = 1.0 - smoothstep(sil * (1.0 - band), sil * 1.005, closest);
  col *= mask;
  a *= mask;

  // safety taper at the frame boundary — colour as well as alpha
  float r2d = length(orbUV());
  float fade = 1.0 - smoothstep(uP_edgeFade, 1.0, r2d);
  col *= fade;
  a *= fade;

  // Emitted light, so rgb is already premultiplied — do NOT scale by alpha
  // again (see the same note in shdr-31).
  gl_FragColor = vec4(col, a);
}
`;

export const shdr04Orb: OrbVariant = {
  key: "shdr-04",
  label: "SHDR-04",
  note: "a hollow shell of light, faceted by a voxel lattice",
  frag: GEODE_FRAG,
  params: [
    { key: "speed", label: "Anim speed", min: 0.015, max: 10, step: 0.05, default: 0.6, integrate: true },
    { key: "camDist", label: "Camera distance", min: 1, max: 60, step: 0.3, default: 9 },
    { key: "focal", label: "Lens", min: 0.15, max: 15, step: 0.05, default: 1.35 },
    { key: "shellR", label: "Shell radius", min: 0.3, max: 20, step: 0.1, default: 5 },
    { key: "pitch", label: "Facet size", min: 0.005, max: 1.5, step: 0.005, default: 0.3 },
    { key: "turb", label: "Displacement", min: 0, max: 5, step: 0.02, default: 0.45 },
    { key: "slack", label: "Trace slack", min: 0.01, max: 0.9, step: 0.005, default: 0.1 },
    { key: "width", label: "Surface width", min: 0.0005, max: 0.3, step: 0.0005, default: 0.003 },
    { key: "hueGain", label: "Position hue", min: 0, max: 6, step: 0.02, default: 1 },
    { key: "floorLevel", label: "White floor", min: 0, max: 4, step: 0.02, default: 0.8 },
    { key: "envScale", label: "Silhouette margin", min: 1, max: 2, step: 0.01, default: 1.16 },
    { key: "exposure", label: "Exposure", min: 20, max: 40000, step: 20, default: 3000 },
    { key: "contrast", label: "Contrast", min: 0.15, max: 15, step: 0.1, default: 1.3 },
    { key: "saturation", label: "Saturation", min: 0, max: 4, step: 0.02, default: 1.3 },
    { key: "alphaGain", label: "Alpha gain", min: 0.05, max: 15, step: 0.1, default: 2 },
    { key: "edge", label: "Edge sharpness", min: 0, max: 1, step: 0.01, default: 1 },
    { key: "edgeFade", label: "Halo falloff", min: 0.1, max: 3, step: 0.015, default: 0.98 }
  ],
  colors: [{ key: "tint", label: "Tint", default: "#ffffff" }],
  /*
    Staged on displacement — how far the lattice pushes the shell out of
    round — and on surface width, which is the only material control this
    shader has.

    FACET SIZE was held still across all three for a reason: it is a
    quantizer, and a gliding quantizer pops instead of fading (the same rule
    as shdr-14's cell grid and shdr-17's grain). Answering now moves it, 0.3
    to 0.22, so the lattice re-snaps through the half second either side of
    that state rather than cross-fading. Deliberate — see the note there.
  */
  statePresets: {
    // at rest: a shallow crust, the surface held thin and bright
    idle: {
      speed: 0.6,
      turb: 0.45,
      width: 0.003,
      slack: 0.1,
      hueGain: 1,
      exposure: 3000,
      contrast: 1.3
    },
    /*
      searching: the lattice pushes HARD — displacement nearly doubled —
      and the surface pulls to under half its idle width, so the facets
      read as sharp shifting plates. The knee rises with them: this is the
      dim, brittle state.
    */
    thinking: {
      speed: 1.8,
      turb: 0.85,
      width: 0.0012,
      slack: 0.07,
      hueGain: 1.7,
      exposure: 4800,
      contrast: 1.75
    },
    /*
      answering: plates AND lamp, which the other two never are at once. The
      lattice pushes almost as hard as it does while searching — 0.8 against
      0.85 — but on ten times the idle surface width instead of a third of
      it, so the facets stay sharp while the shell they sit on is wide open
      and bright. Fastest of the three, on the widest silhouette margin, and
      the most saturated.

      Two things here break the file's own rules on purpose. Facet size drops
      to 0.22, so the quantizer glides on the way in and out and the lattice
      re-snaps rather than fading; see the staging note above. And the tint
      goes COOL — cyan, cooler than the searching blue — against the warm
      answering tint the colour note below describes.
    */
    speaking: {
      speed: 2,
      pitch: 0.22,
      turb: 0.8,
      width: 0.032,
      slack: 0.165,
      hueGain: 0.5,
      floorLevel: 0.82,
      envScale: 1.67,
      exposure: 1220,
      contrast: 0.92,
      saturation: 1.66
    }
  },
  // the position ramp supplies the colour, so the tint only shifts its
  // temperature: neutral at rest and cool for both of the busy states —
  // blue while searching, a brighter cyan while answering
  stateColors: {
    idle: { tint: "#ffffff" },
    thinking: { tint: "#9db8ff" },
    speaking: { tint: "#94f3ff" }
  }
};

export type Shdr04Props = Omit<ShaderOrbProps, "variant">;

export function Shdr04({ size = 280, ...rest }: Shdr04Props) {
  return <ShaderOrb variant={shdr04Orb} size={size} {...rest} />;
}

export default Shdr04;

7. components/ui/shdr-05.tsx

/*
 * Deliberately not a `"use client"` module — see the note in `shdr-11.tsx`.
 *
 * Note for editors: the shader lives in a template literal, so its comments
 * must not contain backticks.
 */
import { ShaderOrb, type OrbVariant, type ShaderOrbProps } from "@/components/ui/orbkit-core";

/* ----------------------------------------------------------------------------
   SHDR-05 — rainbow rings travelling through a lattice of lenses.

   Ported from a two-line twigl listing, the shortest in this library:

     vec2 p=(FC.xy*2.-r)/r.y*5.;
     o=cos(length(tan(p)+p)-t+vec4(0,.7,1,3));

   What it actually is, decoded:

   - tan(p) IS THE LENS GRID. Componentwise tangent has a pole every PI, so
     the plane is cut into a square lattice of cells and the coordinate
     diverges at every cell wall. Inside a cell the map is a gentle
     distortion; at the wall it throws the sample to infinity.
   - ADDING p BACK IS WHAT KEEPS IT LEGIBLE. tan alone would map every cell
     onto the whole plane and the cells would be identical; tan(p) + p
     offsets each one by its own position, so every cell in the lattice
     shows a DIFFERENT part of the ring field. The grid is a lattice of
     lenses looking at different places, not a tiling of one image.
   - THE PICTURE IS ONE COSINE of the length of that. Concentric rings, at
     three channel phases a fraction of a radian apart, travelling on the
     clock. Rings inward, colour fringing where the phases separate,
     and where a cell wall is approached the rings pile up without limit.
   - THERE IS NO TONE MAP. The listing ends on a raw cosine, so everything
     below zero is clamped away by the display — half of every period is
     hard black, which is what makes the bands read as edges rather than
     as a gradient.

   Port decisions, each one a documented trap or rule in the README:

   - THE ORB IS THE OBJECT: a flat 2D field, so it is sampled through a
     stereographic projection of the dome and finished with a fresnel
     sheen, never masked out of the plane as a disc. Motion is
     projection-safe 2D, as in shdr-08.
   - tan() IS SOFTENED, and this is the one change the port could not do
     without. sin/cos reaches infinite ring frequency at every cell wall,
     which no amount of supersampling resolves — it is not a sampling
     problem, it is unbounded bandwidth. sin*cos/(cos*cos + g) equals tan
     wherever tan is finite and caps at 1/(2*sqrt(g)) where it is not, so
     g sets how tightly the rings may crowd before the wall stops them.
     The same softened reciprocal as shdr-26's cell poles and
     shdr-09's rainbow fringe.
   - Even softened the walls are the busiest thing on the ball, so this orb
     supersamples where the smooth-field orbs do not.
   - The listing's clamp at zero is kept: it is not an artefact of the
     display, it is half the image.
   - Surface-lit and mask-bounded, so alpha IS coverage — premultiplied
     output, as in shdr-17.
---------------------------------------------------------------------------- */

/*
 * AA is a `#define`: ES 1.0 requires constant loop bounds. Three is not
 * caution here — the cell walls put the highest spatial frequency in this
 * library right where the light is brightest.
 */
const CAUSTIC_FRAG = `
#define AA 3

// Volume-reactive values, resolved once per fragment in main().
float causticSoft;
float causticGain;
float causticSpread;

/*
  Softened tangent. Equal to sin/cos wherever cos is not near zero, capped
  at 1/(2*sqrt(g)) where it is — see the header for why the raw pole cannot
  be supersampled away.
*/
vec2 tanSoft(vec2 x, float g) {
  vec2 s = sin(x);
  vec2 c = cos(x);
  return s * c / (c * c + g);
}

vec3 causticRender(vec2 fragCoord) {
  vec2 uv = (2.0 * fragCoord - uRes) / min(uRes.x, uRes.y);
  float R = max(uP_radius, 0.001);

  // the dome: the front hemisphere of a unit ball, in screen space
  vec2 pl = uv / R;
  float z = sqrt(max(1.0 - dot(pl, pl), 0.0));

  float ring = uP_ring; // integrated clock: the rings travel

  // stereographic wrap of the unrotated dome, as in shdr-08 — the lens
  // lattice compresses toward the limb the way a texture on a sphere does
  vec2 p = pl / (z + 1.0 + uP_bulge) * uP_scale;

  // projection-safe 2D motion: the lattice turns and slides
  float sw = uP_swirl; // integrated clock
  p = mat2(cos(sw), -sin(sw), sin(sw), cos(sw)) * p;
  p += vec2(uP_slide, uP_slide * 0.6); // integrated clock

  /*
    The lens lattice. Adding p back to its own tangent is what gives every
    cell a different view instead of tiling one image — see the header.
  */
  float L = length(tanSoft(p, causticSoft) * uP_lens + p);

  /*
    One cosine, three phases. The listing's (0, .7, 1) sit well under a
    radian apart, so the channels overlap through most of a band and only
    separate at its shoulders — white cores with coloured edges, not three
    independent rainbows.
  */
  vec3 col = cos(L * uP_freq - ring + vec3(0.0, 0.7, 1.0) * causticSpread);

  // the listing's clamp: half of every period is hard black, and that is
  // what makes these read as bands rather than as a gradient
  col = max(col, vec3(0.0)) * causticGain;

  col = pow(col, vec3(uP_contrast));

  float lum = dot(col, vec3(0.299, 0.587, 0.114));
  col = mix(vec3(lum), col, uP_saturation);
  col *= uC_tint;

  // a dark body under the bands, so the black half of the cosine reads as
  // the ball rather than as a hole in it
  col += uC_body * uP_floorLevel;

  // dome shading keeps the ball a ball under the lattice
  vec3 n = vec3(pl, z);
  float lambert = clamp(dot(n, normalize(vec3(-0.45, 0.55, 0.72))), 0.0, 1.0);
  col *= 0.6 + uP_light * lambert;

  float fres = 1.0 - z;
  fres = fres * fres * fres;
  col += uC_sheen * uP_rim * fres;

  return col;
}

void main() {
  // Volume coupling: the user's voice lets the walls crowd tighter, the
  // agent's brightens the bands and opens the colour split.
  causticSoft = max(uP_poleSoft * (1.0 - 0.5 * uInput), 0.0008);
  causticGain = uP_gain * (0.85 + 0.45 * uOutput);
  causticSpread = uP_spread * (1.0 + 0.5 * uOutput);

  vec2 uv = orbUV();
  float mask = smoothstep(0.012, -0.012, length(uv) - max(uP_radius, 0.001));

  if (mask <= 0.0) {
    gl_FragColor = vec4(0.0);
    return;
  }

  vec3 col = vec3(0.0);
#if AA > 1
  for (int mx = 0; mx < AA; mx++) {
    for (int my = 0; my < AA; my++) {
      vec2 off = (vec2(float(mx), float(my)) + 0.5) / float(AA) - 0.5;
      col += causticRender(gl_FragCoord.xy + off);
    }
  }
  col /= float(AA * AA);
#else
  col = causticRender(gl_FragCoord.xy);
#endif

  // Surface orb bounded by a mask: alpha IS coverage, so premultiply — the
  // opposite convention from the emissive orbs (see shdr-31).
  float a = mask;
  gl_FragColor = vec4(max(col, vec3(0.0)) * a, a);
}
`;

export const shdr05Orb: OrbVariant = {
  key: "shdr-05",
  label: "SHDR-05",
  note: "rainbow rings travelling through a lattice of lenses",
  frag: CAUSTIC_FRAG,
  params: [
    { key: "ring", label: "Ring speed", min: 0, max: 8, step: 0.03, default: 0.9, integrate: true },
    { key: "swirl", label: "Swirl", min: 0, max: 3, step: 0.015, default: 0.05, integrate: true },
    { key: "slide", label: "Lattice slide", min: 0, max: 4, step: 0.02, default: 0.12, integrate: true },
    { key: "radius", label: "Radius", min: 0.15, max: 3, step: 0.015, default: 0.9 },
    { key: "scale", label: "Lattice scale", min: 0.3, max: 20, step: 0.1, default: 5 },
    { key: "bulge", label: "Dome bulge", min: 0, max: 4, step: 0.02, default: 0.3 },
    { key: "lens", label: "Lens strength", min: 0, max: 4, step: 0.02, default: 1 },
    { key: "poleSoft", label: "Wall softness", min: 0.0008, max: 0.5, step: 0.0008, default: 0.02 },
    { key: "freq", label: "Ring frequency", min: 0.05, max: 8, step: 0.05, default: 1 },
    { key: "spread", label: "Colour split", min: 0, max: 4, step: 0.02, default: 1 },
    { key: "gain", label: "Brightness", min: 0.05, max: 4, step: 0.02, default: 1.1 },
    { key: "contrast", label: "Contrast", min: 0.15, max: 6, step: 0.05, default: 1 },
    { key: "saturation", label: "Saturation", min: 0, max: 4, step: 0.02, default: 1.15 },
    { key: "floorLevel", label: "Body fill", min: 0, max: 2, step: 0.01, default: 0.12 },
    { key: "light", label: "Key light", min: 0, max: 3, step: 0.015, default: 0.4 },
    { key: "rim", label: "Rim sheen", min: 0, max: 3, step: 0.015, default: 0.45 }
  ],
  colors: [
    { key: "tint", label: "Tint", default: "#ffffff" },
    { key: "body", label: "Body", default: "#141a30" },
    { key: "sheen", label: "Sheen", default: "#bcd8ff" }
  ],
  /*
    Staged on WALL SOFTNESS, which decides how tightly the rings are
    allowed to crowd before a cell wall stops them, and on ring frequency,
    which decides how many bands are on the ball at all. Lattice scale
    never moves between states — it sets the cell count, and a gliding cell
    count reads as the ball inflating rather than as a change of mood.

    Dome bulge and lens strength are staged too, so the SHAPE moves with the
    mood as well as the lattice: resting flattens both, and the two busy
    states drive them up — answering hardest, which puts bulge at 0 through
    0.36 to 0.96 across the three. They are continuous geometry rather than
    a quantizer, so gliding them is safe.
  */
  statePresets: {
    /*
      at rest: a plain sphere of drifting bands. Dome bulge is at zero and
      the lens down to under half its default — the only state that flattens
      both — so the ball is read straight rather than through a lens, which
      is what lets resting look settled even though the rings never stop
      moving.

      The walls are the TIGHTEST of the three here, under a third of
      searching's and a fifth of answering's, so the rings crowd hard
      against them; the colour split narrows to 0.7 with the key light
      raised well over its default to put back the separation the narrower
      split gives away.
    */
    idle: {
      ring: 0.9,
      swirl: 0.195,
      slide: 0.26,
      bulge: 0,
      lens: 0.38,
      poleSoft: 0.012,
      freq: 1.05,
      spread: 0.7,
      gain: 1.1,
      contrast: 1,
      saturation: 1.14,
      light: 0.66
    },
    /*
      searching: the ball comes UP. Where resting is flat and read straight,
      this bulges the dome and drives the lens past one, so the bands are
      magnified through the middle — and the drift roughly triples, swirl
      and lattice slide together, on a ring clock three times as fast.

      It is also the hardest-looking of the three by some way: contrast more
      than triples over resting and saturation doubles, on a body fill three
      times as deep and with the rim sheen switched off entirely, so nothing
      softens the edge. The walls open to three times resting's, which stops
      the rings being hairlines — this state reads through colour and shape
      now, not through fineness.
    */
    thinking: {
      ring: 3,
      swirl: 0.57,
      slide: 0.88,
      bulge: 0.36,
      lens: 1.28,
      poleSoft: 0.042,
      freq: 1.3,
      spread: 1.82,
      gain: 0.88,
      contrast: 3.2,
      saturation: 2.36,
      floorLevel: 0.4,
      rim: 0
    },
    /*
      answering: the FINEST banding of the three by a long way — ring
      frequency near five times resting's and nearly four times searching's
      — laid over the most strongly domed ball, bulge pushed almost to one
      against resting's flat zero. Many tight bands, spread across a surface
      curving away from you.

      The walls open to five times resting's, which is what keeps banding
      that fine from crowding into a solid field, and the drift is the
      highest of the three on both controls. Colour split comes back to
      about where resting holds it, so it is the fineness that carries this
      state rather than the split.
    */
    speaking: {
      ring: 1.4,
      swirl: 0.6,
      slide: 1,
      bulge: 0.96,
      lens: 1.2,
      poleSoft: 0.064,
      freq: 4.8,
      spread: 0.68,
      gain: 1.6,
      contrast: 0.75
    }
  },
  // the ring phases supply the colour, so the tint only shifts temperature
  // and the body carries the mood: neutral at rest, cold while searching,
  // warm while answering
  stateColors: {
    idle: { tint: "#ffffff", body: "#141a30", sheen: "#bcd8ff" },
    thinking: { tint: "#a6c0ff", body: "#080c26", sheen: "#7ea9ff" },
    speaking: { tint: "#ffc492", body: "#2e1408", sheen: "#ffb277" }
  }
};

export type Shdr05Props = Omit<ShaderOrbProps, "variant">;

export function Shdr05({ size = 280, ...rest }: Shdr05Props) {
  return <ShaderOrb variant={shdr05Orb} size={size} {...rest} />;
}

export default Shdr05;

8. components/ui/shdr-06.tsx

/*
 * Deliberately not a `"use client"` module — see the note in `shdr-11.tsx`.
 *
 * Note for editors: the shader lives in a template literal, so its comments
 * must not contain backticks.
 */
import { ShaderOrb, type OrbVariant, type ShaderOrbProps } from "@/components/ui/orbkit-core";

/* ----------------------------------------------------------------------------
   SHDR-06 — a hundred glowing lattices stacked through the depth of the ball,
   interfering.

   Ported from a one-line twigl listing:

     vec2 p=(FC.xy*2.-r)/r.y/.3;
     for(float f;f++<1e2;p+=.02*sin(p.yx+.5*t))
       o+=(cos(f/27.+vec4(0,1,3,0))+1.1)/length(sin(p*sin(r+f)/.7));
     o=tanh(o*o/4e4);

   What it actually is, decoded:

   - Each iteration draws ONE LATTICE OF GLOWS. sin(p*k) hits zero wherever
     both components do, which is a rectangular grid of points, and
     1/length of it lights every one of them. A hundred iterations lay a
     hundred such grids over each other, and the interference between
     their spacings is the whole image — moire in the strict sense.
   - sin(r+f) is the SEED. r is the resolution and f the layer index, so
     each layer gets its own frequency pair out of a sine of an integer —
     the cheapest pseudo-random vector in shader golf. It is also why the
     grids are axis-aligned but never the same size twice.
   - The loop's increment, p += .02*sin(p.yx+.5t), walks the sample point a
     little between layers, so the stack is not a hundred concentric grids
     but a hundred grids each shifted a little further along a wandering
     path. That is what turns the interference from static moire into
     something that flows.
   - cos(f/27 + vec4(0,1,3,0)) + 1.1 tints by LAYER INDEX, so depth through
     the stack reads as hue.
   - o*o before the knee is a contrast squarer, not a tone map: it crushes
     the field between the glows and lets the glows themselves saturate.

   Port decisions, each one a documented trap or rule in the README:

   - THE ORB IS THE OBJECT: the hundred layers become a hundred DEPTHS
     inside the ball rather than a hundred copies of one texture. Layer at
     depth d is the stereographic projection of the point the view ray has
     reached there, closed form —
     st = pl / (z - d + (1+bulge)*|P|), |P|^2 = 1 - 2*d*z + d*d.
     Be precise about what that is: a depth-graded REPROJECTION, not
     refraction. It grades radially, identical for every layer at the dead
     centre and spread hardest at the limb, which is why the layers all
     agree in the middle of the ball and light it as a focal point. What
     it buys is the one thing a flat texture cannot have — layers that
     shear against each other as they drift instead of sliding as one
     sheet.
   - The listing's sin(r+f) seed is RESOLUTION DEPENDENT and, unlike the
     lattice term in shdr-26, nothing cancels it: every layer would be
     reseeded on a canvas resize. Replaced with a constant.
   - Choosing that constant is not arbitrary. The two components differ
     only by a fixed phase, so the frequency pair traces a closed curve in
     frequency space rather than filling it — inherent to the listing. A
     quarter-turn offset makes that curve a CIRCLE, so every lattice
     aspect gets equal time; an arbitrary offset collapses it toward a
     line and half the layers come out near-identical.
   - length(sin(...)) is exactly zero at every lattice point, which is the
     one place the listing wants to divide. Floored rather than guarded,
     and the floor doubles as the GLOW SIZE — the same trick as
     shdr-02's coreClamp and shdr-09's ring width.
   - The golfed listing relies on f starting at zero; uninitialised locals
     are UNDEFINED in GLSL ES 1.0, explicit here.
   - Surface-lit and mask-bounded, so alpha IS coverage — premultiplied
     output, as in shdr-17.
---------------------------------------------------------------------------- */

/*
 * Step counts are `#define`s: ES 1.0 requires constant loop bounds. LAYERS is
 * the listing's f++ < 1e2. The field is broad and smooth — each lattice puts
 * only a handful of zeros across the ball — so this one does not need
 * supersampling the way the thin-line orbs do.
 */
const MOIRE_FRAG = `
#define LAYERS 100
#define AA 1

/*
 * The listing's sin(r + f), minus the resolution. The components are a
 * quarter turn apart so the per-layer frequency pair walks a circle (see
 * the header) — the base value only sets where on that circle layer one
 * starts.
 */
const vec2 SEED = vec2(11.3, 12.87);

// Volume-reactive values, resolved once per fragment in main().
float moireDrift;
float moireGlow;
float moireHue;

vec3 moireRender(vec2 fragCoord) {
  vec2 uv = (2.0 * fragCoord - uRes) / min(uRes.x, uRes.y);
  float R = max(uP_radius, 0.001);

  // the dome: the front hemisphere of a unit ball, in screen space
  vec2 pl = uv / R;
  float z = sqrt(max(1.0 - dot(pl, pl), 0.0));

  float t = uP_speed; // integrated clock

  /*
    The layer-zero projection: the plain stereographic wrap of the dome's
    surface. Every deeper layer is this divided by its own denominator, so
    the whole parallax below costs one ratio per layer.
  */
  float bulge = 1.0 + uP_bulge;
  float den0 = z + bulge;

  vec2 w = pl / den0 * uP_scale;

  vec3 acc = vec3(0.0);

  for (int li = 0; li < LAYERS; li++) {
    float f = float(li) + 1.0;
    float u = f / float(LAYERS);

    /*
      This layer's depth along the view ray, and the projection from the
      point the ray has reached there. At depth 0 the denominator is den0
      and the ratio is 1; deeper layers see the pattern from further
      inside the ball, which spreads them at the limb and not at all
      through the centre. That gradient is what shears the stack.
    */
    float d = u * uP_depth;
    float denu = z - d + bulge * sqrt(max(1.0 - 2.0 * d * z + d * d, 0.0));
    vec2 q = w * (den0 / max(denu, 0.05));

    /*
      One lattice. sin(q * k) vanishes on a rectangular grid of points and
      the reciprocal of its length lights every one; the floor on that
      length is the glow's radius, and without it the divide is by exactly
      zero at every lattice point.
    */
    vec2 k = sin(SEED + f) / max(uP_freq, 0.001);
    float g = max(length(sin(q * k)), moireGlow);

    // tint by layer index — depth through the stack reads as hue
    vec3 hue = cos(f * moireHue + vec3(0.0, 1.0, 3.0)) + 1.1;

    acc += hue / g;

    // the listing's walk between layers: the stack is a hundred grids
    // each shifted a little further along a wandering path
    w += moireDrift * sin(w.yx + t);
  }

  /*
    The listing's knee is tanh(o*o/4e4) over an unnormalized sum of a
    hundred layers. Dividing by the layer count first pulls the square's
    scale down by 100*100, so the same knee is exactly 4 here — a number
    that fits on a slider. The square is a contrast squarer, not a tone
    map: it crushes the field between the glows.
  */
  vec3 v = acc / float(LAYERS);
  vec3 col = tanh3(v * v / max(uP_exposure, 0.0001));
  col = pow(clamp(col, 0.0, 1.0), vec3(uP_contrast));

  // saturation about luminance, then the tint
  float lum = dot(col, vec3(0.299, 0.587, 0.114));
  col = mix(vec3(lum), col, uP_saturation);
  col *= uC_tint;

  /*
    Dome shading, kept gentle: the layers are emission seen THROUGH the
    ball, so a hard lambert reads as a shadow thrown across the inside of
    a lamp rather than as a lit surface.
  */
  vec3 n = vec3(pl, z);
  float lambert = clamp(dot(n, normalize(vec3(-0.45, 0.55, 0.72))), 0.0, 1.0);
  col *= 0.6 + uP_light * lambert;

  float fres = 1.0 - z;
  fres = fres * fres * fres;
  col += uC_sheen * uP_rim * fres;

  return col;
}

void main() {
  // Volume coupling: the user's voice widens the walk between layers, the
  // agent's opens the glows and runs the hue through the stack faster.
  moireDrift = uP_drift * (1.0 + 0.6 * uInput);
  moireGlow = max(uP_glowSize * (1.0 - 0.3 * uOutput), 0.002);
  moireHue = uP_hueRate * (1.0 + 0.35 * uOutput);

  vec2 uv = orbUV();
  float mask = smoothstep(0.012, -0.012, length(uv) - max(uP_radius, 0.001));

  // A hundred lattices per sample, none of them worth paying for outside
  // the silhouette.
  if (mask <= 0.0) {
    gl_FragColor = vec4(0.0);
    return;
  }

  vec3 col = vec3(0.0);
#if AA > 1
  for (int mx = 0; mx < AA; mx++) {
    for (int my = 0; my < AA; my++) {
      vec2 off = (vec2(float(mx), float(my)) + 0.5) / float(AA) - 0.5;
      col += moireRender(gl_FragCoord.xy + off);
    }
  }
  col /= float(AA * AA);
#else
  col = moireRender(gl_FragCoord.xy);
#endif

  // Surface orb bounded by a mask: alpha IS coverage, so premultiply — the
  // opposite convention from the emissive orbs (see shdr-31).
  float a = mask;
  gl_FragColor = vec4(max(col, vec3(0.0)) * a, a);
}
`;

export const shdr06Orb: OrbVariant = {
  key: "shdr-06",
  label: "SHDR-06",
  note: "a hundred glowing lattices stacked through the ball, interfering",
  frag: MOIRE_FRAG,
  params: [
    { key: "speed", label: "Anim speed", min: 0.015, max: 10, step: 0.05, default: 0.5, integrate: true },
    { key: "drift", label: "Layer walk", min: 0, max: 0.3, step: 0.002, default: 0.02 },
    { key: "radius", label: "Radius", min: 0.15, max: 3, step: 0.015, default: 0.9 },
    { key: "scale", label: "Pattern scale", min: 0.3, max: 20, step: 0.1, default: 4 },
    { key: "bulge", label: "Dome bulge", min: 0, max: 4, step: 0.02, default: 0.3 },
    { key: "depth", label: "Stack depth", min: 0, max: 1.6, step: 0.01, default: 0.7 },
    { key: "freq", label: "Lattice spacing", min: 0.05, max: 5, step: 0.01, default: 0.7 },
    { key: "glowSize", label: "Glow size", min: 0.002, max: 1, step: 0.002, default: 0.05 },
    { key: "hueRate", label: "Hue per layer", min: 0, max: 0.5, step: 0.002, default: 0.037 },
    { key: "exposure", label: "Exposure", min: 0.05, max: 200, step: 0.05, default: 4 },
    { key: "contrast", label: "Contrast", min: 0.15, max: 10, step: 0.05, default: 1.1 },
    { key: "saturation", label: "Saturation", min: 0, max: 4, step: 0.02, default: 1.2 },
    { key: "light", label: "Key light", min: 0, max: 3, step: 0.015, default: 0.45 },
    { key: "rim", label: "Rim sheen", min: 0, max: 3, step: 0.015, default: 0.4 }
  ],
  colors: [
    { key: "tint", label: "Tint", default: "#ffffff" },
    { key: "sheen", label: "Sheen", default: "#bcd8ff" }
  ],
  /*
    Staged on the two controls that decide what the interference looks
    like: the WALK between layers, which sets how far the stack shears
    against itself, and the GLOW SIZE, which sets whether the lattice
    points read as pinpricks or as flooded light.

    The clock is staged hard alongside them, and the two are doing separate
    jobs: the walk is the AMPLITUDE of the shear and the clock is its RATE,
    so a state's character comes from the pair. Small walk on a fast clock
    twitches; a large walk on a fast clock churns.

    Lattice spacing never moves between states — it sets how many grids land
    on the ball, and a gliding count reads as the ball inflating rather than
    as a mood.
  */
  statePresets: {
    // at rest: a slow shallow walk, glows open and soft
    idle: {
      speed: 0.5,
      drift: 0.02,
      depth: 0.7,
      glowSize: 0.05,
      hueRate: 0.037,
      exposure: 4,
      contrast: 1.1
    },
    /*
      searching: quick and granular. The clock runs over five times resting
      speed and the walk is five times as wide, on the deepest stack of the
      three, so the hundred layers shear hard and visibly against each
      other. The glows stay small — a third of resting — which is what keeps
      the movement legible as movement: at this rate, fine points shifting
      read as a scan across the ball rather than as a wash.

      The DOME is pushed out hard too — bulge seven times its default, and
      the only state that moves it. That deepens the denominator the whole
      projection divides by, so the lattice lands finer on the ball and
      spreads less toward the limb: the grain tightens and evens out at the
      same time.
    */
    thinking: {
      speed: 2.6,
      drift: 0.105,
      bulge: 2.18,
      depth: 1.25,
      glowSize: 0.016,
      hueRate: 0.095,
      exposure: 6.5,
      contrast: 1.55
    },
    /*
      answering: the fastest clock of the three and the widest walk, but on
      the SHALLOWEST stack — a third of resting's — so the hundred layers
      barely disagree and travel nearly as one. That is what keeps it
      readable at this rate: a coherent lattice walked hard, rather than a
      hundred of them shearing apart into grain the way searching does.

      The glows stay small, only a little over resting, so the lattice keeps
      its points instead of flooding. What carries the state instead is the
      KEY LIGHT, up to two and a half times its default and the only state
      that touches it — enough dome shading that the flow reads as crossing
      a lit ball rather than as a flat lamp changing pattern.
    */
    speaking: {
      speed: 4.5,
      drift: 0.13,
      depth: 0.26,
      glowSize: 0.062,
      hueRate: 0.045,
      exposure: 2.7,
      contrast: 0.9,
      light: 1.11
    }
  },
  // the layer ramp supplies its own rainbow, so the tint only shifts its
  // temperature: neutral at rest, cooled while searching, warmed while
  // answering
  stateColors: {
    idle: { tint: "#ffffff", sheen: "#bcd8ff" },
    thinking: { tint: "#9db8ff", sheen: "#7ea9ff" },
    speaking: { tint: "#ffc492", sheen: "#ffb277" }
  }
};

export type Shdr06Props = Omit<ShaderOrbProps, "variant">;

export function Shdr06({ size = 280, ...rest }: Shdr06Props) {
  return <ShaderOrb variant={shdr06Orb} size={size} {...rest} />;
}

export default Shdr06;

9. components/ui/shdr-07.tsx

/*
 * Deliberately not a `"use client"` module — see the note in `shdr-11.tsx`.
 *
 * Note for editors: the shader lives in a template literal, so its comments
 * must not contain backticks.
 */
import { ShaderOrb, type OrbVariant, type ShaderOrbProps } from "@/components/ui/orbkit-core";

/* ----------------------------------------------------------------------------
   SHDR-07 — a twist wave travelling out through the ball, wound around a
   lit polar column.

   Ported from a golfed twigl listing:

     for(float i,z,d,h;i++<5e1;o+=vec4(3,z,i,1)/d)
     {vec3 p=z*normalize(FC.rgb*2.-r.xyy),a;a.y++;p.z+=7.;
      a=mix(dot(a,p)*a,p,sin(h=length(p)-t))+cos(h)*cross(a,p);
      for(d=0.;d++<9.;a+=sin(round(a*d)-t).zxy/d);z+=d=.1*length(a.xz);}
     o=tanh(o/1e4);

   A near relative of shdr-22 — same march skeleton, same colour-coded
   accumulator, same cell-quantized turbulence. Three things make it a
   different animal.

   - THE ROTATION IS EXACT, AND ITS ANGLE VARIES. Written out,
     mix(dot(a,p)*a, p, sin h) + cos(h)*cross(a,p) is
     a(a.p)(1 - sin h) + p sin h + (a x p) cos h, which is Rodrigues with
     cos(theta) = sin(h) and sin(theta) = cos(h) — a true orthonormal
     rotation about the unit axis by theta = 90 degrees - h. Worth saying
     plainly, because the README's standing warning is that golfed
     rotations are NOT orthonormal and breathe the silhouette. This one is
     the exception, twice over: shdr-22 hid an exact 90-degree case,
     and this listing hides the general one.
   - h = length(p) - t IS THE WHOLE EFFECT. The rotation angle is the
     sample's RADIUS minus the clock, so every spherical shell is wound by
     a different amount and the winding travels outward with time. Torsion
     in the literal sense: twist per unit radius. It also means the field
     is organised into shells concentric with the ball before anything
     else touches it — this listing is an orb already.
   - THE DENSITY IS AXIAL. length(a.xz) is the distance from the twist
     axis, not from the origin, so the step collapses along the pole and
     the axis lights as a column running through the ball. shdr-22
     multiplies two radial lengths and gets streaks; this gets a spine.

   The axis is a fixed vertical (a.y++ on a zeroed vec3), where vectors
   wanders its axis with time. Fixed is right here: the column has to stay
   somewhere for the twist to be read against.

   Port decisions, each one a documented trap or rule in the README:

   - round() is ES 3.0 and does not exist in GLSL ES 1.0 — hand-written as
     floor(x + 0.5).
   - The golfed listing builds its axis out of an uninitialised vec3
     (a.y++). Uninitialised locals are UNDEFINED in ES 1.0, explicit here.
   - The listing drives the travelling wave and the cell flicker off ONE
     clock. They are separate motions — one is a wave crossing the ball,
     the other is lattice shimmer — so they get separate integrated clocks
     and can be tuned against each other.
   - The axis leans by rotating the SAMPLE into the axis frame rather than
     the axis into the world, so the density term can go on reading the
     perpendicular plane as a.xz and the mechanism stays the listing's.
   - p.z += 7 puts the camera outside already — re-derived as the family's
     ro/rd pair so distance and lens are separate knobs.
   - 1/d spikes where the turbulent point lands on the axis — clamped per
     step, as in every accumulator here.
   - Emitted light, so rgb is already premultiplied and alpha comes from
     the peak channel (see shdr-31).
---------------------------------------------------------------------------- */

/*
 * Step counts are `#define`s: ES 1.0 requires constant loop bounds. STEPS and
 * TURB are the listing's i++ < 5e1 and d++ < 9.
 */
const TORSION_FRAG = `
#define STEPS 50
#define TURB 9
#define AA 1

// Volume-reactive values, resolved once per fragment in main().
float torsionTurb;
float torsionTwist;
float torsionExposure;

// GLSL ES 1.0 has no round() — it arrived in ES 3.0. The listing quantizes
// with it, so it ships here. Halves round up rather than to even, which is
// what a lattice quantizer wants anyway.
vec3 roundv(vec3 x) { return floor(x + 0.5); }

vec3 torsionRender(vec2 fragCoord) {
  vec2 uv = (2.0 * fragCoord - uRes) / min(uRes.x, uRes.y);
  vec3 ro = vec3(0.0, 0.0, uP_camDist);
  vec3 rd = normalize(vec3(uv, -uP_focal));

  float shimmer = uP_speed; // integrated clock: cell flicker
  float wave = uP_wave;     // integrated clock: the travelling twist
  float spin = uP_spin;     // integrated clock: roll about the axis

  // the axis lean and the roll, applied to the SAMPLE rather than to the
  // axis — see the header
  float ct = cos(uP_tilt);
  float st = sin(uP_tilt);
  float cs = cos(spin);
  float ss = sin(spin);

  // the twist axis, unit by construction, which is what makes the
  // Rodrigues rotation below exact
  vec3 axis = vec3(0.0, 1.0, 0.0);

  vec3 acc = vec3(0.0);

  // transmittance carried front-to-back — near shells veil far ones
  float T = 1.0;

  // march only the span the envelope can light, as in shdr-01
  float z = max(uP_camDist - uP_envRadius * 1.3, 0.0);
  float zEnd = uP_camDist + uP_envRadius * 1.3;

  for (int it = 0; it < STEPS; it++) {
    vec3 world = ro + rd * z;

    // into the axis frame: lean about X, then roll about Y
    vec3 p = vec3(world.x, world.y * ct + world.z * st, -world.y * st + world.z * ct);
    p = vec3(p.x * cs - p.z * ss, p.y, p.x * ss + p.z * cs);

    /*
      The travelling twist. h is the sample's radius (scaled by the twist
      knob) minus the wave clock, and the line below is an exact rotation
      about the axis by 90 degrees - h. Because h depends only on radius,
      the winding is constant on spheres: the ball's own shells are the
      structure, and raising uP_twist puts more turns between the core and
      the surface.
    */
    float h = length(p) * torsionTwist - wave;
    vec3 a = mix(dot(axis, p) * axis, p, sin(h)) + cos(h) * cross(axis, p);

    // cell-quantized turbulence: every lattice cell flickers on its own
    // phase, the same construction shdr-22 uses
    for (int j = 0; j < TURB; j++) {
      float dj = float(j) + 1.0;
      a += torsionTurb * sin(roundv(a * dj) - shimmer).zxy / dj;
    }

    /*
      The axial density. At uP_column 0 this is the listing's
      length(a.xz) — distance from the twist axis, so the step collapses
      along the pole and the axis burns as a column. At 1 it is the plain
      radial length and the column dissolves into shells.
    */
    float d = uP_stepScale * mix(length(a.xz), length(a), uP_column);
    d = max(d, uP_envRadius * 0.003);

    /*
      The march's own colour code, from the listing: red constant, green
      by DEPTH into the ball, blue by STEP INDEX — the opposite assignment
      from shdr-22, and the reason this orb runs cyan-blue where that
      one runs red-green. Blue gets twice the clamp headroom: its ramp
      runs to STEPS (50) where red is fixed at 3, and an equal clamp would
      crush the step gradient first.
    */
    vec3 w = vec3(3.0, (z - uP_camDist + uP_envRadius) * uP_hueDepth, float(it) * uP_hueStep) / d;
    w = min(w, vec3(uP_stepClamp) * vec3(1.0, 1.0, 2.0));

    /*
      Normalize the clamped weight back to family units, exactly as in
      shdr-22: without this line the clamp value leaks into total
      energy and Exposure, Body fill and Diffusion all change meaning
      whenever the clamp moves.
    */
    w *= 20.0 / max(uP_stepClamp, 1.0);

    // envelope: plateau through the ball, cut 12% past the radius so the
    // analytic silhouette in main() still has emission left to cut
    float env = smoothstep(uP_envRadius * 1.12, uP_envRadius * uP_envCore, length(world));
    w = (w + uP_fill) * env;

    acc += T * w;
    T *= exp(-dot(w, vec3(0.299, 0.587, 0.114)) * uP_scatter);

    z += d;
    if (T < 0.004 || z > zEnd) break;
  }

  return acc;
}

void main() {
  torsionTurb = uP_turb * (1.0 + 0.5 * uInput);
  torsionExposure = uP_exposure * (1.0 - 0.35 * uOutput);
  // the ball winds tighter while the agent speaks
  torsionTwist = uP_twist * (1.0 + 0.4 * uOutput);

  vec3 acc = vec3(0.0);
#if AA > 1
  for (int mx = 0; mx < AA; mx++) {
    for (int my = 0; my < AA; my++) {
      vec2 offset = (vec2(float(mx), float(my)) + 0.5) / float(AA) - 0.5;
      acc += torsionRender(gl_FragCoord.xy + offset);
    }
  }
  acc /= float(AA * AA);
#else
  acc = torsionRender(gl_FragCoord.xy);
#endif

  // tanh tone map per channel — the envelope and transmittance change the
  // accumulator's scale, so the golfed /1e4 knee is a tunable here
  vec3 col = tanh3(acc / max(torsionExposure, 1.0));
  col = pow(clamp(col, 0.0, 1.0), vec3(uP_contrast));

  // saturation about luminance, then the tint
  float lum = dot(col, vec3(0.299, 0.587, 0.114));
  col = mix(vec3(lum), col, uP_saturation);
  col *= uC_tint;

  // alpha from the brightest channel, not luminance — a deep blue tail
  // has low luminance but must not go transparent
  float peak = max(col.r, max(col.g, col.b));
  float a = clamp(peak * uP_alphaGain, 0.0, 1.0);

  // Analytic silhouette — identical construction to shdr-01: exact
  // ray-to-centre distance against the radius, colour AND alpha.
  vec3 mrd = normalize(vec3(orbUV(), -uP_focal));
  float closest = length(cross(vec3(0.0, 0.0, uP_camDist), mrd));
  float band = mix(0.35, 0.012, clamp(uP_edge, 0.0, 1.0));
  float mask = 1.0 - smoothstep(uP_envRadius * (1.0 - band), uP_envRadius * 1.005, closest);
  col *= mask;
  a *= mask;

  // safety taper at the frame boundary — colour as well as alpha
  float r2d = length(orbUV());
  float fade = 1.0 - smoothstep(uP_edgeFade, 1.0, r2d);
  col *= fade;
  a *= fade;

  // Emitted light, so rgb is already premultiplied — do NOT scale by alpha
  // again (see the same note in shdr-31).
  gl_FragColor = vec4(col, a);
}
`;

export const shdr07Orb: OrbVariant = {
  key: "shdr-07",
  label: "SHDR-07",
  note: "a twist wave travelling out through the ball around a lit column",
  frag: TORSION_FRAG,
  params: [
    { key: "speed", label: "Cell shimmer", min: 0.015, max: 10, step: 0.05, default: 0.5, integrate: true },
    { key: "wave", label: "Wave speed", min: 0, max: 8, step: 0.03, default: 0.7, integrate: true },
    { key: "twist", label: "Twist", min: 0, max: 8, step: 0.02, default: 1 },
    { key: "spin", label: "Roll", min: 0, max: 3, step: 0.015, default: 0.1, integrate: true },
    { key: "tilt", label: "Axis lean", min: -1.5, max: 1.5, step: 0.015, default: 0.3 },
    { key: "camDist", label: "Camera distance", min: 1, max: 50, step: 0.3, default: 7 },
    { key: "focal", label: "Lens", min: 0.15, max: 15, step: 0.1, default: 2.25 },
    { key: "turb", label: "Cell turbulence", min: 0, max: 5, step: 0.03, default: 1 },
    { key: "column", label: "Column release", min: 0, max: 1, step: 0.01, default: 0 },
    { key: "stepScale", label: "Step scale", min: 0.005, max: 1.5, step: 0.005, default: 0.1 },
    { key: "hueDepth", label: "Depth hue", min: 0, max: 10, step: 0.03, default: 0.75 },
    { key: "hueStep", label: "Step hue", min: 0, max: 10, step: 0.03, default: 0.45 },
    { key: "envRadius", label: "Envelope radius", min: 0.15, max: 15, step: 0.1, default: 2.6 },
    { key: "envCore", label: "Envelope core", min: 0.3, max: 1.02, step: 0.01, default: 0.88 },
    { key: "fill", label: "Body fill", min: 0, max: 100, step: 0.3, default: 0.15 },
    { key: "stepClamp", label: "Step clamp", min: 3, max: 5000, step: 10, default: 400 },
    { key: "scatter", label: "Diffusion", min: 0, max: 0.5, step: 0.003, default: 0.01 },
    { key: "exposure", label: "Exposure", min: 1.5, max: 5000, step: 5, default: 60 },
    { key: "contrast", label: "Contrast", min: 0.15, max: 15, step: 0.1, default: 1.3 },
    { key: "saturation", label: "Saturation", min: 0, max: 4, step: 0.02, default: 1.15 },
    { key: "alphaGain", label: "Alpha gain", min: 0.05, max: 15, step: 0.1, default: 2 },
    { key: "edge", label: "Edge sharpness", min: 0, max: 1, step: 0.01, default: 1 },
    { key: "edgeFade", label: "Halo falloff", min: 0.1, max: 3, step: 0.015, default: 0.98 }
  ],
  colors: [{ key: "tint", label: "Tint", default: "#ffffff" }],
  /*
    Staged on the twist, which is the orb's whole subject: how many turns
    of winding sit between the core and the surface. The wave clock is the
    second lever — how fast that winding travels out through the shells —
    and both are phase-safe (twist is a scale on radius, not on a clock).
  */
  statePresets: {
    // at rest: a slow half-turn of winding drifting outward
    idle: {
      speed: 0.5,
      wave: 0.7,
      twist: 1,
      turb: 1,
      column: 0,
      exposure: 60,
      scatter: 0.01,
      alphaGain: 2
    },
    /*
      searching: the ball WINDS UP — three and a half times the twist, so
      the shells shear hard against each other — while the wave almost
      stops. Exposure goes UP, not down: a wound spring is stored energy,
      and this state is the darkest of the three on purpose.
    */
    thinking: {
      speed: 1.5,
      wave: 0.15,
      twist: 3.6,
      turb: 1.35,
      column: 0,
      exposure: 88,
      scatter: 0.011,
      alphaGain: 2
    },
    /*
      answering: the winding UNWINDS to a third of idle and the wave races
      out through the shells at more than four times idle — the tension
      released outward — with the column let go and the knee less than a
      third of the thinking state's. The release is the bright one.
    */
    speaking: {
      speed: 0.9,
      wave: 3.2,
      twist: 0.35,
      turb: 0.85,
      column: 0.45,
      exposure: 26,
      scatter: 0.006,
      alphaGain: 2.7
    }
  },
  // the march colour-codes itself, so the tint only shifts temperature:
  // neutral at rest, cooled while searching, warmed while answering
  stateColors: {
    idle: { tint: "#ffffff" },
    thinking: { tint: "#9db8ff" },
    speaking: { tint: "#ffc492" }
  }
};

export type Shdr07Props = Omit<ShaderOrbProps, "variant">;

export function Shdr07({ size = 280, ...rest }: Shdr07Props) {
  return <ShaderOrb variant={shdr07Orb} size={size} {...rest} />;
}

export default Shdr07;

10. components/ui/shdr-08.tsx

/*
 * Deliberately not a `"use client"` module — see the note in `shdr-11.tsx`.
 *
 * Note for editors: the shader lives in a template literal, so its comments
 * must not contain backticks.
 */
import { ShaderOrb, type OrbVariant, type ShaderOrbProps } from "@/components/ui/orbkit-core";

/* ----------------------------------------------------------------------------
   SHDR-08 — mother-of-pearl: contour bands wrapped around the ball, every band
   carrying its own hue.

   Ported from a five-line twigl listing:

     vec2 p=FC.xy*6./r.y;
     for(float i;i++<1e1;i)
     p+=sin(p.yx*i+i*i+t*i+r)/i;
     o=tanh(.2/tan(p.y+vec4(0,.1,.3,0)));
     o*=o;

   What it actually is, decoded:

   - The loop is a TEN-OCTAVE FEEDBACK WARP. Each octave reads the previous
     octave's result with its components SWAPPED (p.yx), which is what curls
     the field instead of merely rippling it. i*i is a per-octave phase so
     the octaves never line up, and t*i runs octave ten at ten times the
     clock — fine detail boils while the large structure barely moves.
   - The last line is the whole image. tan() has a zero every PI, so
     .2/tan() blows up there and tanh saturates into a flat crest; tan is
     unbounded at PI/2, so .2/tan passes through zero and the valley floor
     is exact black. The band period is PI, not 2PI.
   - vec4(0,.1,.3,0) is a PER-CHANNEL PHASE. The spacing is not linear —
     0, 1, 3 in units of the split — so red and green sit close together
     and blue lags far behind: every band edge breaks into a warm shoulder
     on one side and a deep blue one on the other.
   - o *= o folds the sign (cot is negative on half of every period) and
     squares the contrast in one move.

   Port decisions, each one a documented trap or rule in the README:

   - THE ORB IS THE OBJECT: this is a flat 2D field, so it is sampled
     through a stereographic projection of the dome — the bands compress
     toward the limb the way layers on a real shell do — and finished with
     a fresnel sheen. It is NOT a disc masked out of the plane.
   - The projection is taken on the UNROTATED dome, and all motion is
     projection-safe 2D (a swirl of the plane plus a drift across the
     bands). Rolling the dome in 3D first mixes sp.x into sp.z and the
     divisor collapses — the trap written up at length in shdr-02.
   - Nacre is thin-film interference, so hue follows the layer AND the
     viewing angle: the cosine palette is keyed to the band coordinate
     itself, and 1 - z rotates that hue toward the limb, so the ball's own
     curvature colours the pattern.
   - The listing's +r adds the RESOLUTION as phase. Free entropy in a demo;
     here it would re-seed the whole pattern on every canvas resize, so a
     fixed vec2 does the same job and holds still.
   - .2/tan(x) is .2*cot(x), computed as cos/sin so there is ONE guarded
     division. Dividing by tan() guards nothing — tan is itself unbounded
     where cos is zero.
   - The golfed listing relies on i starting at zero; uninitialised locals
     are UNDEFINED in GLSL ES 1.0, explicit here.
   - Surface-lit and mask-bounded, so alpha IS coverage — premultiplied
     output, as in shdr-17.
---------------------------------------------------------------------------- */

/*
 * Step counts are `#define`s: ES 1.0 requires constant loop bounds. OCTAVES
 * is the listing's i++ < 1e1. AA supersamples the band function, which is the
 * only defence against limb aliasing available here — WebGL 1 has no fwidth
 * without an extension, and deriving the footprint through ten octaves of
 * feedback warp analytically is not worth the algebra for a shader this cheap.
 */
const NACRE_FRAG = `
#define OCTAVES 10
#define AA 2

const float TAU = 6.28318530718;

// The listing's +r, minus the resolution dependence (see the header).
const vec2 SEED = vec2(4.7, 2.3);

// Volume-reactive values, resolved once per fragment in main().
float nacreWarp;
float nacreThick;
float nacreGain;

/*
  The listing's entire tone map: o = tanh(.2 / tan(x)); o *= o.

  The square folds the sign, so abs() on the denominator is not an
  approximation here — it is exact, and it removes the branch.
*/
vec3 cotBands(vec3 x, float k) {
  vec3 b = tanh3(k * cos(x) / max(abs(sin(x)), vec3(1e-4)));
  return b * b;
}

vec3 nacreRender(vec2 fragCoord) {
  vec2 uv = (2.0 * fragCoord - uRes) / min(uRes.x, uRes.y);
  float R = max(uP_radius, 0.001);

  // the dome: the front hemisphere of a unit ball, in screen space
  vec2 pl = uv / R;
  float z = sqrt(max(1.0 - dot(pl, pl), 0.0));
  vec3 n = vec3(pl, z);

  float t = uP_speed; // integrated clock: the boil

  /*
    Stereographic projection, on the UNROTATED dome. Equal steps in screen
    space map to ever-larger steps in pattern space toward the rim, which
    is the foreshortening that sells a flat field as wrapped geometry.
    uP_bulge softens the divisor — higher flattens the shell back toward a
    disc, lower crowds the layers into the limb.
  */
  vec2 p = n.xy / (n.z + 1.0 + uP_bulge) * uP_scale;

  // projection-safe 2D motion, in place of a dome spin: the plane turns,
  // and the bands travel across themselves
  float sw = uP_swirl; // integrated clock
  p = mat2(cos(sw), -sin(sw), sin(sw), cos(sw)) * p;
  p.y -= uP_flow;      // integrated clock

  /*
    The ten-octave feedback warp. q is fed back into itself with the
    components swapped, so each octave curls what the last one drew.
  */
  vec2 q = p;
  for (int j = 0; j < OCTAVES; j++) {
    float i = float(j) + 1.0;
    q += nacreWarp * sin(q.yx * i + i * i + t * i + SEED) / i;
  }

  // the bands, with the listing's uneven per-channel phase kept as a ratio
  // so one slider widens the whole split
  vec3 band = cotBands(vec3(q.y) + vec3(0.0, 1.0, 3.0) * uP_split, nacreThick);
  float lev = dot(band, vec3(1.0 / 3.0));

  /*
    A dark body colour under the bands, so the valleys read as the shell
    itself rather than as holes punched through the ball. The band term
    stays PER-CHANNEL through the palette multiply — that is what carries
    the colour fringing; collapsing it to lev first would throw away the
    only thing the vec4 phase was for.
  */
  vec3 col = uC_deep * uP_floor;
  col += band * mix(uC_low, uC_crest, smoothstep(0.1, 0.9, lev)) * nacreGain;

  /*
    Thin-film interference. The cosine palette is keyed to the band
    coordinate, so every layer carries its own hue — the inside of a shell
    — and uP_view rotates that hue with the viewing angle through 1 - z,
    which means the sphere's curvature is doing the colouring. Multiplied
    in rather than mixed to, so it bends hues without erasing the palette.
  */
  vec3 irid = 0.5 + 0.5 * cos(TAU * (q.y * uP_irisScale + (1.0 - z) * uP_view + t * 0.03 + vec3(0.0, 0.33, 0.67)));
  col = mix(col, col * (0.25 + 1.9 * irid), uP_iris);

  col = pow(max(col, vec3(0.0)), vec3(uP_contrast));

  // dome shading keeps the ball a ball under the pattern
  float lambert = clamp(dot(n, normalize(vec3(-0.45, 0.55, 0.72))), 0.0, 1.0);
  col *= 0.35 + uP_light * lambert;

  // fresnel sheen: the wet gloss of a shell, and the thing that keeps the
  // limb reading as a surface where the bands have compressed to a blur
  float fres = 1.0 - z;
  fres = fres * fres * fres;
  col += uC_sheen * uP_rim * fres;

  return col;
}

void main() {
  /*
    The BEAT: warp driven between 0.6 and 1.8 on a slow, unbroken cycle —
    one full swing every second and a third — with nothing held at either
    end. Warp is the amplitude of the octave loop that folds the bands, so
    sweeping it three to one makes the whole field draw in and open again
    rather than change colour or brightness — a swell, not a flash.

    Absolute bounds, so the range is exactly the range; that is why the mix
    sits outside the volume term below. At beat zero nothing here applies
    and the dialled warp stands, so a state that does not ask for it is
    untouched.

    Well under 3Hz, which matters: above that a full-field oscillation is
    in the band photosensitivity guidance warns about, and this one covers
    the whole ball. The rate is the constant below — raising it much past
    18 walks back into that range.
  */
  float beat = 0.5 - 0.5 * cos(uAnim * 5.0);

  // Volume coupling: the user's voice churns the warp harder, the agent's
  // widens the crests and brightens them.
  nacreWarp = mix(uP_warp * (1.0 + 0.45 * uInput), mix(0.6, 1.8, beat), uP_beat);
  nacreThick = uP_thick * (1.0 + 0.6 * uOutput);
  nacreGain = uP_gain * (0.85 + 0.4 * uOutput);

  vec2 uv = orbUV();
  float mask = smoothstep(0.012, -0.012, length(uv) - max(uP_radius, 0.001));

  // Nothing outside the silhouette is ever visible, so skip AA * AA warps
  // for it rather than shading transparent sky.
  if (mask <= 0.0) {
    gl_FragColor = vec4(0.0);
    return;
  }

  vec3 col = vec3(0.0);
#if AA > 1
  for (int mx = 0; mx < AA; mx++) {
    for (int my = 0; my < AA; my++) {
      vec2 off = (vec2(float(mx), float(my)) + 0.5) / float(AA) - 0.5;
      col += nacreRender(gl_FragCoord.xy + off);
    }
  }
  col /= float(AA * AA);
#else
  col = nacreRender(gl_FragCoord.xy);
#endif

  // Surface orb bounded by a mask: alpha IS coverage, so premultiply — the
  // opposite convention from the emissive orbs (see shdr-31).
  float a = mask;
  gl_FragColor = vec4(max(col, vec3(0.0)) * a, a);
}
`;

export const shdr08Orb: OrbVariant = {
  key: "shdr-08",
  label: "SHDR-08",
  note: "mother-of-pearl contour bands, each layer its own hue",
  frag: NACRE_FRAG,
  params: [
    { key: "speed", label: "Boil", min: 0.015, max: 10, step: 0.05, default: 0.35, integrate: true },
    { key: "flow", label: "Band drift", min: 0, max: 5, step: 0.03, default: 0.25, integrate: true },
    { key: "swirl", label: "Swirl", min: 0, max: 3, step: 0.015, default: 0.06, integrate: true },
    { key: "radius", label: "Radius", min: 0.15, max: 3, step: 0.015, default: 0.9 },
    { key: "scale", label: "Pattern scale", min: 0.3, max: 20, step: 0.1, default: 5.5 },
    { key: "bulge", label: "Dome bulge", min: 0, max: 4, step: 0.02, default: 0.3 },
    { key: "warp", label: "Warp", min: 0, max: 3, step: 0.02, default: 1 },
    { key: "beat", label: "Warp beat", min: 0, max: 1, step: 0.01, default: 0 },
    { key: "thick", label: "Band width", min: 0.02, max: 2, step: 0.01, default: 0.2 },
    { key: "split", label: "Chromatic split", min: 0, max: 1, step: 0.005, default: 0.1 },
    { key: "iris", label: "Iridescence", min: 0, max: 2, step: 0.01, default: 0.7 },
    { key: "irisScale", label: "Iridescence scale", min: 0, max: 2, step: 0.005, default: 0.315 },
    { key: "view", label: "Angle shift", min: 0, max: 3, step: 0.01, default: 1.34 },
    { key: "floor", label: "Body fill", min: 0, max: 3, step: 0.01, default: 0.8 },
    { key: "gain", label: "Brightness", min: 0.05, max: 5, step: 0.05, default: 1.1 },
    { key: "contrast", label: "Contrast", min: 0.15, max: 10, step: 0.05, default: 1.15 },
    { key: "light", label: "Key light", min: 0, max: 3, step: 0.015, default: 0.75 },
    { key: "rim", label: "Rim sheen", min: 0, max: 3, step: 0.015, default: 0.5 }
  ],
  /*
   * Four stops: the shell body the bands sit on, the two ends of the band
   * ramp, and the fresnel sheen. The iridescence multiplies a rainbow over
   * all of them, so the palette sets the mood and the shimmer supplies the
   * rest of the hues.
   */
  colors: [
    { key: "deep", label: "Shell body", default: "#0d1430" },
    { key: "low", label: "Dim layer", default: "#2fb8c6" },
    { key: "crest", label: "Bright layer", default: "#fff1de" },
    { key: "sheen", label: "Sheen", default: "#bfe4ff" }
  ],
  /*
    Staged on the two levers that are phase-safe integrated clocks — the
    boil and the band drift — plus band width, which is the orb's loudest
    single control: narrow crests read as taut lines, wide ones flood the
    shell with light.

    IRIDESCENCE SCALE AND ANGLE SHIFT ARE ALL BUT FIXED, and the reason
    matters. Both multiply their way into the cosine that makes the
    thin-film rainbow, so they are spatial FREQUENCIES, not amounts: easing
    one across a wide span sweeps the field through every frequency in
    between, which races the fringes across the shell instead of
    cross-fading them. Pattern scale is held fixed here for the same reason,
    as are cell count in shdr-05 and lattice spacing in shdr-06.

    So they live on the params above, where resting and answering take them
    untouched. Searching nudges them and only just — scale to 0.83 of the
    fixed value, angle shift to 0.90 — which moves the fringes by well under
    one of their own periods, and reads as the shimmer settling rather than
    as a scramble. That margin is the whole budget: a state that wanted
    twice or a third of these would have to snap them, not glide.

    Iridescence itself — the amount — is safe to stage at any span: it is a
    plain mix toward the same rainbow, so it fades rather than moves.
  */
  statePresets: {
    /*
      at rest: slow and BRIGHT. The clocks stay gentle — a slow boil, the
      layers barely drifting — which is what still reads as at rest, but
      everything about the surface is turned up under them. Crests run wide,
      three quarters of the answering state's width, on more than twice the
      default brightness and the highest gain of the three.

      The shimmer carries the rest: iridescence near half again its default,
      laid over the fixed scale and angle shift the staging note above keeps
      out of the presets — the rainbow swings hard as the dome curves away,
      and it does so identically in all three states. The dome is bulged
      well past default and the body fill pulled back beneath it, so the
      light sits in the bands rather than in the shell behind them.
    */
    idle: {
      speed: 0.37,
      flow: 0.24,
      swirl: 0.06,
      bulge: 0.8,
      warp: 0.96,
      thick: 0.54,
      split: 0.1,
      iris: 1.02,
      floor: 0.57,
      gain: 2.45,
      contrast: 1.65,
      rim: 0.495
    },
    /*
      searching: the field CHURNS in place — boil at over four times resting,
      warp half again — while the layers all but stop drifting, a quarter of
      resting's flow on a third of its swirl. Crests hold exactly resting's
      width, so nothing about the BANDS says searching; what says it is that
      they are boiling hard and going nowhere.

      The shimmer is the other half of it: iridescence at nearly twice
      resting's, the strongest of the three by a wide margin, over a split
      that answering now matches. And still deliberately the dimmest —
      tension reads as held light, not spent light.
    */
    thinking: {
      speed: 1.61,
      flow: 0.06,
      swirl: 0.015,
      warp: 1.56,
      thick: 0.54,
      split: 0.3,
      iris: 1.89,
      irisScale: 0.26,
      view: 1.21,
      floor: 0.77,
      gain: 0.85,
      contrast: 1.65,
      rim: 0.495
    },
    /*
      answering: the field SWELLS and TRAVELS. Warp is on the beat at full
      depth, swinging 0.6 to 1.8 across a second and a third with nothing
      held at either end, so the bands draw in and open again slowly enough
      to watch — that swell IS the state, and the dialled warp below is only
      what you would see with the beat off.

      Under it everything is moving: the fastest boil of the three, and the
      hardest drift anywhere in this orb at ten times resting's, on crests
      pulled to half the width the other two both hold. Iridescence drops to
      the lowest of the three while the split opens out to match searching's
      — so this state spends its light on MOVEMENT rather than on shimmer,
      which is what keeps the two apart now that both run their bands hard.
    */
    speaking: {
      speed: 2,
      flow: 2.49,
      swirl: 0.18,
      warp: 1.84,
      beat: 1,
      thick: 0.27,
      split: 0.3,
      iris: 0.45,
      gain: 1.75,
      contrast: 0.78
    }
  },
  // abalone at rest, cold pearl while searching, warm fire-opal while
  // answering — the at-a-glance read, as in the sibling orbs
  stateColors: {
    idle: {
      deep: "#0d1430",
      low: "#2fb8c6",
      crest: "#fff1de",
      sheen: "#bfe4ff"
    },
    thinking: {
      deep: "#0a0f2c",
      low: "#6f7cff",
      crest: "#dfe8ff",
      sheen: "#9fd0ff"
    },
    speaking: {
      deep: "#2a0f22",
      low: "#ff7a4d",
      crest: "#fff0c9",
      sheen: "#ffc9a8"
    }
  }
};

export type Shdr08Props = Omit<ShaderOrbProps, "variant">;

export function Shdr08({ size = 280, ...rest }: Shdr08Props) {
  return <ShaderOrb variant={shdr08Orb} size={size} {...rest} />;
}

export default Shdr08;

11. components/ui/shdr-09.tsx

/*
 * Deliberately not a `"use client"` module — see the note in `shdr-11.tsx`.
 *
 * Note for editors: the shader lives in a template literal, so its comments
 * must not contain backticks.
 */
import { ShaderOrb, type OrbVariant, type ShaderOrbProps } from "@/components/ui/orbkit-core";

/* ----------------------------------------------------------------------------
   SHDR-09 — torn rings of rainbow light, worn as the ball's own latitudes.

   Ported from a golfed twigl listing (its feedback variant):

     vec2 p=(FC.xy*2.-r)/r.y/.2,v;
     for(float i,l,f;i++<1e1;
         o+=.03/max(l=length(v)-i,-l*3.)*(cos(t-i*.4+.1/l+vec4(0,1,2,3))+1.1))
       for(v=p,f=0.;f++<9.;v+=sin(ceil(v*f+i*.9)-t/2.)/f);
     o=max(tanh(o+(o=texture(b,(FC.xy+r.y*.05*sin(FC.xy+FC.yx/.6))/r))*o),.0);

   What it actually is, decoded:

   - TEN CONCENTRIC RINGS. The accumulator lives in the outer loop's
     increment slot, so it runs after the inner loop: each ring re-warps
     the ORIGINAL point from scratch (v=p) with its own seed i*.9, then
     draws the circle of radius i through that ring's private distortion.
     Ten rings, ten different tears — they are not ten copies of one shape.
   - The warp is CELL-QUANTIZED, sin(ceil(v*f) - t): every lattice cell
     flickers on its own phase, the same construction shdr-22 uses for
     its voxel shimmer, here in 2D and re-seeded per ring.
   - max(l, -l*3.) is an ASYMMETRIC absolute value — l outside the ring,
     3|l| inside. So 1/that lights the outer shoulder of every ring three
     times as hard as the inner one, and the rings read as expanding
     wavefronts rather than as symmetric wires.
   - .1/l inside the colour phase is the good part. It sweeps through the
     entire hue wheel in the last hair of distance before the ring, so
     each ring carries a rainbow fringe that compresses to a hard line
     exactly where the glow peaks.
   - The last line is FRAME FEEDBACK: o + prev*o, where prev is the
     previous frame read through a pixel-scale sinusoidal scramble. That
     is what smears the rings into trails in the original.

   Port decisions, each one a documented trap or rule in the README:

   - THE ORB IS THE OBJECT, and this listing gets a better mapping than
     the family's usual one. A field of concentric circles about the
     origin does not want a stereographic wrap — the circles ARE latitude
     rings, so radius maps to the POLAR ANGLE from the dome's axis and the
     foreshortening is exact instead of approximated.
   - That mapping also has no pole to collapse. acos() is defined across
     the whole sphere, so unlike shdr-08 and shdr-26 — where the
     stereographic divisor forbids it — this dome rolls freely in 3D, and
     rings sweep into view over the limb as it turns.
   - THE FEEDBACK TERM IS NOT PORTED. The engine renders one pass into one
     canvas with no previous-frame texture, so there is nothing to sample;
     adding a ping-pong framebuffer is an engine change, not an orb one.
     What is lost is the temporal smear. What the rings do in space is all
     here.
   - Both singular divisors are softened rather than guarded: the glow
     divisor is floored, which doubles as the LINE WIDTH (the same trick
     as shdr-02's coreClamp), and the .1/l colour phase becomes
     l/(l*l+g), which is 1/l everywhere the listing cared about and finite
     on the ring itself. Left raw, the hue sweep reaches infinite frequency
     exactly where the glow is brightest — the one place aliasing is
     guaranteed to show.
   - The golfed listing relies on i, l and f starting at zero;
     uninitialised locals are UNDEFINED in GLSL ES 1.0, explicit here.
   - Surface-lit and mask-bounded, so alpha IS coverage — premultiplied
     output, as in shdr-17.
---------------------------------------------------------------------------- */

/*
 * Step counts are `#define`s: ES 1.0 requires constant loop bounds. RINGS and
 * TURB are the listing's i++ < 1e1 and f++ < 9. Every ring pays for its own
 * warp, so this is RINGS * TURB sines per sample before supersampling.
 */
const IRIS_FRAG = `
#define RINGS 10
#define TURB 9
#define AA 2

// Volume-reactive values, resolved once per fragment in main().
float irisWarp;
float irisGlow;
float irisFringe;

vec3 irisRender(vec2 fragCoord) {
  vec2 uv = (2.0 * fragCoord - uRes) / min(uRes.x, uRes.y);
  float R = max(uP_radius, 0.001);

  // the dome: the front hemisphere of a unit ball, in screen space
  vec2 pl = uv / R;
  float z = sqrt(max(1.0 - dot(pl, pl), 0.0));
  vec3 n = vec3(pl, z);

  float t = uP_speed; // integrated clock

  // tilt about X, then roll about Y on its own integrated clock
  float ct = cos(uP_tilt);
  float st = sin(uP_tilt);
  vec3 sp = vec3(n.x, n.y * ct - n.z * st, n.y * st + n.z * ct);
  float cr = cos(uP_spin);
  float sr = sin(uP_spin);
  sp = vec3(sp.x * cr - sp.z * sr, sp.y, sp.x * sr + sp.z * cr);

  /*
    Radius becomes the polar angle from the dome's axis (see the header),
    so ring i lands on the latitude at angle i / uP_scale and the rings
    crowd toward the limb the way a globe's latitudes do. acos is defined
    on the whole sphere, so the roll above can put the axis anywhere —
    including behind the visible face, which sweeps the outer rings into
    view over the limb.
  */
  float pol = acos(clamp(sp.z, -1.0, 1.0));
  vec2 dir = sp.xy / max(length(sp.xy), 1e-4);
  vec2 p = dir * pol * uP_scale;

  vec3 acc = vec3(0.0);

  for (int ri = 0; ri < RINGS; ri++) {
    float i = float(ri) + 1.0;

    /*
      Each ring re-warps the ORIGINAL point with its own seed, exactly as
      the listing does — this loop is why the rings tear differently
      instead of nesting like tree rings.
    */
    vec2 v = p;
    for (int j = 0; j < TURB; j++) {
      float f = float(j) + 1.0;
      v += irisWarp * sin(ceil(v * f + i * uP_seed) - t * 0.5) / f;
    }

    float l = length(v) - i;

    // the asymmetric absolute value, with the floor doubling as the line
    // width — a wider floor is a fatter, softer wavefront
    float side = max(max(l, -uP_inner * l), uP_lineSoft);

    /*
      The hue sweep, softened. l/(l*l+g) tracks 1/l off the ring and rolls
      over to a finite peak on it, so the rainbow compresses into a fringe
      of finite width instead of an aliased band.
    */
    float fr = irisFringe * l / (l * l + uP_fringeSoft);
    vec3 hue = cos(t - i * uP_ringPhase + fr + vec3(0.0, 1.0, 2.0)) + 1.1;

    acc += (irisGlow / side) * hue;
  }

  // the listing's tanh knee, with the divisor exposed
  vec3 col = tanh3(acc / max(uP_exposure, 0.001));
  col = pow(clamp(col, 0.0, 1.0), vec3(uP_contrast));

  // saturation about luminance, then the tint
  float lum = dot(col, vec3(0.299, 0.587, 0.114));
  col = mix(vec3(lum), col, uP_saturation);
  col *= uC_tint;

  // dome shading keeps the ball a ball under the rings — gentler than the
  // sibling orbs use, because these rings are emission and a hard lambert
  // reads as a shadow thrown across a light source
  float lambert = clamp(dot(n, normalize(vec3(-0.45, 0.55, 0.72))), 0.0, 1.0);
  col *= 0.55 + uP_light * lambert;

  float fres = 1.0 - z;
  fres = fres * fres * fres;
  col += uC_sheen * uP_rim * fres;

  return col;
}

void main() {
  // Volume coupling: the user's voice tears the rings harder, the agent's
  // brightens them and opens the rainbow fringe.
  irisWarp = uP_warp * (1.0 + 0.5 * uInput);
  irisGlow = uP_glow * (0.85 + 0.5 * uOutput);
  irisFringe = uP_fringe * (1.0 + 0.6 * uOutput);

  vec2 uv = orbUV();
  float mask = smoothstep(0.012, -0.012, length(uv) - max(uP_radius, 0.001));

  // Ninety sines per sample before supersampling — none of them worth
  // paying for outside the silhouette.
  if (mask <= 0.0) {
    gl_FragColor = vec4(0.0);
    return;
  }

  vec3 col = vec3(0.0);
#if AA > 1
  for (int mx = 0; mx < AA; mx++) {
    for (int my = 0; my < AA; my++) {
      vec2 off = (vec2(float(mx), float(my)) + 0.5) / float(AA) - 0.5;
      col += irisRender(gl_FragCoord.xy + off);
    }
  }
  col /= float(AA * AA);
#else
  col = irisRender(gl_FragCoord.xy);
#endif

  // Surface orb bounded by a mask: alpha IS coverage, so premultiply — the
  // opposite convention from the emissive orbs (see shdr-31).
  float a = mask;
  gl_FragColor = vec4(max(col, vec3(0.0)) * a, a);
}
`;

export const shdr09Orb: OrbVariant = {
  key: "shdr-09",
  label: "SHDR-09",
  note: "torn rings of rainbow light worn as the ball's latitudes",
  frag: IRIS_FRAG,
  params: [
    { key: "speed", label: "Anim speed", min: 0.015, max: 10, step: 0.05, default: 0.6, integrate: true },
    { key: "spin", label: "Roll", min: 0, max: 3, step: 0.015, default: 0.12, integrate: true },
    { key: "tilt", label: "Tilt", min: -1.5, max: 1.5, step: 0.015, default: 0.4 },
    { key: "radius", label: "Radius", min: 0.15, max: 3, step: 0.015, default: 0.9 },
    { key: "scale", label: "Ring spacing", min: 0.3, max: 20, step: 0.1, default: 3.5 },
    { key: "warp", label: "Tear", min: 0, max: 3, step: 0.02, default: 0.45 },
    { key: "seed", label: "Ring seed", min: 0, max: 3, step: 0.01, default: 0.9 },
    { key: "glow", label: "Ring glow", min: 0, max: 1, step: 0.002, default: 0.05 },
    { key: "lineSoft", label: "Ring width", min: 0.002, max: 1, step: 0.002, default: 0.05 },
    { key: "inner", label: "Inner falloff", min: 0.2, max: 12, step: 0.05, default: 3 },
    { key: "fringe", label: "Rainbow fringe", min: 0, max: 2, step: 0.005, default: 0.1 },
    { key: "fringeSoft", label: "Fringe width", min: 0.001, max: 1, step: 0.001, default: 0.003 },
    { key: "ringPhase", label: "Ring hue step", min: 0, max: 3, step: 0.01, default: 0.8 },
    { key: "exposure", label: "Exposure", min: 0.05, max: 20, step: 0.05, default: 1.1 },
    { key: "contrast", label: "Contrast", min: 0.15, max: 10, step: 0.05, default: 1.1 },
    { key: "saturation", label: "Saturation", min: 0, max: 4, step: 0.02, default: 1.2 },
    { key: "light", label: "Key light", min: 0, max: 3, step: 0.015, default: 0.5 },
    { key: "rim", label: "Rim sheen", min: 0, max: 3, step: 0.015, default: 0.4 }
  ],
  colors: [
    { key: "tint", label: "Tint", default: "#ffffff" },
    { key: "sheen", label: "Sheen", default: "#b9d6ff" }
  ],
  /*
    Staged on the two things the rings own: how hard they TEAR, and how
    wide the wavefront is. Ring spacing never moves between states — it
    sets how many rings are on the ball, and a gliding count reads as the
    ball inflating rather than as a change of mood.
  */
  statePresets: {
    // at rest: slow, softly torn, wide calm wavefronts
    idle: {
      speed: 0.6,
      spin: 0.12,
      warp: 0.45,
      glow: 0.05,
      lineSoft: 0.05,
      fringe: 0.1,
      exposure: 1.1,
      contrast: 1.15
    },
    /*
      searching: the rings tear right open — warp near six times idle — and
      broaden rather than thin, so the ball reads as churning instead of
      brittle. The rainbow fringe runs almost to full and softens by two
      orders of magnitude, which is what turns the tears into wide spectral
      bands; exposure, saturation and the rim all lift together to keep that
      readable. Speed and spin sit at the schema defaults, so the ball keeps
      idle's rotation and the whole change reads in the surface, not the
      motion.
    */
    thinking: {
      warp: 2.54,
      glow: 0.2,
      lineSoft: 0.152,
      fringe: 0.96,
      fringeSoft: 0.854,
      ringPhase: 2.53,
      exposure: 1.75,
      contrast: 1.15,
      saturation: 2.22,
      light: 1.035,
      rim: 0.66
    },
    /*
      answering: the tears RELAX to half idle's and the wavefronts broaden
      to four times it — bands of light rather than rings — but the ball is
      travelling under them at near eight times idle speed, the fastest of
      the three by a wide margin. Broad calm shapes moving fast, which is
      the opposite of searching's tight shapes churning in place.

      The colour comes off the FRINGE rather than the glow: eight times
      idle's fringe strength on twenty times its width, with the ring glow
      pulled just under idle's and the knee down, so the light gathers in
      the spectral edges instead of flooding the rings themselves. The key
      light doubles to keep a dome under all that, and the rim sheen is off
      almost entirely.

      Note the RING SEED is staged here, and it is the one value in this
      preset that cannot glide: it sits inside a ceil() in the octave loop,
      so it quantizes, and easing it across a state change steps rather than
      fades. The move is small — 0.9 to 1.02 — but if the transition pops,
      that is what is popping, and the fix is to hold it equal in all three
      states rather than to slow it down.
    */
    speaking: {
      speed: 4.7,
      spin: 0.34,
      warp: 0.22,
      seed: 1.02,
      glow: 0.04,
      lineSoft: 0.22,
      fringe: 0.81,
      fringeSoft: 0.058,
      ringPhase: 1.03,
      exposure: 0.68,
      contrast: 0.85,
      light: 0.99,
      rim: 0.015
    }
  },
  // the rings supply their own rainbow, so the tint only shifts its
  // temperature: neutral at rest, cooled while searching, warmed while
  // answering
  stateColors: {
    idle: { tint: "#ffffff", sheen: "#b9d6ff" },
    thinking: { tint: "#9db8ff", sheen: "#7ba6ff" },
    speaking: { tint: "#ffc492", sheen: "#ffb277" }
  }
};

export type Shdr09Props = Omit<ShaderOrbProps, "variant">;

export function Shdr09({ size = 280, ...rest }: Shdr09Props) {
  return <ShaderOrb variant={shdr09Orb} size={size} {...rest} />;
}

export default Shdr09;

12. components/ui/shdr-10.tsx

/*
 * Deliberately not a `"use client"` module — see the note in `shdr-11.tsx`.
 *
 * Note for editors: the shader lives in a template literal, so its comments
 * must not contain backticks.
 */
import { ShaderOrb, type OrbVariant, type ShaderOrbProps } from "@/components/ui/orbkit-core";

/* ----------------------------------------------------------------------------
   SHDR-10 — a lattice of light knitted into the ball's own skin.

   Ported from a golfed twigl listing:

     for(float z,d,i;i++<4e1;){
       vec3 p=z*normalize(FC.rgb*2.-r.xyx);
       p=vec3(atan(p.z+=9.,p.x+1.)*2.,.6*p.y+t+t,length(p.xz)-3.);
       for(d=1.;d<7.;d++)p+=sin(p.yzx*d+t+.5*i)/d;
       z+=d=.4*length(vec4(.3*cos(p)-.3,p.z));
       o+=(cos(p.y+i*.4+vec4(6,1,2,0))+1.)/d;}
     o=tanh(o*o/6e3);

   What it actually is, decoded:

   - THE SECOND LINE IS AN UNWRAP. Rewriting the sample as
     (angle, height, radius - 3) is the standard tunnel unwrap: it flattens
     a CYLINDER of radius three into a strip, so the field can be written
     in plain coordinates and still come out wrapped around a pipe.
   - THE DENSITY IS A LATTICE ON THAT SURFACE. length of
     (.3*cos(p) - .3, p.z) is small only where all three cosines sit at one
     — a 3D lattice in unwrapped space — AND p.z is near zero, which is the
     shell. Cells of the lattice that land on the shell light up; the rest
     is empty. A knitted skin, not a volume.
   - EVERY MARCH STEP GETS ITS OWN PHASE. .5*i in the warp and i*.4 in the
     colour mean consecutive samples are not looking at the same field, so
     the sum comes out as layered gauze rather than as forty copies of one
     surface. It is the cheapest volumetric trick in the listing.
   - o*o BEFORE the knee is a contrast squarer, not a tone map, the same
     move shdr-06 ends on.

   Port decisions, each one a documented trap or rule in the README:

   - THE ORB IS THE OBJECT, and here that is a ONE TOKEN change: the
     unwrap's radius term reads length(p.xz), the distance from an axis,
     which makes a pipe. Read length(p) instead — the distance from a
     POINT — and the identical field wraps a sphere. This is exactly the
     move the README describes for shdr-20, whose sigmoid cliff became
     the ball's own shell. Nothing else about the listing had to move.
   - THE WRAP MULTIPLIER MUST STAY AN INTEGER. atan jumps by 2*PI across
     the seam behind the ball, so the unwrapped angle jumps by 2*PI times
     the multiplier; every consumer of it here is a sine or cosine of an
     integer multiple, so at integer wrap the jump is a whole number of
     periods and the seam is invisible. The listing's 2 is not decorative.
     Set a fraction and a hard line opens down the back.
   - The listing offsets its axis by one in x, which is a cylinder-axis
     offset with no meaning on a sphere. Dropped.
   - The golfed listing relies on z, d and i starting at zero;
     uninitialised locals are UNDEFINED in GLSL ES 1.0, explicit here.
   - No step-length weighting: 1/d is the density, as in shdr-22 and
     shdr-18, and multiplying by the step would cancel it exactly.
   - The accumulator is divided by the step count before the square, so the
     golfed 6e3 knee lands on a number that fits a slider — the same
     normalization as shdr-06, and for the same reason.
   - Emitted light, so rgb is already premultiplied and alpha comes from
     the peak channel (see shdr-31).
---------------------------------------------------------------------------- */

/*
 * Step counts are `#define`s: ES 1.0 requires constant loop bounds. STEPS and
 * TURB are the listing's i++ < 4e1 and d < 7.
 */
const WEAVE_FRAG = `
#define STEPS 40
#define TURB 6
#define AA 1

// Volume-reactive values, resolved once per fragment in main().
float weaveTurb;
float weaveCell;
float weaveExposure;

vec3 weaveRender(vec2 fragCoord) {
  vec2 uv = (2.0 * fragCoord - uRes) / min(uRes.x, uRes.y);
  vec3 ro = vec3(0.0, 0.0, uP_camDist);
  vec3 rd = normalize(vec3(uv, -uP_focal));

  float animTime = uP_speed;  // integrated clock: the warp
  float scroll = uP_scroll;   // integrated clock: the skin climbs

  vec3 acc = vec3(0.0);

  // transmittance carried front-to-back — the near skin veils the far one
  float T = 1.0;

  // march only the span the envelope can light, as in shdr-01
  float z = max(uP_camDist - uP_envRadius * 1.3, 0.0);
  float zEnd = uP_camDist + uP_envRadius * 1.3;

  for (int it = 0; it < STEPS; it++) {
    float fi = float(it) + 1.0;
    vec3 world = ro + rd * z;

    /*
      The unwrap, with the listing's cylinder swapped for the ball: angle
      about the axis, height, and distance from the CENTRE less the shell
      radius. uP_wrap wants to stay a whole number — see the header.
    */
    float rl = length(world);
    vec3 p = vec3(
      atan(world.z, world.x) * uP_wrap,
      world.y * uP_climb + scroll,
      rl - uP_shellR
    );

    // six octaves of feedback warp, each march step on its own phase
    for (int j = 0; j < TURB; j++) {
      float dj = float(j) + 1.0;
      p += weaveTurb * sin(p.yzx * dj + animTime + uP_layer * fi) / dj;
    }

    /*
      The lattice. Small only where all three cosines sit at one and the
      sample is on the shell — so the cells of a 3D lattice in unwrapped
      space are cut by the ball's surface, and what is left is a knitted
      skin. uP_cell is the listing's .3: the amplitude of the cosine terms
      against the shell term, and therefore how much the lattice matters
      relative to simply being on the surface.
    */
    float d = uP_stepScale * length(vec4(weaveCell * cos(p) - weaveCell, p.z));
    d = max(d, uP_envRadius * 0.004);

    // colour by unwrapped height, with each step offset again
    vec3 w = cos(p.y + fi * uP_hueStep + vec3(6.0, 1.0, 2.0) * uP_spread) + 1.0;
    w /= d;
    w = min(w, vec3(uP_stepClamp));

    // envelope: plateau through the ball, cut 12% past the radius so the
    // analytic silhouette in main() still has emission left to cut
    float env = smoothstep(uP_envRadius * 1.12, uP_envRadius * uP_envCore, rl);
    w = (w + uP_fill) * env;

    acc += T * w;
    T *= exp(-dot(w, vec3(0.299, 0.587, 0.114)) * uP_scatter);

    z += d;
    if (T < 0.004 || z > zEnd) break;
  }

  return acc;
}

void main() {
  weaveTurb = uP_turb * (1.0 + 0.4 * uInput);
  weaveCell = uP_cell * (1.0 + 0.3 * uInput);
  weaveExposure = uP_exposure * (1.0 - 0.3 * uOutput);

  vec3 acc = vec3(0.0);
#if AA > 1
  for (int mx = 0; mx < AA; mx++) {
    for (int my = 0; my < AA; my++) {
      vec2 offset = (vec2(float(mx), float(my)) + 0.5) / float(AA) - 0.5;
      acc += weaveRender(gl_FragCoord.xy + offset);
    }
  }
  acc /= float(AA * AA);
#else
  acc = weaveRender(gl_FragCoord.xy);
#endif

  /*
    The listing's knee is tanh(o*o/6e3) over an unnormalized sum of forty
    steps. Dividing by the step count first pulls the square's scale down
    by forty squared, so the same knee lands near four — a number that fits
    on a slider. The square is a contrast squarer, not a tone map.
  */
  vec3 v = acc / float(STEPS);
  vec3 col = tanh3(v * v / max(weaveExposure, 0.0001));
  col = pow(clamp(col, 0.0, 1.0), vec3(uP_contrast));

  // saturation about luminance, then the tint
  float lum = dot(col, vec3(0.299, 0.587, 0.114));
  col = mix(vec3(lum), col, uP_saturation);
  col *= uC_tint;

  // alpha from the brightest channel, not luminance — a deep blue thread
  // has low luminance but must not go transparent
  float peak = max(col.r, max(col.g, col.b));
  float a = clamp(peak * uP_alphaGain, 0.0, 1.0);

  // Analytic silhouette — identical construction to shdr-01: exact
  // ray-to-centre distance against the radius, colour AND alpha.
  vec3 mrd = normalize(vec3(orbUV(), -uP_focal));
  float closest = length(cross(vec3(0.0, 0.0, uP_camDist), mrd));
  float band = mix(0.35, 0.012, clamp(uP_edge, 0.0, 1.0));
  float mask = 1.0 - smoothstep(uP_envRadius * (1.0 - band), uP_envRadius * 1.005, closest);
  col *= mask;
  a *= mask;

  // safety taper at the frame boundary — colour as well as alpha
  float r2d = length(orbUV());
  float fade = 1.0 - smoothstep(uP_edgeFade, 1.0, r2d);
  col *= fade;
  a *= fade;

  // Emitted light, so rgb is already premultiplied — do NOT scale by alpha
  // again (see the same note in shdr-31).
  gl_FragColor = vec4(col, a);
}
`;

export const shdr10Orb: OrbVariant = {
  key: "shdr-10",
  label: "SHDR-10",
  note: "a lattice of light knitted into the ball's own skin",
  frag: WEAVE_FRAG,
  params: [
    { key: "speed", label: "Anim speed", min: 0.015, max: 10, step: 0.05, default: 0.6, integrate: true },
    { key: "scroll", label: "Climb", min: 0, max: 8, step: 0.03, default: 1.2, integrate: true },
    { key: "camDist", label: "Camera distance", min: 1, max: 50, step: 0.3, default: 7 },
    { key: "focal", label: "Lens", min: 0.15, max: 15, step: 0.05, default: 2 },
    { key: "shellR", label: "Shell radius", min: 0.2, max: 20, step: 0.1, default: 2.6 },
    { key: "wrap", label: "Wraps around", min: 1, max: 14, step: 1, default: 8 },
    { key: "climb", label: "Band spacing", min: 0.05, max: 12, step: 0.05, default: 4 },
    { key: "turb", label: "Warp", min: 0, max: 3, step: 0.02, default: 0.35 },
    { key: "layer", label: "Layer offset", min: 0, max: 2, step: 0.01, default: 0.5 },
    { key: "cell", label: "Lattice weight", min: 0, max: 2, step: 0.01, default: 0.45 },
    { key: "stepScale", label: "Step scale", min: 0.02, max: 3, step: 0.005, default: 0.15 },
    { key: "hueStep", label: "Layer hue", min: 0, max: 3, step: 0.01, default: 0.4 },
    { key: "spread", label: "Colour spread", min: 0, max: 3, step: 0.02, default: 1 },
    { key: "envRadius", label: "Envelope radius", min: 0.15, max: 20, step: 0.1, default: 2.9 },
    { key: "envCore", label: "Envelope core", min: 0.3, max: 1.02, step: 0.01, default: 0.92 },
    { key: "fill", label: "Body fill", min: 0, max: 40, step: 0.05, default: 0.1 },
    { key: "stepClamp", label: "Step clamp", min: 5, max: 5000, step: 5, default: 300 },
    { key: "scatter", label: "Diffusion", min: 0, max: 0.2, step: 0.0005, default: 0.004 },
    { key: "exposure", label: "Exposure", min: 0.05, max: 500, step: 0.5, default: 30 },
    { key: "contrast", label: "Contrast", min: 0.15, max: 15, step: 0.05, default: 1.15 },
    { key: "saturation", label: "Saturation", min: 0, max: 4, step: 0.02, default: 1.2 },
    { key: "alphaGain", label: "Alpha gain", min: 0.05, max: 15, step: 0.1, default: 2 },
    { key: "edge", label: "Edge sharpness", min: 0, max: 1, step: 0.01, default: 1 },
    { key: "edgeFade", label: "Halo falloff", min: 0.1, max: 3, step: 0.015, default: 0.98 }
  ],
  colors: [{ key: "tint", label: "Tint", default: "#ffffff" }],
  /*
    Staged on LATTICE WEIGHT, which decides whether the ball wears a knitted
    net or a smooth shell, and on the two clocks, which are integrated and
    so change rate without ever jumping phase.

    THE LAYER OFFSET IS STAGED ONLY ON A BUDGET, and the budget is
    arithmetic rather than taste. It multiplies the SAMPLE INDEX inside the
    warp — sin(... + uP_layer * fi), with fi running to forty — so it is a
    phase rate across the stack, not an amount: a step of D moves the
    deepest sample by 40*D radians while the nearest barely moves. The old
    0.5 -> 2 stage was 60 radians, nine and a half turns, and the skin
    boiled through every arrangement in between instead of cross-fading.

    Hold |D| at or under 2*PI/40, about 0.157, and even the deepest sample
    travels less than one full turn, which reads as the layers settling.
    Resting sits at 0.5 and searching at 0.65 — 0.15, just inside it — and
    answering stays at 0.5, so every transition in the set is within one
    turn. Anything wider has to be reached with lattice weight and warp
    instead: both are plain amplitudes, and both glide cleanly.

    COLOUR SPLIT is staged on the same budget and the same reasoning. It
    scales a fixed vec3 inside the hue cosine, so its worst channel moves
    six radians per unit; searching's step of 0.55 is about half a turn on
    that channel, which crossfades. A step past one unit would not.

    Wraps around never moves either: it must stay a whole number or the seam
    opens, and a slider gliding through 1.5 would tear the ball open in the
    middle of a transition. Band spacing and layer hue are held for the same
    class of reason — both are frequencies read off a coordinate.
  */
  statePresets: {
    // at rest: an open net climbing slowly
    idle: {
      speed: 0.6,
      scroll: 1.2,
      turb: 0.35,
      cell: 0.45,
      exposure: 30,
      scatter: 0.004,
      alphaGain: 2
    },
    /*
      searching: the net DECOHERES. Three things pull in the same direction.
      Lattice weight drops to two thirds of resting's, so the cosine terms
      no longer close hard on their cells; the warp runs at nearly two and a
      half times resting and churns what is left of them; and the layer
      offset steps up by 0.15 — the whole phase budget above, and as far as
      the forty samples can be pushed out of agreement without the
      transition boiling.

      And it is BRIGHT. The knee drops to well under half resting's — near
      answering's, so this is no longer the dim state — with saturation
      raised half again over resting, contrast pulled back so nothing
      crushes, and the colour split opened to 1.55, which spreads the three
      channels further apart in phase and is what turns the decohering skin
      into full spectrum rather than a blue haze. The tint goes with it:
      near white with only a cool cast, where a saturated blue would have
      thrown all that colour away again.

      Step scale drops to two thirds under all of it, so the ray resolves
      finer detail over less depth, and the diffusion stays low enough that
      the far side still shows through.
    */
    thinking: {
      speed: 1.9,
      scroll: 0.5,
      turb: 0.85,
      layer: 0.65,
      cell: 0.3,
      stepScale: 0.09,
      spread: 1.55,
      fill: 0.12,
      exposure: 13,
      scatter: 0.006,
      contrast: 1.05,
      saturation: 1.7,
      alphaGain: 2.6
    },
    /*
      answering: the net SNAPS IN. Lattice weight goes to more than twice
      resting's and nearly five times searching's, so the cells close hard
      and the skin reads as knitted rope rather than gauze, with the warp
      down to a fifth of searching's so nothing blurs it.

      And it CLIMBS: the scroll clock runs three and a half times resting's,
      the fastest of the three, so the whole net travels up the ball while
      holding its shape. The knee drops to a quarter of searching's and the
      alpha gain lifts — this is unmistakably the bright state.
    */
    speaking: {
      speed: 1,
      scroll: 4.2,
      turb: 0.18,
      cell: 0.95,
      exposure: 11,
      scatter: 0.0015,
      contrast: 0.9,
      saturation: 1.45,
      alphaGain: 2.8
    }
  },
  // the height ramp supplies the colour, so the tint only shifts its
  // temperature: neutral at rest, barely cooled while searching — a
  // saturated blue there would cancel the colour split that state is built
  // on — and warmed while answering
  stateColors: {
    idle: { tint: "#ffffff" },
    thinking: { tint: "#e8f4ff" },
    speaking: { tint: "#ffc492" }
  }
};

export type Shdr10Props = Omit<ShaderOrbProps, "variant">;

export function Shdr10({ size = 280, ...rest }: Shdr10Props) {
  return <ShaderOrb variant={shdr10Orb} size={size} {...rest} />;
}

export default Shdr10;

13. components/ui/shdr-11.tsx

/*
 * Deliberately not a `"use client"` module. The directive lives on the runtime
 * in `orbkit-core`, which owns the hooks; keeping it off this file lets server
 * components read `shdr11Orb` as real data (its param schema drives the docs
 * tables and the playground controls) instead of an opaque client reference.
 */
import {
  ShaderOrb,
  type OrbVariant,
  type ShaderOrbProps
} from "@/components/ui/orbkit-core";

/* ----------------------------------------------------------------------------
   SHDR-11 — the quantum-orbital orb.

   Renders |psi|^2 of a hydrogen-like wave function projected onto the orb's
   dome, shaded with rainbow chromatic bands over a dark metallic sphere.

   The dome point is rotated around Y (the fake 3D of a flat disc), its spin
   axis precesses so the pattern never settles into a visible loop, and a
   drifting fbm field warps the 3D domain so the wave function smears and
   migrates around the sphere instead of wobbling in place.
---------------------------------------------------------------------------- */

const HYDROGEN_FRAG = `
const float PI = 3.14159265359;
void main() {
  vec2 uv = orbUV();
  float r2d = length(uv);
  float R = uP_radius + uP_swell * uInput;
  float mask = smoothstep(0.012, -0.012, r2d - R);
  float nr = clamp(r2d / max(R, 0.001), 0.0, 1.0);
  float z = sqrt(max(1.0 - nr * nr, 0.0));

  // uP_speed and uP_flowSpeed arrive pre-integrated as clocks (see
  // OrbParamDef.integrate), so state transitions stay phase-continuous.
  // The state volumes reshape the orbital itself: the params set the base,
  // input/output excitement bends zoom, radial form, probability and chroma,
  // so each state settles into a different interference pattern.
  float posScale = uP_posScale * (0.8 + 0.45 * uOutput + 0.2 * uInput);
  float radialPow = uP_radialPow * (0.7 + 0.8 * uOutput);
  float radialDecay = uP_radialDecay * (1.25 - 0.5 * uOutput);
  float probPow = uP_probPow * (1.3 - 0.55 * uOutput);
  float probGain = uP_probGain * (0.7 + 0.6 * uOutput + 0.5 * uInput);
  float waveFreq = uP_waveFreq * (0.6 + 1.0 * uOutput);
  float chromaSpread = uP_chromaSpread * (0.6 + 0.9 * uOutput + 0.5 * uInput);

  // dome point rotated around Y — the fake 3D of the flat disc
  float animTime = uP_speed; // integrated clock
  float cosT = cos(animTime * uP_rotSpeed);
  float sinT = sin(animTime * uP_rotSpeed);
  vec3 sp = vec3(uv / max(R, 0.001), z) * posScale;
  vec3 pos = vec3(sp.x * cosT - sp.z * sinT, sp.y, sp.x * sinT + sp.z * cosT);

  // precession: the rotation axis itself drifts, so the pattern never
  // settles into a repeating spin
  float tilt = sin(animTime * 0.21 + 1.7) * uP_precess;
  float cx = cos(tilt), sx = sin(tilt);
  pos = vec3(pos.x, pos.y * cx - pos.z * sx, pos.y * sx + pos.z * cx);

  // liquid flow: drifting fbm warps the 3D domain, so the wave function
  // smears and migrates around the sphere instead of wobbling in place.
  // (sampled on pos components — continuous everywhere, no phi seam)
  float flowT = uP_flowSpeed; // integrated clock
  float fAmp = uP_flowAmp * (0.7 + 0.6 * uOutput + 0.4 * uInput);
  vec3 w;
  w.x = fbm(pos.yz * uP_flowScale + vec2(flowT * 0.70, -flowT * 0.40));
  w.y = fbm(pos.zx * uP_flowScale + vec2(-flowT * 0.55, flowT * 0.62) + 3.7);
  w.z = fbm(pos.xy * uP_flowScale + vec2(flowT * 0.50, flowT * 0.85) + 7.1);
  pos += (w - 0.5) * fAmp;

  float r = length(pos) + 0.001;
  float theta = acos(clamp(pos.y / r, -1.0, 1.0));
  float phi = atan(pos.z, pos.x);

  float a0 = 0.5;
  float rho = 2.0 * r / (5.0 * a0);
  float radial = pow(rho, radialPow) * exp(-rho / radialDecay);
  float angular = pow(sin(theta), 3.0) * cos(phi + animTime * 0.2); // single lobe

  float psi = radial * angular;
  float probability = psi * psi;

  // travelling spiral wave — the modulation moves across the surface instead
  // of pulsing in place. The azimuthal harmonic count must be a whole number,
  // else sin(phi * f) doesn't line up across the +/-PI wrap and leaves a
  // vertical meridian seam. Snap it to the nearest integer.
  float waveN = max(1.0, floor(waveFreq + 0.5));
  float wavePhase = phi * waveN + theta * 2.5 - animTime * 2.0;
  probability *= (0.85 + 0.15 * sin(wavePhase));

  // drifting bright patches, like convection cells wandering the surface
  float patches = fbm(pos.xy * 1.6 + vec2(flowT * 0.4, -flowT * 0.3));
  probability *= 0.65 + 0.7 * patches;

  probability = pow(probability, probPow) * probGain;
  probability = clamp(probability, 0.0, 1.0);

  float fresnel = pow(1.0 - z, 1.5);

  // rainbow chromatic aberration
  float chromaOffset = phi * 2.0 + theta * 1.5 + animTime * 0.3 + probability * 3.0;
  vec3 rainbow;
  rainbow.r = sin(chromaOffset) * 0.5 + 0.5;
  rainbow.g = sin(chromaOffset + chromaSpread) * 0.5 + 0.5;
  rainbow.b = sin(chromaOffset + chromaSpread * 2.0) * 0.5 + 0.5;
  rainbow = normalize(rainbow + 0.01) * length(rainbow);

  float bandFreq = chromaOffset * 3.0 + fresnel * 2.4;
  vec3 chromaticBands;
  chromaticBands.r = sin(bandFreq) * 0.5 + 0.5;
  chromaticBands.g = sin(bandFreq + 2.094) * 0.5 + 0.5;
  chromaticBands.b = sin(bandFreq + 4.189) * 0.5 + 0.5;

  vec3 glowColor = mix(rainbow, chromaticBands, 0.12);
  glowColor = pow(glowColor, vec3(0.8));

  vec3 darkMetal = vec3(uP_metalDark);
  vec3 lightMetal = mix(vec3(0.9, 0.92, 0.95), glowColor, 0.7);

  float metalGradient = smoothstep(0.0, 1.0, probability * 0.7 + fresnel * 0.3);
  vec3 metalColor = mix(darkMetal, lightMetal, metalGradient);

  float orbGlow = uP_glow + 0.6 * uOutput;
  float totalGlow = (0.25 + fresnel * 0.6 + probability * 0.8) * orbGlow;
  float glowAmount = clamp(pow(totalGlow, 0.7), 0.0, 1.0);

  vec3 surfaceColor = mix(metalColor, glowColor, glowAmount);

  vec3 normal = vec3(uv / max(R, 0.001), z);
  float specular = pow(max(dot(normal, normalize(vec3(1.0, 1.0, 2.0))), 0.0), 32.0);
  surfaceColor += mix(vec3(1.0), glowColor, 0.6) * specular * 0.4;

  float visibility = clamp(probability * 1.2 + fresnel * 0.3 + uP_baseVis + uInput * 0.15, 0.0, 1.0);

  float a = mask * visibility;
  gl_FragColor = vec4(surfaceColor * a, a);
}
`;

export const shdr11Orb: OrbVariant = {
  key: "shdr-11",
  label: "SHDR-11",
  note: "quantum orbital, rainbow chroma",
  frag: HYDROGEN_FRAG,
  params: [
    { key: "speed", label: "Anim speed", min: 0.015, max: 10, step: 0.05, default: 0.9, integrate: true },
    { key: "rotSpeed", label: "Rotation speed", min: 0, max: 5, step: 0.05, default: 0.5 },
    { key: "radius", label: "Radius", min: 0.15, max: 3, step: 0.015, default: 0.9 },
    { key: "swell", label: "Input swell", min: 0, max: 1, step: 0.01, default: 0.07 },
    { key: "posScale", label: "Orbital zoom", min: 0.15, max: 10, step: 0.05, default: 0.5 },
    { key: "flowSpeed", label: "Flow speed", min: 0, max: 10, step: 0.05, default: 0.35, integrate: true },
    { key: "flowAmp", label: "Flow amount", min: 0, max: 4, step: 0.05, default: 0.45 },
    { key: "flowScale", label: "Flow scale", min: 0.3, max: 10, step: 0.1, default: 0.3 },
    { key: "precess", label: "Precession", min: 0, max: 4, step: 0.05, default: 0.3 },
    { key: "radialPow", label: "Radial power", min: 0.5, max: 15, step: 0.1, default: 0.5 },
    { key: "radialDecay", label: "Radial decay", min: 0.3, max: 30, step: 0.15, default: 1 },
    { key: "probPow", label: "Probability curve", min: 0.1, max: 3, step: 0.015, default: 0.4 },
    { key: "probGain", label: "Probability gain", min: 0.15, max: 15, step: 0.1, default: 3 },
    { key: "waveFreq", label: "Wave frequency", min: 0, max: 20, step: 0.5, default: 4 },
    { key: "chromaSpread", label: "Chroma spread", min: 0, max: 1.5, step: 0.01, default: 0.18 },
    { key: "glow", label: "Glow", min: 0, max: 5, step: 0.05, default: 0.9 },
    { key: "metalDark", label: "Metal darkness", min: 0, max: 3, step: 0.015, default: 0 },
    { key: "baseVis", label: "Base visibility", min: 0, max: 1.5, step: 0.01, default: 0.12 }
  ],
  colors: [],
  statePresets: {
    // idle look, tuned by hand — the schema defaults mirror this set
    idle: {
      speed: 0.9,
      rotSpeed: 0.5,
      radius: 0.9,
      swell: 0.07,
      posScale: 0.5,
      flowSpeed: 0.35,
      flowAmp: 0.45,
      flowScale: 0.3,
      precess: 0.3,
      radialPow: 0.5,
      radialDecay: 1,
      probPow: 0.4,
      probGain: 3,
      waveFreq: 4,
      chromaSpread: 0.18,
      glow: 0.9,
      metalDark: 0,
      baseVis: 0.12
    },
    // thinking: wider zoom, heavier flow, tighter shells, wide chroma —
    // restless but not loud
    thinking: {
      speed: 0.9,
      rotSpeed: 0.5,
      radius: 0.9,
      swell: 0.07,
      posScale: 0.65,
      flowSpeed: 0.35,
      flowAmp: 1.1,
      flowScale: 0.3,
      precess: 0,
      radialPow: 0.5,
      radialDecay: 1.9,
      probPow: 0.4,
      probGain: 3,
      waveFreq: 4,
      chromaSpread: 0.41,
      glow: 0.9,
      metalDark: 0,
      baseVis: 0.12
    },
    // speaking: fast anim, full zoom, quick fine-grained flow, strong
    // precession, bright gain — the loudest, most energetic pattern
    speaking: {
      speed: 2.45,
      rotSpeed: 0.5,
      radius: 0.9,
      swell: 0.07,
      posScale: 1,
      flowSpeed: 2.75,
      flowAmp: 0.8,
      flowScale: 2.2,
      precess: 1.3,
      radialPow: 0.5,
      radialDecay: 1,
      probPow: 0.31,
      probGain: 4.3,
      waveFreq: 4,
      chromaSpread: 0.12,
      glow: 0.9,
      metalDark: 0,
      baseVis: 0.12
    }
  }
};

export type Shdr11Props = Omit<ShaderOrbProps, "variant">;

export function Shdr11({ size = 280, ...rest }: Shdr11Props) {
  return <ShaderOrb variant={shdr11Orb} size={size} {...rest} />;
}

export default Shdr11;

14. components/ui/shdr-12.tsx

/*
 * Deliberately not a `"use client"` module — see the note in `shdr-11.tsx`.
 *
 * Note for editors: the shader lives in a template literal, so its comments
 * must not contain backticks.
 */
import { ShaderOrb, type OrbVariant, type ShaderOrbProps } from "@/components/ui/orbkit-core";

/* ----------------------------------------------------------------------------
   SHDR-12 — a ball built from glossy toy bricks, studs up.

   THE ORB IS THE OBJECT: the sphere is assembled from interlocking plastic
   bricks the way a brick-built globe is. A DDA march walks a rectilinear
   lattice whose cells are one stud wide and one brick tall (bricks are 1.2
   stud pitches high, the real proportion); a cell is solid wherever its
   centre sits inside the unit sphere.

   What sells the toy:

   - BRICKWORK BONDING: bricks are 2x4 studs. Each layer alternates its
     long axis, and every (layer, row) course staggers by a hashed offset —
     so seams never align vertically, exactly like a proper build. A cell
     maps to its owning brick analytically; the brick id drives colour,
     mold variance, seams and the rebuild blink.
   - STUDS: every upward face gets its stud embossed — a perturbed normal
     around the stud rim, a lifted highlight on the cap and a contact ring
     outside it. The lighting does the work; the silhouette stays brick.
   - SEAMS: thin dark joints drawn only on true brick boundaries (the two
     axes tangent to the struck face), not around every stud.
   - PLASTIC: five tunable brick colours picked per brick — the patch
     parameter slides the palette from per-brick confetti to big moulded
     colour regions — with a white Blinn specular for the ABS sheen.
   - REBUILD: bricks in the outer two courses blink out and back on an
     integrated clock, revealing the darker bricks beneath. Idle loses an
     occasional brick; THINKING churns the whole shell — the ball visibly
     rebuilding itself — and speaking snaps it whole, fast and glossy.

   Construction notes:

   - The march is BOUNDED by an analytic sphere just past the brick
     corners, so empty pixels cost two dot products. No noise runs in the
     solid test — interior cells answer with one length() — which makes
     this one of the cheapest marched orbs in the library.
   - The DDA is anisotropic (cell height differs from pitch): the standard
     Amanatides & Woo setup generalizes by using per-axis cell sizes.
   - No fwidth, no round, constant loop bound with inner breaks —
     GLSL ES 1.0 throughout, as everywhere in this repo.
   - Surface-lit and hit-bounded, so alpha IS coverage — premultiplied
     output (trivially: hits are opaque, misses are clear).
---------------------------------------------------------------------------- */

const BRICK_FRAG = `
#define STEPS 96

// Per-fragment constants, resolved once in main().
vec3 lgCell;  // cell sizes: (stud pitch, brick height, stud pitch)
float lgGap;

// Brick-lookup results (GLSL ES 1.0 has no out-struct ergonomics).
vec3 lgBid;     // unique id of the owning brick
float lgOff;    // long-axis stagger offset of its course, in studs
float lgOrient; // 0: long axis runs along x, 1: along z

mat2 lgRot(float a) {
  float c = cos(a);
  float s = sin(a);
  return mat2(c, -s, s, c);
}

/*
  Which 2x4 brick owns this stud cell? Layers alternate their long axis
  and every (layer, row) course staggers by a hashed offset — brickwork
  bonding, so vertical seams never stack.
*/
void lgBrick(vec3 cellIdx) {
  lgOrient = mod(cellIdx.y, 2.0);
  float lc = lgOrient < 0.5 ? cellIdx.x : cellIdx.z;
  float sc = lgOrient < 0.5 ? cellIdx.z : cellIdx.x;
  float srow = floor(sc / 2.0);
  lgOff = floor(hash(vec2(cellIdx.y * 3.17, srow * 7.31)) * 4.0);
  lgBid = vec3(floor((lc + lgOff) / 4.0), cellIdx.y, srow + lgOrient * 913.0);
}

/*
  The world function: inside the ball, minus bricks currently blinked out
  of the outer two courses. Interior cells answer with a single length —
  the brick lookup only runs in the shell.
*/
float lgSolid(vec3 cc) {
  float r = length(cc);
  if (r >= 1.0) return 0.0;
  if (r > 1.0 - 2.2 * lgCell.y) {
    lgBrick(floor(cc / lgCell));
    float blink = fract(hash(lgBid.xy * 0.173 + lgBid.z * 0.089) + uP_rebuild * 0.03);
    if (blink < lgGap) return 0.0; // this brick is off the build right now
  }
  return 1.0;
}

void main() {
  // Volume coupling: agent output stokes the sheen and the gain; user
  // input brightens the key light.
  float glossNow = uP_gloss * (0.7 + 0.9 * uOutput);
  float gainNow = uP_gain * (0.92 + 0.25 * uOutput);
  float lightNow = uP_light * (1.0 + 0.3 * uInput);

  float pitch = 2.0 / clamp(uP_studs, 8.0, 48.0);
  lgCell = vec3(pitch, pitch * 1.2, pitch); // real brick proportion
  lgGap = clamp(uP_gap, 0.0, 0.9);

  float bound = 1.0 + length(lgCell) * 0.5 + 0.001;

  vec2 uv = orbUV() / uP_radius;
  vec3 ro = vec3(uv * bound, 2.6);
  vec3 rd = vec3(0.0, 0.0, -1.0);

  // rotate the RAY into object space (inverse tumble) — the lattice stays
  // axis-aligned and the studs stay up while the ball turns. Light and
  // view rotate along, keeping the sun fixed relative to the viewer.
  mat2 tiltM = lgRot(uP_tilt); // positive tilt looks DOWN at the studs
  mat2 spinM = lgRot(-uP_spin); // integrated clock
  ro.yz = tiltM * ro.yz;
  ro.xz = spinM * ro.xz;
  rd.yz = tiltM * rd.yz;
  rd.xz = spinM * rd.xz;
  vec3 Lo = normalize(vec3(-0.5, 0.7, 0.55));
  Lo.yz = tiltM * Lo.yz;
  Lo.xz = spinM * Lo.xz;
  vec3 Vo = vec3(0.0, 0.0, 1.0);
  Vo.yz = tiltM * Vo.yz;
  Vo.xz = spinM * Vo.xz;

  // DDA needs nonzero direction components — nudge, keep the sign
  vec3 sgn = vec3(
    rd.x >= 0.0 ? 1.0 : -1.0,
    rd.y >= 0.0 ? 1.0 : -1.0,
    rd.z >= 0.0 ? 1.0 : -1.0
  );
  rd = normalize(sgn * max(abs(rd), vec3(1.0e-4)));

  // analytic bounding sphere: empty pixels exit here
  float b = dot(rd, ro);
  float c = dot(ro, ro) - bound * bound;
  float disc = b * b - c;
  if (disc < 0.0) {
    gl_FragColor = vec4(0.0);
    return;
  }
  float sq = sqrt(disc);
  vec3 p0 = ro + rd * (-b - sq + pitch * 0.001);
  float tSpan = 2.0 * sq;

  // Amanatides & Woo, anisotropic cells: per-axis sizes throughout
  vec3 vp = floor(p0 / lgCell);
  vec3 tDelta = lgCell / abs(rd);
  vec3 tMax = ((vp + step(vec3(0.0), rd)) * lgCell - p0) / rd;

  float hitF = 0.0;
  vec3 mask = vec3(0.0, 0.0, 1.0); // first-voxel fallback: face the viewer
  float tCur = 0.0;

  for (int i = 0; i < STEPS; i++) {
    if (lgSolid((vp + 0.5) * lgCell) > 0.5) {
      hitF = 1.0;
      break;
    }
    if (tMax.x < tMax.y && tMax.x < tMax.z) {
      tCur = tMax.x;
      tMax.x += tDelta.x;
      vp.x += sgn.x;
      mask = vec3(1.0, 0.0, 0.0);
    } else if (tMax.y < tMax.z) {
      tCur = tMax.y;
      tMax.y += tDelta.y;
      vp.y += sgn.y;
      mask = vec3(0.0, 1.0, 0.0);
    } else {
      tCur = tMax.z;
      tMax.z += tDelta.z;
      vp.z += sgn.z;
      mask = vec3(0.0, 0.0, 1.0);
    }
    if (tCur > tSpan) break; // left the bound: miss
  }

  if (hitF < 0.5) {
    gl_FragColor = vec4(0.0);
    return;
  }

  // the hit cell, its brick, and the struck face
  vec3 cc = (vp + 0.5) * lgCell;
  float r = length(cc);
  vec3 dir = cc / max(r, 1.0e-4);
  lgBrick(vp);
  vec3 n = -mask * sgn;
  vec3 hp = p0 + rd * tCur;

  /*
    Brick colour: a per-brick hash picks one of the five plastic colours.
    The patch parameter slides the pick toward a smooth field over the
    sphere, so 0 is per-brick confetti and 1 is big moulded colour
    regions; the field is range-stretched so all five colours appear.
  */
  float cph = hash(lgBid.xy * 1.37 + lgBid.z * 0.91);
  float rn = noise(dir.xy * 2.6 + 7.0) * 0.5 + noise(dir.yz * 2.6 + 13.0) * 0.5;
  rn = clamp(0.5 + (rn - 0.5) * 2.2, 0.0, 0.999);
  float idx = floor(clamp(mix(cph, rn, clamp(uP_patch, 0.0, 1.0)), 0.0, 0.999) * 5.0);
  vec3 albedo = idx < 0.5 ? uC_brickA
    : (idx < 1.5 ? uC_brickB
    : (idx < 2.5 ? uC_brickC
    : (idx < 3.5 ? uC_brickD : uC_brickE)));
  albedo *= 0.93 + 0.14 * hash(lgBid.xy * 0.53 + lgBid.z * 1.7); // mold variance

  /*
    Seams: distance to the nearest BRICK boundary along each lattice axis,
    from the continuous within-brick coordinates. Only the two axes
    tangent to the struck face draw — stud grid lines never do.
  */
  vec3 sp = hp / lgCell;
  float lcC = lgOrient < 0.5 ? sp.x : sp.z;
  float scC = lgOrient < 0.5 ? sp.z : sp.x;
  float u4 = fract((lcC + lgOff) / 4.0);
  float v2 = fract(scC / 2.0);
  float wY = fract(sp.y);
  float dL = min(u4, 1.0 - u4) * 4.0 * pitch;
  float dS = min(v2, 1.0 - v2) * 2.0 * pitch;
  float dY = min(wY, 1.0 - wY) * lgCell.y;
  float seamD;
  if (mask.y > 0.5) seamD = min(dL, dS);
  else if (mask.x > 0.5) seamD = min(dY, lgOrient < 0.5 ? dS : dL);
  else seamD = min(dY, lgOrient < 0.5 ? dL : dS);
  float seam = (1.0 - smoothstep(0.0, 0.07 * pitch, seamD)) * clamp(uP_seam, 0.0, 1.0);

  /*
    Studs, embossed the way the real brick photographs: the normal tilts
    hard around the stud shoulder so the light wraps it like a cylinder
    edge, the cap lifts, a contact shadow falls on the side facing away
    from the light, and a faint ring engraved into the cap stands in for
    the moulded logo.
  */
  vec3 nEff = n;
  float studF = 0.0;
  float shadowF = 0.0;
  float engrave = 0.0;
  float studAmt = clamp(uP_stud, 0.0, 1.0);
  if (mask.y > 0.5 && n.y > 0.5) {
    vec2 cuv = fract(hp.xz / pitch) - 0.5;
    float sd = length(cuv);
    float rim = smoothstep(0.14, 0.29, sd) * (1.0 - smoothstep(0.29, 0.335, sd));
    vec3 tiltN = normalize(vec3(cuv.x, 0.42, cuv.y));
    nEff = normalize(mix(n, tiltN, rim * studAmt));
    studF = 1.0 - smoothstep(0.285, 0.33, sd);
    vec2 lxz = normalize(Lo.xz + vec2(1.0e-5));
    float away = clamp(dot(normalize(cuv + vec2(1.0e-5)), -lxz), 0.0, 1.0);
    shadowF = smoothstep(0.47, 0.335, sd) * (1.0 - studF) * (0.35 + 0.65 * away);
    engrave = smoothstep(0.11, 0.135, sd) * (1.0 - smoothstep(0.155, 0.18, sd)) * studF;
  }

  // plastic shading: lambert + wrap for roundness + white Blinn sheen,
  // dimmed toward the interior so revealed under-bricks read as inside
  float lam = clamp(dot(nEff, Lo), 0.0, 1.0);
  float wrap = clamp(dot(dir, Lo) * 0.5 + 0.5, 0.0, 1.0);
  float depthDim = mix(1.0, 0.55, clamp((1.0 - r) / (3.0 * lgCell.y), 0.0, 1.0));
  float shade = (0.34 + 0.42 * wrap * wrap + 0.8 * lam * lightNow) * depthDim;

  vec3 col = albedo * shade * (1.0 + 0.1 * studF);
  col *= 1.0 - shadowF * 0.38 * studAmt; // stud contact shadow
  col *= 1.0 - engrave * 0.14 * studAmt; // moulded logo ring

  // chamfered edge: a thin bright bevel line just inside the dark joint,
  // catching the light the way the real brick's edges do
  float bevel = smoothstep(0.05 * pitch, 0.085 * pitch, seamD)
    * (1.0 - smoothstep(0.085 * pitch, 0.16 * pitch, seamD));
  col += albedo * bevel * (0.18 + 0.5 * lam) * clamp(uP_seam, 0.0, 1.0);
  col *= 1.0 - seam * 0.8; // dark joints

  // two-lobe plastic sheen: a sharp hotspot over a broad soft gloss
  float ndh = clamp(dot(nEff, normalize(Lo + Vo)), 0.0, 1.0);
  float spec = pow(ndh, 48.0) + 0.22 * pow(ndh, 8.0);
  col += vec3(1.0) * spec * glossNow * (1.0 - seam) * depthDim;

  col *= gainNow;
  col = pow(max(col, 0.0), vec3(uP_contrast));

  // Surface-lit orb bounded by the hit test: alpha IS coverage, and a hit
  // is fully opaque — premultiplied output, trivially (see shdr-28).
  gl_FragColor = vec4(col, 1.0);
}
`;

export const shdr12Orb: OrbVariant = {
  key: "shdr-12",
  label: "SHDR-12",
  note: "a ball of glossy toy bricks, studs up — it rebuilds itself while it thinks",
  frag: BRICK_FRAG,
  params: [
    { key: "spin", label: "Spin", min: 0, max: 5, step: 0.03, default: 0.25, integrate: true },
    { key: "tilt", label: "Tilt", min: 0, max: 4, step: 0.02, default: 0.55 },
    { key: "rebuild", label: "Rebuild rate", min: 0, max: 20, step: 0.1, default: 0.4, integrate: true },
    { key: "gap", label: "Missing bricks", min: 0, max: 0.8, step: 0.01, default: 0.07 },
    { key: "studs", label: "Studs", min: 8, max: 48, step: 1, default: 18 },
    { key: "radius", label: "Radius", min: 0.15, max: 3, step: 0.015, default: 0.95 },
    { key: "patch", label: "Colour patches", min: 0, max: 1, step: 0.01, default: 0.35 },
    { key: "stud", label: "Stud relief", min: 0, max: 1, step: 0.01, default: 0.85 },
    { key: "seam", label: "Seams", min: 0, max: 1, step: 0.01, default: 0.6 },
    { key: "gloss", label: "Gloss", min: 0, max: 3, step: 0.02, default: 1 },
    { key: "light", label: "Key light", min: 0, max: 3, step: 0.015, default: 1 },
    { key: "gain", label: "Gain", min: 0.05, max: 5, step: 0.05, default: 1 },
    { key: "contrast", label: "Contrast", min: 0.15, max: 10, step: 0.05, default: 1 }
  ],
  colors: [
    { key: "brickA", label: "Red", default: "#c4281c" },
    { key: "brickB", label: "Yellow", default: "#f2cd37" },
    { key: "brickC", label: "Blue", default: "#1e5aa8" },
    { key: "brickD", label: "Green", default: "#00852b" },
    { key: "brickE", label: "White", default: "#f4f4f4" }
  ],
  /*
    The rebuild blink is the state read:

      idle SETTLES     lazy tumble, an occasional brick popped off the shell
      thinking BUILDS  the tumble all but stops while the outer courses
                       churn — bricks blinking out and back everywhere,
                       the ball visibly rebuilding itself
      speaking SNAPS   whole and glossy: the gaps close, the sheen flares,
                       and the ball turns fast to answer

    Two controls are deliberately NOT staged, and both for the same reason:
    they quantize, so easing them steps instead of fading. STUDS sets the
    grid pitch, and a gliding pitch re-tiles the whole shell — the ball
    reads as inflating rather than changing mood. COLOUR PATCHES lands
    inside a floor() that picks one of the five brick colours, so easing it
    flips bricks between colours one at a time, which reads as a fault
    rather than a transition. Everything staged below is either an
    amplitude or one of the two integrated clocks, whose rate can change
    without their phase ever jumping.

    The surface finish carries as much of the read as the blink does:
    searching wears full stud relief and hard seams on a matt gloss — every
    brick edge visible, an object mid-assembly — and answering flattens the
    studs, sinks the seams and flares the sheen, so it resolves into one
    moulded piece.
  */
  statePresets: {
    idle: {
      spin: 0.25,
      rebuild: 0.4,
      gap: 0.07,
      gloss: 1,
      gain: 1,
      light: 1
    },
    thinking: {
      spin: 0.03,
      tilt: 0.9,
      rebuild: 9,
      gap: 0.55,
      stud: 1,
      seam: 0.95,
      gloss: 0.55,
      gain: 1.05,
      light: 1.15,
      contrast: 1.05
    },
    speaking: {
      spin: 1.6,
      tilt: 0.42,
      rebuild: 0.5,
      gap: 0,
      stud: 0.6,
      seam: 0.3,
      gloss: 2.4,
      gain: 1.25,
      light: 1.35,
      contrast: 0.95
    }
  },
  /*
    Answering swaps the whole box out. Resting and searching keep the
    classic five above — that palette is the joke, and it should be what
    the orb looks like most of the time — but the state that snaps whole
    and glossy gets a hot set to snap INTO: the same five slots, pushed to
    high chroma, so the sheen at gloss 2.4 has something saturated to sit
    on rather than a flat primary.

    Safe to stage, unlike the patch control that picks between these. Each
    brick keeps its slot through the change and only the colour in that
    slot eases, so the shell cross-fades where flipping bricks between
    slots would pop. Omitting idle and thinking is deliberate: an unlisted
    state falls back to the colour defaults, which is exactly the classic
    palette.
  */
  stateColors: {
    speaking: {
      brickA: "#ff3b6b",
      brickB: "#ffc93c",
      brickC: "#21d4fd",
      brickD: "#7af5a0",
      brickE: "#ffffff"
    }
  }
};

export type Shdr12Props = Omit<ShaderOrbProps, "variant">;

export function Shdr12({ size = 280, ...rest }: Shdr12Props) {
  return <ShaderOrb variant={shdr12Orb} size={size} {...rest} />;
}

export default Shdr12;

15. components/ui/shdr-13.tsx

/*
 * Deliberately not a `"use client"` module — see the note in `shdr-11.tsx`.
 *
 * Note for editors: the shader lives in a template literal, so its comments
 * must not contain backticks.
 */
import { ShaderOrb, type OrbVariant, type ShaderOrbProps } from "@/components/ui/orbkit-core";

/* ----------------------------------------------------------------------------
   SHDR-13 — a plasma globe: crawling lightning filaments inside a glass ball.

   The reference is the physical object: a tesla ball with a hot nucleus,
   pink-to-violet streamers writhing out to the glass, and bright flares
   where a filament touches the shell.

   How the filaments are built, and why they behave like the real thing:

   - A curve in 3D is the intersection of two level surfaces. Two independent
     trig fields are evaluated on the DIRECTION of each march point
     (q = dir * fils, |dir| = 1), so their joint zero set is a set of
     directions — extruded radially, those are filaments running from the
     nucleus to the glass, never a floating blob. Filament count rides the
     angular frequency uP_fils, so a state preset can literally grow more
     streamers, and because the field morphs smoothly the new ones split off
     from existing ones instead of popping in.
   - Each field carries its own additive time phases, so the zero curves
     drift, merge, and reconnect — the crawl of a real streamer hunting
     across the glass. The whole array also precesses on an integrated spin
     clock.
   - A radial writhe term bends the direction before sampling, gated by
     smoothstep from the centre so every filament stays ROOTED at the
     nucleus while its far end wanders on the shell.
   - Proximity to the curve is measured in field space (f1^2 + f2^2), and
     brightness is its inverse — the same 1/d accumulation as the other
     volumetric orbs, so thickness varies naturally along a filament with
     the field gradient.
   - The march is bounded to the exact ray/sphere chord (entry to exit), and
     each step is weighted by its true length, so limb rays integrate short
     chords and dim correctly; the tip flare (a smoothstep in r near the
     shell) is what lights the glass from inside where streamers land.
   - Colour is a radial mix: the inner colour near the nucleus, the arc
     colour toward the glass, cores whitened by their own intensity — the
     pink-core / violet-tip gradient of a real discharge.

   House rules followed from the other orbs: per-step clamp before the
   spikes own the frame, front-to-back transmittance, tanh tone map with an
   exposure knee, alpha from the brightest channel, and the analytic
   silhouette cut from each ray's closest approach — colour AND alpha.
---------------------------------------------------------------------------- */

/*
 * Step count is a `#define`: ES 1.0 requires constant loop bounds.
 */
const ION_FRAG = `
#define STEPS 64

// Volume-reactive values, resolved once per fragment in main().
float ionSharp;
float ionWrithe;
float ionCore;
float ionExposure;
float ionRadius;

mat2 ionRot2(float a) {
  float c = cos(a);
  float s = sin(a);
  return mat2(c, -s, s, c);
}

vec3 ionRender(vec2 fragCoord) {
  float t = uP_speed;      // integrated clock: filament crawl
  float spinAng = uP_spin; // integrated clock: array precession

  vec2 uv = (2.0 * fragCoord - uRes) / min(uRes.x, uRes.y);
  vec3 ro = vec3(0.0, 0.0, uP_camDist);
  vec3 rd = normalize(vec3(uv, -uP_focal));

  // exact ray/sphere chord — the march never leaves the globe, so no
  // envelope fade is needed and every step length is meaningful
  float proj = dot(-ro, rd);
  float b2 = dot(ro, ro) - proj * proj;
  float half_ = sqrt(max(ionRadius * ionRadius - b2, 0.0));
  float zNear = proj - half_;
  float stepLen = 2.0 * half_ / float(STEPS);
  // per-pixel jitter of the march start: a filament grazed at a shallow
  // angle is crossed periodically by the fixed step grid and renders as a
  // dotted chain — the jitter decorrelates neighbouring rays and melts the
  // dots into plasma grain
  zNear += (hash(fragCoord) - 0.5) * stepLen;

  vec3 acc = vec3(0.0);
  float T = 1.0;

  for (int i = 0; i < STEPS; i++) {
    vec3 p = ro + rd * (zNear + (float(i) + 0.5) * stepLen);

    // precess the whole filament array; a static tilt keeps the spin axis
    // off-vertical so the motion reads in 3D
    vec3 pr = p;
    pr.xz = ionRot2(spinAng) * pr.xz;
    pr.yz = ionRot2(uP_tilt) * pr.yz;

    float r = length(pr);
    vec3 dir = pr / max(r, 1e-4);
    float rr = r / max(ionRadius, 1e-3);

    // writhe: bend the sampling direction with radius and time, rooted at
    // the nucleus by the smoothstep so filaments stay attached
    float wr = ionWrithe * smoothstep(0.0, ionRadius * 0.35, r);
    vec3 q = dir * uP_fils;
    q += wr * vec3(
      sin(r * uP_writheFreq        - t * 1.2 + q.y * 1.8),
      sin(r * uP_writheFreq * 0.83 + t * 1.0 + q.z * 1.8),
      sin(r * uP_writheFreq * 1.19 - t * 0.7 + q.x * 1.8));

    // two independent fields over the direction sphere; their joint zero
    // set is the filament curves. Time enters as additive phase only.
    float f1 = sin(q.x + t * 0.70)
             + sin(q.y * 1.31 - t * 0.50)
             + sin(q.z * 1.13 + t * 0.90);
    float f2 = sin(q.y * 1.21 + t * 0.60 + 1.7)
             + sin(q.z * 1.43 - t * 0.80 + 3.1)
             + sin(q.x * 0.87 + t * 0.40 + 5.0);
    float d2 = f1 * f1 + f2 * f2;
    float g = 1.0 / (d2 * ionSharp + uP_soft);

    // flare where a streamer lands on the glass, and the hot nucleus
    g *= 1.0 + uP_tipGain * smoothstep(0.55, 0.95, rr);
    float core = ionCore / (r * r * 8.0 + 0.05);

    // pink near the nucleus, violet-blue at the glass, cores whitened by
    // their own intensity
    vec3 fCol = mix(uC_inner, uC_arc, smoothstep(0.1, 0.75, rr));
    vec3 w = (fCol + vec3(uP_whiten) * g) * g + uC_inner * core + vec3(uP_fill);
    w = min(w, vec3(uP_stepClamp));
    w *= stepLen; // length-fair: limb chords are short and dim correctly

    acc += T * w;
    T *= exp(-dot(w, vec3(0.299, 0.587, 0.114)) * uP_scatter);
    if (T < 0.004) break;
  }

  return acc;
}

void main() {
  // Louder agent output softens and thickens the arcs and quickens the
  // writhe; user input flares the nucleus — the globe answers being spoken
  // to the way the real toy answers a fingertip.
  ionSharp = uP_sharp * (1.0 - 0.25 * uOutput);
  ionWrithe = uP_writhe * (1.0 + 0.6 * uOutput);
  ionCore = uP_coreGain * (1.0 + 1.6 * uInput + 0.4 * uOutput);
  ionExposure = uP_exposure * (1.0 - 0.35 * uOutput);
  ionRadius = uP_envRadius + uP_swell * uInput;

  vec3 acc = ionRender(gl_FragCoord.xy);

  // tanh tone map with a tunable knee, then the usual finishing chain
  vec3 col = tanh3(acc / max(ionExposure, 0.01));
  col = pow(clamp(col, 0.0, 1.0), vec3(uP_contrast));

  float lum = dot(col, vec3(0.299, 0.587, 0.114));
  col = mix(vec3(lum), col, uP_saturation);
  col *= uC_tint;

  // alpha from the brightest channel — a saturated violet streamer has low
  // luminance but must not go transparent
  float peak = max(col.r, max(col.g, col.b));
  float a = clamp(peak * uP_alphaGain, 0.0, 1.0);

  // analytic silhouette, identical construction to shdr-01: exact
  // ray-to-centre distance against the radius, colour AND alpha
  vec3 mrd = normalize(vec3(orbUV(), -uP_focal));
  float closest = length(cross(vec3(0.0, 0.0, uP_camDist), mrd));
  float band = mix(0.35, 0.012, clamp(uP_edge, 0.0, 1.0));
  float mask = 1.0 - smoothstep(ionRadius * (1.0 - band), ionRadius * 1.005, closest);
  col *= mask;
  a *= mask;

  // Emitted light, so rgb is already premultiplied — do NOT scale by alpha
  // again (see the same note in shdr-31).
  gl_FragColor = vec4(col, a);
}
`;

export const shdr13Orb: OrbVariant = {
  key: "shdr-13",
  label: "SHDR-13",
  note: "plasma globe: crawling lightning filaments",
  frag: ION_FRAG,
  params: [
    { key: "speed", label: "Anim speed", min: 0.015, max: 10, step: 0.05, default: 1, integrate: true },
    { key: "spin", label: "Spin rate", min: 0, max: 5, step: 0.03, default: 0.2, integrate: true },
    { key: "camDist", label: "Camera distance", min: 1, max: 50, step: 0.3, default: 7 },
    { key: "focal", label: "Lens", min: 0.15, max: 15, step: 0.1, default: 2.25 },
    { key: "envRadius", label: "Globe radius", min: 0.15, max: 15, step: 0.1, default: 2.6 },
    { key: "swell", label: "Input swell", min: 0, max: 1, step: 0.01, default: 0.15 },
    { key: "tilt", label: "Axis tilt", min: 0, max: 4, step: 0.02, default: 0.4 },
    { key: "fils", label: "Filament density", min: 0.5, max: 12, step: 0.1, default: 6 },
    { key: "writhe", label: "Writhe", min: 0, max: 3, step: 0.02, default: 0.9 },
    { key: "writheFreq", label: "Writhe frequency", min: 0.2, max: 8, step: 0.05, default: 1.6 },
    { key: "sharp", label: "Arc sharpness", min: 0.5, max: 60, step: 0.5, default: 4 },
    { key: "soft", label: "Arc core softness", min: 0.002, max: 0.5, step: 0.002, default: 0.06 },
    { key: "whiten", label: "Core whitening", min: 0, max: 0.2, step: 0.002, default: 0.008 },
    { key: "coreGain", label: "Nucleus glow", min: 0, max: 5, step: 0.05, default: 1.6 },
    { key: "tipGain", label: "Glass flare", min: 0, max: 6, step: 0.05, default: 1.8 },
    { key: "fill", label: "Body haze", min: 0, max: 2, step: 0.01, default: 0.02 },
    { key: "stepClamp", label: "Step clamp", min: 0.3, max: 300, step: 1.5, default: 40 },
    { key: "scatter", label: "Diffusion", min: 0, max: 0.5, step: 0.003, default: 0.012 },
    { key: "exposure", label: "Exposure", min: 0.1, max: 200, step: 0.5, default: 11 },
    { key: "contrast", label: "Contrast", min: 0.15, max: 15, step: 0.1, default: 1 },
    { key: "saturation", label: "Saturation", min: 0, max: 4, step: 0.02, default: 1.2 },
    { key: "alphaGain", label: "Alpha gain", min: 0.05, max: 15, step: 0.1, default: 2.5 },
    { key: "edge", label: "Edge sharpness", min: 0, max: 1, step: 0.01, default: 1 }
  ],
  colors: [
    { key: "inner", label: "Nucleus", default: "#ff70d8" },
    { key: "arc", label: "Arc", default: "#5a5cff" },
    { key: "tint", label: "Tint", default: "#ffffff" }
  ],
  statePresets: {
    // a full globe of swooping arcs around a hot nucleus
    idle: {
      speed: 1,
      spin: 0.2,
      fils: 6,
      writhe: 0.9,
      sharp: 4,
      soft: 0.06,
      whiten: 0.008,
      tipGain: 1.8,
      coreGain: 1.6,
      exposure: 11
    },
    // hunting: even more filaments, softer and more nebular, restless writhe
    thinking: {
      speed: 1.8,
      spin: 0.45,
      fils: 7,
      writhe: 1.2,
      sharp: 3,
      soft: 0.08,
      whiten: 0.012,
      tipGain: 1.5,
      coreGain: 1.2,
      exposure: 10
    },
    // discharge: the densest, brightest state — spiky arcs flaring hard on
    // the glass around a blazing core
    speaking: {
      speed: 2.6,
      spin: 0.3,
      fils: 8,
      writhe: 1.3,
      sharp: 3.5,
      soft: 0.05,
      whiten: 0.012,
      tipGain: 2.4,
      coreGain: 2,
      exposure: 8.5
    }
  }
};

export type Shdr13Props = Omit<ShaderOrbProps, "variant">;

export function Shdr13({ size = 280, ...rest }: Shdr13Props) {
  return <ShaderOrb variant={shdr13Orb} size={size} {...rest} />;
}

export default Shdr13;

16. components/ui/shdr-14.tsx

/*
 * Deliberately not a `"use client"` module — see the note in `shdr-11.tsx`.
 *
 * Note for editors: the shader lives in a template literal, so its comments
 * must not contain backticks.
 */
import { ShaderOrb, type OrbVariant, type ShaderOrbProps } from "@/components/ui/orbkit-core";

/* ----------------------------------------------------------------------------
   SHDR-14 — demoscene sine-plasma on a rolling dome, quantized to chunky
   two-tone pixels.

   The one retro renderer in the family, and it animates like one: the
   luminance is the CLASSIC demo plasma — three interfering sine waves —
   evaluated on the sphere's rotating dome point so the wavefronts roll
   around the ball, plus a ripple source that orbits the dome and pushes
   expanding rings through the interference. A lambert term keeps the ball
   solid and a fresnel rim edges it. All of it collapses into one luminance,
   and an 8x8 ORDERED BAYER DITHER snaps that onto a short tone ladder
   between two colours, ink and paper. At 2 tone steps it is the classic
   1-bit look.

   Construction notes:

   - The Bayer matrix is generated PROCEDURALLY. GLSL ES 1.0 has no array
     initializer lists and no bitwise operators, so the usual lookup-table
     and bit-interleave constructions are both unavailable. The fract() form
     below produces the exact 2x2 base matrix, and the recursion
     M(2n) = M(n)/4 + M(2) builds 8x8 from it.
   - The content is sampled at each CELL's centre, not per fragment, so the
     dots are crisp squares — including the silhouette, which goes blocky at
     the rim on purpose. Averaging per fragment would anti-alias the grid
     away and leave gray mush.
   - The dither threshold is per screen cell and static: ordered dither does
     not crawl. All motion lives in the content underneath it.
   - Clocks enter only as additive phase (plasma drift) or through cos/sin
     (light orbit) — integrated clocks, phase-safe as everywhere here.
   - Surface-lit and mask-bounded, so alpha IS coverage — premultiplied
     output, as in shdr-28.
---------------------------------------------------------------------------- */

const DITHER_FRAG = `
// 2x2 Bayer base: floor/fract only. (0,0)=0, (1,0)=.5, (0,1)=.75, (1,1)=.25
// — the 0,2,3,1 ordering over 4.
float bayer2(vec2 a) {
  a = floor(a);
  return fract(a.x / 2.0 + a.y * a.y * 0.75);
}

// 8x8 by recursion: M8 = M2(a/4)/16 + M2(a/2)/4 + M2(a). No arrays, no
// bitwise — neither exists in GLSL ES 1.0.
float bayer8(vec2 a) {
  return bayer2(a * 0.25) * 0.0625 + bayer2(a * 0.5) * 0.25 + bayer2(a);
}

void main() {
  // Volume coupling: user input deepens the waves, agent output brightens
  // the whole tone ladder — the dot field visibly blooms while it speaks.
  float plasmaAmt = uP_plasma * (1.0 + 0.4 * uInput);
  float gainNow = uP_gain * (0.85 + 0.5 * uOutput);

  /*
    Chunky pixel grid, RESOLUTION-RELATIVE: uP_cells is how many cells span
    the canvas, so a 190px gallery card and a 420px playground orb show the
    same composition — the same wave resolved by the same number of dots.
    Sized in device pixels instead, small canvases collapse to a few dozen
    blotches. All content below samples at the cell centre so every dot is
    one flat square.
  */
  float cellPx = max(min(uRes.x, uRes.y) / max(uP_cells, 8.0), 1.0);
  vec2 pix = floor(gl_FragCoord.xy / cellPx);
  vec2 cellCentre = (pix + 0.5) * cellPx;

  vec2 suv = (2.0 * cellCentre - uRes) / min(uRes.x, uRes.y);
  vec2 uv = suv / uP_radius;
  float r2 = dot(uv, uv);

  // blocky silhouette — cut on the cell grid, deliberately not smoothed
  float mask = 1.0 - step(1.0, r2);

  float z = sqrt(max(1.0 - r2, 0.0));
  vec3 n = vec3(uv, z);

  /*
    The plasma is evaluated in a ROTATING frame: the dome point spins about
    Y on its own integrated clock, so the wavefronts roll around the ball
    instead of sliding across a flat disc. The light stays screen-fixed —
    the form shading holds still while the pattern travels over it.
  */
  float rot = uP_spin; // integrated clock
  float cr = cos(rot);
  float sr = sin(rot);
  vec3 sp = vec3(n.x * cr - n.z * sr, n.y, n.x * sr + n.z * cr);

  float t = uP_speed; // integrated clock

  // the classic demoscene plasma: three interfering sine waves, each on its
  // own direction and rate
  float f = uP_scale;
  float v = sin(sp.x * f * 3.1 + t)
    + sin((sp.y * 0.85 + sp.z * 0.4) * f * 3.6 - t * 1.3)
    + sin((sp.x + sp.y + sp.z) * f * 2.2 + t * 0.7);

  // a ripple source orbiting the dome — expanding rings pushed through the
  // interference; the clock enters only as additive phase
  vec2 src = 0.55 * vec2(cos(t * 0.5), sin(t * 0.5));
  v += sin(length(uv - src) * f * 5.0 - t * 2.2);
  v *= 0.25; // four unit waves back to -1..1

  float lambert = clamp(dot(n, normalize(vec3(-0.45, 0.55, 0.7))), 0.0, 1.0);
  float fres = pow(1.0 - z, 2.0);

  // waves modulated by the dome shading, so the ball stays a ball under
  // the rolling pattern; everything collapses into one luminance
  float lum = (0.5 + 0.5 * v * plasmaAmt) * (0.3 + uP_light * lambert)
    + uP_rim * fres;
  lum = pow(clamp(lum * gainNow, 0.0, 1.0), uP_contrast);

  // ordered dither onto the tone ladder — levels 2 is the classic 1-bit
  // look, higher values keep the grain but add mid-tones
  float steps = max(uP_levels - 1.0, 1.0);
  float q = clamp(floor(lum * steps + bayer8(pix)) / steps, 0.0, 1.0);

  vec3 col = mix(uC_ink, uC_paper, q);

  // Surface-lit orb bounded by a mask: alpha IS coverage, so premultiply —
  // the opposite convention from the emissive orbs (see shdr-31).
  float a = mask;
  gl_FragColor = vec4(col * a, a);
}
`;

export const shdr14Orb: OrbVariant = {
  key: "shdr-14",
  label: "SHDR-14",
  note: "a lit plasma dome quantized to chunky two-tone pixels",
  frag: DITHER_FRAG,
  params: [
    { key: "speed", label: "Wave speed", min: 0.015, max: 10, step: 0.05, default: 0.5, integrate: true },
    { key: "spin", label: "Roll", min: 0, max: 5, step: 0.03, default: 0.15, integrate: true },
    { key: "radius", label: "Radius", min: 0.15, max: 3, step: 0.015, default: 0.9 },
    { key: "cells", label: "Grid cells", min: 32, max: 320, step: 2, default: 140 },
    { key: "levels", label: "Tone steps", min: 2, max: 8, step: 1, default: 3 },
    { key: "scale", label: "Wave scale", min: 0.3, max: 12, step: 0.1, default: 1.5 },
    { key: "plasma", label: "Wave amount", min: 0, max: 3, step: 0.015, default: 0.9 },
    { key: "light", label: "Key light", min: 0, max: 3, step: 0.015, default: 0.9 },
    { key: "rim", label: "Rim light", min: 0, max: 3, step: 0.015, default: 0.35 },
    { key: "gain", label: "Brightness", min: 0.05, max: 5, step: 0.05, default: 1 },
    { key: "contrast", label: "Contrast", min: 0.15, max: 10, step: 0.05, default: 1.1 }
  ],
  colors: [
    { key: "ink", label: "Ink", default: "#101426" },
    { key: "paper", label: "Paper", default: "#cfe6ff" }
  ],
  /*
    Staged in the family language: thinking churns the plasma in place while
    the light freezes, speaking sweeps the light fast and brightens the
    ladder. `pixel` and `levels` never move between states — both quantize,
    and a gliding quantizer pops instead of fading.
  */
  statePresets: {
    // calm: waves rolling slowly, dome barely turning
    idle: {
      speed: 0.5,
      spin: 0.15,
      plasma: 0.9,
      gain: 1,
      contrast: 1.1
    },
    // computing: the interference races IN PLACE — wave clock at three
    // times idle, deeper waves — while the dome stops turning
    thinking: {
      speed: 1.6,
      spin: 0.05,
      plasma: 1.15,
      gain: 0.95,
      contrast: 1.15
    },
    // answering: the whole dome rolls fast and the tones bloom bright
    speaking: {
      speed: 1.3,
      spin: 0.8,
      plasma: 1,
      gain: 1.3,
      contrast: 1.05
    }
  },
  // ink/paper carry the at-a-glance read: cool print at rest, violet-blue
  // while computing, warm amber while answering
  stateColors: {
    idle: { ink: "#101426", paper: "#cfe6ff" },
    thinking: { ink: "#140f38", paper: "#a9b9ff" },
    speaking: { ink: "#2a1410", paper: "#ffd9a4" }
  }
};

export type Shdr14Props = Omit<ShaderOrbProps, "variant">;

export function Shdr14({ size = 280, ...rest }: Shdr14Props) {
  return <ShaderOrb variant={shdr14Orb} size={size} {...rest} />;
}

export default Shdr14;

17. components/ui/shdr-15.tsx

/*
 * Deliberately not a `"use client"` module — see the note in `shdr-11.tsx`.
 *
 * Note for editors: the shader lives in a template literal, so its comments
 * must not contain backticks.
 */
import { ShaderOrb, type OrbVariant, type ShaderOrbProps } from "@/components/ui/orbkit-core";

/* ----------------------------------------------------------------------------
   SHDR-15 — an iridescent particle-track web, worn as the ball's own skin.

   Ported from a golfed twigl listing:

     for(float i,z,d,s;i++<1e1;o+=(cos(z/.03+t+vec4(0,2,3,0))+1.)/d/s){
       vec3 p=z*normalize(FC.rgb*2.-r.xyy),
            a=normalize(cos(vec3(7,1,0)+t-s));
       p.z+=9.,a=a*dot(a,p)-cross(a,p);
       for(d=1.;d++<9.;)a+=sin(a*d+t).yzx/d;
       z+=d=.03*abs(sin(s=length(a)));}
     o=tanh(o/3e3);

   What it actually is, decoded:

   - TEN steps, each at most 0.03 long: the whole march is a MICRO-SLAB a
     third of a unit thick. This is not a volume, it is ten nested layers
     of one interference pattern.
   - d = .03*abs(sin(length(a))) sticks the march wherever the turbulent
     field magnitude sits on a multiple of pi — concentric shells in field
     space. The 1/d weight genuinely reaches infinity there; tanh eats it,
     and those near-zeros ARE the bright web cores.
   - a*dot(a,p) - cross(a,p) is the minus-90-degree twin of shdr-22'
     exact Rodrigues rotation. The axis cos((7,1,0)+t-s) carries FEEDBACK:
     s is last step's field magnitude, so every layer takes a differently
     jittered axis and the ten layers interfere.
   - cos(z/.03 + t + (0,2,3)) cycles the palette once per layer or so —
     the iridescent banding.
   - p.z += 9 puts the camera OUTSIDE, like shdr-22.

   Port decisions, each one a documented trap or rule in the README:

   - THE ORB IS THE OBJECT: the micro-slab is anchored to the sphere
     ANALYTICALLY — each ray starts its ten tiny steps at its own
     sphere-entry point, so the slab follows the ball's curve and the web
     becomes the orb's actual skin, wrapping the limb for free. The depth
     hue uses (z - entry), turning layer banding into skin iridescence.
   - The golfed listing relies on i, z, d, s starting at zero —
     uninitialised locals are UNDEFINED in GLSL ES 1.0, explicit here.
   - The 1/d spike is clamped and the clamped weight is normalized back to
     family units, exactly as in shdr-22 (the golfed knee here is 3e3)
     — so stepClamp stays a pure dynamic-range knob.
   - Clocks enter only as additive phase; the axis gets its own integrated
     clock so its wander rate tunes without jumping the web.
---------------------------------------------------------------------------- */

/*
 * Step counts are `#define`s: ES 1.0 requires constant loop bounds.
 * TURB is 8 to match the original's octaves (d = 2..9).
 */
const MUONS_FRAG = `
#define STEPS 10
#define TURB 8
#define AA 1

// Volume-reactive values, resolved once per fragment in main().
float muonsTurb;
float muonsExposure;

vec3 muonsRender(vec2 fragCoord) {
  float animTime = uP_speed; // integrated clock: weave + hue phase
  float wander = uP_wander;  // integrated clock: axis drift

  vec2 uv = (2.0 * fragCoord - uRes) / min(uRes.x, uRes.y);
  vec3 ro = vec3(0.0, 0.0, uP_camDist);
  vec3 rd = normalize(vec3(uv, -uP_focal));

  /*
    Anchor the micro-slab to the ball: intersect the ray with the shell
    analytically and start the ten steps AT the entry point, so the slab
    hugs the sphere's curve. Rays that miss fall back to their closest
    approach — the envelope and silhouette cut them anyway.
  */
  float proj = dot(-ro, rd);
  float b2 = dot(ro, ro) - proj * proj;
  float R = uP_envRadius * 0.96;
  float entry = proj - sqrt(max(R * R - b2, 0.0));

  vec3 acc = vec3(0.0);
  float T = 1.0;
  float z = entry;
  float s = 0.0;

  for (int it = 0; it < STEPS; it++) {
    vec3 p = ro + rd * z;

    // shell points scaled into field space — the original worked around
    // magnitude 9, and the ring density rides on that magnitude
    vec3 q = p * uP_fieldScale;

    // the per-layer axis, with the original's s feedback — each of the
    // ten layers takes a differently jittered axis
    vec3 axis = normalize(cos(vec3(7.0, 1.0, 0.0) + wander - s));

    // the minus-90-degree Rodrigues twin of shdr-22
    vec3 a = axis * dot(axis, q) - cross(axis, q);

    for (int j = 0; j < TURB; j++) {
      float dj = float(j) + 2.0;
      a += muonsTurb * sin(a * dj + animTime).yzx / dj;
    }

    // the shells: the march sticks where the field magnitude sits on a
    // multiple of pi, and 1/d blows up — that is the web
    s = length(a);
    float d = uP_stepScale * abs(sin(s));
    d = max(d, 1e-5);
    z += d;

    /*
      Layer-cycled palette, with the depth measured from the ENTRY point
      so the banding follows the ball's skin. Clamp then normalize to
      family units, as in shdr-22 — the raw spikes run to 1/1e-5.
    */
    vec3 w = (cos((z - entry) / max(uP_stepScale, 1e-3) + animTime + vec3(0.0, 2.0, 3.0) * uP_disperse) + 1.0)
      / d / max(s, 0.5);
    w = min(w, vec3(uP_stepClamp));
    w *= 20.0 / max(uP_stepClamp, 1.0);

    // envelope: plateau through the ball, cut 12% past the radius so the
    // analytic silhouette in main() still has emission left to cut
    float env = smoothstep(uP_envRadius * 1.12, uP_envRadius * uP_envCore, length(p));
    w = (w + uP_fill) * env;

    acc += T * w;
    T *= exp(-dot(w, vec3(0.299, 0.587, 0.114)) * uP_scatter);

    if (T < 0.004) break;
  }

  return acc;
}

void main() {
  muonsTurb = uP_turb * (1.0 + 0.5 * uInput);
  muonsExposure = uP_exposure * (1.0 - 0.35 * uOutput);

  vec3 acc = vec3(0.0);
#if AA > 1
  for (int mx = 0; mx < AA; mx++) {
    for (int my = 0; my < AA; my++) {
      vec2 offset = vec2(float(mx), float(my)) / float(AA) - 0.5;
      acc += muonsRender(gl_FragCoord.xy + offset);
    }
  }
  acc /= float(AA * AA);
#else
  acc = muonsRender(gl_FragCoord.xy);
#endif

  // tanh tone map per channel — the golfed /3e3 knee is a tunable here
  vec3 col = tanh3(acc / max(muonsExposure, 1.0));
  col = pow(clamp(col, 0.0, 1.0), vec3(uP_contrast));

  // saturation about luminance, then the tint
  float lum = dot(col, vec3(0.299, 0.587, 0.114));
  col = mix(vec3(lum), col, uP_saturation);
  col *= uC_tint;

  // alpha from the brightest channel, not luminance — a saturated violet
  // thread has low luminance but must not go transparent
  float peak = max(col.r, max(col.g, col.b));
  float a = clamp(peak * uP_alphaGain, 0.0, 1.0);

  // Analytic silhouette — identical construction to shdr-01: exact
  // ray-to-centre distance against the radius, colour AND alpha.
  vec3 mrd = normalize(vec3(orbUV(), -uP_focal));
  float closest = length(cross(vec3(0.0, 0.0, uP_camDist), mrd));
  float band = mix(0.35, 0.012, clamp(uP_edge, 0.0, 1.0));
  float mask = 1.0 - smoothstep(uP_envRadius * (1.0 - band), uP_envRadius * 1.005, closest);
  col *= mask;
  a *= mask;

  // safety taper at the frame boundary — colour as well as alpha
  float r2d = length(orbUV());
  float fade = 1.0 - smoothstep(uP_edgeFade, 1.0, r2d);
  col *= fade;
  a *= fade;

  // Emitted light, so rgb is already premultiplied — do NOT scale by alpha
  // again (see the same note in shdr-31).
  gl_FragColor = vec4(col, a);
}
`;

export const shdr15Orb: OrbVariant = {
  key: "shdr-15",
  label: "SHDR-15",
  note: "an iridescent particle-track web worn as the ball's skin",
  frag: MUONS_FRAG,
  params: [
    { key: "speed", label: "Anim speed", min: 0.015, max: 10, step: 0.05, default: 0.5, integrate: true },
    { key: "wander", label: "Axis wander", min: 0, max: 3, step: 0.015, default: 0.15, integrate: true },
    { key: "camDist", label: "Camera distance", min: 1, max: 50, step: 0.3, default: 7 },
    { key: "focal", label: "Lens", min: 0.15, max: 15, step: 0.1, default: 2.25 },
    { key: "fieldScale", label: "Web density", min: 1, max: 20, step: 0.1, default: 2.4 },
    { key: "turb", label: "Weave", min: 0, max: 5, step: 0.03, default: 0.8 },
    { key: "stepScale", label: "Skin depth", min: 0.0015, max: 0.4, step: 0.005, default: 0.015 },
    { key: "disperse", label: "Dispersion", min: 0, max: 5, step: 0.03, default: 1 },
    { key: "envRadius", label: "Envelope radius", min: 0.15, max: 15, step: 0.1, default: 2.6 },
    { key: "envCore", label: "Envelope core", min: 0.3, max: 1.02, step: 0.01, default: 1 },
    { key: "fill", label: "Body fill", min: 0, max: 100, step: 0.3, default: 0.3 },
    { key: "stepClamp", label: "Step clamp", min: 3, max: 5000, step: 30, default: 150 },
    { key: "scatter", label: "Diffusion", min: 0, max: 0.5, step: 0.003, default: 0.01 },
    { key: "exposure", label: "Exposure", min: 1.5, max: 1500, step: 10, default: 50 },
    { key: "contrast", label: "Contrast", min: 0.15, max: 15, step: 0.1, default: 1.35 },
    { key: "saturation", label: "Saturation", min: 0, max: 4, step: 0.02, default: 1.35 },
    { key: "alphaGain", label: "Alpha gain", min: 0.05, max: 15, step: 0.1, default: 2 },
    { key: "edge", label: "Edge sharpness", min: 0, max: 1, step: 0.01, default: 1 },
    { key: "edgeFade", label: "Halo falloff", min: 0.1, max: 3, step: 0.015, default: 0.98 }
  ],
  colors: [{ key: "tint", label: "Tint", default: "#ffffff" }],
  /*
    Staged on the two integrated clocks, as across the family: thinking
    sends the AXIS hunting (the web continuously reweaves in place) while
    speaking is speed-led (the layer-cycled iridescence shimmers fast and
    bright). turb and disperse are amplitudes/phases — everything glides.
  */
  statePresets: {
    // calm: slow weave, near-still axis
    idle: {
      speed: 0.4,
      wander: 0.12,
      turb: 0.75,
      disperse: 1,
      exposure: 72,
      scatter: 0.01,
      alphaGain: 2
    },
    // reweaving: the axis hunts at six times idle and the weave deepens —
    // the web knits and unknits in place, spectrum pulled tighter
    thinking: {
      speed: 1,
      wander: 0.7,
      turb: 0.95,
      disperse: 0.8,
      exposure: 66,
      scatter: 0.0095,
      alphaGain: 2.1
    },
    // answering: fast iridescent shimmer, wide spectrum, hot threads
    speaking: {
      speed: 2,
      wander: 0.3,
      turb: 1.1,
      disperse: 1.6,
      exposure: 46,
      scatter: 0.0075,
      alphaGain: 2.5
    }
  }
};

export type Shdr15Props = Omit<ShaderOrbProps, "variant">;

export function Shdr15({ size = 280, ...rest }: Shdr15Props) {
  return <ShaderOrb variant={shdr15Orb} size={size} {...rest} />;
}

export default Shdr15;

18. components/ui/shdr-16.tsx

/*
 * Deliberately not a `"use client"` module — see the note in `shdr-11.tsx`.
 *
 * Note for editors: the shader lives in a template literal, so its comments
 * must not contain backticks.
 */
import { ShaderOrb, type OrbVariant, type ShaderOrbProps } from "@/components/ui/orbkit-core";

/* ----------------------------------------------------------------------------
   SHDR-16 — sunlight through water: a caustic net crawling over the ball,
   split into colour at its edges, surging with the agent's voice.

   Not a port. Built from the README's own rule for surface detail — an
   isoline through a warped field — pushed until it reads as the thing it
   is imitating: the bright branching network a pool throws on its floor.

   What it is:

   - TWO FAMILIES OF LINES, CROSSED. 1 - abs(sin(q)) is one on a crest and
     zero between, on each axis; raised to a power it is a thin line or a
     broad wash, and the product of the two families is added back so the
     crossings burn hotter than the lines. That product is what makes it
     caustic rather than grid: real caustics are brightest where wavefronts
     focus together, and the crossings are exactly those foci. The sum is
     normalised to one at a crossing, on purpose: past the tone knee an
     unbounded sum clips every channel and the sun colour goes white, so
     gain would only ever buy you washout. Bounded, gold stays gold at the
     hottest focus.
   - THE WATER IS A FOLD. Before the lines are read, the plane is folded on
     its own sines three times at rising frequency, each octave on its own
     clock rate. That is refraction through a rippling surface, stated as a
     domain warp: the grid does not scroll, it is bent, and the lines branch
     and pinch where the bends pile up.
   - CHROMATIC SPLIT ON TIME, NOT SPACE. The net is read three times, one
     per channel, each at a slightly different moment of the fold. Where a
     line is moving its three readings disagree; where it holds still they
     agree. The light itself is the net's LUMINANCE under the sun colour,
     and the disagreement is split off as a zero-mean residual added back
     on its own amplitude — so the rainbow is a fringe on moving edges,
     never a tint on the whole net, and the sun colour survives it. That is
     how dispersion through water actually looks: white light, coloured
     edges.
   - THE SURGE is a round trip on an integrated clock through cos — it
     eases through both ends and never wraps, the shdr-31 construction —
     and it drives gain and ripple depth together, so the water brightens
     as it deepens. Depth is one amplitude, so a state can have all of it,
     none, or a flicker of it.

   Design decisions, each one a rule in the README:

   - THE ORB IS THE OBJECT. The net lives on the sphere's own surface
     direction, not on a disc cut out of a plane. But the README's move for
     flat fields — a stereographic projection of the dome — has a POLE, and
     a ball that turns indefinitely carries whatever texture is behind it
     through that pole, where the map stretches it toward infinity. The
     aurora hid an atan seam by crossfading two wrappings; this uses
     TRIPLANAR mapping instead, which has no seam and no pole: the plane
     field is read on the three axis planes of the surface direction and
     blended by how squarely each faces it. Three reads per channel, nine in
     all — the field is a handful of sines, so it is cheap.
   - EVERY INTERNAL FREQUENCY IS A LITERAL. The fold frequencies and the
     per-octave clock rates are written into the shader, so the net's
     spacing is ONE control — uP_scale — and it is the only spatial
     frequency in the orb. It is never staged. Everything a state does
     touch is an amplitude or an integrated clock, which is what lets all
     three states cross-fade with nothing racing across the surface.
   - pow() only ever sees a base in [0, 1] here — 1 - abs(sin) — so the
     edge exponent is safe to sweep. The tone knee is applied after a max()
     for the same reason.
   - Surface-lit and mask-bounded, so alpha IS coverage: premultiplied
     output, the opposite convention from the emissive orbs (see shdr-31).
---------------------------------------------------------------------------- */

const CAUSTIC_FRAG = `
// Volume- and surge-reactive values, resolved once per fragment in main().
float causticWarp;

/*
  The water. The plane is folded on its own sines three times, each octave
  at a literal frequency and on its own share of the clock, so the ripples
  refract the net rather than scroll it. Amplitude is the one control.
*/
vec2 fold(vec2 p, float t) {
  p += causticWarp        * sin(p.yx * 1.31 + vec2( t * 0.90, -t * 0.70));
  p += causticWarp * 0.60 * sin(p.yx * 2.17 + vec2(-t * 1.30,  t * 1.10));
  p += causticWarp * 0.35 * sin(p.yx * 3.73 + vec2( t * 1.90,  t * 1.60));
  return p;
}

/*
  The light. Two crossed families of crest lines, sharpened by the edge
  exponent — the base is 1 - abs(sin), always in [0, 1], so pow is defined —
  with their product added back so the crossings, where wavefronts focus,
  burn hotter than the lines between them.
*/
float net(vec2 p, float t) {
  vec2 q = fold(p, t);
  vec2 s = 1.0 - abs(sin(q));
  vec2 l = pow(s, vec2(uP_edge));
  // Normalised to [0, 1]: the sum peaks at four on a crossing, and left
  // unbounded it drove the tone knee into clipping all three channels,
  // which turns any sun colour white. Bounded, the sun colour survives
  // the knee at the foci and gain is a real brightness rather than a
  // race to white.
  return (l.x + l.y + 2.0 * l.x * l.y) * 0.25;
}

/*
  Triplanar: the plane field read on the three axis planes of the surface
  direction and blended by the fourth power of each component, so each
  plane only shows where it faces squarely. No pole and no seam — the ball
  can turn forever.
*/
float netOn(vec3 sp, float t) {
  vec3 w = sp * sp;
  w *= w;
  w /= (w.x + w.y + w.z);
  float k = uP_scale;
  return w.x * net(sp.yz * k, t) + w.y * net(sp.zx * k, t) + w.z * net(sp.xy * k, t);
}

void main() {
  vec2 uv = orbUV();
  float rd = length(uv);
  float R = uP_radius;
  float mask = smoothstep(0.012, -0.012, rd - R);
  if (mask <= 0.0) {
    gl_FragColor = vec4(0.0);
    return;
  }

  vec2 pl = uv / R;
  float z = sqrt(max(1.0 - dot(pl, pl), 0.0));
  vec3 n = vec3(pl, z);

  // the ball turns about Y on its own integrated clock
  float cr = cos(uP_spin);
  float sr = sin(uP_spin);
  vec3 sp = vec3(n.x * cr - n.z * sr, n.y, n.x * sr + n.z * cr);

  float t = uP_flow; // integrated clock: the water

  /*
    The surge: a round trip on an integrated clock through cos, so it eases
    through both ends and never wraps. It lifts the gain and deepens the
    ripple together — brighter as the water heaves — and uP_swell is how
    much of that a state takes.

    Volume coupling in the family language: the agent's voice brightens the
    light, the user's deepens the water.
  */
  float surge = 0.5 - 0.5 * cos(uP_swellRate);
  float gainNow = uP_gain * mix(1.0, 0.55 + 0.9 * surge, uP_swell) * (0.8 + 0.5 * uOutput);
  causticWarp = uP_warp * mix(1.0, 0.8 + 0.4 * surge, uP_swell) * (1.0 + 0.35 * uInput);

  /*
    Three moments of the fold, one per channel. The LIGHT is the net's
    luminance under the sun colour, so gold is gold at the foci; the
    per-channel disagreement is split off as a zero-mean residual and added
    back scaled by uP_split, so the rainbow is a fringe that rides on the
    edges where they move and vanishes where they hold — never a tint on
    the whole net. Read once with the offset baked in and once without
    would cost the same three evaluations, so the offset is constant and
    the split is a plain amplitude on the residual: safe to stage.
  */
  float ds = 0.09;
  vec3 c = vec3(netOn(sp, t + ds), netOn(sp, t), netOn(sp, t - ds));
  float cLum = dot(c, vec3(1.0 / 3.0));
  vec3 fringe = (c - cLum) * uP_split;

  float lambert = clamp(dot(n, normalize(vec3(-0.45, 0.55, 0.7))), 0.0, 1.0);
  float fres = pow(1.0 - z, 2.5);

  // the floor of the pool, then the light thrown on it — dimmer round the
  // limb, where the floor tilts away from the sun
  vec3 col = uC_deep * (0.35 + 0.65 * uP_light * lambert);
  col += (uC_sun * cLum + fringe) * gainNow * (0.55 + 0.45 * lambert);
  col += uC_sheen * uP_rim * fres;

  col = pow(max(col, vec3(0.0)), vec3(uP_contrast));
  col = tanh3(col);

  // Surface orb bounded by a mask: alpha IS coverage, so premultiply — the
  // opposite convention from the emissive orbs (see shdr-31).
  float a = mask;
  gl_FragColor = vec4(max(col, vec3(0.0)) * a, a);
}
`;

export const shdr16Orb: OrbVariant = {
  key: "shdr-16",
  label: "SHDR-16",
  note: "sunlight through water — a caustic net crawling over the ball, fringing into colour where it moves",
  frag: CAUSTIC_FRAG,
  params: [
    { key: "flow", label: "Water flow", min: 0.015, max: 10, step: 0.05, default: 0.9, integrate: true },
    { key: "spin", label: "Turn", min: 0, max: 5, step: 0.03, default: 0.08, integrate: true },
    { key: "swellRate", label: "Surge rate", min: 0, max: 8, step: 0.05, default: 0.6, integrate: true },
    { key: "radius", label: "Radius", min: 0.15, max: 3, step: 0.015, default: 0.9 },
    { key: "scale", label: "Net scale", min: 3, max: 30, step: 0.1, default: 9 },
    { key: "warp", label: "Ripple depth", min: 0, max: 3, step: 0.02, default: 0.8 },
    { key: "edge", label: "Line sharpness", min: 0.5, max: 10, step: 0.05, default: 2.2 },
    { key: "split", label: "Colour fringe", min: 0, max: 3, step: 0.02, default: 0.6 },
    { key: "swell", label: "Surge depth", min: 0, max: 1, step: 0.01, default: 0.15 },
    { key: "gain", label: "Sun power", min: 0.05, max: 6, step: 0.05, default: 1.6 },
    { key: "contrast", label: "Tone knee", min: 0.15, max: 6, step: 0.05, default: 1.1 },
    { key: "light", label: "Floor light", min: 0, max: 3, step: 0.015, default: 0.9 },
    { key: "rim", label: "Rim sheen", min: 0, max: 3, step: 0.015, default: 0.6 }
  ],
  // the pool floor, the light thrown on it, and the wet gloss at the limb
  colors: [
    { key: "deep", label: "Water", default: "#0b2f6e" },
    { key: "sun", label: "Caustic light", default: "#7ff6ff" },
    { key: "sheen", label: "Sheen", default: "#bfe8ff" }
  ],
  /*
    Staged on the three integrated clocks and on amplitudes only — nothing
    a state touches is a spatial frequency, so every transition cross-fades
    with nothing racing across the surface. NET SCALE is the one frequency
    in the orb and it is never staged: it multiplies the surface direction
    before the fold, so gliding it would sweep the whole net through every
    spacing in between. Line sharpness is a power exponent on a base in
    [0, 1], an amplitude, and safe at any span.

    The read is in the water:

      idle     a slow pool, lit HARD. Gentle ripple, moderate lines, the
               faintest surge, the ball barely turning — but the sun at
               full power on a firmer knee, so the caustics burn white
               over dark red water.
      thinking NERVOUS water. The flow runs at three and a half times
               resting on a ripple nearly double rest's, and the lines
               go broad under it — a coarse, fast, restless net. The
               surge is shallow but quick, a flicker rather than a heave,
               and the ball all but freezes so the motion is in the
               light. The sun is dropped to half rest's power with the
               key light and rim pulled down: white on a darker red,
               dimmer than rest.
      speaking the water HEAVES, fast. Full surge depth on a rate five
               times rest's, so the light pumps in quick breaths, on the
               deepest ripple and the broadest lines — sheets of light
               rather than threads — with the colour split wide so every
               edge fringes, and the ball plainly turning beneath it. The
               rim is all but cut so the light is the sun alone. Warm:
               gold on brick red.
  */
  statePresets: {
    idle: {
      flow: 0.92,
      spin: 0.09,
      swellRate: 0.6,
      warp: 0.8,
      edge: 3,
      split: 0.6,
      swell: 0.15,
      gain: 6,
      contrast: 1.3,
      light: 0.9,
      rim: 0.6
    },
    thinking: {
      flow: 3.22,
      spin: 0.03,
      swellRate: 2.4,
      warp: 1.52,
      edge: 2.8,
      split: 0.36,
      swell: 0.26,
      gain: 3.35,
      contrast: 1.15,
      light: 0.525,
      rim: 0.21
    },
    speaking: {
      flow: 1.8,
      spin: 0.7,
      swellRate: 5.4,
      warp: 1.7,
      edge: 1.7,
      split: 1.1,
      swell: 1,
      gain: 3.6,
      contrast: 0.85,
      light: 0.96,
      rim: 0.18
    }
  },
  // aqua light on dark red water at rest, pure white on a darker red while
  // searching, gold on brick-red water while answering
  stateColors: {
    idle: {
      deep: "#6f0b0b",
      sun: "#7ff6ff",
      sheen: "#bfe8ff"
    },
    thinking: {
      deep: "#3a0808",
      sun: "#ffffff",
      sheen: "#ffffff"
    },
    speaking: {
      deep: "#8d2525",
      sun: "#ffb914",
      sheen: "#ffb3c6"
    }
  }
};

export type Shdr16Props = Omit<ShaderOrbProps, "variant">;

export function Shdr16({ size = 280, ...rest }: Shdr16Props) {
  return <ShaderOrb variant={shdr16Orb} size={size} {...rest} />;
}

export default Shdr16;

19. components/ui/shdr-17.tsx

/*
 * Deliberately not a `"use client"` module — see the note in `shdr-11.tsx`.
 *
 * Note for editors: the shader lives in a template literal, so its comments
 * must not contain backticks.
 */
import { ShaderOrb, type OrbVariant, type ShaderOrbProps } from "@/components/ui/orbkit-core";

/* ----------------------------------------------------------------------------
   SHDR-17 — a grainy, hyper-saturated storm raging on the ball.

   The weather is the classic two-level DOMAIN WARP: the storm field is fbm
   sampled where a previous fbm said to look, so the clouds tear and curl
   instead of just scrolling. It lives on a stereographic wrap of a rotating
   dome, with JOVIAN BAND SHEAR on top — latitude rings flowing past each
   other at different speeds, banded like a gas giant, because sp.y is
   invariant under the dome's Y-roll the bands stay horizontal while the
   weather rolls beneath them.

   Colour is where it goes loud: a four-stop gradient (deep → low → mid → hot)
   climbs the storm field, and an iridescent cosine rainbow keyed to the same
   field is multiplied over it, so every pressure level of the storm carries
   its own hue and the whole ball cycles through many colours at once.

   The GRAIN is the signature. Two taps of animated white noise, refreshed on
   the ambient clock at about 24 fps — cinema rate: fast enough to read as
   film flicker rather than stutter, and deliberately NOT the integrated
   clock, because the flicker rate should not follow the agent state:

     FIELD GRAIN  folded into the storm field BEFORE the gradient, so the
                  colour stops dither into speckle instead of smooth bands —
                  the risograph read.
     FILM GRAIN   multiplied over the final colour, plain photographic noise.

   Lightning: a hashed gate per flash interval with an exponential decay —
   most intervals stay dark, some flash — and the gate opens wide with agent
   output, so a speaking orb strobes its high-pressure cells.

   Surface-lit and mask-bounded, so alpha IS coverage — premultiplied output,
   as in shdr-14.
---------------------------------------------------------------------------- */

const TEMPEST_FRAG = `
const float PI = 3.14159265359;

// Animated white noise, one tap per grain cell per grain frame. The seed
// decorrelates the two taps so field grain and film grain never line up.
float grainNoise(vec2 gpix, float frame, float seed) {
  return hash(gpix + vec2(frame * 13.71 + seed, frame * 7.37 - seed));
}

void main() {
  // Volume coupling: user input churns the warp harder, agent output
  // brightens the field — the lightning gate opens separately below.
  float warpNow = uP_warp * (1.0 + 0.55 * uInput);
  float gainNow = uP_gain * (0.85 + 0.45 * uOutput);

  vec2 uv = orbUV();
  float rd = length(uv);
  float R = uP_radius;
  float mask = smoothstep(0.012, -0.012, rd - R);

  // The storm below costs five fbm evaluations per fragment — skip all of it
  // outside the silhouette instead of computing weather for transparent sky.
  if (mask <= 0.0) {
    gl_FragColor = vec4(0.0);
    return;
  }

  vec2 pl = uv / R;
  float r2 = dot(pl, pl);
  float z = sqrt(max(1.0 - r2, 0.0));
  vec3 n = vec3(pl, z);

  // roll the dome about Y on its own integrated clock
  float cr = cos(uP_spin);
  float sr = sin(uP_spin);
  vec3 sp = vec3(n.x * cr - n.z * sr, n.y, n.x * sr + n.z * cr);

  float t = uP_speed; // integrated clock

  // stereographic wrap: the weather travels around the ball and compresses
  // toward the limb instead of sliding across a flat disc
  vec2 st = sp.xy / (1.3 + sp.z) * uP_scale;

  /*
    Jovian band flow: a uniform stream plus a BOUNDED traveling wave of
    shear, so latitude rings appear to slip past each other. The obvious
    construction — t * sin(latitude) — accumulates the differential forever
    and rakes the field into hairline streaks within seconds of the random
    mount phase; the wave form keeps the shear amplitude fixed while its
    phase travels. sp.y is untouched by the Y-roll, so the bands hold
    horizontal while the dome turns underneath them.
  */
  st.x -= t * 0.3;
  st.x += uP_shear * sin(sp.y * uP_bands - t * 0.45);

  // two-level domain warp, the storm-cloud construction: q says where to
  // look, w says where q said to look, the field reads there
  vec2 q = vec2(
    fbm(st + vec2(0.0, t * 0.35)),
    fbm(st + vec2(5.2, 1.3) - vec2(t * 0.28, 0.0))
  );
  vec2 w = vec2(
    fbm(st + warpNow * q + vec2(1.7, 9.2) + vec2(t * 0.12, 0.0)),
    fbm(st + warpNow * q + vec2(8.3, 2.8) - vec2(0.0, t * 0.1))
  );
  float f = fbm(st + uP_churn * w);

  /*
    Grain tap 1: speckle folded into the FIELD itself, before the gradient,
    so the colour stops below dither into grain instead of smooth bands.
    Refreshed on the ambient clock — the flicker rate stays constant across
    states on purpose (see the header note).
  */
  vec2 gpix = floor(gl_FragCoord.xy / max(uP_grainSize, 1.0));
  float frame = floor(uTime * 48.0);
  float g1 = grainNoise(gpix, frame, 3.1);
  f += (g1 - 0.5) * uP_grain;

  f = pow(clamp(f * gainNow, 0.0, 1.0), uP_contrast);

  // four-stop palette climbing the storm field
  vec3 col = mix(uC_deep, uC_low, smoothstep(0.05, 0.35, f));
  col = mix(col, uC_mid, smoothstep(0.35, 0.62, f));
  col = mix(col, uC_hot, smoothstep(0.62, 0.88, f));

  // iridescent shimmer: a cosine rainbow keyed to the field AND to the warp
  // vector — q varies at storm-cell scale, so the rainbow lands as coherent
  // coloured weather cells instead of hue noise that optically averages to
  // grey — multiplied in so it bends hues without erasing the palette
  vec3 shimmer = 0.5 + 0.5 * cos(2.0 * PI * (f * 0.9 + q.x * 1.1 + t * 0.06 + vec3(0.0, 0.33, 0.67)));
  col = mix(col, col * (0.35 + 1.9 * shimmer), uP_rainbow);

  /*
    Lightning: one hashed gate per flash interval with an exponential decay,
    so most intervals stay dark and some strike. Agent output opens the gate
    — an idle orb flickers occasionally, a speaking one strobes. The strike
    lands hardest on the high-pressure cells of the field.
  */
  float ft = t * uP_flashRate;
  float gate = step(1.0 - (0.1 + 0.5 * uOutput), hash(vec2(floor(ft), 7.7)));
  float flashEnv = gate * exp(-fract(ft) * 6.0);
  // squared so the strike stays inside the storm cells — a linear weight
  // tints the whole ball and reads as the canvas strobing, not as weather
  float high = smoothstep(0.55, 0.95, f);
  col += uC_flash * (flashEnv * uP_flash) * (0.06 + 0.94 * high * high);

  // dome shading keeps the ball a ball under the weather
  float lambert = clamp(dot(n, normalize(vec3(-0.45, 0.55, 0.7))), 0.0, 1.0);
  col *= 0.35 + uP_light * lambert;
  float fres = pow(1.0 - z, 2.5);
  col += uC_flash * uP_rim * fres * (0.4 + 0.35 * flashEnv);

  // grain tap 2: plain film grain over the final colour
  float g2 = grainNoise(gpix, frame, 27.9);
  col *= 1.0 + (g2 - 0.5) * uP_filmGrain;

  // Surface orb bounded by a mask: alpha IS coverage, so premultiply — the
  // opposite convention from the emissive orbs (see shdr-31).
  float a = mask;
  gl_FragColor = vec4(max(col, vec3(0.0)) * a, a);
}
`;

export const shdr17Orb: OrbVariant = {
  key: "shdr-17",
  label: "SHDR-17",
  note: "a grainy many-coloured storm with band shear and lightning",
  frag: TEMPEST_FRAG,
  params: [
    { key: "speed", label: "Storm speed", min: 0.015, max: 10, step: 0.05, default: 0.9, integrate: true },
    { key: "spin", label: "Roll", min: 0, max: 5, step: 0.03, default: 0.12, integrate: true },
    { key: "radius", label: "Radius", min: 0.15, max: 3, step: 0.015, default: 0.9 },
    { key: "scale", label: "Weather scale", min: 0.3, max: 12, step: 0.1, default: 2.4 },
    { key: "bands", label: "Band count", min: 0, max: 20, step: 0.1, default: 6 },
    { key: "shear", label: "Band shear", min: 0, max: 5, step: 0.03, default: 1.1 },
    { key: "warp", label: "Warp", min: 0, max: 8, step: 0.05, default: 2.2 },
    { key: "churn", label: "Churn", min: 0, max: 8, step: 0.05, default: 1.4 },
    { key: "gain", label: "Brightness", min: 0.05, max: 5, step: 0.05, default: 1.15 },
    { key: "contrast", label: "Contrast", min: 0.15, max: 10, step: 0.05, default: 1.35 },
    { key: "grain", label: "Field grain", min: 0, max: 2, step: 0.01, default: 0.4 },
    { key: "filmGrain", label: "Film grain", min: 0, max: 2, step: 0.01, default: 0.35 },
    { key: "grainSize", label: "Grain size", min: 1, max: 8, step: 1, default: 2 },
    { key: "rainbow", label: "Iridescence", min: 0, max: 2, step: 0.01, default: 0.65 },
    { key: "flashRate", label: "Flash rate", min: 0, max: 10, step: 0.05, default: 1.6 },
    { key: "flash", label: "Flash power", min: 0, max: 5, step: 0.03, default: 1.2 },
    { key: "light", label: "Key light", min: 0, max: 3, step: 0.015, default: 0.85 },
    { key: "rim", label: "Rim light", min: 0, max: 3, step: 0.015, default: 0.5 }
  ],
  /*
   * Five stops: four climbing the storm field plus the lightning colour.
   * The iridescence param multiplies a rainbow over all of them, so the
   * palette here sets the mood and the shimmer supplies the extra hues.
   */
  colors: [
    { key: "deep", label: "Deep", default: "#2a0f4e" },
    { key: "low", label: "Low pressure", default: "#0fd0c3" },
    { key: "mid", label: "Mid pressure", default: "#ff5e9d" },
    { key: "hot", label: "High pressure", default: "#ffd166" },
    { key: "flash", label: "Lightning", default: "#eaf4ff" }
  ],
  /*
    Staged in the family language. Grain and grain size never move between
    states — grain is a quantizer, and a gliding quantizer pops instead of
    fading (same rule as the dither orb's cell grid).
  */
  statePresets: {
    // brooding: bands drifting, the odd distant flicker
    idle: {
      speed: 0.9,
      shear: 1.1,
      warp: 2.2,
      churn: 1.4,
      flash: 0.7,
      gain: 1.15,
      contrast: 1.35
    },
    // computing: the storm churns IN PLACE — clock at twice idle, deeper
    // warp, bands almost stalled, lightning held back
    thinking: {
      speed: 2.2,
      shear: 0.6,
      warp: 3.4,
      churn: 2.1,
      flash: 0.6,
      gain: 1.05,
      contrast: 1.5
    },
    // answering: bands race, the field blooms bright, lightning strobes
    speaking: {
      speed: 1.6,
      shear: 2.2,
      warp: 2.6,
      churn: 1.6,
      flash: 2.6,
      gain: 1.45,
      contrast: 1.2
    }
  },
  // teal-magenta-amber carnival at rest, cold indigo-cyan while computing,
  // hot magma while answering
  stateColors: {
    idle: {
      deep: "#2a0f4e",
      low: "#0fd0c3",
      mid: "#ff5e9d",
      hot: "#ffd166",
      flash: "#eaf4ff"
    },
    thinking: {
      deep: "#0d1440",
      low: "#4c4cf0",
      mid: "#9d4ce0",
      hot: "#4ce0ff",
      flash: "#d5e5ff"
    },
    speaking: {
      deep: "#3a0f1e",
      low: "#ff6a3d",
      mid: "#ff2e88",
      hot: "#ffd23f",
      flash: "#fff3e0"
    }
  }
};

export type Shdr17Props = Omit<ShaderOrbProps, "variant">;

export function Shdr17({ size = 280, ...rest }: Shdr17Props) {
  return <ShaderOrb variant={shdr17Orb} size={size} {...rest} />;
}

export default Shdr17;

20. components/ui/shdr-18.tsx

/*
 * Deliberately not a `"use client"` module — see the note in `shdr-11.tsx`.
 *
 * Note for editors: the shader lives in a template literal, so its comments
 * must not contain backticks.
 */
import { ShaderOrb, type OrbVariant, type ShaderOrbProps } from "@/components/ui/orbkit-core";

/* ----------------------------------------------------------------------------
   SHDR-18 — a crystal folded out of one eighth of space, tumbling.

   Ported from a golfed twigl listing:

     for(float i,z,d;i++<5e1;o+=(cos(.2*z+vec4(0,2,3,0))+1.)/d/z)
     {vec3 p=z*normalize(FC.rgb*2.-r.xyy),a=normalize(cos(vec3(0,2,4)+t));
      p.z+=4.,a=abs(a*dot(a,p)-cross(a,p));
      z+=d=.3*length(cos(max(a,a.yzx)));}
     o=tanh(o/1e2);

   What it actually is, decoded:

   - THE FOLD IS THE WHOLE ORB. abs() on a 3-vector reflects all eight
     octants into one, so whatever is drawn in that eighth appears seven
     more times, mirrored — the field is forced into the symmetry of a
     crystal without a single explicit mirror plane being written down.
   - max(a, a.yzx) FOLDS AGAIN, on the diagonals. Component-wise max
     against the vector's own rolled swizzle creases the field wherever
     two components are equal, which are exactly the diagonal planes of
     the cube. Between them and the octant fold, the field carries the
     full symmetry of an octahedron.
   - THE ROTATION IS EXACT AND NEGATIVE. a*dot(a,p) - cross(a,p) with unit
     a is Rodrigues at exactly MINUS 90 degrees — shdr-22 decodes the
     same construction with a plus. It matters here beyond handedness:
     the fold happens AFTER the rotation, so the mirror planes are carried
     around by the axis, and the axis wanders on the clock. The crystal
     tumbles; it is not a static mandala with a moving texture.
   - The step and the density are one quantity again, length of a cosine
     of the folded point — periodic, so the crystal repeats through space,
     and small wherever the cosines null together, which lights the cell
     walls.
   - cos(.2*z + vec4(0,2,3,0)) + 1 tints by DEPTH ALONG THE RAY, with the
     channels far enough apart to run most of a hue wheel, and the 1/z
     brightens what is near.

   Port decisions, each one a documented trap or rule in the README:

   - THE ORB IS THE OBJECT: the crystal is periodic and fills space, so it
     is bounded by the family's envelope and analytic silhouette. That also
     disposes of the listing's brightest region, which is the 1/z bloom
     sitting on the lens at z near zero — outside the envelope, cut. What
     survives of 1/z inside the ball is a gentle front-to-back shading,
     which is the useful half of it.
   - The golfed rotation IS orthonormal here, against the README's standing
     warning — see above. Left as written.
   - The listing relies on i, z and d starting at zero; uninitialised
     locals are UNDEFINED in GLSL ES 1.0, explicit here.
   - Accumulation is weighted by the step length, as the README requires:
     the step collapses on the cell walls, so an unweighted sum piles up
     hundreds of samples exactly where the field is already brightest.
   - This orb does NOT carry the clamp-normalization line its siblings do
     (shdr-22, shdr-07, orb-nova). Theirs exists because their raw
     step weights run into the hundreds, so the clamp would leak into total
     energy; here the 1/d spike is bounded by the step FLOOR long before
     the clamp sees it, the raw weights are order one, and normalizing
     would only darken everything by a constant.
   - Emitted light, so rgb is already premultiplied and alpha comes from
     the peak channel (see shdr-31).
---------------------------------------------------------------------------- */

/*
 * Step counts are `#define`s: ES 1.0 requires constant loop bounds. STEPS is
 * the listing's i++ < 5e1.
 */
const OCTANT_FRAG = `
#define STEPS 50
#define AA 1

// Volume-reactive values, resolved once per fragment in main().
float octantFold;
float octantFreq;
float octantExposure;

vec3 octantRender(vec2 fragCoord) {
  vec2 uv = (2.0 * fragCoord - uRes) / min(uRes.x, uRes.y);
  vec3 ro = vec3(0.0, 0.0, uP_camDist);
  vec3 rd = normalize(vec3(uv, -uP_focal));

  float wander = uP_wander; // integrated clock: the axis, and with it the
                            // mirror planes, tumble

  /*
    The wandering axis. Unit by construction, which is what makes the
    rotation below an exact one — and the three phases are far enough
    apart that the cosines can never null together, so the normalize is
    safe without a guard.
  */
  vec3 axis = normalize(cos(wander + vec3(0.0, 2.0, 4.0)));

  vec3 acc = vec3(0.0);

  // transmittance carried front-to-back — near cells veil far ones
  float T = 1.0;

  // march only the span the envelope can light, as in shdr-01
  float z = max(uP_camDist - uP_envRadius * 1.3, 0.0);
  float zEnd = uP_camDist + uP_envRadius * 1.3;

  for (int it = 0; it < STEPS; it++) {
    vec3 p = ro + rd * z;

    // the exact minus-90-degree rotation about the wandering axis
    vec3 a = dot(axis, p) * axis - cross(axis, p);

    /*
      The two folds, both blendable. uP_fold reflects the octants together
      and uP_crease creases the diagonals; at zero the crystal dissolves
      back into an ordinary periodic field, which is worth being able to
      see, because the symmetry is doing more work here than the field is.
    */
    a = mix(a, abs(a), octantFold);
    a = mix(a, max(a, a.yzx), uP_crease);

    // step and density in one quantity, as in the listing
    float d = uP_stepScale * length(cos(a * octantFreq));
    d = max(d, uP_envRadius * 0.004);

    /*
      Depth as hue, near as bright.

      The hue is keyed to depth measured from where the envelope BEGINS,
      not from the camera. The listing marches from the lens out to twenty
      units and cycles its ramp several times over that; bounded to the
      ball, the same .2 slope covers barely a quarter turn of the wheel —
      and the quarter it covers has both green and blue sitting at the
      bottom of their cosines, so the first build of this orb came out a
      flat dark red. Anchored to the ball, the ramp spans it, and moving
      the camera no longer repaints the crystal.

      No step-length weighting here, unlike orb-nova and against the
      README's usual rule — because 1/d IS this shader's density, not an
      artefact of sphere tracing. Multiply it by the step and the two
      cancel exactly, leaving a flat sum with every trace of the cell walls
      gone. The clamp does the job the step weight would have, which is
      also what shdr-22 does with the same construction.
    */
    float zRel = z - (uP_camDist - uP_envRadius);
    vec3 w = cos(uP_hue * zRel + vec3(0.0, 2.0, 3.0) * uP_spread) + 1.0;
    w /= d * max(z, 0.05);
    w = min(w, vec3(uP_stepClamp));

    // envelope: plateau through the ball, cut 12% past the radius so the
    // analytic silhouette in main() still has emission left to cut
    float env = smoothstep(uP_envRadius * 1.12, uP_envRadius * uP_envCore, length(p));
    w = (w + uP_fill) * env;

    acc += T * w;
    T *= exp(-dot(w, vec3(0.299, 0.587, 0.114)) * uP_scatter);

    z += d;
    if (T < 0.004 || z > zEnd) break;
  }

  return acc;
}

void main() {
  octantFold = clamp(uP_fold * (1.0 + 0.3 * uInput), 0.0, 1.0);
  octantFreq = uP_freq * (1.0 + 0.25 * uInput);
  octantExposure = uP_exposure * (1.0 - 0.35 * uOutput);

  vec3 acc = vec3(0.0);
#if AA > 1
  for (int mx = 0; mx < AA; mx++) {
    for (int my = 0; my < AA; my++) {
      vec2 offset = (vec2(float(mx), float(my)) + 0.5) / float(AA) - 0.5;
      acc += octantRender(gl_FragCoord.xy + offset);
    }
  }
  acc /= float(AA * AA);
#else
  acc = octantRender(gl_FragCoord.xy);
#endif

  // tanh tone map per channel — the envelope and transmittance change the
  // accumulator's scale, so the golfed /1e2 knee is a tunable here
  vec3 col = tanh3(acc / max(octantExposure, 0.01));
  col = pow(clamp(col, 0.0, 1.0), vec3(uP_contrast));

  // saturation about luminance, then the tint
  float lum = dot(col, vec3(0.299, 0.587, 0.114));
  col = mix(vec3(lum), col, uP_saturation);
  col *= uC_tint;

  // alpha from the brightest channel, not luminance — a deep blue cell
  // has low luminance but must not go transparent
  float peak = max(col.r, max(col.g, col.b));
  float a = clamp(peak * uP_alphaGain, 0.0, 1.0);

  // Analytic silhouette — identical construction to shdr-01: exact
  // ray-to-centre distance against the radius, colour AND alpha.
  vec3 mrd = normalize(vec3(orbUV(), -uP_focal));
  float closest = length(cross(vec3(0.0, 0.0, uP_camDist), mrd));
  float band = mix(0.35, 0.012, clamp(uP_edge, 0.0, 1.0));
  float mask = 1.0 - smoothstep(uP_envRadius * (1.0 - band), uP_envRadius * 1.005, closest);
  col *= mask;
  a *= mask;

  // safety taper at the frame boundary — colour as well as alpha
  float r2d = length(orbUV());
  float fade = 1.0 - smoothstep(uP_edgeFade, 1.0, r2d);
  col *= fade;
  a *= fade;

  // Emitted light, so rgb is already premultiplied — do NOT scale by alpha
  // again (see the same note in shdr-31).
  gl_FragColor = vec4(col, a);
}
`;

export const shdr18Orb: OrbVariant = {
  key: "shdr-18",
  label: "SHDR-18",
  note: "a crystal folded out of one eighth of space, tumbling",
  frag: OCTANT_FRAG,
  params: [
    { key: "wander", label: "Tumble", min: 0, max: 5, step: 0.02, default: 0.5, integrate: true },
    { key: "camDist", label: "Camera distance", min: 1, max: 50, step: 0.3, default: 4 },
    { key: "focal", label: "Lens", min: 0.15, max: 15, step: 0.05, default: 1.5 },
    { key: "fold", label: "Octant fold", min: 0, max: 1, step: 0.01, default: 1 },
    { key: "crease", label: "Diagonal crease", min: 0, max: 1, step: 0.01, default: 1 },
    { key: "freq", label: "Crystal frequency", min: 0.1, max: 20, step: 0.05, default: 5 },
    { key: "stepScale", label: "Step scale", min: 0.02, max: 2, step: 0.01, default: 0.3 },
    { key: "hue", label: "Depth hue", min: 0, max: 4, step: 0.01, default: 1.5 },
    { key: "spread", label: "Colour spread", min: 0, max: 3, step: 0.02, default: 1 },
    { key: "envRadius", label: "Envelope radius", min: 0.15, max: 15, step: 0.1, default: 2.1 },
    { key: "envCore", label: "Envelope core", min: 0.3, max: 1.02, step: 0.01, default: 0.9 },
    { key: "fill", label: "Body fill", min: 0, max: 20, step: 0.01, default: 0.02 },
    { key: "stepClamp", label: "Step clamp", min: 1, max: 2000, step: 1, default: 40 },
    { key: "scatter", label: "Diffusion", min: 0, max: 0.2, step: 0.001, default: 0.004 },
    { key: "exposure", label: "Exposure", min: 0.2, max: 500, step: 0.5, default: 18 },
    { key: "contrast", label: "Contrast", min: 0.15, max: 15, step: 0.05, default: 1.2 },
    { key: "saturation", label: "Saturation", min: 0, max: 4, step: 0.02, default: 1.25 },
    { key: "alphaGain", label: "Alpha gain", min: 0.05, max: 15, step: 0.1, default: 2 },
    { key: "edge", label: "Edge sharpness", min: 0, max: 1, step: 0.01, default: 1 },
    { key: "edgeFade", label: "Halo falloff", min: 0.1, max: 3, step: 0.015, default: 0.98 }
  ],
  colors: [{ key: "tint", label: "Tint", default: "#ffffff" }],
  /*
    Staged on the two folds, which is the only orb here where SYMMETRY is
    the mood: a crystal at rest, the mirrors relaxing open while it works,
    and locked hard shut while it answers. The tumble carries the tempo.
  */
  statePresets: {
    // at rest: fully folded, turning slowly — a still crystal
    idle: {
      wander: 0.5,
      fold: 1,
      crease: 1,
      freq: 5,
      exposure: 18,
      scatter: 0.004,
      alphaGain: 2
    },
    /*
      searching: the crystal is pushed BACK and the mirrors loosen a hair.
      The lens nearly doubles so the fold sits deeper in the frame, the
      octant fold slips just under one — enough for the field to drift out
      of register without dissolving — on a tumble twice idle and a
      slightly coarser cell. The step clamp is thrown wide open and the
      diffusion raised fivefold, so the march runs long and the light
      fogs: the brightest state, but hazed rather than sharp.
    */
    thinking: {
      wander: 1.08,
      focal: 2.7,
      fold: 0.91,
      crease: 1,
      freq: 4.1,
      stepClamp: 1200,
      scatter: 0.02,
      exposure: 26,
      alphaGain: 2
    },
    /*
      answering: the mirrors LOCK SHUT again and the crystal goes finer
      than idle, marched on a step nearly twice as long so the cell walls
      read as crisp lines rather than fog. The envelope core drops to half,
      which hollows the ball and leaves the crystal floating in it; the
      depth hue runs faster and the saturation is pushed hard, at less than
      half the idle knee — the sharpest, most coloured state.
    */
    speaking: {
      wander: 0.7,
      fold: 1,
      crease: 1,
      freq: 6.05,
      stepScale: 0.51,
      hue: 2,
      envCore: 0.49,
      exposure: 8,
      scatter: 0.002,
      saturation: 2,
      alphaGain: 2.7
    }
  },
  // the depth ramp supplies the colour, so the tint only shifts its
  // temperature: neutral at rest, cooled while searching, warmed while
  // answering
  stateColors: {
    idle: { tint: "#ffffff" },
    thinking: { tint: "#9db8ff" },
    speaking: { tint: "#ffc492" }
  }
};

export type Shdr18Props = Omit<ShaderOrbProps, "variant">;

export function Shdr18({ size = 280, ...rest }: Shdr18Props) {
  return <ShaderOrb variant={shdr18Orb} size={size} {...rest} />;
}

export default Shdr18;

21. components/ui/shdr-19.tsx

/*
 * Deliberately not a `"use client"` module — see the note in `shdr-11.tsx`.
 *
 * Note for editors: the shader lives in a template literal, so its comments
 * must not contain backticks.
 */
import { ShaderOrb, type OrbVariant, type ShaderOrbProps } from "@/components/ui/orbkit-core";

/* ----------------------------------------------------------------------------
   SHDR-19 — beads swelling and shrinking in their cells, packed over the ball.

   Ported from a one-line twigl listing:

     vec2 p=FC.xy/8e1,v;
     for(int i;i++<9;o=max(o,(dot(cos(v-t),sin(v.yx*.62+t))/6.+.2
         -length(p+v+cos(v.yx+t)*.4-1.))*5e1))
       v=vec2(i%3,i/3)-ceil(p);

   What it actually is, decoded:

   - IT IS A CELLULAR FIELD, written as compactly as one can be. Nine
     iterations walk the 3x3 neighbourhood of the fragment's cell; each
     neighbour gets a jittered feature point and a radius, and max() over
     the nine takes the union of the discs. Everything else is the two
     expressions that generate radius and jitter.
   - THE RADIUS IS A DOT OF A COSINE AGAINST A SINE of the cell index, one
     of them swizzled and detuned by .62 so the two never fall into step.
     That is the entire random number generator, and because both terms
     carry the clock, every bead swells and shrinks on its own phase.
     Radii run from about -.13 to .53, so cells with a negative radius
     simply have no bead — the field thins and fills on its own.
   - THE x50 IS AN EDGE RAMP, not a brightness. A signed distance scaled
     that hard and clamped is a hard-edged disc with a pixel of feather,
     which is why beads read as objects with rims rather than as blobs.
   - The listing writes a scalar into a vec4, so its output is greyscale:
     white discs on black.

   Two golf bugs, both fixed here, both worth knowing:

   - THE CELL IDENTITY IS NOT STABLE. The hash input is
     v = neighbour offset - ceil(p), which for a fixed absolute cell C
     works out to C - 2*ceil(p): it depends on WHICH FRAGMENT IS LOOKING.
     So a bead's radius and jitter change across every cell boundary and
     the discs are chopped up on the grid. Keyed on the absolute cell
     index instead, as here, a bead is one bead and crosses borders whole.
   - THE NEIGHBOURHOOD IS OFF BY ONE. i runs 1..9 rather than 0..8, so
     vec2(i%3, i/3) covers (0,-1) through (-1,2) after centring — nine
     cells, but not the nine that surround you. The corner at (-1,-1) is
     missing, and beads there are clipped. Walked properly here.

   Orb decisions, each one a rule in the README:

   - THE ORB IS THE OBJECT: a flat 2D field, so it is sampled through a
     stereographic projection of the dome — the packing compresses toward
     the limb the way beads on a real sphere would — and motion is
     projection-safe 2D, as in shdr-08.
   - FLAT WHITE DISCS ON BLACK IS THE SUBJECT, and the defaults ship that
     way: the listing writes a scalar into a vec4 and stops. What the ball
     adds is the wrap, a little dome shading and the sheen — enough to be
     a sphere, not enough to stop being a dot screen.
   - uP_bead is an option, off by default. Since the winning cell is
     already known, its own distance and radius give a hemisphere for
     free — sqrt(1 - (dist/radius)^2) is a height, and that is a normal to
     light — so the same field can be read as packed glass instead of
     printed circles. It is a different orb, so it is a slider rather than
     a default.
   - Bead size and wander are kept low enough that discs stay separate.
     They are drawn with max() of (radius - distance), so two that overlap
     do not blend — the larger simply wins and bites a straight edge out
     of the smaller. Whole discs with gaps between them is the look; a
     field of clipped crescents is what happens when they are pushed too
     big or shaken too far.
   - The golfed listing relies on i starting at zero; uninitialised locals
     are UNDEFINED in GLSL ES 1.0, explicit here.
   - Surface-lit and mask-bounded, so alpha IS coverage — premultiplied
     output, as in shdr-17.
---------------------------------------------------------------------------- */

/*
 * AA is a `#define`: ES 1.0 requires constant loop bounds. The bead rims are
 * about a pixel wide by construction, so they need it.
 */
const FOAM_FRAG = `
#define AA 2

// Volume-reactive values, resolved once per fragment in main().
float foamGrow;
float foamJitter;
float foamGain;

vec3 foamRender(vec2 fragCoord) {
  vec2 uv = (2.0 * fragCoord - uRes) / min(uRes.x, uRes.y);
  float R = max(uP_radius, 0.001);

  // the dome: the front hemisphere of a unit ball, in screen space
  vec2 pl = uv / R;
  float z = sqrt(max(1.0 - dot(pl, pl), 0.0));

  float t = uP_speed; // integrated clock

  // stereographic wrap of the unrotated dome, as in shdr-08
  vec2 p = pl / (z + 1.0 + uP_bulge) * uP_scale;

  // projection-safe 2D motion: the packing turns and drifts
  float sw = uP_swirl; // integrated clock
  p = mat2(cos(sw), -sin(sw), sin(sw), cos(sw)) * p;
  p += vec2(uP_slide, uP_slide * 0.7); // integrated clock

  vec2 cell = ceil(p);
  vec2 f = p - cell; // in (-1, 0], the fragment's place in its own cell

  /*
    The 3x3 walk, keyed on the ABSOLUTE cell index so a bead is one bead —
    see the header. Tracking the winner as well as the max costs nothing
    and is what makes the shading below possible.
  */
  float cover = 0.0;
  float bestRel = 1e9;
  vec2 bestDelta = vec2(0.0);
  float bestRad = 1.0;
  vec2 bestId = vec2(0.0);

  for (int gy = -1; gy <= 1; gy++) {
    for (int gx = -1; gx <= 1; gx++) {
      vec2 g = vec2(float(gx), float(gy));
      vec2 id = cell + g;

      /*
        The listing's generator: a dot of a cosine against a detuned,
        swizzled sine of the cell index, both carrying the clock. Mind the
        range — a dot of two 2-vectors of unit-bounded components spans
        FOUR, not two, so the listing's /6 puts radii within a third of a
        cell of the mean. Read it as half that and the largest discs
        overlap their neighbours, which is what turns a dot screen into a
        litter of merged blobs.
      */
      float rad = dot(cos(id - t), sin(id.yx * uP_skew + t)) * uP_vary + foamGrow;
      /*
        Fragment to feature point, and the signs matter more than they
        look. The disc labelled id sits at id + jitter in absolute
        coordinates, so delta = p - (id + jitter) = f - g - jitter. Write
        it as f + g and the disc's IDENTITY and its POSITION end up using
        opposite offsets: every fragment then draws disc id in a different
        place, and the field comes out as clumps of half-agreeing circles
        rather than circles.
      */
      vec2 jit = cos(id.yx + t) * foamJitter;
      vec2 delta = f - g - jit;
      float dist = length(delta);

      /*
        The union is taken over COVERAGE, not over the signed distance the
        listing maxes. Those differ exactly where two discs overlap: max of
        (radius - distance) hands the whole overlap to whichever disc wins
        and bites a straight edge out of the other, so the field comes out
        a litter of crescents and pinwheels. Max of the clamped coverage
        keeps every disc whole and merely lets overlapping ones merge.

        The x50 ramp is the listing's, and it is an edge width rather than
        a brightness: a signed distance scaled that hard and clamped is a
        hard-edged disc with about a pixel of feather.
      */
      cover = max(cover, clamp((rad - dist) * uP_edge, 0.0, 1.0));

      /*
        The winner is tracked separately, by RELATIVE depth rather than
        absolute — which disc this fragment is furthest inside, in units of
        that disc's own radius. Only the optional bead shading reads it,
        and relative depth is what keeps a small disc from being shaded as
        though it were the large one beside it.
      */
      float rel = dist / max(rad, 1e-4);
      if (rel < bestRel) {
        bestRel = rel;
        bestDelta = delta;
        bestRad = rad;
        bestId = id;
      }
    }
  }

  /*
    The bead. The winning cell's own distance and radius give the height of
    a hemisphere over the disc, and that is a normal — so the flat decal
    becomes a lit piece of glass without a second field being evaluated.
  */
  float rr = max(bestRad, 1e-4);
  float dome = clamp(1.0 - dot(bestDelta, bestDelta) / (rr * rr), 0.0, 1.0);
  vec3 bn = normalize(vec3(bestDelta / rr, sqrt(dome) + 0.001));

  vec3 key = normalize(vec3(-0.45, 0.55, 0.72));
  float beadLam = clamp(dot(bn, key), 0.0, 1.0);

  // per-disc colour, hashed on the cell index — near-flat at the defaults,
  // which is what keeps the field reading as a dot screen
  vec3 beadCol = mix(uC_low, uC_high, hash(bestId + 0.5));

  // uP_bead at 0 leaves the listing's flat disc, which is the default
  float shade = mix(1.0, 0.45 + 0.85 * beadLam, uP_bead);
  vec3 col = beadCol * shade * foamGain * cover;

  // a dark body under the packing, so the gaps read as the ball rather
  // than as holes in it
  col += uC_body * uP_floorLevel;

  col = pow(max(col, vec3(0.0)), vec3(uP_contrast));

  float lum = dot(col, vec3(0.299, 0.587, 0.114));
  col = mix(vec3(lum), col, uP_saturation);

  // dome shading keeps the ball a ball under the packing
  vec3 n = vec3(pl, z);
  float lambert = clamp(dot(n, key), 0.0, 1.0);
  col *= 0.55 + uP_light * lambert;

  float fres = 1.0 - z;
  fres = fres * fres * fres;
  col += uC_sheen * uP_rim * fres;

  return col;
}

void main() {
  // Volume coupling: the user's voice shakes the beads off their centres,
  // the agent's swells them and brightens the packing.
  foamGrow = uP_grow * (1.0 + 0.35 * uOutput);
  foamJitter = uP_jitter * (1.0 + 0.5 * uInput);
  foamGain = uP_gain * (0.85 + 0.4 * uOutput);

  vec2 uv = orbUV();
  float mask = smoothstep(0.012, -0.012, length(uv) - max(uP_radius, 0.001));

  if (mask <= 0.0) {
    gl_FragColor = vec4(0.0);
    return;
  }

  vec3 col = vec3(0.0);
#if AA > 1
  for (int mx = 0; mx < AA; mx++) {
    for (int my = 0; my < AA; my++) {
      vec2 off = (vec2(float(mx), float(my)) + 0.5) / float(AA) - 0.5;
      col += foamRender(gl_FragCoord.xy + off);
    }
  }
  col /= float(AA * AA);
#else
  col = foamRender(gl_FragCoord.xy);
#endif

  // Surface orb bounded by a mask: alpha IS coverage, so premultiply — the
  // opposite convention from the emissive orbs (see shdr-31).
  float a = mask;
  gl_FragColor = vec4(max(col, vec3(0.0)) * a, a);
}
`;

export const shdr19Orb: OrbVariant = {
  key: "shdr-19",
  label: "SHDR-19",
  note: "beads swelling and shrinking in their cells, packed over the ball",
  frag: FOAM_FRAG,
  params: [
    { key: "speed", label: "Anim speed", min: 0.015, max: 10, step: 0.05, default: 0.5, integrate: true },
    { key: "swirl", label: "Swirl", min: 0, max: 3, step: 0.015, default: 0.05, integrate: true },
    { key: "slide", label: "Drift", min: 0, max: 4, step: 0.02, default: 0.1, integrate: true },
    { key: "radius", label: "Radius", min: 0.15, max: 3, step: 0.015, default: 0.9 },
    { key: "scale", label: "Packing scale", min: 0.3, max: 30, step: 0.1, default: 10 },
    { key: "bulge", label: "Dome bulge", min: 0, max: 4, step: 0.02, default: 0.3 },
    { key: "grow", label: "Dot size", min: -0.2, max: 1.2, step: 0.005, default: 0.185 },
    { key: "vary", label: "Size variation", min: 0, max: 0.4, step: 0.005, default: 0.13 },
    { key: "skew", label: "Generator detune", min: 0, max: 3, step: 0.01, default: 0.62 },
    { key: "jitter", label: "Dot wander", min: 0, max: 1.5, step: 0.01, default: 0.1 },
    { key: "edge", label: "Rim hardness", min: 1, max: 200, step: 1, default: 50 },
    { key: "bead", label: "Bead shading", min: 0, max: 1, step: 0.01, default: 0 },
    { key: "gain", label: "Brightness", min: 0.05, max: 4, step: 0.02, default: 1 },
    { key: "contrast", label: "Contrast", min: 0.15, max: 6, step: 0.05, default: 1 },
    { key: "saturation", label: "Saturation", min: 0, max: 4, step: 0.02, default: 1.1 },
    { key: "floorLevel", label: "Body fill", min: 0, max: 2, step: 0.01, default: 0.06 },
    { key: "light", label: "Key light", min: 0, max: 3, step: 0.015, default: 0.3 },
    { key: "rim", label: "Rim sheen", min: 0, max: 3, step: 0.015, default: 0.35 }
  ],
  /*
   * Four stops: the two ends of the per-bead hash, the body the packing
   * sits on, and the glass.
   */
  colors: [
    { key: "low", label: "Dot", default: "#ffffff" },
    { key: "high", label: "Dot accent", default: "#eef5ff" },
    { key: "body", label: "Body", default: "#05070c" },
    { key: "sheen", label: "Sheen", default: "#9dbfe4" }
  ],
  /*
    Staged on BEAD SIZE, which decides whether the ball is a scatter of
    separate beads or a packed foam, and on wander, which decides how far
    each one strays from its cell. Packing scale never moves between
    states — it sets the bead count, and a gliding count reads as the ball
    inflating rather than as a change of mood.
  */
  statePresets: {
    /*
      at rest: a sparse, restless screen. The dots sit small with a wide
      size spread and wander most of a cell, on a dome flattened almost to
      a disc, so the halftone reads as grain rather than pattern — pushed
      bright and hard-contrasted so the few dots that land carry.
    */
    idle: {
      speed: 0.52,
      swirl: 0.045,
      slide: 0.1,
      bulge: 0.08,
      grow: 0.12,
      vary: 0.19,
      skew: 0.63,
      jitter: 0.74,
      edge: 51,
      gain: 2.28,
      contrast: 2.6,
      rim: 0.345
    },
    /*
      searching: the screen is set MOVING. The clock runs five times idle,
      the swirl and slide both open up an order of magnitude, and the
      generator detunes near double, so the dots stream across the ball
      rather than sit on it. They swell a little and wander half as far as
      idle, under a softer contrast and a stronger key light — a flatter,
      brighter, busier screen.
    */
    thinking: {
      speed: 2.55,
      swirl: 0.57,
      slide: 0.84,
      bulge: 0.14,
      grow: 0.19,
      vary: 0.165,
      skew: 1.13,
      jitter: 0.37,
      gain: 1.04,
      contrast: 0.55,
      light: 0.585,
      rim: 0.24
    },
    /*
      answering: the screen goes HARD. The generator detune drops to zero,
      so every dot's size runs on the clock alone and the whole screen
      pulses in step; the size spread opens to its widest and the rim
      hardness nearly triples, so the dots read as punched holes rather
      than beads. The dome rises, the dots settle to a quarter of idle's
      wander, and the body fill is cut — pure white dots on black, at the
      thinking tempo and a harder contrast still.
    */
    speaking: {
      speed: 2.85,
      swirl: 0.585,
      slide: 0.55,
      bulge: 0.28,
      grow: 0.22,
      vary: 0.365,
      skew: 0,
      jitter: 0.17,
      edge: 141,
      gain: 1.35,
      contrast: 3.2,
      saturation: 2.04,
      floorLevel: 0
    }
  },
  // cool glass at rest, then pure white on black for both working states —
  // the answering one keeps the faintly warm body
  stateColors: {
    idle: { low: "#ffffff", high: "#eef5ff", body: "#05070c", sheen: "#9dbfe4" },
    thinking: { low: "#dae6ff", high: "#ffffff", body: "#000000", sheen: "#ffffff" },
    speaking: { low: "#ffffff", high: "#ffffff", body: "#140a06", sheen: "#ffffff" }
  }
};

export type Shdr19Props = Omit<ShaderOrbProps, "variant">;

export function Shdr19({ size = 280, ...rest }: Shdr19Props) {
  return <ShaderOrb variant={shdr19Orb} size={size} {...rest} />;
}

export default Shdr19;

22. components/ui/shdr-20.tsx

/*
 * Deliberately not a `"use client"` module — see the note in `shdr-11.tsx`.
 *
 * Note for editors: the shader lives in a template literal, so its comments
 * must not contain backticks.
 */
import { ShaderOrb, type OrbVariant, type ShaderOrbProps } from "@/components/ui/orbkit-core";

/* ----------------------------------------------------------------------------
   SHDR-20 — a water film rushing down the ball, fountain-style.

   Ported from a golfed twigl listing:

     vec3 x,c,p;x.x+=9.;
     for(float i,z,f;i++<5e1;
       p=mix(c,p,.3),
       z+=f=.2*(abs(p.z+p.x+16.+tanh(p.y)/.1)+sin(p.x-p.z+t+t)+1.),
       o+=(cos(p.x*.2+f+vec4(6,1,2,0))+2.)/f/z)
     for(c=p=z*normalize(FC.rgb*2.-r.xyy),p.y*=f=.3;f++<5.;
       p+=cos(p.yzx*f+i+z+x*t)/f);
     o=tanh(o/3e1);

   What it actually is, decoded:

   - p.y *= .3 SQUASHES the vertical axis before five cos octaves, so every
     structure stretches into tall streaks — the falling-water grain.
   - x is built by x.x += 9. on a zero-init local, so x*t = (9t,0,0): the
     octave phases scroll at NINE times the clock. That rush is the fall.
   - mix(c,p,.3) blends the turbulent point back toward the clean ray
     point — only 30% of the displacement survives, a film of foam over a
     coherent surface.
   - abs(p.z+p.x+16.+tanh(p.y)/.1) is a SIGMOID CLIFF: two diagonal planes
     (x+z = -6 low, -26 high) blended by 10*tanh(y), with a traveling
     ripple sin(p.x-p.z+2t) running across the face.
   - The palette (cos(p.x*.2+f+(6,1,2))+2)/f/z hangs vertical hue sheets
     on the face, bright where the march grazes the surface.

   Port decisions, each one a documented trap or rule in the README:

   - THE ORB IS THE OBJECT: the cliff face becomes the sphere's own shell,
     so the film flows down the BALL — a fountain, not a wall in a jar.
     The squash, the 9t rush and the ripple all carry over unchanged; only
     the geometry they decorate is swapped for the orb itself.
   - The golfed listing relies on x, c, p, i, z, f starting at zero —
     uninitialised locals are UNDEFINED in GLSL ES 1.0, explicit here.
   - f can reach exactly ZERO (abs at the surface, ripple at -1, +1) and
     the weight divides by it — guarded, as is the march step so it cannot
     stall.
   - Clocks enter only as additive phase. The rush gets its own integrated
     clock so flow speed tunes without jumping the film.
---------------------------------------------------------------------------- */

/*
 * Step counts are `#define`s: ES 1.0 requires constant loop bounds.
 * TURB is 5 to match the original's octaves (divisors 1.3 .. 5.3).
 */
const FALLS_FRAG = `
#define STEPS 50
#define TURB 5
#define AA 1

// Volume-reactive values, resolved once per fragment in main().
float fallsFoam;
float fallsExposure;

mat2 fallsRot(float a) {
  float c = cos(a);
  float s = sin(a);
  return mat2(c, -s, s, c);
}

vec3 fallsRender(vec2 fragCoord) {
  float animTime = uP_speed; // integrated clock: ripple phase
  float flow = uP_flow;      // integrated clock: the 9t rush, tunable

  vec2 uv = (2.0 * fragCoord - uRes) / min(uRes.x, uRes.y);
  vec3 ro = vec3(0.0, 0.0, uP_camDist);
  vec3 rd = normalize(vec3(uv, -uP_focal));

  float rShell = uP_envRadius * 0.92;

  vec3 acc = vec3(0.0);

  // transmittance carried front-to-back — near foam veils far foam
  float T = 1.0;

  // march only the span the envelope can light, as in shdr-01
  float z = max(uP_camDist - uP_envRadius * 1.3, 0.0);
  float zEnd = uP_camDist + uP_envRadius * 1.3;

  for (int it = 0; it < STEPS; it++) {
    vec3 c = ro + rd * z;

    // a slight static tilt of the flow axis
    c.yz = fallsRot(uP_tilt) * c.yz;

    /*
      The fall grain: squash the vertical axis, then the five octaves with
      the rush phase on the first component — cos(p.yzx*f + ...) writes
      that component to x, so height and time drive the sideways waves,
      exactly the original's x*t construction.
    */
    vec3 p = c;
    p.y *= uP_stretch;
    for (int j = 0; j < TURB; j++) {
      float fj = float(j) + 1.3;
      p += cos(p.yzx * fj + float(it) + z + vec3(flow, 0.0, 0.0)) / fj;
    }

    // the foam blend — most of the displacement is thrown away, leaving a
    // film of detail over a coherent surface
    vec3 pm = mix(c, p, fallsFoam);

    /*
      The surface, swapped from the sigmoid cliff to the ball's own shell:
      distance to the sphere (sharpened by uP_wall) plus the original's
      traveling ripple. f can still reach zero exactly — the guard feeds
      both the division and the march step.
    */
    float f = uP_stepScale * (abs(length(pm) - rShell) * uP_wall
      + sin(pm.x - pm.z + animTime * 2.0) + 1.0);
    f = max(f, 1e-3);
    z += f;

    /*
      Vertical hue sheets from the listing, bright where the march grazes
      the film. The CLAMP is load-bearing, as in every accumulator here —
      one f-null step would own the whole 50-step sum.
    */
    vec3 w = (cos(pm.x * uP_hueScale + f + vec3(6.0, 1.0, 2.0)) + 2.0) / f / max(z, 1.0);
    w = min(w, vec3(uP_stepClamp));

    // envelope: plateau through the ball, cut 12% past the radius so the
    // analytic silhouette in main() still has emission left to cut
    float env = smoothstep(uP_envRadius * 1.12, uP_envRadius * uP_envCore, length(ro + rd * z));
    w = (w + uP_fill) * env;

    acc += T * w;
    T *= exp(-dot(w, vec3(0.299, 0.587, 0.114)) * uP_scatter);

    if (T < 0.004 || z > zEnd) break;
  }

  return acc;
}

void main() {
  fallsFoam = uP_foam * (1.0 + 0.5 * uInput);
  fallsExposure = uP_exposure * (1.0 - 0.35 * uOutput);

  vec3 acc = vec3(0.0);
#if AA > 1
  for (int mx = 0; mx < AA; mx++) {
    for (int my = 0; my < AA; my++) {
      vec2 offset = vec2(float(mx), float(my)) / float(AA) - 0.5;
      acc += fallsRender(gl_FragCoord.xy + offset);
    }
  }
  acc /= float(AA * AA);
#else
  acc = fallsRender(gl_FragCoord.xy);
#endif

  // tanh tone map per channel — the golfed /3e1 knee is a tunable here
  vec3 col = tanh3(acc / max(fallsExposure, 1.0));
  col = pow(clamp(col, 0.0, 1.0), vec3(uP_contrast));

  // saturation about luminance, then the tint
  float lum = dot(col, vec3(0.299, 0.587, 0.114));
  col = mix(vec3(lum), col, uP_saturation);
  col *= uC_tint;

  // alpha from the brightest channel, not luminance — a deep blue sheet
  // has low luminance but must not go transparent
  float peak = max(col.r, max(col.g, col.b));
  float a = clamp(peak * uP_alphaGain, 0.0, 1.0);

  // Analytic silhouette — identical construction to shdr-01: exact
  // ray-to-centre distance against the radius, colour AND alpha.
  vec3 mrd = normalize(vec3(orbUV(), -uP_focal));
  float closest = length(cross(vec3(0.0, 0.0, uP_camDist), mrd));
  float band = mix(0.35, 0.012, clamp(uP_edge, 0.0, 1.0));
  float mask = 1.0 - smoothstep(uP_envRadius * (1.0 - band), uP_envRadius * 1.005, closest);
  col *= mask;
  a *= mask;

  // safety taper at the frame boundary — colour as well as alpha
  float r2d = length(orbUV());
  float fade = 1.0 - smoothstep(uP_edgeFade, 1.0, r2d);
  col *= fade;
  a *= fade;

  // Emitted light, so rgb is already premultiplied — do NOT scale by alpha
  // again (see the same note in shdr-31).
  gl_FragColor = vec4(col, a);
}
`;

export const shdr20Orb: OrbVariant = {
  key: "shdr-20",
  label: "SHDR-20",
  note: "a water film rushing down the ball, fountain-style",
  frag: FALLS_FRAG,
  params: [
    { key: "speed", label: "Ripple speed", min: 0.015, max: 10, step: 0.05, default: 0.5, integrate: true },
    { key: "flow", label: "Fall rush", min: 0, max: 20, step: 0.1, default: 3, integrate: true },
    { key: "camDist", label: "Camera distance", min: 1, max: 50, step: 0.3, default: 7 },
    { key: "focal", label: "Lens", min: 0.15, max: 15, step: 0.1, default: 2.25 },
    { key: "tilt", label: "Flow tilt", min: 0, max: 4, step: 0.02, default: 0.15 },
    { key: "stretch", label: "Fall stretch", min: 0.03, max: 3, step: 0.015, default: 0.3 },
    { key: "foam", label: "Foam", min: 0, max: 3, step: 0.015, default: 0.3 },
    { key: "wall", label: "Film sharpness", min: 0.15, max: 20, step: 0.1, default: 3 },
    { key: "stepScale", label: "Step scale", min: 0.015, max: 1.5, step: 0.01, default: 0.2 },
    { key: "hueScale", label: "Hue banding", min: 0, max: 3, step: 0.015, default: 0.85 },
    { key: "envRadius", label: "Envelope radius", min: 0.15, max: 15, step: 0.1, default: 2.6 },
    { key: "envCore", label: "Envelope core", min: 0.3, max: 1.02, step: 0.01, default: 1 },
    { key: "fill", label: "Body fill", min: 0, max: 100, step: 0.3, default: 0.4 },
    { key: "stepClamp", label: "Step clamp", min: 0.3, max: 300, step: 1.5, default: 20 },
    { key: "scatter", label: "Diffusion", min: 0, max: 0.5, step: 0.003, default: 0.01 },
    { key: "exposure", label: "Exposure", min: 1.5, max: 1500, step: 10, default: 22 },
    { key: "contrast", label: "Contrast", min: 0.15, max: 15, step: 0.1, default: 1.15 },
    { key: "saturation", label: "Saturation", min: 0, max: 4, step: 0.02, default: 1.55 },
    { key: "alphaGain", label: "Alpha gain", min: 0.05, max: 15, step: 0.1, default: 2 },
    { key: "edge", label: "Edge sharpness", min: 0, max: 1, step: 0.01, default: 1 },
    { key: "edgeFade", label: "Halo falloff", min: 0.1, max: 3, step: 0.015, default: 0.98 }
  ],
  colors: [{ key: "tint", label: "Tint", default: "#ffffff" }],
  statePresets: {
    idle: {
      speed: 0.5,
      flow: 3,
      foam: 0.3,
      exposure: 22,
      scatter: 0.01,
      alphaGain: 2
    },
    thinking: {
      speed: 0.6,
      flow: 3.3,
      foam: 0.33,
      exposure: 21,
      scatter: 0.0095,
      alphaGain: 2.1
    },
    // loudest: full rush, thick foam, hot film
    speaking: {
      speed: 1,
      flow: 5.5,
      foam: 0.45,
      exposure: 16,
      scatter: 0.0075,
      alphaGain: 2.5
    }
  }
};

export type Shdr20Props = Omit<ShaderOrbProps, "variant">;

export function Shdr20({ size = 280, ...rest }: Shdr20Props) {
  return <ShaderOrb variant={shdr20Orb} size={size} {...rest} />;
}

export default Shdr20;

23. components/ui/shdr-21.tsx

/*
 * Deliberately not a `"use client"` module — see the note in `shdr-11.tsx`.
 *
 * Note for editors: the shader lives in a template literal, so its comments
 * must not contain backticks.
 */
import { ShaderOrb, type OrbVariant, type ShaderOrbProps } from "@/components/ui/orbkit-core";

/* ----------------------------------------------------------------------------
   SHDR-21 — light diffusing through a cloud.

   A real volumetric integration rather than a surface. The march walks a
   density field bounded by a sphere and, at every step, does three things:

     TRANSMITTANCE  how much of the background still gets through, tracked as
                    T *= exp(-density * dt * absorb). Beer-Lambert.
     SHADOW         a short second march toward the light, so the far side of a
                    dense clump is dimmer than the lit side. This is the whole
                    reason the orb reads as volume and not as a flat glow.
     IN-SCATTER     light added at this step, weighted by density, by the shadow
                    term, and by a Henyey-Greenstein phase function.

   The phase function is what makes it feel like light rather than paint. It
   biases scattering forward, so the limb facing the light blooms and the rest
   stays soft — the same reason a cloud is blinding when you look toward the sun
   through it and merely bright otherwise.

   Alpha is 1 - T, which is exactly what the volume occludes, so this one needs
   no radial fade to hide the canvas edge: density falls to zero at the sphere
   boundary and the alpha goes with it.
---------------------------------------------------------------------------- */

/*
 * Step counts are `#define`s: ES 1.0 requires constant loop bounds. The march
 * is STEPS * (1 + LIGHT_STEPS) density evaluations, so LIGHT_STEPS is the
 * expensive knob — 4 is enough for readable self-shadowing.
 */
const NIMBUS_FRAG = `
#define STEPS 56
#define LIGHT_STEPS 4
#define DENSITY_OCT 4
#define AA 1

const float PI = 3.14159265359;

// Volume-reactive values, resolved once per fragment in main().
float nimbusPower;
float nimbusDensity;

/*
  Density inside the sphere.

  The radial term falls to zero at the boundary, which both bounds the volume
  and gives the soft edge for free. The cos-warp folds the sample point a few
  times — the same cheap turbulence the other orbs use — and the threshold
  carves that into clumps rather than an even fog.
*/
float density(vec3 p, float animTime) {
  float shell = 1.0 - length(p) / uP_radius;
  if (shell <= 0.0) return 0.0;

  vec3 q = p * uP_scale;
  float f = 1.0;
  for (int k = 0; k < DENSITY_OCT; k++) {
    q += cos(q.yzx * f + animTime * uP_churn) / f;
    f *= 1.8;
  }

  float n = (sin(q.x) + sin(q.y) + sin(q.z)) / 3.0 * 0.5 + 0.5;
  // smoothstep against the threshold is the clump control: high threshold
  // leaves sparse wisps, low fills the sphere with even fog
  float clump = smoothstep(uP_threshold, 1.0, n);
  return clump * pow(shell, uP_edgeSoft) * nimbusDensity;
}

/*
  Henyey-Greenstein: g > 0 biases scattering forward, which is what gives the
  bloom on the limb facing the light.

  The physical form carries a 1/(4*PI) normalisation. It is dropped here and
  folded into uP_power instead — kept in, the whole term sits around 0.02 and
  the orb renders black unless power is pushed into the hundreds, which makes
  the slider useless.
*/
float phaseHG(float c, float g) {
  float g2 = g * g;
  return (1.0 - g2) / pow(max(1.0 + g2 - 2.0 * g * c, 0.0001), 1.5);
}

vec4 nimbusRender(vec2 fragCoord) {
  float animTime = uP_speed; // integrated clock

  vec2 uv = (2.0 * fragCoord - uRes) / min(uRes.x, uRes.y);
  vec3 ro = vec3(0.0, 0.0, -uP_camDist);
  vec3 rd = normalize(vec3(uv, uP_focal));

  /*
    Light direction, slowly orbiting so the shading is never static.

    The z term is kept POSITIVE — the camera looks along +z, so a light also
    pointing along +z sits behind the cloud. That is the back-lit case, where
    dot(rd, L) approaches 1 and the forward-scattering phase blooms. Put the
    light on the camera's side instead and every ray samples the phase function
    on its back-scatter tail, where it is roughly ten times smaller, and the orb
    goes muddy.
  */
  vec3 L = normalize(vec3(
    cos(animTime * uP_lightSpin) * 0.7,
    0.45,
    sin(animTime * uP_lightSpin) * 0.35 + 0.65
  ));

  float phase = phaseHG(dot(rd, L), uP_aniso);

  // Start the march at the sphere's front face instead of the camera — every
  // step before that contributes nothing, and at 56 steps they are expensive.
  float toCentre = uP_camDist;
  float tStart = max(toCentre - uP_radius, 0.0);
  float span = 2.0 * uP_radius;
  float dt = span / float(STEPS);

  float T = 1.0;
  vec3 scattered = vec3(0.0);

  for (int i = 0; i < STEPS; i++) {
    float t = tStart + (float(i) + 0.5) * dt;
    vec3 p = ro + rd * t;

    float dn = density(p, animTime);
    if (dn > 0.001) {
      // short march toward the light for self-shadowing
      float shadow = 1.0;
      float lstep = uP_radius / float(LIGHT_STEPS);
      for (int k = 1; k <= LIGHT_STEPS; k++) {
        vec3 lp = p + L * (float(k) - 0.5) * lstep;
        shadow *= exp(-density(lp, animTime) * lstep * uP_shadowAbsorb);
      }

      /*
        In-scattered light: warm where lit, cool where the volume shadows
        itself.

        The shadow term appears ONCE, inside the mix. Multiplying by it again
        as a factor — the obvious-looking thing to write — scales the shadowed
        end of the mix toward zero, so the cool colour is always multiplied
        away and the cloud comes out monochrome beige however it is tinted.
        uP_shadowLift is how much light still reaches the shadowed side.
      */
      vec3 lit = mix(uC_shadow * uP_shadowLift, uC_light, shadow);
      scattered += T * dn * dt * lit * phase * nimbusPower;

      T *= exp(-dn * dt * uP_absorb);
      if (T < 0.01) break;
    }
  }

  // a soft ambient body so the unlit side is not pure black
  float body = 1.0 - T;
  scattered += uC_shadow * body * uP_ambient;

  return vec4(scattered, body);
}

void main() {
  /*
    Agent output turns the light up; user input thickens the cloud. Both are
    AMPLITUDES. Churn is deliberately NOT volume-scaled: it multiplies the
    accumulated clock into a phase (animTime * churn), so scaling it by the
    live volume would turn every volume wobble into a phase jump the size of
    the whole clock — the cloud scrambles chaotically on each state change
    instead of gliding, and gets worse the longer the page is open.
  */
  nimbusPower = uP_power * (0.7 + 0.9 * uOutput);
  nimbusDensity = uP_density * (1.0 + 0.35 * uInput);

  vec4 acc = vec4(0.0);
#if AA > 1
  for (int mx = 0; mx < AA; mx++) {
    for (int my = 0; my < AA; my++) {
      vec2 offset = vec2(float(mx), float(my)) / float(AA) - 0.5;
      acc += nimbusRender(gl_FragCoord.xy + offset);
    }
  }
  acc /= float(AA * AA);
#else
  acc = nimbusRender(gl_FragCoord.xy);
#endif

  vec3 col = tanh3(acc.rgb * uP_exposure);
  float a = clamp(acc.a * uP_alphaGain, 0.0, 1.0);

  // Emitted/scattered light, so rgb is already premultiplied — do NOT multiply
  // by alpha again (see the same note in shdr-31).
  gl_FragColor = vec4(col, a);
}
`;

export const shdr21Orb: OrbVariant = {
  key: "shdr-21",
  label: "SHDR-21",
  note: "light diffusing through a cloud",
  frag: NIMBUS_FRAG,
  params: [
    { key: "speed", label: "Anim speed", min: 0.015, max: 10, step: 0.05, default: 10, integrate: true },
    { key: "camDist", label: "Camera distance", min: 0.5, max: 40, step: 0.2, default: 4.4 },
    { key: "focal", label: "Lens", min: 0.3, max: 15, step: 0.1, default: 1.8 },
    { key: "radius", label: "Cloud radius", min: 0.15, max: 10, step: 0.05, default: 2 },
    { key: "scale", label: "Cloud scale", min: 0.1, max: 15, step: 0.1, default: 0.8 },
    { key: "churn", label: "Churn", min: 0, max: 5, step: 0.03, default: 0.3 },
    { key: "threshold", label: "Clumping", min: 0, max: 3, step: 0.015, default: 0.075 },
    { key: "edgeSoft", label: "Edge softness", min: 0.1, max: 10, step: 0.05, default: 0.8 },
    { key: "density", label: "Density", min: 0.03, max: 20, step: 0.1, default: 3.2 },
    { key: "absorb", label: "Absorption", min: 0.03, max: 15, step: 0.1, default: 1.4 },
    { key: "shadowAbsorb", label: "Shadow depth", min: 0, max: 20, step: 0.1, default: 2.4 },
    { key: "shadowLift", label: "Shadow lift", min: 0, max: 5, step: 0.03, default: 0.55 },
    { key: "aniso", label: "Forward scatter", min: -0.9, max: 0.9, step: 0.01, default: 0.45 },
    { key: "lightSpin", label: "Light orbit", min: 0, max: 3, step: 0.015, default: 0.12 },
    { key: "power", label: "Light power", min: 0.03, max: 40, step: 0.2, default: 1.9 },
    { key: "ambient", label: "Ambient", min: 0, max: 3, step: 0.015, default: 0.12 },
    { key: "exposure", label: "Exposure", min: 0.03, max: 10, step: 0.05, default: 1 },
    { key: "alphaGain", label: "Alpha gain", min: 0.05, max: 10, step: 0.05, default: 1.5 }
  ],
  /*
   * The engine uploads these as uC_<key> vec3 uniforms. Warm light against a
   * cool shadow is what reads as depth — a single-hue cloud looks flat however
   * well it is shadowed.
   */
  colors: [
    { key: "light", label: "Light", default: "#ffd7a3" },
    { key: "shadow", label: "Shadow", default: "#3a4a8c" }
  ],
  statePresets: {
    /*
      Every state shares the same speed, geometry and cloud shape — only the
      AMBIENCE and the palette move, so switching state relights the cloud
      instead of restaging it. The engine glides params and cross-fades
      colours on one shared easing, so the change reads as a mood shift.
    */
    idle: {
      ambient: 0.12,
      power: 1.9,
      shadowLift: 0.55
    },
    thinking: {
      ambient: 0.22,
      power: 2.15,
      shadowLift: 0.65
    },
    speaking: {
      ambient: 0.46,
      power: 3.1,
      shadowLift: 0.95
    }
  },
  /*
    The palette carries the rest of the state read: a warm lamp over cool
    shadow at rest, shifting violet while it thinks, and burning hot while
    speaking.
  */
  stateColors: {
    idle: { light: "#ffd7a3", shadow: "#3a4a8c" },
    thinking: { light: "#e6d4ff", shadow: "#3b3f96" },
    speaking: { light: "#ffb066", shadow: "#7a2f6e" }
  }
};

export type Shdr21Props = Omit<ShaderOrbProps, "variant">;

export function Shdr21({ size = 280, ...rest }: Shdr21Props) {
  return <ShaderOrb variant={shdr21Orb} size={size} {...rest} />;
}

export default Shdr21;

24. components/ui/shdr-22.tsx

/*
 * Deliberately not a `"use client"` module — see the note in `shdr-11.tsx`.
 *
 * Note for editors: the shader lives in a template literal, so its comments
 * must not contain backticks.
 */
import { ShaderOrb, type OrbVariant, type ShaderOrbProps } from "@/components/ui/orbkit-core";

/* ----------------------------------------------------------------------------
   SHDR-22 — field lines swirling around the ball about a wandering axis.

   Ported from a golfed twigl listing:

     for(float i,z,d;i++<7e1;o+=vec4(9,i,z,1)/d){
       vec3 p=z*normalize(FC.rgb*2.-r.xyy),
            a=normalize(sin(t/4.+vec3(0,2,4))),v;
       p.z+=7.;
       v=a=dot(a,p)*a+cross(a,p);
       for(d=2.;d++<9.;a+=sin(ceil(a*d)-t).yzx/d);
       z+=d=.1*length(sin(a*a))*sqrt(length(v*sin(v.yzx)));}
     o=tanh(o/6e4);

   What it actually is, decoded:

   - dot(a,p)*a + cross(a,p) with unit a is Rodrigues at EXACTLY 90 degrees
     — a real orthonormal rotation, for once not a golf approximation. The
     axis a = normalize(sin(t/4 + (0,2,4))) wanders slowly with time.
   - v = a = ... forks the rotated point: v stays CLEAN, a takes seven
     octaves of turbulence. The density then multiplies a turbulent factor,
     length(sin(a*a)), by a clean one, sqrt(length(v*sin(v.yzx))) — big
     smooth streak surfaces detailed by turbulence.
   - The turbulence is sin(ceil(a*d) - t): CELL-QUANTIZED. Every lattice
     cell flickers on its own phase — the voxel shimmer that gives the
     effect its name.
   - vec4(9,i,z,1)/d colour-codes the march itself: red is constant, green
     is the STEP INDEX, blue is DEPTH — cores run red-orange, deep tails go
     green-blue. The listing's alpha (1/d) is never displayed; this port
     derives its own.
   - p.z += 7 puts the camera OUTSIDE already — this listing wanted to be
     an orb.

   Port decisions, each one a documented trap or rule in the README:

   - THE ORB IS THE OBJECT: unbounded, the tangle is a storm in a jar. The
     field coordinates are pulled onto the sphere's own shell BEFORE
     evaluation — mix(q, normalize(q)*R, hug) — so the streaks read as
     field lines wrapped around the ball, swirling about the wandering
     axis. The envelope and analytic silhouette bound the residue.
   - The golfed listing relies on i, z, d, v starting at zero —
     uninitialised locals are UNDEFINED in GLSL ES 1.0, explicit here.
   - The clock enters only as additive phase (the axis wander gets its own
     integrated clock, so its rate tunes without jumping the cells).
   - 1/d spikes where both density factors null together — clamped per
     step, as in every accumulator here.
---------------------------------------------------------------------------- */

/*
 * Step counts are `#define`s: ES 1.0 requires constant loop bounds.
 * TURB is 7 to match the original's octaves (d = 3..9).
 */
const VECTORS_FRAG = `
#define STEPS 70
#define TURB 7
#define AA 1

// Volume-reactive values, resolved once per fragment in main().
float vectorsTurb;
float vectorsExposure;
float vectorsGlow;

vec3 vectorsRender(vec2 fragCoord) {
  float animTime = uP_speed; // integrated clock: cell flicker phase
  float wander = uP_wander;  // integrated clock: axis drift

  vec2 uv = (2.0 * fragCoord - uRes) / min(uRes.x, uRes.y);
  vec3 ro = vec3(0.0, 0.0, uP_camDist);
  vec3 rd = normalize(vec3(uv, -uP_focal));

  // the wandering rotation axis — unit by construction, which is what
  // makes the 90-degree Rodrigues below exact
  vec3 axis = normalize(sin(wander + vec3(0.0, 2.0, 4.0)));

  vec3 acc = vec3(0.0);

  // transmittance carried front-to-back — near streaks veil far ones
  float T = 1.0;

  // march only the span the envelope can light, as in shdr-01
  float z = max(uP_camDist - uP_envRadius * 1.3, 0.0);
  float zEnd = uP_camDist + uP_envRadius * 1.3;

  for (int it = 0; it < STEPS; it++) {
    vec3 p = ro + rd * z;

    /*
      THE ORB IS THE OBJECT — pull the sample toward the sphere's shell
      before the field ever sees it. At hug 0 this is the raw 3D tangle;
      at 1 the field is purely angular, painted on the ball's skin. The
      guard keeps normalize() defined through the centre.
    */
    float rl = max(length(p), 1e-3);
    vec3 q = mix(p, p / rl * uP_envRadius, uP_hug);

    // the exact 90-degree rotation about the wandering axis; v stays
    // clean, a takes the turbulence — the fork is the original's v=a=...
    vec3 v = dot(axis, q) * axis + cross(axis, q);
    vec3 a = v;

    // cell-quantized turbulence: every ceil() lattice cell flickers on
    // its own phase
    for (int j = 0; j < TURB; j++) {
      float dj = float(j) + 3.0;
      a += vectorsTurb * sin(ceil(a * dj) - animTime).yzx / dj;
    }

    // the density product — turbulent detail times clean streak surfaces
    float d = uP_stepScale * length(sin(a * a)) * sqrt(length(v * sin(v.yzx)));
    d = max(d, 1e-4);

    /*
      The march's own colour code, from the listing: red constant, green
      by step index, blue by depth into the ball — with the two ramps
      exposed. The CLAMP is load-bearing as everywhere: one grazing step
      would own the whole 70-step sum at some phases. Green gets twice the
      clamp headroom: its ramp runs to STEPS (70) where red is fixed at 9,
      and an equal clamp would crush the step gradient first.
    */
    vec3 w = vec3(9.0, float(it) * uP_hueStep, (z - uP_camDist + uP_envRadius) * uP_hueDepth) / d;
    w = min(w, vec3(uP_stepClamp) * vec3(1.0, 2.0, 1.0));

    /*
      Normalize the clamped weight back to family units (a ceiling of ~20,
      like the sibling orbs). This orb's raw weights run in the hundreds —
      the golfed knee was 6e4 — and without this one line the clamp value
      leaks into total energy, so Exposure, Body fill and Diffusion would
      all change meaning whenever the clamp moves. Normalized, stepClamp
      is a pure dynamic-range knob: low flattens the streaks, high lets
      the 1/d spikes whiten.
    */
    w *= 20.0 / max(uP_stepClamp, 1.0);

    /*
      A few vectors GLOW. Cells of the clean rotated frame are hashed, and
      each cell's hash cycles against the clock so only a small fraction
      (uP_glowFew of the cycle) are hot at any moment. Where a hot cell
      meets a streak null, the same 1/d spike is re-read on a far higher
      ceiling than the step clamp — deliberately bypassing it — and pushed
      as warm-white light. The two smoothsteps make a triangle window, so
      each glow blooms and fades instead of popping at the cycle wrap.
    */
    float few = max(uP_glowFew, 1e-3);
    vec3 vc = ceil(v * 2.0);
    float hcell = hash(vc.xy + vc.z * vec2(7.31, 3.17));
    float cyc = fract(hcell + animTime * 0.05);
    float sel = smoothstep(1.0 - few, 1.0 - 0.5 * few, cyc) * smoothstep(1.0, 1.0 - 0.5 * few, cyc);
    // QUADRATIC in 1/d, unlike the linear base weight — the glow hugs the
    // filament core and falls off fast, a hot wire rather than a lit sector
    w += vec3(1.0, 0.96, 0.88) * min(0.08 / (d * d), 500.0) * sel * vectorsGlow;

    // envelope: plateau through the ball, cut 12% past the radius so the
    // analytic silhouette in main() still has emission left to cut
    float env = smoothstep(uP_envRadius * 1.12, uP_envRadius * uP_envCore, length(p));
    w = (w + uP_fill) * env;

    acc += T * w;
    T *= exp(-dot(w, vec3(0.299, 0.587, 0.114)) * uP_scatter);

    z += d;
    if (T < 0.004 || z > zEnd) break;
  }

  return acc;
}

void main() {
  vectorsTurb = uP_turb * (1.0 + 0.5 * uInput);
  vectorsExposure = uP_exposure * (1.0 - 0.35 * uOutput);
  // the glows flare when the agent speaks
  vectorsGlow = uP_glow * (1.0 + 0.8 * uOutput);

  vec3 acc = vec3(0.0);
#if AA > 1
  for (int mx = 0; mx < AA; mx++) {
    for (int my = 0; my < AA; my++) {
      vec2 offset = vec2(float(mx), float(my)) / float(AA) - 0.5;
      acc += vectorsRender(gl_FragCoord.xy + offset);
    }
  }
  acc /= float(AA * AA);
#else
  acc = vectorsRender(gl_FragCoord.xy);
#endif

  // tanh tone map per channel — the envelope and transmittance change the
  // accumulator's scale, so the golfed /6e4 knee is a tunable here
  vec3 col = tanh3(acc / max(vectorsExposure, 1.0));
  col = pow(clamp(col, 0.0, 1.0), vec3(uP_contrast));

  // saturation about luminance, then the tint
  float lum = dot(col, vec3(0.299, 0.587, 0.114));
  col = mix(vec3(lum), col, uP_saturation);
  col *= uC_tint;

  // alpha from the brightest channel, not luminance — a deep blue tail
  // has low luminance but must not go transparent
  float peak = max(col.r, max(col.g, col.b));
  float a = clamp(peak * uP_alphaGain, 0.0, 1.0);

  // Analytic silhouette — identical construction to shdr-01: exact
  // ray-to-centre distance against the radius, colour AND alpha.
  vec3 mrd = normalize(vec3(orbUV(), -uP_focal));
  float closest = length(cross(vec3(0.0, 0.0, uP_camDist), mrd));
  float band = mix(0.35, 0.012, clamp(uP_edge, 0.0, 1.0));
  float mask = 1.0 - smoothstep(uP_envRadius * (1.0 - band), uP_envRadius * 1.005, closest);
  col *= mask;
  a *= mask;

  // safety taper at the frame boundary — colour as well as alpha
  float r2d = length(orbUV());
  float fade = 1.0 - smoothstep(uP_edgeFade, 1.0, r2d);
  col *= fade;
  a *= fade;

  // Emitted light, so rgb is already premultiplied — do NOT scale by alpha
  // again (see the same note in shdr-31).
  gl_FragColor = vec4(col, a);
}
`;

export const shdr22Orb: OrbVariant = {
  key: "shdr-22",
  label: "SHDR-22",
  note: "field lines swirling around the ball about a wandering axis",
  frag: VECTORS_FRAG,
  params: [
    { key: "speed", label: "Anim speed", min: 0.015, max: 10, step: 0.05, default: 0.5, integrate: true },
    { key: "wander", label: "Axis wander", min: 0, max: 3, step: 0.015, default: 0.12, integrate: true },
    { key: "camDist", label: "Camera distance", min: 1, max: 50, step: 0.3, default: 7 },
    { key: "focal", label: "Lens", min: 0.15, max: 15, step: 0.1, default: 2.25 },
    { key: "hug", label: "Surface hug", min: 0, max: 3, step: 0.015, default: 0.8 },
    { key: "turb", label: "Cell shimmer", min: 0, max: 5, step: 0.03, default: 0.6 },
    { key: "stepScale", label: "Step scale", min: 0.005, max: 1.5, step: 0.01, default: 0.07 },
    { key: "hueStep", label: "Step hue", min: 0, max: 10, step: 0.03, default: 0.12 },
    { key: "hueDepth", label: "Depth hue", min: 0, max: 10, step: 0.03, default: 1.4 },
    { key: "glow", label: "Vector glow", min: 0, max: 10, step: 0.05, default: 1.4 },
    { key: "glowFew", label: "Glow density", min: 0, max: 1.5, step: 0.01, default: 0.12 },
    { key: "envRadius", label: "Envelope radius", min: 0.15, max: 15, step: 0.1, default: 2.6 },
    { key: "envCore", label: "Envelope core", min: 0.3, max: 1.02, step: 0.01, default: 0.88 },
    { key: "fill", label: "Body fill", min: 0, max: 100, step: 0.3, default: 0.15 },
    { key: "stepClamp", label: "Step clamp", min: 3, max: 5000, step: 30, default: 800 },
    { key: "scatter", label: "Diffusion", min: 0, max: 0.5, step: 0.003, default: 0.01 },
    { key: "exposure", label: "Exposure", min: 1.5, max: 5000, step: 15, default: 260 },
    { key: "contrast", label: "Contrast", min: 0.15, max: 15, step: 0.1, default: 1.3 },
    { key: "saturation", label: "Saturation", min: 0, max: 4, step: 0.02, default: 1.15 },
    { key: "alphaGain", label: "Alpha gain", min: 0.05, max: 15, step: 0.1, default: 2 },
    { key: "edge", label: "Edge sharpness", min: 0, max: 1, step: 0.01, default: 1 },
    { key: "edgeFade", label: "Halo falloff", min: 0.1, max: 3, step: 0.015, default: 0.98 }
  ],
  colors: [{ key: "tint", label: "Tint", default: "#ffffff" }],
  /*
    The states are staged on this orb's two best levers, both phase-safe
    integrated clocks: the AXIS WANDER (the whole field re-orients as the
    axis moves) and the cell-flicker speed. glowFew is the third lever —
    how many of the hot-wire vectors are lit at once.
  */
  statePresets: {
    // calm: slow shimmer, near-still axis, a few soft glows
    idle: {
      speed: 0.4,
      wander: 0.1,
      turb: 0.55,
      glow: 1.3,
      glowFew: 0.12,
      exposure: 260,
      scatter: 0.01,
      alphaGain: 2
    },
    /*
      searching: the axis HUNTS — wander runs six times idle, so the field
      lines continuously re-orient as if trying directions — while the
      glows go SPARSER but sharper: rare single sparks, ideas catching.
    */
    thinking: {
      speed: 1.2,
      wander: 0.6,
      turb: 0.7,
      glow: 1.7,
      glowFew: 0.07,
      exposure: 230,
      scatter: 0.0095,
      alphaGain: 2.1
    },
    /*
      answering: the axis settles (it found the direction) and the energy
      moves to the field itself — fast flicker, many hot wires at once
      (glowFew 0.22, further flared by the output volume), bright.
    */
    speaking: {
      speed: 2.2,
      wander: 0.3,
      turb: 0.85,
      glow: 2.4,
      glowFew: 0.22,
      exposure: 170,
      scatter: 0.0075,
      alphaGain: 2.5
    }
  },
  // the tint carries the at-a-glance read, as in chords: neutral at rest,
  // cooled while searching, warmed while answering
  stateColors: {
    idle: { tint: "#ffffff" },
    thinking: { tint: "#c3d2ff" },
    speaking: { tint: "#ffd9c4" }
  }
};

export type Shdr22Props = Omit<ShaderOrbProps, "variant">;

export function Shdr22({ size = 280, ...rest }: Shdr22Props) {
  return <ShaderOrb variant={shdr22Orb} size={size} {...rest} />;
}

export default Shdr22;

25. components/ui/shdr-23.tsx

/*
 * Deliberately not a `"use client"` module — see the note in `shdr-11.tsx`.
 *
 * Note for editors: the shader lives in a template literal, so its comments
 * must not contain backticks.
 */
import { ShaderOrb, type OrbVariant, type ShaderOrbProps } from "@/components/ui/orbkit-core";

/* ----------------------------------------------------------------------------
   SHDR-23 — an ASCII glyph matrix in CRT green, wrapped on the ball.

   The terminal renderer of the family. The screen is a fixed grid of glyph
   cells, and each cell draws a woven character — stacked horizontal dashes
   split by thin vertical gaps — whose LIT ROW COUNT quantizes the field
   behind it. That is exactly how ASCII art shades: space, dot, dash, block.
   The field itself is drifting fbm sampled through a stereographic
   projection of the rotating dome, so the character weave streams and
   compresses around the sphere. On top of it, a DROPOUT mask sampled on a
   coarser super-grid eats blocky rectangular voids out of the matrix —
   the field's dark zones become the chunky black holes of the reference
   aesthetic rather than dim glyphs.

   Construction notes:

   - The cell grid is RESOLUTION-RELATIVE (uP_cells across the canvas), the
     lesson shdr-14 learned: sized in device pixels, a gallery card
     collapses to a few dozen blotches while the playground looks right.
   - The FIELD is sampled once per cell (at the cell's centre) so a glyph
     is one character, not a gradient; the glyph geometry itself renders
     per fragment so its dashes stay crisp at any size. The dropout mask
     snaps to 2x2 super-cells, which is what makes the voids blocky.
   - Clocks enter only as additive phase (field drift) or through the dome
     rotation (integrated spin clock) — phase-safe as everywhere here.
   - Surface-lit and mask-bounded, so alpha IS coverage — premultiplied
     output, as in shdr-28.
---------------------------------------------------------------------------- */

const PHOSPHOR_FRAG = `
void main() {
  // Volume coupling: user input densifies the glyphs, agent output turns
  // the phosphor up — the matrix visibly burns brighter while it speaks.
  float densBias = uP_density + 0.2 * uInput;
  float gainNow = uP_gain * (0.85 + 0.5 * uOutput);

  // resolution-relative glyph grid — same character count at every size
  float cellPx = max(min(uRes.x, uRes.y) / max(uP_cells, 8.0), 4.0);
  vec2 cellIdx = floor(gl_FragCoord.xy / cellPx);
  vec2 cellCentre = (cellIdx + 0.5) * cellPx;
  vec2 g = fract(gl_FragCoord.xy / cellPx); // 0..1 inside the cell

  vec2 suv = (2.0 * cellCentre - uRes) / min(uRes.x, uRes.y);
  vec2 uv = suv / uP_radius;
  float r2 = dot(uv, uv);

  // blocky silhouette, cut on the cell grid like the rest of the matrix
  float mask = 1.0 - step(1.0, r2);

  float z = sqrt(max(1.0 - r2, 0.0));
  vec3 n = vec3(uv, z);

  // rotating dome, stereographic projection — the weave compresses toward
  // the rim and rolls around the ball as the dome turns
  float rot = uP_spin; // integrated clock
  float cr = cos(rot);
  float sr = sin(rot);
  vec3 sp = vec3(n.x * cr - n.z * sr, n.y, n.x * sr + n.z * cr);
  vec2 p2 = sp.xy / (abs(sp.z) + 1.2) * uP_scale * 3.0;

  /*
    Three motions, one per state, each on its OWN integrated clock so a
    state change morphs the movement instead of jumping it:

      DRIFT   diagonal lava-flow streaming        (idle)
      SCROLL  vertical paging, terminal-style     (thinking)
      PULSE   radial waves radiating from centre  (speaking)

    The clocks are rates in the presets — a rate gliding to zero freezes
    that motion in place, phase intact. The pulse's amplitude is a separate
    non-integrated param, so idle carries no static rings.
  */
  float driftT = uP_drift;   // integrated clock: diagonal stream
  float scrollT = uP_scroll; // integrated clock: vertical paging
  float t = uP_speed;        // integrated clock: pulse phase
  vec2 flow = vec2(driftT * 0.6, -driftT * 0.45 - scrollT);

  float field = fbm(p2 + flow);
  float lambert = clamp(dot(n, normalize(vec3(-0.45, 0.55, 0.7))), 0.0, 1.0);
  float dens = clamp((field - 0.5) * 1.8 + densBias + 0.4 * uP_light * lambert
    + uP_pulse * 0.35 * sin(length(uv) * 5.5 - t * 2.4), 0.0, 1.0);

  /*
    The glyph: four dash rows split by three stripe gaps. Rows light from
    the bottom as density rises — the step() against the row index IS the
    ASCII quantizer, so a cell is always a whole character.
  */
  float rowI = floor(g.y * 4.0);
  float bar = step(0.22, fract(g.y * 4.0)) * step(fract(g.y * 4.0), 0.9);
  float stripe = step(0.18, fract(g.x * 3.0));
  float lit = step(rowI + 0.5, dens * 4.0 * gainNow);
  float glyph = bar * stripe * lit;

  /*
    Blocky dropouts: the same field, resampled on a 2x2 super-grid and
    thresholded. Because whole super-cells fail together, the dark zones
    become hard rectangular holes instead of dim characters.
  */
  vec2 superCentre = (floor(cellIdx / 2.0) * 2.0 + 1.0) * cellPx;
  vec2 sSuv = (2.0 * superCentre - uRes) / min(uRes.x, uRes.y);
  vec2 sUv2 = sSuv / uP_radius;
  float sz = sqrt(max(1.0 - dot(sUv2, sUv2), 0.0));
  vec3 ssp = vec3(sUv2.x * cr - sz * sr, sUv2.y, sUv2.x * sr + sz * cr);
  float superField = fbm(ssp.xy / (abs(ssp.z) + 1.2) * uP_scale * 3.0 + flow);
  float keep = step(uP_dropout, superField + 0.15 * uOutput);
  glyph *= keep;

  // phosphor ramp: deep green floor to hot glow, whitening at the top end
  vec3 glyphCol = mix(uC_deep, uC_glow, dens);
  glyphCol += vec3(0.7, 1.0, 0.9) * pow(dens, 3.0) * 0.35;

  // a dark body under the matrix plus a glow-coloured fresnel rim, so the
  // orb reads as a solid ball and not loose characters
  float fres = pow(1.0 - z, 2.2);
  vec3 col = uC_deep * 0.22 + glyphCol * glyph + uC_glow * fres * uP_rim;

  col = pow(max(col, 0.0), vec3(uP_contrast));

  // Surface-lit orb bounded by a mask: alpha IS coverage, so premultiply —
  // the opposite convention from the emissive orbs (see shdr-31).
  float a = mask;
  gl_FragColor = vec4(col * a, a);
}
`;

export const shdr23Orb: OrbVariant = {
  key: "shdr-23",
  label: "SHDR-23",
  note: "an ASCII glyph matrix in CRT green, wrapped on the ball",
  frag: PHOSPHOR_FRAG,
  params: [
    { key: "drift", label: "Drift", min: 0, max: 10, step: 0.05, default: 0.55, integrate: true },
    { key: "scroll", label: "Scroll", min: 0, max: 10, step: 0.05, default: 0.05, integrate: true },
    { key: "speed", label: "Pulse rate", min: 0.015, max: 10, step: 0.05, default: 0.5, integrate: true },
    { key: "pulse", label: "Pulse depth", min: 0, max: 2, step: 0.01, default: 0 },
    { key: "spin", label: "Roll", min: 0, max: 5, step: 0.03, default: 0.12, integrate: true },
    { key: "radius", label: "Radius", min: 0.15, max: 3, step: 0.015, default: 0.9 },
    { key: "cells", label: "Glyph grid", min: 16, max: 120, step: 2, default: 40 },
    { key: "scale", label: "Field scale", min: 0.3, max: 10, step: 0.1, default: 1.6 },
    { key: "density", label: "Glyph density", min: 0, max: 2, step: 0.01, default: 0.48 },
    { key: "dropout", label: "Dropout", min: 0, max: 1, step: 0.01, default: 0.48 },
    { key: "light", label: "Key light", min: 0, max: 3, step: 0.015, default: 0.6 },
    { key: "rim", label: "Rim glow", min: 0, max: 3, step: 0.015, default: 0.45 },
    { key: "gain", label: "Phosphor gain", min: 0.05, max: 5, step: 0.05, default: 1 },
    { key: "contrast", label: "Contrast", min: 0.15, max: 10, step: 0.05, default: 1 }
  ],
  colors: [
    { key: "glow", label: "Glow", default: "#57ffc9" },
    { key: "deep", label: "Deep", default: "#0b3b2d" }
  ],
  /*
    Each state ANIMATES differently — its own kind of motion, not just its
    own speed — and each has its own phosphor colour. Every motion has its own integrated clock, so a rate gliding to zero
    freezes that motion in place with its phase intact; the pulse depth is
    an amplitude.
  */
  statePresets: {
    /*
      idle DRIFTS: slow diagonal lava-flow, lazy roll — on a far finer,
      sparser screen than the working states. The glyph grid is nearly
      doubled and the field scale tripled, with the density down by a third
      and half the dropout, so the matrix reads as a fine violet mesh under
      a strong key light and a bright rim, slightly dimmed.
    */
    idle: {
      drift: 0.55,
      scroll: 0.05,
      pulse: 0,
      speed: 0.52,
      spin: 0.12,
      cells: 68,
      scale: 5.1,
      density: 0.31,
      dropout: 0.24,
      light: 1.11,
      rim: 0.66,
      gain: 0.9
    },
    /*
      thinking STREAMS: the drift runs at six times idle with a steady
      vertical scroll under it and a pulse near speaking depth, on a dome
      almost stopped — the matrix pours across the ball rather than paging.
      The finest grid of the three and the least dropout, so the field is
      nearly solid, on idle's field scale with a touch more contrast.
    */
    thinking: {
      drift: 3.05,
      scroll: 0.65,
      pulse: 0.58,
      speed: 0.45,
      spin: 0.04,
      cells: 86,
      density: 0.33,
      dropout: 0.13,
      light: 0.855,
      rim: 0.51,
      contrast: 1.3,
      gain: 0.95
    },
    /*
      speaking PULSES, hard: radial waves at more than double the thinking
      depth, driven at five times its rate, radiate through the glyphs while
      the dome rolls at nearly a full spin. The grid goes to its finest and
      the field scale past idle's, with the densest glyphs and the heaviest
      dropout of the three — a coarse, flickering red screen — under a dim
      key light with the phosphor gain tripled.
    */
    speaking: {
      drift: 0.4,
      scroll: 0.1,
      pulse: 1.27,
      speed: 2.85,
      spin: 1.05,
      cells: 120,
      scale: 7.2,
      density: 0.56,
      dropout: 0.59,
      light: 0.39,
      rim: 0.51,
      gain: 2.95
    }
  },
  // violet at rest, aqua while searching, red while answering
  stateColors: {
    idle: { glow: "#6a57ff" },
    thinking: { glow: "#57ffe3" },
    speaking: { glow: "#ff5757" }
  }
};

export type Shdr23Props = Omit<ShaderOrbProps, "variant">;

export function Shdr23({ size = 280, ...rest }: Shdr23Props) {
  return <ShaderOrb variant={shdr23Orb} size={size} {...rest} />;
}

export default Shdr23;

26. components/ui/shdr-24.tsx

/*
 * Deliberately not a `"use client"` module — see the note in `shdr-11.tsx`.
 *
 * Note for editors: the shader lives in a template literal, so its comments
 * must not contain backticks.
 */
import { ShaderOrb, type OrbVariant, type ShaderOrbProps } from "@/components/ui/orbkit-core";

/* ----------------------------------------------------------------------------
   SHDR-24 — a Minecraft Earth: a voxel planet with continents, oceans and
   biomes wrapped around the whole ball.

   THE ORB IS THE OBJECT taken literally: instead of wrapping a block texture
   onto a smooth ball, the ball is BUILT FROM BLOCKS. A DDA voxel march
   (Amanatides & Woo) walks an axis-aligned grid in object space; a voxel is
   solid wherever its centre sits below the terrain radius for its direction.
   Up is RADIAL, as on a planet seen from space.

   THE GROUND IS A PERFECT SPHERE. Terrain relief is strictly ADDITIVE —
   max(noise - sea level, 0) — so oceans and coastal plains sit exactly on
   the unit sphere and only the mountains climb above it. The silhouette
   reads as a clean round globe with peaks and tree fuzz breaking the rim,
   not as a lumpy rock. From space down:

   - OCEANS: where the field sits below sea level the ground stays at the
     sphere and its surface blocks render as water — flush on the globe,
     lighter over coastal shallows, deep blue mid-ocean, shimmering on the
     flow clock. SAND beaches band every shore.
   - BIOMES: a continent-scale noise field paints deserts (sand runs deep,
     no trees), plains (grass, scattered trees) and forests (dense cover).
     Elevation cuts across all of them: rising ground bares brown
     hillsides, and the peaks stand as naked stone.
   - SEASONS: an integrated season clock carries the whole planet through
     five worlds on a cycle — lush, CHERRY GROVE, ICE, MESA, desert —
     crossfading two at a time (blossom thaws into snow, terracotta dries
     into sand), racing while the orb thinks. The ice world is drawn from
     the ice-spikes and frozen-peaks biomes: dense tapering packed-ice
     spires instead of trees, glacier-blue patches over the risen faces,
     elevation browns buried under snow, oceans stilled to a frozen
     sheet. The mesa amplifies its relief into big flat-topped banded
     towers with red-sand flats and cacti. The cherry grove covers vivid
     meadows with broad flat blossom-pink puffs on short dark trunks.
   - TREES: block trees on a cubemap-cell lattice — the direction is
     projected onto its dominant cube face and quantized, so every voxel
     on a radial line agrees which tree cell it is in. Trunks grow
     radially, which is what fuzzes the silhouette green over forests;
     canopy radius is re-hashed per voxel for ragged blocky foliage.
   - STRATA under the surface: grass (or desert sand) on the outward
     faces of surface blocks, MUD on their sides and for a band below —
     mottled with hashed stone patches — then STONE, seamed with ORE.
   - ORE VEINS: a coarse cell grid hashes multi-block clusters into the
     deep stone — diamond (the tunable ore colour), lapis (a deep-blue
     remap of it), or coal (unlit) — and each ore block is stone FLECKED
     with the hue on its texture grain, like the actual ore tile. Only
     the flecks glow.
   - CAVES: a band of a second 3D field is carved to air, but ONLY where
     the ground has risen — mountainsides get entrances, the smooth
     lowland sphere stays pristine — and the rock warms toward a MOLTEN
     CORE that blazes through the deep pits when the agent speaks.

   Construction notes:

   - The terrain noise is a tri-planar sum of the prelude's 2D value noise
     (seam-free, no atan). Averaging three samples squeezes the
     distribution toward 0.5, so the range is stretched back out — without
     that the field never reaches the extremes where oceans and peaks live.
   - The march is BOUNDED: rays intersect an analytic sphere around the
     tallest possible tree first, so empty pixels cost two dot products.
   - Faces are shaded flat off their axis-aligned normals — the Minecraft
     read — plus a radial wrap term for planetary roundness, a crevice AO,
     and a depth dimming that sinks cave interiors into darkness so the
     ore and core glow read as underground.
   - No fwidth, no round, constant loop bound with inner breaks —
     GLSL ES 1.0 throughout, as everywhere in this repo.
   - Surface-lit and hit-bounded, so alpha IS coverage — premultiplied
     output (trivially: hits are opaque, misses are clear). The blocky
     aliasing on the rim is the aesthetic, not a bug.
---------------------------------------------------------------------------- */

const CHUNK_FRAG = `
#define STEPS 160

// Per-fragment state, resolved once in main() before the march.
vec3 ckDrift;
float ckVs;
float ckMaxH;
float ckSeaN;
// Climate weights (lush, desert, ice, mesa) plus the cherry grove — a
// partition of unity driven by the integrated season clock — and the tree
// density they imply.
vec4 ckClim;
float ckCherry;
float ckTreeMul;

// Tree-cell lookup results (GLSL ES 1.0 has no out-struct ergonomics).
vec3 ckTreeDir;
float ckTreeH1;
float ckTreeH2;

mat2 ckRot(float a) {
  float c = cos(a);
  float s = sin(a);
  return mat2(c, -s, s, c);
}

// Seam-free noise on the direction sphere: tri-planar sum of the prelude's
// 2D value noise, range-stretched (see the header) and clamped so the
// terrain bound stays a true bound.
float ckN3(vec3 p) {
  float v = (noise(p.xy) + noise(p.yz + 19.1) + noise(p.zx + 47.3)) / 3.0;
  return clamp(0.5 + (v - 0.5) * 1.9, 0.0, 1.0);
}

// The raw terrain field for a surface direction, 0..1. Two octaves only —
// the voxel grid quantizes away anything finer.
float ckField(vec3 dir) {
  vec3 q = dir * uP_scale + ckDrift;
  return ckN3(q) * 0.65 + ckN3(q * 2.6 + 31.7) * 0.35;
}

// Local terrain radius. Relief is strictly ADDITIVE above the unit sphere:
// oceans and plains sit exactly on it, mountains climb from the shoreline.
// Under the mesa climate the relief terraces into three-block steps —
// flat-topped buttes and benches, the badlands profile.
float ckTerrain(vec3 dir) {
  // the mesa amplifies its relief into big banded towers
  float h = 1.0 + uP_rough * max(ckField(dir) - ckSeaN, 0.0) * 1.2
    * (1.0 + ckClim.w * 0.8);
  float stepH = 3.0 * ckVs;
  float hq = 1.0 + floor((h - 1.0) / stepH) * stepH;
  return mix(h, hq, ckClim.w * 0.85);
}

// The continent-scale biome field: below 0.3 desert, above 0.58 forest,
// plains between. Drifts with the terrain so biomes move with their land.
float ckBiome(vec3 dir) {
  return ckN3(dir * 1.3 + ckDrift + 57.9);
}

/*
  Which tree cell does this direction fall in? The direction is projected
  onto its dominant cube face and quantized there — every voxel along a
  radial line lands in the same cell, which is what keeps a tree's trunk
  and canopy agreeing across grid levels. The anchor direction is rebuilt
  from the jittered cell centre.
*/
void ckTreeCell(vec3 dir) {
  vec3 ad = abs(dir);
  vec2 fuv;
  float face;
  if (ad.x >= ad.y && ad.x >= ad.z) {
    fuv = dir.yz / ad.x;
    face = dir.x > 0.0 ? 0.0 : 1.0;
  } else if (ad.y >= ad.z) {
    fuv = dir.xz / ad.y;
    face = dir.y > 0.0 ? 2.0 : 3.0;
  } else {
    fuv = dir.xy / ad.z;
    face = dir.z > 0.0 ? 4.0 : 5.0;
  }
  float grid = max(uP_blocks / 6.0, 2.0);
  vec2 cell = floor((fuv * 0.5 + 0.5) * grid);
  ckTreeH1 = hash(cell * 1.17 + face * 19.3);
  ckTreeH2 = hash(cell * 0.71 + face * 7.7 + 9.3);
  vec2 jit = vec2(hash(cell + 7.1 + face), hash(cell + 13.7 + face)) - 0.5;
  vec2 auv = ((cell + 0.5 + jit * 0.3) / grid) * 2.0 - 1.0;
  vec3 cp;
  if (face < 1.5) cp = vec3(face < 0.5 ? 1.0 : -1.0, auv.x, auv.y);
  else if (face < 3.5) cp = vec3(auv.x, face < 2.5 ? 1.0 : -1.0, auv.y);
  else cp = vec3(auv.x, auv.y, face < 4.5 ? 1.0 : -1.0);
  ckTreeDir = normalize(cp);
}

/*
  The world function: what fills this voxel?
    0 air   1 ground   2 trunk   3 leaves
  Ground is the terrain sphere; caves are carved ONLY where the ground has
  risen above the base sphere, so the smooth lowlands stay pristine. Water
  is not a voxel here — ocean surface blocks are painted as water in the
  material pass. Trees grow radially from dry anchors below the tree line,
  dense where the biome says forest.
*/
float ckVoxel(vec3 cc) {
  float r = length(cc);
  vec3 dir = cc / max(r, 1.0e-4);
  if (r < ckMaxH) {
    float h = ckTerrain(dir);
    if (r < h) {
      // carve caves into risen ground only — mountainsides get entrances,
      // the perfect lowland sphere keeps its silhouette
      if (h > 1.0 + 1.5 * ckVs) {
        float cv = ckN3(cc * (uP_scale * 1.9) + 71.3);
        float cw = uP_cave * 0.16 * smoothstep(ckMaxH, ckMaxH - 0.45, r);
        if (abs(cv - 0.5) < cw) return 0.0;
      }
      return 1.0;
    }
  }
  // trees live in a thin shell above the tallest terrain
  if (r < ckMaxH + 8.0 * ckVs && uP_trees > 0.001) {
    ckTreeCell(dir);
    float thrMax = clamp(uP_trees, 0.0, 1.0) * 0.8;
    if (ckTreeH1 > 1.0 - thrMax) {
      // forest density comes from the biome at the ANCHOR, so a whole
      // tree agrees with itself about existing
      float bioA = ckBiome(ckTreeDir);
      float dens = bioA > 0.58 ? 1.0 : (bioA > 0.3 ? 0.25 : 0.0);
      dens *= ckTreeMul; // forests thin out under desert, ice and mesa skies
      if (ckTreeH1 > 1.0 - thrMax * dens) {
        float fA = ckField(ckTreeDir);
        float ha = 1.0 + uP_rough * max(fA - ckSeaN, 0.0) * 1.2;
        // dry land only, below the stone tree line
        if (fA > ckSeaN + 0.015 && ha < 1.0 + uP_rough * 0.42) {
          float lat = length(cc - dot(cc, ckTreeDir) * ckTreeDir);
          if (ckClim.z > 0.5) {
            // ICE SPIKES: the lattice grows tapering packed-ice spires in
            // place of trees. Squaring the height hash makes many stubs
            // and a few tall spires, the ice-plains skyline.
            float spikeH = (2.0 + 6.0 * ckTreeH2 * ckTreeH2) * ckVs;
            float w = mix(1.15, 0.3, clamp((r - ha) / spikeH, 0.0, 1.0)) * ckVs;
            if (lat < w && r > ha - ckVs && r < ha + spikeH) return 3.0;
          } else if (ckClim.w > 0.5) {
            // CACTI: short green columns dotting the badlands flats
            float cacH = (1.5 + 2.0 * ckTreeH2) * ckVs;
            if (lat < 0.6 * ckVs && r > ha - ckVs && r < ha + cacH) return 3.0;
          } else if (ckCherry > 0.5) {
            // CHERRY GROVE: broad flat blossom puffs on short dark trunks —
            // the radial component of the canopy test is stretched, which
            // squashes the puff wide and flat like the cherry grove trees
            float trunkTop = ha + (2.0 + 1.5 * ckTreeH2) * ckVs;
            if (lat < 0.75 * ckVs && r > ha - ckVs && r < trunkTop) return 2.0;
            vec3 dd = cc - ckTreeDir * (trunkTop + 0.6 * ckVs);
            dd += ckTreeDir * dot(dd, ckTreeDir) * 0.8;
            vec3 lv = floor(cc / ckVs);
            float rag = hash(lv.xy * 0.61 + lv.z * 2.23);
            if (length(dd) < (2.2 + 0.5 * rag) * ckVs) return 3.0;
          } else {
            float trunkTop = ha + (2.5 + 2.0 * ckTreeH2) * ckVs;
            if (lat < 0.75 * ckVs && r > ha - ckVs && r < trunkTop) return 2.0;
            vec3 dd = cc - ckTreeDir * (trunkTop + 0.7 * ckVs);
            // canopy radius re-hashed per voxel — ragged blocky foliage
            vec3 lv = floor(cc / ckVs);
            float rag = hash(lv.xy * 0.61 + lv.z * 2.23);
            if (length(dd) < (1.7 + 0.5 * rag) * ckVs) return 3.0;
          }
        }
      }
    }
  }
  return 0.0;
}

void main() {
  // Volume coupling: agent output stokes the glow, the gain and the molten
  // core; user input brightens the key light.
  float glowNow = uP_glow * (0.7 + 1.0 * uOutput);
  float gainNow = uP_gain * (0.9 + 0.3 * uOutput);
  float lightNow = uP_light * (1.0 + 0.3 * uInput);

  // the terrain field drifts on its own integrated clock — in the thinking
  // state it streams, and blocks pop in and out like chunks loading
  ckDrift = vec3(uP_drift * 0.31, uP_drift * 0.17, -uP_drift * 0.23);

  /*
    CLIMATE: the integrated season clock carries the planet through four
    worlds — lush, desert, ice, mesa — on a cycle. The triangular weights
    overlap so exactly two adjacent climates crossfade at any moment, and
    because the clock integrates, changing the season rate never snaps the
    phase: the world just weathers faster or slower.
  */
  // five worlds in crossfade order: lush, cherry, ice, mesa, desert —
  // blossom thaws into snow, terracotta dries into sand
  float t5 = fract(uP_season * 0.05) * 5.0;
  ckClim = vec4(
    clamp(1.0 - min(abs(t5), abs(t5 - 5.0)), 0.0, 1.0), // lush (wraps)
    clamp(1.0 - abs(t5 - 4.0), 0.0, 1.0),               // desert
    clamp(1.0 - abs(t5 - 2.0), 0.0, 1.0),               // ice
    clamp(1.0 - abs(t5 - 3.0), 0.0, 1.0)                // mesa
  );
  ckCherry = clamp(1.0 - abs(t5 - 1.0), 0.0, 1.0);      // cherry grove
  // ice and cherry run HIGH (dense spikes / dense groves); mesa keeps cacti
  ckTreeMul = dot(ckClim, vec4(1.0, 0.15, 0.9, 0.3)) + ckCherry * 0.9;

  ckVs = 2.0 / clamp(uP_blocks, 8.0, 96.0);   // voxel size, planet radius 1
  // sea level in FIELD space: 0.5 puts about half the sphere under water
  ckSeaN = 0.25 + clamp(uP_sea, 0.0, 1.0) * 0.5;
  // tallest possible terrain — sized for the mesa's amplified towers so
  // the viewport holds steady while the seasons turn
  ckMaxH = 1.0 + uP_rough * (1.0 - ckSeaN) * 1.2 * 1.8 + 0.001;
  float bound = ckMaxH + 8.5 * ckVs;          // ...plus the tree shell

  vec2 uv = orbUV() / uP_radius;

  // orthographic camera, viewport sized to the bound so the treetops fit
  vec3 ro = vec3(uv * bound, 2.9);
  vec3 rd = vec3(0.0, 0.0, -1.0);

  // rotate the RAY into object space (inverse tumble) — the grid stays
  // axis-aligned, the planet appears to spin. The light rotates along,
  // keeping the sun fixed relative to the viewer.
  mat2 tiltM = ckRot(uP_tilt); // positive tilt looks DOWN at the north pole
  mat2 spinM = ckRot(-uP_spin); // integrated clock
  ro.yz = tiltM * ro.yz;
  ro.xz = spinM * ro.xz;
  rd.yz = tiltM * rd.yz;
  rd.xz = spinM * rd.xz;
  vec3 Lo = normalize(vec3(-0.5, 0.7, 0.55));
  Lo.yz = tiltM * Lo.yz;
  Lo.xz = spinM * Lo.xz;

  // DDA needs nonzero direction components — nudge, keep the sign
  vec3 sgn = vec3(
    rd.x >= 0.0 ? 1.0 : -1.0,
    rd.y >= 0.0 ? 1.0 : -1.0,
    rd.z >= 0.0 ? 1.0 : -1.0
  );
  rd = normalize(sgn * max(abs(rd), vec3(1.0e-4)));

  // analytic bounding sphere: empty pixels exit here, and the march below
  // only ever walks the chord inside the bound
  float b = dot(rd, ro);
  float c = dot(ro, ro) - bound * bound;
  float disc = b * b - c;
  if (disc < 0.0) {
    gl_FragColor = vec4(0.0);
    return;
  }
  float sq = sqrt(disc);
  vec3 p0 = ro + rd * (-b - sq + ckVs * 0.001);
  float tSpan = 2.0 * sq;

  // Amanatides & Woo init: current voxel, per-axis distance to the next
  // grid plane, per-axis crossing stride
  vec3 vp = floor(p0 / ckVs);
  vec3 tDelta = ckVs / abs(rd);
  vec3 tMax = ((vp + step(vec3(0.0), rd)) * ckVs - p0) / rd;

  float mat = 0.0;
  vec3 mask = vec3(0.0, 0.0, 1.0); // first-voxel fallback: face the viewer
  float tCur = 0.0;

  for (int i = 0; i < STEPS; i++) {
    float m = ckVoxel((vp + 0.5) * ckVs);
    if (m > 0.5) {
      mat = m;
      break;
    }
    // step to the next voxel across the nearest grid plane
    if (tMax.x < tMax.y && tMax.x < tMax.z) {
      tCur = tMax.x;
      tMax.x += tDelta.x;
      vp.x += sgn.x;
      mask = vec3(1.0, 0.0, 0.0);
    } else if (tMax.y < tMax.z) {
      tCur = tMax.y;
      tMax.y += tDelta.y;
      vp.y += sgn.y;
      mask = vec3(0.0, 1.0, 0.0);
    } else {
      tCur = tMax.z;
      tMax.z += tDelta.z;
      vp.z += sgn.z;
      mask = vec3(0.0, 0.0, 1.0);
    }
    if (tCur > tSpan) break; // left the bound: miss
  }

  if (mat < 0.5) {
    gl_FragColor = vec4(0.0);
    return;
  }

  // the hit voxel, its radial "up", and the face that was struck
  vec3 cc = (vp + 0.5) * ckVs;
  float r = length(cc);
  vec3 dir = cc / max(r, 1.0e-4);
  vec3 n = -mask * sgn;
  vec3 hp = p0 + rd * tCur;

  // per-voxel hashes: core phase and material variety
  vec2 vseed = vec2(dot(vp, vec3(1.0, 57.0, 113.0)), dot(vp, vec3(27.0, 7.0, 91.0)));
  float h1 = hash(vseed * 0.013);
  float h2 = hash(vseed * 0.029 + 5.7);

  // block-texture grain: a 4x4 hash grid on the struck face
  vec2 uvFace;
  if (mask.x > 0.5) uvFace = hp.yz;
  else if (mask.y > 0.5) uvFace = hp.xz;
  else uvFace = hp.xy;
  float grain = hash(floor(fract(uvFace / ckVs) * 4.0) * 0.37 + vseed * 0.11);
  float texMul = mix(1.0, 0.72 + 0.55 * grain, uP_texture);

  // flat face lambert + radial wrap for roundness + crevice AO
  float lam = clamp(dot(n, Lo), 0.0, 1.0);
  float wrap = clamp(dot(dir, Lo) * 0.5 + 0.5, 0.0, 1.0);
  float ao = 0.55 + 0.45 * clamp(dot(n, dir) * 0.5 + 0.5, 0.0, 1.0);
  float shade = (0.32 + 0.5 * wrap * wrap + 0.85 * lam * lightNow) * ao;

  vec3 col;
  if (mat < 1.5) {
    float f = ckField(dir);
    float h = 1.0 + uP_rough * max(f - ckSeaN, 0.0) * 1.2;
    float depth = h - r;
    float topF = step(depth, ckVs * 1.15);

    /*
      The climate palette. Every material the strata paint with is a
      blend over the four climate weights: snow caps the ice world, the
      mesa runs banded terracotta hashed per RADIAL LAYER (the same band
      wraps the whole planet, the badlands look), desert bleaches the
      land to sand, and lush keeps the tunable colours.
    */
    vec3 snow = vec3(0.92, 0.95, 1.0);
    /*
      Mesa strata: two-block-tall bands hashed per radial layer, weighted
      the way real badlands run — long terracotta stretches broken by
      thin red, white, yellow and dark-brown accent stripes. The same
      band circles the whole planet at its height.
    */
    float layer = hash(vec2(floor(r / (ckVs * 2.0)) * 0.371, 5.3));
    vec3 mesaBand = layer < 0.5 ? vec3(0.74, 0.42, 0.21)
      : (layer < 0.68 ? vec3(0.63, 0.26, 0.15)
      : (layer < 0.8 ? vec3(0.88, 0.79, 0.67)
      : (layer < 0.9 ? vec3(0.84, 0.65, 0.27) : vec3(0.4, 0.25, 0.18))));
    // mesa tops: red-sand flats low down, banded rock on the risen buttes
    vec3 mesaTop = mix(vec3(0.72, 0.38, 0.2), mesaBand, step(1.0 + uP_rough * 0.1, h));
    vec3 climGrass = uC_grass * ckClim.x + uC_sand * ckClim.y
      + snow * ckClim.z + mesaTop * ckClim.w
      + mix(uC_grass, vec3(0.62, 0.85, 0.3), 0.6) * ckCherry; // vivid meadow
    vec3 climDirt = uC_dirt * (ckClim.x + ckClim.y + ckCherry)
      + uC_dirt * vec3(0.75, 0.85, 1.05) * ckClim.z + mesaBand * ckClim.w;
    vec3 climSand = uC_sand * (ckClim.x + ckClim.y + ckCherry)
      + mix(uC_sand, snow, 0.9) * ckClim.z + vec3(0.72, 0.35, 0.2) * ckClim.w;
    vec3 climWater = uC_water * (ckClim.x + ckClim.y + ckCherry)
      + vec3(0.62, 0.82, 0.92) * ckClim.z
      + mix(uC_water, vec3(0.42, 0.3, 0.22), 0.4) * ckClim.w;

    if (topF > 0.5 && f < ckSeaN) {
      // OCEAN: the surface of the perfect sphere painted as water —
      // lighter over coastal shallows, deep blue mid-ocean, with a sun
      // glint and a shimmer on the flow clock. The ice world stills the
      // shimmer and pales the depths: a frozen sheet.
      float deep = clamp((ckSeaN - f) / 0.12, 0.0, 1.0) * (1.0 - 0.55 * ckClim.z);
      vec3 wc = climWater * mix(1.3, 0.55, deep);
      float shim = 0.85 + 0.25 * sin(uAnim * 2.5 + grain * 6.2831 + dir.x * 4.0);
      shim = mix(shim, 1.02, ckClim.z);
      col = wc * (0.45 + 0.55 * wrap) * shim + wc * lam * 0.35;
    } else {
      /*
        LAND. Strata by radial depth below the local surface — grass or
        desert sand on the outward faces of surface blocks, mud with
        hashed stone patches beneath, then ore-seamed stone. Elevation
        overrides the biome: rising ground bares brown hillsides, peaks
        stand as naked stone, and every shore gets a sand band.
      */
      float dirtF = step(depth, ckVs * 2.4);
      float up = clamp(dot(n, dir), 0.0, 1.0);

      vec3 albedo = mix(uC_stone, climDirt, dirtF);
      // stone patches in the exposed mud, below the grass line
      albedo = mix(albedo, uC_stone, dirtF * (1.0 - topF) * step(h2, 0.3));

      float bio = ckBiome(dir);
      float desertF = step(bio, 0.3);
      albedo = mix(albedo, climGrass, topF * step(0.45, up) * (1.0 - desertF));
      albedo = mix(albedo, climSand, desertF * dirtF); // desert sand runs deep
      // elevation bands: brown hillsides, then bare stone peaks — both
      // buried under snow when the ice climate holds (frozen peaks stay
      // white with only crevice shadow, not brown or gray)
      albedo = mix(albedo, climDirt,
        topF * step(1.0 + uP_rough * 0.28, h) * 0.85 * (1.0 - 0.9 * ckClim.z));
      albedo = mix(albedo, uC_stone,
        topF * step(1.0 + uP_rough * 0.45, h) * (1.0 - 0.85 * ckClim.z));
      // beach: a narrow field-space band above the shoreline turns to sand
      albedo = mix(albedo, climSand, topF * step(abs(f - ckSeaN - 0.017), 0.018));

      // the coarse cluster cells serve ore veins AND glacier patches
      vec3 oc = floor(cc / (2.5 * ckVs));
      vec2 oseed = vec2(dot(oc, vec3(1.0, 57.0, 113.0)), dot(oc, vec3(27.0, 7.0, 91.0)));
      float fleck = step(0.5, hash(floor(fract(uvFace / ckVs) * 4.0) * 0.53 + oseed * 0.19));

      // ICE climate: packed-ice blue patches cluster over the risen
      // ground — glacier faces streaking the snowy mountainsides
      float icePatch = ckClim.z * step(hash(oseed * 0.023 + 9.1), 0.5)
        * step(1.0 + uP_rough * 0.06, h);
      albedo = mix(albedo, vec3(0.55, 0.7, 0.92), icePatch * (0.45 + 0.4 * fleck));

      /*
        Ore veins: a coarse cell grid hashes veins into the deep stone, so
        ore comes in multi-block clusters like the cross-section dioramas.
        Each vein rolls a type — diamond (the tunable ore colour), lapis
        (a deep-blue remap of it), or coal (unlit) — and each ore block is
        stone FLECKED with the hue on its texture grain, the way the
        actual ore tile is drawn. Only the flecks glow.
      */
      float veinF = (1.0 - dirtF) * step(1.0 - uP_ore, hash(oseed * 0.017)) * step(h1, 0.8);
      float oreType = hash(oseed * 0.041 + 2.9);
      vec3 oreHue = oreType < 0.4
        ? uC_ore
        : (oreType < 0.75 ? uC_ore * vec3(0.25, 0.45, 1.2) : vec3(0.16));
      float oreLit = oreType < 0.75 ? 1.0 : 0.0;
      albedo = mix(albedo, oreHue, veinF * (0.2 + 0.65 * fleck));
      float twinkle = 0.55 + 0.45 * sin(uP_shuffle + hash(oseed * 0.013) * 37.0); // integrated clock

      // depth below the surface darkens: cave interiors and cleft walls
      // sink into shadow, which makes the glow read as underground
      float depthDim = mix(1.0, 0.62, clamp(depth / max(uP_rough * 0.9, 0.05), 0.0, 1.0));

      // the deeper the rock, the closer to the molten core
      float coreR = 1.0 - uP_rough * 0.6;
      float coreF = uP_core * smoothstep(coreR + 0.15, coreR - 0.05, r);

      vec3 emis = oreHue * veinF * fleck * oreLit * glowNow * twinkle
        + uC_lava * coreF * (0.9 + 0.4 * sin(uP_shuffle * 1.6 + h1 * 51.0))
          * (0.6 + 1.4 * uOutput);

      col = albedo * shade * depthDim + emis;
    }
  } else if (mat < 2.5) {
    // trunk: dark wood, derived from the mud so the palette stays small
    col = uC_dirt * 0.5 * shade;
  } else {
    // leaves: heavier grain reads as foliage clumps. Under the ice climate
    // this material IS the spikes, so it turns packed-ice blue and the
    // grain smooths toward faceted ice.
    vec3 climLeaf = uC_leaf * (ckClim.x + ckClim.y * 0.9)
      + vec3(0.62, 0.76, 0.95) * ckClim.z
      + mix(uC_leaf, vec3(0.45, 0.62, 0.25), 0.5) * ckClim.w // cactus green
      + vec3(0.93, 0.7, 0.82) * ckCherry; // blossom pink
    col = climLeaf * shade;
    float leafGrain = mix(0.5 + 0.9 * grain, 0.85 + 0.3 * grain, ckClim.z);
    texMul = mix(1.0, leafGrain, uP_texture);
  }

  col *= texMul * gainNow;
  col = pow(max(col, 0.0), vec3(uP_contrast));

  // Surface-lit orb bounded by the hit test: alpha IS coverage, and a hit
  // is fully opaque — premultiplied output, trivially (see shdr-28).
  gl_FragColor = vec4(col, 1.0);
}
`;

export const shdr24Orb: OrbVariant = {
  key: "shdr-24",
  label: "SHDR-24",
  note: "a Minecraft Earth — a perfect voxel sphere whose seasons cycle it through lush, cherry-grove, ice, mesa and desert worlds",
  frag: CHUNK_FRAG,
  params: [
    { key: "spin", label: "Spin", min: 0, max: 5, step: 0.03, default: 0.22, integrate: true },
    { key: "tilt", label: "Tilt", min: 0, max: 4, step: 0.02, default: 0.45 },
    { key: "drift", label: "Terrain drift", min: 0, max: 10, step: 0.05, default: 0.12, integrate: true },
    { key: "season", label: "Season rate", min: 0, max: 10, step: 0.05, default: 0.3, integrate: true },
    { key: "shuffle", label: "Ember rate", min: 0, max: 20, step: 0.1, default: 0.8, integrate: true },
    { key: "radius", label: "Radius", min: 0.15, max: 3, step: 0.015, default: 1.15 },
    { key: "blocks", label: "Blocks", min: 16, max: 96, step: 1, default: 64 },
    { key: "rough", label: "Mountains", min: 0, max: 0.8, step: 0.01, default: 0.45 },
    { key: "scale", label: "Terrain scale", min: 0.5, max: 8, step: 0.05, default: 2.4 },
    { key: "sea", label: "Sea level", min: 0, max: 1, step: 0.01, default: 0.5 },
    { key: "trees", label: "Trees", min: 0, max: 1, step: 0.01, default: 0.75 },
    { key: "cave", label: "Caves", min: 0, max: 1, step: 0.01, default: 0.4 },
    { key: "ore", label: "Ore density", min: 0, max: 0.6, step: 0.01, default: 0.12 },
    { key: "glow", label: "Ore glow", min: 0, max: 5, step: 0.03, default: 0.9 },
    { key: "core", label: "Molten core", min: 0, max: 1, step: 0.01, default: 0.5 },
    { key: "texture", label: "Texture grain", min: 0, max: 1, step: 0.01, default: 0.6 },
    { key: "light", label: "Key light", min: 0, max: 3, step: 0.015, default: 1 },
    { key: "gain", label: "Gain", min: 0.05, max: 5, step: 0.05, default: 1 },
    { key: "contrast", label: "Contrast", min: 0.15, max: 10, step: 0.05, default: 1 }
  ],
  colors: [
    { key: "grass", label: "Grass", default: "#6abe30" },
    { key: "dirt", label: "Mud", default: "#6f4a2f" },
    { key: "stone", label: "Stone", default: "#8a8a90" },
    { key: "sand", label: "Sand", default: "#dbcf9c" },
    { key: "water", label: "Water", default: "#2f66d0" },
    { key: "leaf", label: "Leaves", default: "#3e8f27" },
    { key: "ore", label: "Ore", default: "#4de3ff" },
    { key: "lava", label: "Lava", default: "#ff7b26" }
  ],
  /*
    Each state animates DIFFERENTLY on the integrated clocks — same palette
    and biomes throughout (no stateColors on purpose):

      idle DRIFTS      lazy spin, terrain barely morphing, embers twinkling
      thinking LOADS   the spin all but stops while the terrain field
                       streams — continents morph and blocks pop in and out
                       like chunks loading — and the ore twinkle races
      speaking ERUPTS  the planet turns fast to answer, the molten core
                       blazes through the caves, ore glow flares
  */
  statePresets: {
    idle: {
      spin: 0.22,
      drift: 0.12,
      season: 0.3,
      shuffle: 0.8,
      glow: 0.9,
      core: 0.5,
      gain: 1,
      light: 1
    },
    // thinking races the seasons as well as the terrain: the planet cycles
    // through its worlds while it considers
    thinking: {
      spin: 0.04,
      drift: 1.7,
      season: 1.8,
      shuffle: 4.5,
      glow: 1.3,
      core: 0.35,
      gain: 0.95,
      light: 0.9
    },
    speaking: {
      spin: 0.85,
      drift: 0.35,
      season: 0.6,
      shuffle: 1.6,
      glow: 1.6,
      core: 1,
      gain: 1.1,
      light: 1.15
    }
  }
};

export type Shdr24Props = Omit<ShaderOrbProps, "variant">;

export function Shdr24({ size = 280, ...rest }: Shdr24Props) {
  return <ShaderOrb variant={shdr24Orb} size={size} {...rest} />;
}

export default Shdr24;

27. components/ui/shdr-25.tsx

/*
 * Deliberately not a `"use client"` module — see the note in `shdr-11.tsx`.
 *
 * Note for editors: the shader lives in a template literal, so its comments
 * must not contain backticks.
 */
import { ShaderOrb, type OrbVariant, type ShaderOrbProps } from "@/components/ui/orbkit-core";

/* ----------------------------------------------------------------------------
   SHDR-25 — the folds of a warped field, drawn by their own steepness.

   Ported from a one-line twigl listing:

     vec2 p=FC.xy/r.y*2e1+t;
     for(float i;i++<8.;)
       p+=sin(p+t/.2+i)*.4,
       p*=mat2(6,-8,8,6)/9.;
     o=vec4(tanh(length(fwidth(sin(p*.3)/.1))),texture(b,FC.xy/r));

   What it actually is, decoded:

   - THE IMAGE IS A DERIVATIVE. Nothing here draws a shape; it measures how
     fast a rippled field changes from one pixel to the next and paints
     that. Where eight octaves of warp have folded the plane onto itself
     the field races, and those folds come out as bright filigree. Where it
     is stretched flat, nothing.
   - mat2(6,-8,8,6)/9 IS AN EXACT SIMILARITY, and it is the neatest thing
     in the listing. 6-8-10 is a Pythagorean triple, so that matrix is
     precisely a rotation through the 3-4-5 angle — about 53.13 degrees —
     times a scale of 10/9. Rotating between octaves is the standard way to
     stop a warp from lining up with the axes; doing it with integers and
     one divide is not.
   - THE CLOCK RUNS TWICE. t translates the whole field once at the start,
     and t/.2 — five times faster — drives the warp inside every octave, so
     the pattern drifts slowly while its detail boils.
   - vec4(scalar, texture(...)) puts the derivative in RED and takes green,
     blue and alpha from the previous frame.

   Port decisions, each one a documented trap or rule in the README:

   - fwidth() CANNOT BE USED. It needs OES_standard_derivatives in WebGL 1,
     and the #extension directive that enables it cannot legally follow the
     prelude's declarations — the README says so, and shdr-28 works
     around it by deriving its pixel footprint analytically. There is no
     analytic footprint through eight octaves of feedback warp, so the
     derivative is taken by FINITE DIFFERENCES instead: evaluate the whole
     chain at the two neighbouring pixel centres and difference. Three
     evaluations rather than one, and closer to the truth than fwidth,
     which is constant across each 2x2 quad.
   - THE FEEDBACK TERM IS NOT PORTED. One pass, one canvas, no
     previous-frame texture (the same limit as shdr-09). Green and blue
     arrive only from that line, so without a substitute this orb would be
     red and nothing else — the derivative is instead run through a palette
     keyed to the field itself.
   - THE ORB IS THE OBJECT: a flat 2D field, so it is sampled through a
     stereographic projection of the dome, and motion is projection-safe
     2D as in shdr-08. That wrap pays for itself twice here — because the
     measurement is a SCREEN-SPACE derivative, the projection's own
     compression toward the limb steepens the field there, so the filigree
     tightens at the rim exactly the way a texture on a real sphere would.
   - The golfed listing relies on i starting at zero; uninitialised locals
     are UNDEFINED in GLSL ES 1.0, explicit here.
   - Surface-lit and mask-bounded, so alpha IS coverage — premultiplied
     output, as in shdr-17.
---------------------------------------------------------------------------- */

/*
 * Step counts are `#define`s: ES 1.0 requires constant loop bounds. OCTAVES
 * is the listing's i++ < 8. The finite difference costs three passes of it
 * per sample, so this is 24 warp steps before supersampling.
 */
const CREASE_FRAG = `
#define OCTAVES 8
#define AA 2

// Volume-reactive values, resolved once per fragment in main().
float creaseWarp;
float creaseGain;

// GLSL ES 1.0 has no scalar tanh; the prelude ships the vec3 form only.
float tanh1(float x) {
  x = clamp(x, -10.0, 10.0);
  float e = exp(2.0 * x);
  return (e - 1.0) / (e + 1.0);
}

/*
  The whole chain for one pixel centre: dome, stereographic wrap, then the
  eight-octave warp. Called three times per sample so the derivative below
  can be differenced — see the header for why fwidth is unavailable.
*/
vec2 creaseField(vec2 fragCoord, float t, float drift, float sw) {
  vec2 uv = (2.0 * fragCoord - uRes) / min(uRes.x, uRes.y);
  vec2 pl = uv / max(uP_radius, 0.001);
  float z = sqrt(max(1.0 - dot(pl, pl), 0.0));

  vec2 p = pl / (z + 1.0 + uP_bulge) * uP_scale;
  p = mat2(cos(sw), -sin(sw), sin(sw), cos(sw)) * p;
  p += drift;

  /*
    The listing's matrix, decomposed. mat2(6,-8,8,6)/9 is exactly
    (10/9) * mat2(.6,-.8,.8,.6), and that second factor is a true rotation
    because 6-8-10 is a Pythagorean triple — so the octave transform is a
    rotation through the 3-4-5 angle times a clean zoom, with the zoom
    pulled out as a slider.
  */
  for (int i = 0; i < OCTAVES; i++) {
    float fi = float(i) + 1.0;
    p += sin(p + t + fi) * creaseWarp;
    p = uP_zoom * (mat2(0.6, -0.8, 0.8, 0.6) * p);
  }

  return p;
}

vec3 creaseRender(vec2 fragCoord) {
  float t = uP_speed;      // integrated clock: the boil
  float drift = uP_drift;  // integrated clock: the slow travel
  float sw = uP_swirl;     // integrated clock

  /*
    The three taps the difference needs. uP_blur is how far apart they sit:
    at one pixel this is fwidth exactly, and wider is a deliberate blur —
    the derivative of a folded field is a hairline, and a rim wants width.
  */
  vec2 p0 = creaseField(fragCoord, t, drift, sw);
  vec2 px = creaseField(fragCoord + vec2(uP_blur, 0.0), t, drift, sw);
  vec2 py = creaseField(fragCoord + vec2(0.0, uP_blur), t, drift, sw);

  /*
    fwidth, by hand and once per channel. The sum of the absolute
    differences on each axis is exactly what the built-in returns — but
    taking it three times at slightly offset ripple phases puts each
    channel's rim in a slightly different place, which is where the warm
    and cool fringes on the edges come from. One field evaluation still
    serves all three.
  */
  vec3 e;
  for (int c = 0; c < 3; c++) {
    float ph = float(c) * uP_fringe;
    vec2 v0 = sin(p0 * uP_ripple + ph);
    vec2 d = abs(sin(px * uP_ripple + ph) - v0)
           + abs(sin(py * uP_ripple + ph) - v0);
    float m = tanh1(length(d) * creaseGain / max(uP_exposure, 0.001));
    if (c == 0) e.r = m;
    else if (c == 1) e.g = m;
    else e.b = m;
  }

  e = pow(clamp(e, 0.0, 1.0), vec3(uP_contrast));

  vec3 col = uC_tint * e;

  // a dark body under the filigree, so the flat regions read as the ball
  col += uC_body * uP_floorLevel;

  float lum = dot(col, vec3(0.299, 0.587, 0.114));
  col = mix(vec3(lum), col, uP_saturation);

  // dome shading keeps the ball a ball under the folds
  vec2 pl = ((2.0 * fragCoord - uRes) / min(uRes.x, uRes.y)) / max(uP_radius, 0.001);
  float z = sqrt(max(1.0 - dot(pl, pl), 0.0));
  vec3 n = vec3(pl, z);
  float lambert = clamp(dot(n, normalize(vec3(-0.45, 0.55, 0.72))), 0.0, 1.0);
  col *= 0.62 + uP_light * lambert;

  float fres = 1.0 - z;
  fres = fres * fres * fres;
  col += uC_sheen * uP_rim * fres;

  return col;
}

void main() {
  // Volume coupling: the user's voice folds the field harder, the agent's
  // steepens what counts as a crease.
  creaseWarp = uP_warp * (1.0 + 0.4 * uInput);
  creaseGain = uP_edgeGain * (1.0 + 0.5 * uOutput);

  vec2 uv = orbUV();
  float mask = smoothstep(0.012, -0.012, length(uv) - max(uP_radius, 0.001));

  // Twenty-four warp steps per sample — none of them worth paying for
  // outside the silhouette.
  if (mask <= 0.0) {
    gl_FragColor = vec4(0.0);
    return;
  }

  vec3 col = vec3(0.0);
#if AA > 1
  for (int mx = 0; mx < AA; mx++) {
    for (int my = 0; my < AA; my++) {
      vec2 off = (vec2(float(mx), float(my)) + 0.5) / float(AA) - 0.5;
      col += creaseRender(gl_FragCoord.xy + off);
    }
  }
  col /= float(AA * AA);
#else
  col = creaseRender(gl_FragCoord.xy);
#endif

  // Surface orb bounded by a mask: alpha IS coverage, so premultiply — the
  // opposite convention from the emissive orbs (see shdr-31).
  float a = mask;
  gl_FragColor = vec4(max(col, vec3(0.0)) * a, a);
}
`;

export const shdr25Orb: OrbVariant = {
  key: "shdr-25",
  label: "SHDR-25",
  note: "the folds of a warped field, drawn by their own steepness",
  frag: CREASE_FRAG,
  params: [
    { key: "speed", label: "Boil", min: 0.015, max: 20, step: 0.05, default: 3, integrate: true },
    { key: "drift", label: "Drift", min: 0, max: 8, step: 0.02, default: 0.6, integrate: true },
    { key: "swirl", label: "Swirl", min: 0, max: 3, step: 0.015, default: 0.05, integrate: true },
    { key: "radius", label: "Radius", min: 0.15, max: 3, step: 0.015, default: 0.9 },
    { key: "scale", label: "Cell scale", min: 0.3, max: 60, step: 0.1, default: 7.5 },
    { key: "bulge", label: "Dome bulge", min: 0, max: 4, step: 0.02, default: 0.3 },
    { key: "warp", label: "Fold", min: 0, max: 2, step: 0.01, default: 0.4 },
    { key: "zoom", label: "Octave zoom", min: 0.6, max: 2, step: 0.005, default: 1.111 },
    { key: "ripple", label: "Ripple", min: 0.02, max: 3, step: 0.01, default: 0.3 },
    { key: "edgeGain", label: "Edge gain", min: 0.5, max: 60, step: 0.5, default: 10 },
    { key: "blur", label: "Rim width", min: 0.5, max: 12, step: 0.25, default: 2.5 },
    { key: "fringe", label: "Chromatic fringe", min: 0, max: 1.5, step: 0.005, default: 0.09 },
    { key: "exposure", label: "Exposure", min: 0.05, max: 40, step: 0.05, default: 0.8 },
    { key: "contrast", label: "Contrast", min: 0.15, max: 8, step: 0.05, default: 1.15 },
    { key: "saturation", label: "Saturation", min: 0, max: 4, step: 0.02, default: 1.5 },
    { key: "floorLevel", label: "Body fill", min: 0, max: 2, step: 0.01, default: 0.16 },
    { key: "light", label: "Key light", min: 0, max: 3, step: 0.015, default: 0.35 },
    { key: "rim", label: "Rim sheen", min: 0, max: 3, step: 0.015, default: 0.45 }
  ],
  colors: [
    { key: "tint", label: "Rim", default: "#dbe8f7" },
    { key: "body", label: "Body", default: "#0d1118" },
    { key: "sheen", label: "Sheen", default: "#a8c8f0" }
  ],
  /*
    Staged on the FOLD, which is what makes creases exist at all, and on
    edge gain, which decides how steep a slope has to be to count as one.
    Cell scale moves only for the answer — it sets how much pattern is on
    the ball, and as it glides in the ball reads as inflating; that is the
    answer's entrance.
  */
  statePresets: {
    // at rest: a slow boil, folds moderate, rims clean — the octave zoom
    // pulled in a touch under the default and the edge gain a quarter up,
    // so slightly gentler slopes count as creases
    idle: {
      speed: 3,
      drift: 0.6,
      swirl: 0.05,
      warp: 0.4,
      zoom: 1.07,
      edgeGain: 12.5,
      exposure: 0.8,
      contrast: 1.15
    },
    /*
      searching: the folds RELAX a touch below idle, but the edge gain
      nearly triples so even the shallowest slope lights up as a crease,
      on a boil half again idle's. The ripple tightens, the rim widens with
      more than double the chromatic fringe, and the saturation, key light
      and rim sheen all come up — every cell rims at once in colour, and
      none of it settles.
    */
    thinking: {
      speed: 4.7,
      drift: 0.15,
      swirl: 0.02,
      bulge: 0.38,
      warp: 0.34,
      zoom: 1.12,
      ripple: 0.18,
      edgeGain: 33,
      blur: 2.75,
      fringe: 0.2,
      exposure: 0.55,
      contrast: 1.5,
      saturation: 2,
      light: 0.525,
      rim: 0.69
    },
    /*
      answering: the field FOLDS hardest of the three and the gain drops to
      under a sixth of the thinking state, so the creases are deep but only
      the steepest rims light. The cell scale is pushed past idle's and the
      dome bulged to near a hemisphere, the swirl opened an order of
      magnitude, the ripple widened — a slow, heavy, swirling boil, with
      the exposure tripled so what does light, burns.
    */
    speaking: {
      speed: 1.4,
      drift: 0.8,
      swirl: 0.6,
      scale: 11.5,
      bulge: 2.22,
      warp: 1.55,
      zoom: 0.945,
      ripple: 1.18,
      edgeGain: 5,
      blur: 2,
      fringe: 0.23,
      exposure: 2.35,
      contrast: 1.35
    }
  },
  // cool steel at rest, cold indigo for both working states — the answer
  // is told apart by its fold and scale, not its colour
  stateColors: {
    idle: { tint: "#dbe8f7", body: "#0d1118", sheen: "#a8c8f0" },
    thinking: { tint: "#c2d6f5", body: "#090d1c", sheen: "#8fb4f2" },
    speaking: { tint: "#c2d6f5", body: "#090d1c", sheen: "#8fb4f2" }
  }
};

export type Shdr25Props = Omit<ShaderOrbProps, "variant">;

export function Shdr25({ size = 280, ...rest }: Shdr25Props) {
  return <ShaderOrb variant={shdr25Orb} size={size} {...rest} />;
}

export default Shdr25;

28. components/ui/shdr-26.tsx

/*
 * Deliberately not a `"use client"` module — see the note in `shdr-11.tsx`.
 *
 * Note for editors: the shader lives in a template literal, so its comments
 * must not contain backticks.
 */
import { ShaderOrb, type OrbVariant, type ShaderOrbProps } from "@/components/ui/orbkit-core";

/* ----------------------------------------------------------------------------
   SHDR-26 — a crazed web of thin coloured threads, knotted to a cell grid.

   Ported from a three-line listing in one of the golf dialects:

     f2 c = C.xy / R.y*4+R, s = flr(c), i;
     @(9) c+=cos(++i * c.yx + .1/(s-c) + T) / i
     O = exp(-3*abs(sin(c.y+f4(,.4,.2,))))

   (f2/f4 are vec2/vec4, flr is floor, @(9) is a nine-iteration loop, and the
   empty slots in f4(,.4,.2,) are zeros.)

   What it actually is, decoded:

   - The warp is the same nine-octave FEEDBACK curl as shdr-08's — each
     octave reads the last one's result with its components swapped — but
     with two differences that change everything: the clock enters as a
     SHARED phase rather than one multiplied per octave, so this field
     boils coherently instead of shimmering, and each octave carries a
     LATTICE POLE term.
   - .1/(s-c) is the pole, and it is the whole idea. s is the sample's cell
     corner, taken ONCE before the loop, so s - c starts as -fract(c) and
     then drifts as the warp moves c away from its own cell. The phase
     blows up as a sample approaches a lattice point, so the field knots
     violently around a grid of singularities: cell interiors flow, cell
     corners tear.
   - The listing adds the RESOLUTION to c, which looks like it seeds the
     lattice — but s - c subtracts it straight back out, so it cancels
     exactly and only shifts the lattice phase. Dropped here (the same
     resolution-as-phase trap written up in shdr-08, where it did NOT
     cancel).
   - exp(-3*abs(sin(x))) is a completely different band function from
     Nacre's saturating cot: a THIN bright ridge every PI with an
     exponential falloff and a dim floor at exp(-3), not a wide crest. That
     floor is what keeps the shell lit between threads.
   - vec4(0,.4,.2,0) offsets green furthest and blue between. Against a
     ridge this thin the offset is a large fraction of the line width, so
     every thread splits into three coloured filaments running in parallel
     rather than merely fringing at its edges.

   Port decisions, each one a documented trap or rule in the README:

   - THE ORB IS THE OBJECT: a flat 2D field, so it is sampled through a
     stereographic projection of the dome and finished with a fresnel
     sheen, never masked out of the plane as a disc.
   - Like shdr-08, and for a reason worth reading before changing it,
     it projects the unrotated dome and moves in 2D. The abs(sp.z) form
     that lets shdr-28 and shdr-29 roll their domes was tried first
     and had to go: it turns the ball into an exact mirror image of itself
     once a quarter turn, which a cellular grid hides and a web of long
     threads does not. The full derivation is at the projection.
   - The pole is SOFTENED, not guarded: x/(x*x+g) is 1/x everywhere the
     listing cared about and finite where it did not. A hard guard leaves a
     sign discontinuity on every lattice line, and the raw divide reaches
     infinite phase frequency at every lattice point, which no amount of
     supersampling can resolve.
   - The golfed listing relies on i starting at zero; uninitialised locals
     are UNDEFINED in GLSL ES 1.0, explicit here.
   - Surface-lit and mask-bounded, so alpha IS coverage — premultiplied
     output, as in shdr-17.
---------------------------------------------------------------------------- */

/*
 * Step counts are `#define`s: ES 1.0 requires constant loop bounds. OCTAVES is
 * the listing's @(9). AA supersamples the threads, which are a pixel or two
 * wide at the centre of the ball and thinner than that toward the limb —
 * WebGL 1 has no fwidth without an extension, so brute force is the defence.
 */
const LATTICE_FRAG = `
#define OCTAVES 9
#define AA 3

const float TAU = 6.28318530718;

// Volume-reactive values, resolved once per fragment in main().
float latticePole;
float latticeSharp;
float latticeGain;

vec3 latticeRender(vec2 fragCoord) {
  vec2 uv = (2.0 * fragCoord - uRes) / min(uRes.x, uRes.y);
  float R = max(uP_radius, 0.001);

  // the dome: the front hemisphere of a unit ball, in screen space
  vec2 pl = uv / R;
  float z = sqrt(max(1.0 - dot(pl, pl), 0.0));
  vec3 n = vec3(pl, z);

  float t = uP_speed; // integrated clock

  /*
    Stereographic projection of the UNROTATED dome.

    This orb was built on the abs(sp.z) form first — the one shdr-28
    and shdr-29 use, which survives a 3D roll because the divisor can
    only fall TO zero at the terminator, never through it. It renders, but
    every quarter turn it makes the visible hemisphere an EXACT mirror
    image about the view axis: at a roll of 90 degrees sp becomes
    (-n.z, n.y, n.x), so the projected coordinate depends on n.z and on
    abs(n.x), and both of those are even in screen x. A cellular grid
    hides that. A web of long threads does not — the ball turns into a
    Rorschach blot for a quarter of every revolution.

    So the dome stays put, as in shdr-08, and all the motion below is
    projection-safe 2D.
  */
  vec2 p = n.xy / (n.z + 1.0 + uP_bulge) * uP_scale;

  // the plane turns while the lattice drifts across it, so the crazing
  // migrates over the glaze instead of sitting welded to it
  float sw = uP_swirl; // integrated clock
  p = mat2(cos(sw), -sin(sw), sin(sw), cos(sw)) * p;
  p.x += uP_drift;     // integrated clock

  /*
    A fractional offset off the pattern origin. The listing carries the
    resolution here; it cancels out of the pole term exactly (see the
    header) but it does keep the lattice off centre, and without something
    in its place a cell corner sits pinned at the dead middle of the ball.
  */
  p += vec2(0.37, 0.21);

  // the cell the sample starts in, fixed before the warp — everything the
  // pole term does is relative to THIS corner, not to wherever c wanders
  vec2 cell = floor(p);

  /*
    Each cell knots on its own hashed phase, so the grid breathes instead
    of pulsing as one sheet. The rate is a constant, not a slider: this
    multiplies the integrated clock, and a slider there would jump the
    phase of every cell on a state change.
  */
  float breathe = 1.0 + uP_pulse * sin(TAU * hash(cell) + t * 0.35);

  vec2 c = p;
  for (int j = 0; j < OCTAVES; j++) {
    float i = float(j) + 1.0;

    /*
      The lattice pole, softened. d/(d*d+g) tracks 1/d away from the cell
      corner and rolls over to a finite peak at it, so the phase stays
      band-limited and the knot has a SIZE — uP_poleSoft is that size, and
      it is the difference between crisp cell knots and a corner full of
      aliased noise.
    */
    vec2 d = cell - c;
    vec2 pole = latticePole * breathe * d / (d * d + vec2(uP_poleSoft));

    c += uP_warp * cos(i * c.yx + pole + t) / i;
  }

  /*
    The listing's tone map: a thin ridge every PI with an exponential
    falloff. The per-channel offsets are kept as their original ratio
    (0, 2, 1) so one slider widens the whole split, and against a ridge
    this thin they separate the thread into three coloured filaments.
  */
  vec3 x = vec3(c.y) + vec3(0.0, 2.0, 1.0) * uP_split;
  vec3 thread = exp(-latticeSharp * abs(sin(x)));

  float lev = dot(thread, vec3(1.0 / 3.0));

  /*
    The exp() floor never reaches zero, so the shell is lit between the
    threads by construction — the body colour is added under it rather
    than filling a hole. The thread term stays PER-CHANNEL through the
    palette multiply; collapsing it to lev first would throw away the
    filament split, which is the only thing the vec4 phase was for.
  */
  vec3 col = uC_deep * uP_floor;
  /*
    The ramp between the two thread colours runs nearly the whole range of
    lev deliberately. Started at 0.35 it reached uC_hot — which is near-white in
    every state — across most of the visible web, and the state palettes,
    which ride on uC_line, never got to the eye at all.

    The other half of that fix is in the presets: the chromatic split makes
    the three channels independent, so at a wide split THREAD sets the hue
    and no palette can. It is staged with the rest — widest while
    searching, nearly closed while answering, which is when the warm
    palette has to carry.
  */
  col += thread * mix(uC_line, uC_hot, smoothstep(0.12, 1.0, lev)) * latticeGain;

  /*
    A tight second read of the same ridge, added on top: raising a value
    that is already exp(-k*|sin|) to a high power is the same ridge at a
    fraction of the width, which lands as a hot core inside each thread.
    Taken from lev — the MEAN of the three channels — deliberately, so the
    core is achromatic and lands only where all three filaments coincide.
    Read per-channel it would just be a fourth colour-separated ridge, and
    the web would stay a scatter of green and magenta flecks instead of
    resolving into white threads with coloured shoulders. Squared twice
    rather than pow() — pow is undefined for a negative base and this is
    cheaper anyway (see the README).
  */
  float core = lev * lev;
  core = core * core;
  col += uC_hot * core * uP_core;

  col = pow(max(col, vec3(0.0)), vec3(uP_contrast));

  // dome shading keeps the ball a ball under the web
  float lambert = clamp(dot(n, normalize(vec3(-0.45, 0.55, 0.72))), 0.0, 1.0);
  col *= 0.35 + uP_light * lambert;

  // fresnel sheen: the glaze the threads are crazed into, and the thing
  // that keeps the limb reading as a surface where the cells have
  // compressed past resolving
  float fres = 1.0 - z;
  fres = fres * fres * fres;
  col += uC_sheen * uP_rim * fres;

  return col;
}

void main() {
  // Volume coupling: the user's voice tightens the knots, the agent's
  // thickens the threads and brightens them.
  latticePole = uP_pole * (1.0 + 0.8 * uInput);
  latticeSharp = uP_sharp * (1.0 - 0.25 * uOutput);
  latticeGain = uP_gain * (0.85 + 0.4 * uOutput);

  vec2 uv = orbUV();
  float mask = smoothstep(0.012, -0.012, length(uv) - max(uP_radius, 0.001));

  // Nothing outside the silhouette is ever visible, so skip AA * AA warps
  // for it rather than shading transparent sky.
  if (mask <= 0.0) {
    gl_FragColor = vec4(0.0);
    return;
  }

  vec3 col = vec3(0.0);
#if AA > 1
  for (int mx = 0; mx < AA; mx++) {
    for (int my = 0; my < AA; my++) {
      vec2 off = (vec2(float(mx), float(my)) + 0.5) / float(AA) - 0.5;
      col += latticeRender(gl_FragCoord.xy + off);
    }
  }
  col /= float(AA * AA);
#else
  col = latticeRender(gl_FragCoord.xy);
#endif

  // Surface orb bounded by a mask: alpha IS coverage, so premultiply — the
  // opposite convention from the emissive orbs (see shdr-31).
  float a = mask;
  gl_FragColor = vec4(max(col, vec3(0.0)) * a, a);
}
`;

/*
  The look at rest, which thinking builds on: slow boil, threads fine —
  on a dome bulged nearly all the way and the warp pushed to more than
  double, so the field wraps hard around the ball. The knots are halved in
  strength and pinched to the smallest size, the hot core is run up five
  times and the body fill cut to a third: a dark ball with a fierce centre.
*/
const KNOT_REST = {
  speed: 0.42,
  swirl: 0.075,
  drift: 0.18,
  bulge: 3.28,
  warp: 2,
  pole: 0.13,
  poleSoft: 0.001,
  pulse: 0.35,
  split: 0.045,
  sharp: 4.5,
  core: 2.235,
  floor: 0.26,
  gain: 1,
  contrast: 1.35,
  light: 0.705
};

const KNOT_PALETTE = {
  deep: "#111a2e",
  line: "#3fd2ff",
  hot: "#fff4d6",
  sheen: "#a9d8ff"
};

export const shdr26Orb: OrbVariant = {
  key: "shdr-26",
  label: "SHDR-26",
  note: "a crazed web of coloured threads knotted to a cell grid",
  frag: LATTICE_FRAG,
  params: [
    { key: "speed", label: "Boil", min: 0.015, max: 10, step: 0.05, default: 0.4, integrate: true },
    { key: "swirl", label: "Swirl", min: 0, max: 3, step: 0.015, default: 0.07, integrate: true },
    { key: "drift", label: "Crazing drift", min: 0, max: 5, step: 0.03, default: 0.18, integrate: true },
    { key: "radius", label: "Radius", min: 0.15, max: 3, step: 0.015, default: 0.9 },
    { key: "scale", label: "Cell scale", min: 0.3, max: 20, step: 0.1, default: 9 },
    { key: "bulge", label: "Dome bulge", min: 0, max: 4, step: 0.02, default: 0.25 },
    { key: "warp", label: "Warp", min: 0, max: 3, step: 0.02, default: 0.85 },
    { key: "pole", label: "Knot strength", min: 0, max: 2, step: 0.005, default: 0.25 },
    { key: "poleSoft", label: "Knot size", min: 0.001, max: 1, step: 0.001, default: 0.012 },
    { key: "pulse", label: "Cell breathing", min: 0, max: 2, step: 0.01, default: 0.35 },
    { key: "sharp", label: "Thread width", min: 0.3, max: 20, step: 0.05, default: 4.5 },
    { key: "split", label: "Chromatic split", min: 0, max: 1, step: 0.005, default: 0.045 },
    { key: "core", label: "Hot core", min: 0, max: 3, step: 0.015, default: 0.45 },
    { key: "floor", label: "Body fill", min: 0, max: 3, step: 0.01, default: 0.9 },
    { key: "gain", label: "Brightness", min: 0.05, max: 5, step: 0.05, default: 1 },
    { key: "contrast", label: "Contrast", min: 0.15, max: 10, step: 0.05, default: 1.35 },
    { key: "light", label: "Key light", min: 0, max: 3, step: 0.015, default: 0.7 },
    { key: "rim", label: "Rim sheen", min: 0, max: 3, step: 0.015, default: 0.45 }
  ],
  /*
   * Four stops: the glaze the web is crazed into, the two ends of the thread
   * ramp, and the fresnel sheen. The chromatic split runs the threads apart
   * into three filaments on its own, so the palette only has to set the mood.
   */
  colors: [
    { key: "deep", label: "Glaze", default: "#111a2e" },
    { key: "line", label: "Thread", default: "#3fd2ff" },
    { key: "hot", label: "Hot thread", default: "#fff4d6" },
    { key: "sheen", label: "Sheen", default: "#a9d8ff" }
  ],
  /*
    Staged on the pole, which is this orb's loudest control: knot strength
    decides whether the field flows past the lattice or tears itself around
    it. Thread width is the second lever, and the two integrated clocks —
    boil and roll — carry the tempo.
  */
  statePresets: {
    /*
      at rest: slow boil, threads fine — on a dome bulged nearly all the
      way and the warp pushed to more than double, so the field wraps hard
      around the ball. The knots are halved in strength and pinched to the
      smallest size, the hot core is run up five times and the body fill
      cut to a third: a dark ball with a fierce centre.
    */
    idle: KNOT_REST,
    /*
      searching: the rest look, set MOVING. The boil runs at nearly two and
      a half times idle, the swirl four times and the drift three, so the
      web migrates over the glaze instead of sitting on it. The knots come
      up a third but breathe less, the threads soften a touch on a tighter
      split, and the contrast is pushed — busier, but no brighter.
    */
    thinking: {
      ...KNOT_REST,
      speed: 1,
      swirl: 0.3,
      drift: 0.51,
      pole: 0.18,
      pulse: 0.22,
      sharp: 3.6,
      split: 0.03,
      floor: 0.28,
      contrast: 1.6
    },
    /*
      answering: the web goes FAST and FLOODS. The boil runs at six times
      thinking and the drift more than three, the knots breathe at their
      deepest, and the threads spread to their softest, so the ridges bloom
      into broad light. The dome is flattened back toward the default and
      the warp relaxed to a third of rest, with the cell scale nudged up;
      the core is halved from rest, but the fill, gain and contrast all
      come up — the brightest, busiest state.
    */
    speaking: {
      speed: 6.45,
      swirl: 0.555,
      drift: 1.74,
      scale: 11,
      bulge: 0.78,
      warp: 0.76,
      pole: 0.195,
      poleSoft: 0.001,
      pulse: 1.39,
      sharp: 2.1,
      split: 0.045,
      core: 1.08,
      floor: 0.48,
      gain: 1.3,
      contrast: 1.95
    }
  },
  // one palette, cold cyan porcelain, across all three states — unlike the
  // sibling orbs, this one tells its states apart by the knots and the
  // tempo alone, not by colour
  stateColors: {
    idle: KNOT_PALETTE,
    thinking: KNOT_PALETTE,
    speaking: KNOT_PALETTE
  }
};

export type Shdr26Props = Omit<ShaderOrbProps, "variant">;

export function Shdr26({ size = 280, ...rest }: Shdr26Props) {
  return <ShaderOrb variant={shdr26Orb} size={size} {...rest} />;
}

export default Shdr26;

29. components/ui/shdr-27.tsx

/*
 * Deliberately not a `"use client"` module — see the note in `shdr-11.tsx`.
 *
 * Note for editors: the shader lives in a template literal, so its comments
 * must not contain backticks.
 */
import { ShaderOrb, type OrbVariant, type ShaderOrbProps } from "@/components/ui/orbkit-core";

/* ----------------------------------------------------------------------------
   SHDR-27 — a weather-radar mosaic: fronts of colour-classed pixels sweeping
   across the ball.

   The reference is a precipitation map drawn as a coarse grid of square
   dots on cream paper: each dot is one of a handful of flat colours — grey
   where there is barely anything, then blue, cyan, green, red, yellow, a
   rare magenta at the peaks — and the dots thin out to nothing in the
   quiet areas, so the fronts have speckled edges and the calm between
   them is mostly paper. The fronts are long and diagonal, streaked along
   their direction of travel.

   - THE FIELD. The sphere is twisted by a handful of vortices — hashed
     points on it, each rotating the space about the axis through it by a
     Gaussian-falloff angle, alternating in sense — and two 3D fbm scales
     are then read through that twist and MULTIPLIED: a broad
     one decides where the storm systems are, a finer one gives each a
     core and ragged edges, so the calm between systems is empty and the
     peaks sit inside the cells, which is what draws the concentric class
     rings. The noise drifts through the twist on the integrated clock, so
     every system winds into a spiral around the nearest centre and keeps
     swirling as new noise slides in. A threshold window and a response
     curve cut the intensity out of it.
   - THE MOSAIC. The wrapped plane is cut into a grid and the field is
     read ONCE per cell, at the cell's centre, so every dot is one flat
     colour — no gradient ever crosses a dot, which is the whole look. The
     intensity is quantized into seven classes, with a per-cell hash added
     first so the class boundaries dither into speckle rather than draw a
     contour.
   - DROPOUT. Each cell hashes against a density that rises with the
     intensity: the quiet areas keep only a sparse scatter of grey dots and
     the fronts fill in solid. The hash re-rolls on the ambient clock at a
     slow rate, so the scatter twinkles without the fronts moving.
   - THE DOT. A square, inset in its cell so the paper shows as a lattice
     between dots.

   The grid lives on a stereographic wrap of the dome as it faces the
   viewer, so the mosaic compresses gently toward the limb — the pixels are
   ON the sphere — while the field is evaluated in 3D on the sphere's own
   points (3D value noise, vortices as rotations about points on the
   sphere), so the roll only rotates the picture under the pixels and no
   projection ever distorts it. Surface-lit and
   mask-bounded, so alpha IS coverage — premultiplied output, as in
   shdr-14.
---------------------------------------------------------------------------- */

const RADAR_FRAG = `
#define VORTICES 6
#define FBM3_OCT 4

// Volume-reactive values, resolved once per fragment in main().
float radarLoNow;
float radarDensityNow;

/*
  3D value noise. The prelude's noise is 2D, and a 2D field wrapped onto
  the ball has to be projected — and every projection either distorts
  somewhere or seams somewhere, which is exactly what the roll dragged
  into view. Evaluating the field ON the sphere's own points needs
  nothing projected: the roll is just a rotation of the sample point.
*/
float hash3(vec3 p) {
  return fract(sin(dot(p, vec3(127.1, 311.7, 74.7))) * 43758.5453123);
}
float noise3(vec3 p) {
  vec3 i = floor(p);
  vec3 f = fract(p);
  f = f * f * (3.0 - 2.0 * f);
  float n000 = hash3(i);
  float n100 = hash3(i + vec3(1.0, 0.0, 0.0));
  float n010 = hash3(i + vec3(0.0, 1.0, 0.0));
  float n110 = hash3(i + vec3(1.0, 1.0, 0.0));
  float n001 = hash3(i + vec3(0.0, 0.0, 1.0));
  float n101 = hash3(i + vec3(1.0, 0.0, 1.0));
  float n011 = hash3(i + vec3(0.0, 1.0, 1.0));
  float n111 = hash3(i + vec3(1.0, 1.0, 1.0));
  return mix(
    mix(mix(n000, n100, f.x), mix(n010, n110, f.x), f.y),
    mix(mix(n001, n101, f.x), mix(n011, n111, f.x), f.y),
    f.z
  );
}
float fbm3(vec3 p) {
  float v = 0.0;
  float a = 0.5;
  for (int i = 0; i < FBM3_OCT; i++) {
    v += a * noise3(p);
    p = p * 2.03 + vec3(11.7, 7.3, 3.1);
    a *= 0.5;
  }
  return v;
}

// rotate v about the unit axis k by angle a (Rodrigues)
vec3 rotateAbout(vec3 v, vec3 k, float a) {
  float c = cos(a);
  float s = sin(a);
  return v * c + cross(k, v) * s + k * dot(k, v) * (1.0 - c);
}

// the precipitation intensity, 0..1, at a point of the unit sphere
float intensity(vec3 sp, float t) {
  vec3 q = sp;
  /*
    The vortices: hashed points on the sphere, each twisting the space
    around itself — a rotation about the axis through it, by an angle
    that falls off with the angular distance, alternating in sense. The
    centres wander slowly so no spiral sits still.
  */
  for (int k = 0; k < VORTICES; k++) {
    float fk = float(k);
    vec3 c = normalize(vec3(
      hash(vec2(fk * 3.7, 1.1)) - 0.5,
      hash(vec2(fk * 5.9, 2.3)) - 0.5,
      hash(vec2(fk * 7.1, 4.9)) - 0.5
    ));
    c = rotateAbout(c, vec3(0.0, 1.0, 0.0), sin(t * 0.09 + fk * 1.7) * 0.25);
    float ang = acos(clamp(dot(q, c), -1.0, 1.0));
    float fall = exp(-ang * ang / (uP_vortex * uP_vortex));
    float a = uP_swirl * fall * (mod(fk, 2.0) < 0.5 ? 1.0 : -1.0);
    q = rotateAbout(q, c, a);
  }

  // bend, then drift the noise through the twisted space
  vec3 w = vec3(noise3(q * 1.3 + 2.1), noise3(q * 1.3 + 7.3), noise3(q * 1.3 + 4.4)) - 0.5;
  q += w * uP_warp;
  vec3 pq = q * uP_freq + vec3(t * 0.22, -t * 0.13, t * 0.07);

  /*
    Two scales multiplied, not added: a broad mask decides WHERE the storms
    are, a finer field gives each one a core and ragged edges. Adding them
    fills the whole sphere with mid-tones; multiplying leaves the calm
    between systems genuinely empty and puts the peaks inside the cells,
    which is what draws the concentric class rings.
  */
  float big = clamp((fbm3(pq) - 0.5) * 3.0 + 0.5, 0.0, 1.0);
  float fine = clamp((fbm3(pq * 2.6 + 4.7) - 0.5) * 2.4 + 0.5, 0.0, 1.0);
  float f = big * (0.55 + 0.45 * fine);

  f = clamp((f - radarLoNow) / max(uP_hi - radarLoNow, 0.01), 0.0, 1.0);
  // a response curve: the top classes are the rare peaks of a real map
  return pow(f, uP_curve);
}

void main() {
  // input lowers the window (more of the field reads as weather), output
  // fills the dots in
  radarLoNow = uP_lo - 0.08 * uInput;
  radarDensityNow = uP_density * (1.0 + 0.5 * uOutput);

  vec2 uv = orbUV();
  float rd = length(uv);
  float R = uP_radius;
  float mask = smoothstep(0.012, -0.012, rd - R);

  if (mask <= 0.0) {
    gl_FragColor = vec4(0.0);
    return;
  }

  vec2 pl = uv / R;
  float r2 = dot(pl, pl);
  float z = sqrt(max(1.0 - r2, 0.0));
  vec3 n = vec3(pl, z);

  float t = uP_speed; // integrated clock: the weather drifts

  /*
    The pixel grid lives on the UNROLLED dome: a stereographic wrap of the
    front hemisphere as it faces the viewer, which compresses gently toward
    the limb and never changes. The picture rolls underneath it — the
    pixels are the screen, the weather is what is on it. Laying the grid on
    the rolled dome instead put the projection's blow-up (the far side of
    the ball) wherever the roll had turned it, and the cells smeared into
    streaks there.
  */
  vec2 st = n.xy / (1.3 + n.z) * uP_scale;
  vec2 g = st * uP_cells;
  vec2 cell = floor(g);
  vec2 fr = fract(g) - 0.5;

  // the cell centre, back on the dome: invert the wrap, then roll it and
  // read the field there — once per cell, so every dot is one flat colour
  vec2 v = (cell + 0.5) / uP_cells / uP_scale;
  float vv = dot(v, v);
  float A = vv + 1.0;
  float B = 2.6 * vv;
  float C = 1.69 * vv - 1.0;
  float zc = (-B + sqrt(max(B * B - 4.0 * A * C, 0.0))) / (2.0 * A);
  vec3 nc = vec3(v * (1.3 + zc), zc);
  float cr = cos(uP_spin);
  float sr = sin(uP_spin);
  vec3 spc = vec3(nc.x * cr - nc.z * sr, nc.y, nc.x * sr + nc.z * cr);

  float f = intensity(spc, t);

  // dither the class boundaries with a per-cell hash, then quantize into
  // the legend's seven classes. The bands are NOT even: red is broad and
  // green thin, as on the reference, and magenta is the rare peak.
  float h = hash(cell + 11.7);
  float fd = clamp(f + (h - 0.5) * uP_dither, 0.0, 1.0);
  float cls = 0.0;
  cls += step(0.10, fd);
  cls += step(0.26, fd);
  cls += step(0.38, fd);
  cls += step(0.46, fd);
  cls += step(0.78, fd);
  cls += step(0.94, fd);

  // dropout: density rises with the intensity; the hash re-rolls slowly
  float frame = floor(uTime * uP_twinkle);
  float roll = hash(cell + vec2(frame * 3.7, -frame * 1.3));
  float density = mix(uP_sparse, 1.0, smoothstep(0.0, 0.6, f)) * radarDensityNow;
  float keep = step(roll, density);

  // the dot: a square inset in its cell
  float dsq = max(abs(fr.x), abs(fr.y));
  float dotMask = 1.0 - smoothstep(uP_dot - 0.06, uP_dot + 0.06, dsq);

  // the class palette
  vec3 ink = uC_c0;
  ink = cls > 0.5 && cls < 1.5 ? uC_c1 : ink;
  ink = cls > 1.5 && cls < 2.5 ? uC_c2 : ink;
  ink = cls > 2.5 && cls < 3.5 ? uC_c3 : ink;
  ink = cls > 3.5 && cls < 4.5 ? uC_c4 : ink;
  ink = cls > 4.5 && cls < 5.5 ? uC_c5 : ink;
  ink = cls > 5.5 ? uC_c6 : ink;

  vec3 col = mix(uC_paper, ink, dotMask * keep);

  // paper grain, so the flats are not dead
  col *= 1.0 + (hash(floor(gl_FragCoord.xy / 2.0) + frame) - 0.5) * uP_grain;

  // dome shading keeps the ball a ball under the mosaic
  float lambert = clamp(dot(n, normalize(vec3(-0.45, 0.55, 0.7))), 0.0, 1.0);
  col *= 1.0 - uP_light * (1.0 - lambert);
  float fres = pow(1.0 - z, 3.0);
  col = mix(col, uC_c0, fres * uP_rim);

  // Surface orb bounded by a mask: alpha IS coverage, so premultiply — the
  // opposite convention from the emissive orbs (see shdr-31).
  float a = mask;
  gl_FragColor = vec4(max(col, vec3(0.0)) * a, a);
}
`;

export const shdr27Orb: OrbVariant = {
  key: "shdr-27",
  label: "SHDR-27",
  note: "a weather-radar mosaic, fronts of coloured pixels sweeping the ball",
  frag: RADAR_FRAG,
  params: [
    { key: "speed", label: "Front speed", min: 0.015, max: 10, step: 0.05, default: 0.6, integrate: true },
    { key: "spin", label: "Roll", min: 0, max: 5, step: 0.03, default: 0.04, integrate: true },
    { key: "radius", label: "Radius", min: 0.15, max: 3, step: 0.015, default: 0.9 },
    { key: "scale", label: "Grid zoom", min: 0.3, max: 8, step: 0.05, default: 2.4 },
    { key: "cells", label: "Grid", min: 8, max: 120, step: 1, default: 34 },
    { key: "dot", label: "Dot size", min: 0.1, max: 0.5, step: 0.01, default: 0.36 },
    { key: "swirl", label: "Swirl", min: 0, max: 8, step: 0.05, default: 1.5 },
    { key: "vortex", label: "Vortex size", min: 0.1, max: 2, step: 0.01, default: 0.45 },
    { key: "freq", label: "Storm scale", min: 0.2, max: 8, step: 0.05, default: 1.6 },
    { key: "warp", label: "Bend", min: 0, max: 3, step: 0.02, default: 0.5 },
    { key: "lo", label: "Quiet threshold", min: 0, max: 1, step: 0.005, default: 0.12 },
    { key: "hi", label: "Peak threshold", min: 0, max: 1, step: 0.005, default: 0.82 },
    { key: "curve", label: "Response curve", min: 0.5, max: 4, step: 0.05, default: 1.4 },
    { key: "dither", label: "Class dither", min: 0, max: 0.6, step: 0.005, default: 0.12 },
    { key: "sparse", label: "Quiet density", min: 0, max: 1, step: 0.01, default: 0.16 },
    { key: "density", label: "Fill", min: 0, max: 1.5, step: 0.01, default: 1 },
    { key: "twinkle", label: "Twinkle rate", min: 0, max: 30, step: 0.5, default: 3 },
    { key: "grain", label: "Paper grain", min: 0, max: 1, step: 0.01, default: 0.1 },
    { key: "light", label: "Key light", min: 0, max: 1, step: 0.01, default: 0.18 },
    { key: "rim", label: "Rim", min: 0, max: 1, step: 0.01, default: 0.35 }
  ],
  /*
   * The paper and the seven classes, quiet to peak: grey, blue, cyan,
   * green, red, yellow, magenta — the radar legend of the reference.
   */
  colors: [
    { key: "paper", label: "Paper", default: "#efe9dc" },
    { key: "c0", label: "Quiet", default: "#a9a9a6" },
    { key: "c1", label: "Class 1", default: "#2e5df0" },
    { key: "c2", label: "Class 2", default: "#38d9ec" },
    { key: "c3", label: "Class 3", default: "#22c35c" },
    { key: "c4", label: "Class 4", default: "#e8322a" },
    { key: "c5", label: "Class 5", default: "#f5d020" },
    { key: "c6", label: "Peak", default: "#e030c0" }
  ],
  /*
    Staged on the drift, the swirl, the window and the fill. The grid, the
    zoom and the storm scale all multiply a coordinate or sit inside a
    floor, so they never move between states. The swirl is an angle,
    bounded, and glides safely.
  */
  statePresets: {
    // at rest: fronts drifting, the ball rolling at a steady turn, the
    // quiet areas sparse, a slow twinkle
    idle: {
      speed: 1,
      spin: 0.27,
      lo: 0.12,
      hi: 0.82,
      curve: 1.4,
      sparse: 0.16,
      density: 1,
      twinkle: 3,
      warp: 0.5,
      swirl: 1.5,
      dither: 0.12
    },
    /*
      searching: the storms go FINE and the swirl hard — the storm scale at
      three times rest, the vortices twisting at nearly three times the
      rest angle on a doubled bend, the roll doubled — with the window
      thrown open (quiet threshold at zero, peak at half), so the whole
      ball is small, tightly wound systems. The storm scale multiplies a
      coordinate, so the glide into and out of thinking passes through a
      rescale — chosen deliberately.
    */
    thinking: {
      speed: 2.4,
      spin: 0.51,
      lo: 0,
      hi: 0.545,
      curve: 1.7,
      sparse: 0.22,
      density: 0.9,
      twinkle: 12,
      freq: 5.2,
      warp: 1.06,
      swirl: 4,
      dither: 0.18
    },
    /*
      answering: the weather FILLS IN and RACES. The quiet threshold drops
      to zero so every cell reads as weather, the fronts widen into red and
      yellow with magenta peaks, the drift runs at six times rest on the
      thinking roll, and the paper grain comes up.
    */
    speaking: {
      speed: 6.5,
      spin: 0.51,
      lo: 0,
      hi: 0.8,
      curve: 1.3,
      sparse: 0.22,
      density: 1.15,
      twinkle: 5,
      warp: 0.45,
      swirl: 2,
      grain: 0.35,
      dither: 0.1
    }
  },
  // the legend holds; the paper cools while searching and warms while
  // answering
  stateColors: {
    idle: { paper: "#efe9dc" },
    thinking: { paper: "#e6e9ee" },
    speaking: { paper: "#f5e6d0" }
  }
};

export type Shdr27Props = Omit<ShaderOrbProps, "variant">;

export function Shdr27({ size = 280, ...rest }: Shdr27Props) {
  return <ShaderOrb variant={shdr27Orb} size={size} {...rest} />;
}

export default Shdr27;

30. components/ui/shdr-28.tsx

/*
 * Deliberately not a `"use client"` module — see the note in `shdr-11.tsx`.
 *
 * Note for editors: the shader lives in a template literal, so its comments
 * must not contain backticks.
 */
import { ShaderOrb, type OrbVariant, type ShaderOrbProps } from "@/components/ui/orbkit-core";

/* ----------------------------------------------------------------------------
   SHDR-28 — a tumbling bit-sphere: nested binary grids shuttering on a ball.

   Ported from a golfed twigl listing:

     vec2 p=(round(FC.xy)-.5*r)/r.y,v;
     for(float i;i++<20.;o+=vec4(fwidth(v=ceil(p)).xyy,
       fract(length(v)/i-t*.2))*(1.-o.a))p+=p;

   What it actually is, decoded:

   - p += p is the whole engine: a BINARY ZOOM. Twenty doublings give
     twenty nested power-of-two grids over the same pixel.
   - fwidth(v = ceil(p)) is zero inside a cell and spikes exactly where the
     cell index jumps — it draws the grid lines of every level. The .xyy
     swizzle splits them: x-boundaries in red, y-boundaries in green+blue.
   - The alpha channel accumulates fract(length(v)/i - t*.2) — an animated
     value per CELL — and the *(1.-o.a) factor is front-to-back UNDER
     compositing: each level's cells shutter the levels beneath. That
     occlusion cascade is the whole bit-plane flicker.
   - Deep levels alias (a cell per pixel) into solid planes; the shutter is
     what keeps them from whiting out.

   Port decisions:

   - THE ORB IS THE OBJECT — the standing design rule (see README). A flat
     binary lattice masked to a disc reads as a coin. Instead the lattice is
     wrapped ON the ball: dome point, a real 3D tilt + spin rotation (the
     spin on its own integrated clock), then a stereographic projection so
     the grids curve and compress around the sphere. A shaded body and a
     fresnel rim make it a solid, tumbling bit-sphere.
   - fwidth() needs the OES_standard_derivatives EXTENSION in WebGL 1, and
     the #extension directive cannot legally follow the prelude's
     declarations. It is not needed: the zoom chain is exact, so the pixel
     footprint at level i is analytic — px0 * 2^i — and the same edge test
     falls out of fract() against it, with line width as a free parameter.
   - round() is ES 3.0 (the original only pixel-snaps with it) and o, v, i
     rely on twigl's zero-init — uninitialised locals are UNDEFINED in
     GLSL ES 1.0, so everything is explicit here.
   - The clock enters only as an additive phase inside fract(), so the
     unbounded integrated clock stays safe, as everywhere in this repo.
   - Surface-lit and mask-bounded, so alpha IS coverage — premultiplied
     output, the opposite convention from the emissive orbs (see README).
---------------------------------------------------------------------------- */

/*
 * The level cap is a `#define`: ES 1.0 requires constant loop bounds.
 * uP_levels breaks out early below it.
 */
const BITDUMB_FRAG = `
#define LEVELS 20

// Volume-reactive values, resolved once per fragment in main().
float bitdumbGain;
float bitdumbBody;

mat2 bdRot(float a) {
  float c = cos(a);
  float s = sin(a);
  return mat2(c, -s, s, c);
}

void main() {
  bitdumbGain = uP_gain * (1.0 + 0.6 * uInput);
  bitdumbBody = uP_body * (1.0 + 0.8 * uOutput);

  vec2 uv = orbUV() / uP_radius;
  float r2 = dot(uv, uv);

  // analytic disc silhouette — this orb is parameterised on the dome, so
  // the exact edge is just the unit circle, with the same tunable band as
  // the raymarched orbs
  float band = mix(0.35, 0.012, clamp(uP_edge, 0.0, 1.0));
  float mask = 1.0 - smoothstep(1.0 - band, 1.005, length(uv));

  // front dome point and its normal (view space)
  float zc = sqrt(max(1.0 - r2, 0.0));
  vec3 n = vec3(uv, zc);

  // tumble the sphere point with real rotations, then project. abs() on z
  // mirror-wraps the hemisphere the tumble turns away, avoiding the
  // stereographic pole blow-up.
  vec3 sp = n;
  sp.yz = bdRot(uP_tilt) * sp.yz;
  sp.xz = bdRot(uP_spin) * sp.xz; // integrated clock
  vec2 p = sp.xy / (abs(sp.z) + 1.0) * uP_gridScale;

  /*
    Analytic pixel footprint in grid space, in place of fwidth(): one
    screen pixel in uv units, through the radius scale, the dome stretch
    (grids compress toward the rim, so a pixel covers more of them there),
    and the grid scale. Doubled alongside p every level.
  */
  float px = (2.0 / min(uRes.x, uRes.y)) / uP_radius / max(zc, 0.2) * uP_gridScale;

  vec4 acc = vec4(0.0);
  float phase = uP_speed * 0.2; // integrated clock, additive phase

  for (int i = 0; i < LEVELS; i++) {
    float fi = float(i) + 1.0;
    if (fi > uP_levels) break;

    // the listing's engine, kept verbatim: binary zoom
    p += p;
    px += px;

    vec2 v = ceil(p);
    vec2 f = fract(p);

    // distance to the nearest cell line, against this level's footprint —
    // the extension-free fwidth. Deep levels saturate to solid planes,
    // exactly like the original's aliasing.
    vec2 e2 = 1.0 - smoothstep(vec2(0.0), vec2(px * uP_lineW), min(f, 1.0 - f));

    // x-lines and y-lines separately tintable — the original's .xyy
    vec3 edgeCol = uC_lineA * e2.x + uC_lineB * e2.y;

    // the per-cell shutter value, and the under-compositing that makes
    // level i occlude level i+1 — both straight from the listing
    float aBit = fract(length(v) / fi - phase) * uP_shutter;
    acc += vec4(edgeCol, aBit) * (1.0 - acc.a);

    if (acc.a > 0.996) break;
  }

  vec3 col = acc.rgb * bitdumbGain;

  // the ball body: a lambert-shaded base under the lattice, so the orb
  // reads as a solid object rather than lines floating on nothing
  vec3 L = normalize(vec3(-0.4, 0.5, 0.75));
  float shade = 0.25 + 0.75 * clamp(dot(n, L), 0.0, 1.0);
  col += uC_base * shade * bitdumbBody;

  // fresnel rim to sell the sphere
  col += uC_rim * pow(1.0 - zc, uP_rimPow) * uP_rim;

  col = pow(max(col, 0.0), vec3(uP_contrast));

  // coverage alpha; safety taper fades colour AND alpha, as always
  float fade = 1.0 - smoothstep(uP_edgeFade, 1.0, length(orbUV()));
  float a = mask * fade;

  // Surface-lit orb bounded by a mask: alpha IS coverage, so premultiply —
  // the opposite of the emissive orbs (see the note in shdr-31).
  gl_FragColor = vec4(col * a, a);
}
`;

export const shdr28Orb: OrbVariant = {
  key: "shdr-28",
  label: "SHDR-28",
  note: "nested binary grids shuttering on a tumbling bit-sphere",
  frag: BITDUMB_FRAG,
  params: [
    { key: "speed", label: "Shutter drift", min: 0.015, max: 10, step: 0.05, default: 0.5, integrate: true },
    { key: "spin", label: "Tumble rate", min: 0, max: 5, step: 0.03, default: 0.15, integrate: true },
    { key: "tilt", label: "Tumble tilt", min: 0, max: 4, step: 0.02, default: 0.5 },
    { key: "radius", label: "Radius", min: 0.1, max: 3, step: 0.015, default: 0.9 },
    { key: "gridScale", label: "Grid scale", min: 0.5, max: 20, step: 0.1, default: 2 },
    // 12 levels / wider lines: past ~12 the deep grids alias into solid white
    // planes that swallow the line palette — the state staging below depends
    // on the coarse lines and body actually carrying their colours
    { key: "levels", label: "Bit depth", min: 4, max: 20, step: 1, default: 12 },
    { key: "lineW", label: "Line width", min: 0.5, max: 15, step: 0.1, default: 2 },
    { key: "shutter", label: "Shutter", min: 0, max: 4, step: 0.02, default: 1 },
    { key: "gain", label: "Line gain", min: 0.05, max: 10, step: 0.05, default: 1 },
    { key: "body", label: "Body glow", min: 0, max: 5, step: 0.03, default: 1 },
    { key: "rim", label: "Rim light", min: 0, max: 5, step: 0.03, default: 0.6 },
    { key: "rimPow", label: "Rim tightness", min: 0.3, max: 20, step: 0.1, default: 3 },
    { key: "contrast", label: "Contrast", min: 0.15, max: 10, step: 0.05, default: 1 },
    { key: "edge", label: "Edge sharpness", min: 0, max: 1, step: 0.01, default: 1 },
    { key: "edgeFade", label: "Halo falloff", min: 0.1, max: 3, step: 0.015, default: 0.98 }
  ],
  colors: [
    { key: "lineA", label: "X lines", default: "#ff5a4d" },
    { key: "lineB", label: "Y lines", default: "#59d8ff" },
    { key: "base", label: "Body", default: "#101528" },
    { key: "rim", label: "Rim", default: "#bcd8ff" }
  ],
  /*
    The states are staged on the two integrated clocks: thinking runs the
    shutter cascade hot AND sets the sphere tumbling — the bits computing
    furiously while the orb turns them over — and speaking tumbles harder
    still while the flicker stays moderate: the orb turning to answer. Both
    clocks integrate, so every rate change glides without a phase jump.
  */
  statePresets: {
    /*
      calm: a steady flicker at double the old rate, lazy tumble. Two bits
      shallower and the lines a quarter wider, so the grid reads bolder
      and coarser; the body glow eased down and the rim pulled tight —
      red and white lines on black, no halo.
    */
    idle: {
      speed: 1,
      spin: 0.1,
      levels: 10,
      lineW: 2.5,
      shutter: 1.02,
      gain: 1,
      body: 0.9,
      rim: 0.6,
      rimPow: 5.2,
      contrast: 1.2
    },
    // computing: the shutter cascade races (2.4x idle) and the tumble goes
    // with it, eight times idle, on wider lines and a lifted gain; the body
    // dims so the flickering cells carry the light
    thinking: {
      speed: 2.4,
      spin: 0.81,
      lineW: 2.9,
      shutter: 0.94,
      gain: 1.2,
      body: 0.85,
      rim: 0.7
    },
    /*
      answering: hard fast tumble on a grid five times finer and seven bits
      deeper, so the sphere goes dense with cells; the body is all but cut
      and the rim brought up hard and pulled tight, so the light sits on
      the limb and the circuitry, not the ball.

      gain stays LOW on purpose: the shader multiplies it by (1 + 0.6 *
      input volume), and speaking synthesizes input around 0.65 — a 1.35
      preset lands near x1.9 effective, which clamps the lines to white
      and reads as a pale wash. 0.95 keeps the effective gain near 1.3,
      where the red survives.
    */
    speaking: {
      speed: 3,
      spin: 1.1,
      gridScale: 10.3,
      levels: 17,
      shutter: 1.2,
      gain: 0.95,
      body: 0.27,
      rim: 1.53,
      rimPow: 10,
      contrast: 1.45
    }
  },
  /*
    Four stageable colours, and one palette across all three states: red
    and white circuitry on pure black. The states are told apart by the
    tumble, the grid and the line weight, not the colour.
  */
  stateColors: {
    idle: { lineA: "#ff1100", lineB: "#ffffff", base: "#000000", rim: "#000000" },
    thinking: { lineA: "#ff0000", lineB: "#ffffff", base: "#000000", rim: "#000000" },
    speaking: { lineA: "#ff0000", lineB: "#ffffff", base: "#000000", rim: "#000000" }
  }
};

export type Shdr28Props = Omit<ShaderOrbProps, "variant">;

export function Shdr28({ size = 280, ...rest }: Shdr28Props) {
  return <ShaderOrb variant={shdr28Orb} size={size} {...rest} />;
}

export default Shdr28;

31. components/ui/shdr-29.tsx

/*
 * Deliberately not a `"use client"` module — see the note in `shdr-11.tsx`.
 *
 * Note for editors: the shader lives in a template literal, so its comments
 * must not contain backticks.
 */
import { ShaderOrb, type OrbVariant, type ShaderOrbProps } from "@/components/ui/orbkit-core";

/* ----------------------------------------------------------------------------
   SHDR-29 — an LED tile wall lighting up in flowing blobs, wrapped on the
   ball, with confetti tiles scattered through the bright regions.

   Every screen cell is one physical TILE: a bright bevelled face inside a
   visible frame, and even an unlit tile keeps a faint dark presence — the
   wall itself never disappears, which is what makes the lit blobs read as
   light ON hardware rather than paint. A drifting fbm field, sampled
   through the stereographic wrap of a rotating dome, decides which tiles
   light and how hard; most lit tiles burn warm white, but a per-tile hash
   promotes a scattering of them to fully saturated confetti hues that
   reshuffle on their own clock.

   Construction notes:

   - The tile grid is RESOLUTION-RELATIVE (uP_cells across the canvas) —
     the lesson shdr-14 learned — so a gallery card and the playground
     show the same wall.
   - The FIELD samples once per tile (cell centre): one tile, one light
     level. The tile geometry renders per fragment so bevels stay crisp.
   - Confetti hues come from cos palettes over per-tile hashes; the
     promotion cycles with an integrated shuffle clock, so which tiles are
     coloured slowly reshuffles — and races while the orb thinks.
   - Each state ANIMATES differently on its own integrated clock (drift /
     shuffle / pulse), phase-safe as everywhere in this repo.
   - Surface-lit and mask-bounded, so alpha IS coverage — premultiplied
     output, as in shdr-28.
---------------------------------------------------------------------------- */

const MOSAIC_FRAG = `
void main() {
  // Volume coupling: user input widens the lit coverage, agent output turns
  // the panel brightness up.
  float coverNow = uP_coverage + 0.07 * uInput;
  float gainNow = uP_gain * (0.85 + 0.5 * uOutput);

  // resolution-relative tile grid — same wall at every size
  float cellPx = max(min(uRes.x, uRes.y) / max(uP_cells, 8.0), 4.0);
  vec2 cellIdx = floor(gl_FragCoord.xy / cellPx);
  vec2 cellCentre = (cellIdx + 0.5) * cellPx;
  vec2 g = fract(gl_FragCoord.xy / cellPx); // 0..1 inside the tile

  vec2 suv = (2.0 * cellCentre - uRes) / min(uRes.x, uRes.y);
  vec2 uv = suv / uP_radius;
  float r2 = dot(uv, uv);

  // blocky silhouette, cut on the tile grid like the wall itself
  float mask = 1.0 - step(1.0, r2);

  float z = sqrt(max(1.0 - r2, 0.0));
  vec3 n = vec3(uv, z);

  // rotating dome, stereographic projection — the blobs roll around the
  // ball as the dome turns
  float rot = uP_spin; // integrated clock
  float cr = cos(rot);
  float sr = sin(rot);
  vec3 sp = vec3(n.x * cr - n.z * sr, n.y, n.x * sr + n.z * cr);
  vec2 p2 = sp.xy / (abs(sp.z) + 1.2) * uP_scale * 3.0;

  /*
    Per-state motion, each on its own integrated clock:
      DRIFT    the blob field streams across the wall     (idle flows)
      CHURN    the fluid warp evolves in place            (thinking boils)
      SHUFFLE  the confetti promotion cycles              (thinking races it)
      PULSE    rings radiate from the centre              (speaking)
    Rates glide; a rate at zero freezes that motion with its phase intact.
    The pulse depth is an amplitude, so idle carries no static rings.
  */
  float driftT = uP_drift;     // integrated clock: blob stream
  float churnT = uP_churn;     // integrated clock: warp evolution
  float shuffleT = uP_shuffle; // integrated clock: confetti reshuffle
  vec2 f1 = vec2(driftT * 0.5, -driftT * 0.35);
  vec2 f2 = vec2(-churnT * 0.4, churnT * 0.6);

  /*
    FLUID domain warp: two decorrelated fbm channels displace the sample
    point before the blob field reads it, and the displacement itself
    evolves on the churn clock. The blobs curl, stretch and merge like
    liquid instead of sliding across the wall as one rigid sheet.
  */
  vec2 warp = vec2(
    fbm(p2 * 0.9 + f2),
    fbm(p2 * 0.9 + f2.yx + 13.7)
  ) - 0.5;
  float field = fbm(p2 + f1 + warp * uP_swirl * 2.4);

  float lambert = clamp(dot(n, normalize(vec3(-0.45, 0.55, 0.7))), 0.0, 1.0);
  float lum = smoothstep(1.0 - coverNow, 1.14 - coverNow, field
    + 0.25 * uP_light * lambert
    + uP_pulse * 0.3 * sin(length(uv) * 5.0 - driftT * 3.2));
  lum *= gainNow;

  /*
    The tile: a bevelled square face inside a frame. The face is the lit
    part; the frame stays dark; an unlit tile keeps a faint presence so the
    wall reads as hardware even where nothing is lit.
  */
  vec2 d2 = abs(g - 0.5);
  float d = max(d2.x, d2.y);
  float face = 1.0 - smoothstep(0.26, 0.36, d);
  float tile = 1.0 - smoothstep(0.42, 0.48, d);
  // a soft centre hot-spot on the face, like an LED under a diffuser
  float hot = 1.0 - smoothstep(0.0, 0.34, length(d2));

  /*
    Confetti: a per-tile hash cycles against the shuffle clock, and the top
    uP_confetti slice of the cycle is promoted from warm white to a fully
    saturated hue drawn from a second hash. Which tiles are coloured
    therefore reshuffles continuously — slowly at rest, fast in thought.
  */
  float h1 = hash(cellIdx * 1.618 + 7.3);
  float h2 = hash(cellIdx * 2.113 + 41.7);
  float cyc = fract(h1 + shuffleT * 0.06);
  float promoted = step(1.0 - uP_confetti, cyc);
  vec3 confetti = 0.5 + 0.5 * cos(6.2831 * (h2 + vec3(0.0, 0.33, 0.67)));
  confetti = normalize(confetti + 0.05) * 1.2;
  vec3 litCol = mix(uC_lit, confetti, promoted);

  // lit face over the dark wall; frames and off-tiles stay faintly present
  vec3 offCol = uC_wall * tile;
  vec3 onCol = litCol * (face * 1.05 + hot * 0.5) * lum;
  vec3 col = offCol + onCol;

  col = pow(max(col, 0.0), vec3(uP_contrast));

  // Surface-lit orb bounded by a mask: alpha IS coverage, so premultiply —
  // the opposite convention from the emissive orbs (see shdr-31).
  float a = mask;
  gl_FragColor = vec4(col * a, a);
}
`;

export const shdr29Orb: OrbVariant = {
  key: "shdr-29",
  label: "SHDR-29",
  note: "an LED tile wall lighting up in flowing blobs, wrapped on the ball",
  frag: MOSAIC_FRAG,
  params: [
    { key: "drift", label: "Drift", min: 0, max: 10, step: 0.05, default: 0.45, integrate: true },
    { key: "churn", label: "Churn", min: 0, max: 10, step: 0.05, default: 0.5, integrate: true },
    { key: "swirl", label: "Fluidity", min: 0, max: 3, step: 0.015, default: 1.2 },
    { key: "shuffle", label: "Shuffle", min: 0, max: 20, step: 0.1, default: 0.6, integrate: true },
    { key: "pulse", label: "Pulse depth", min: 0, max: 2, step: 0.01, default: 0 },
    { key: "spin", label: "Roll", min: 0, max: 5, step: 0.03, default: 0.1, integrate: true },
    { key: "radius", label: "Radius", min: 0.15, max: 3, step: 0.015, default: 0.9 },
    { key: "cells", label: "Tile grid", min: 16, max: 160, step: 2, default: 48 },
    { key: "scale", label: "Blob scale", min: 0.3, max: 10, step: 0.1, default: 1.3 },
    { key: "coverage", label: "Coverage", min: 0, max: 1.2, step: 0.01, default: 0.52 },
    { key: "confetti", label: "Confetti", min: 0, max: 1, step: 0.01, default: 0.22 },
    { key: "light", label: "Key light", min: 0, max: 3, step: 0.015, default: 0.6 },
    { key: "gain", label: "Panel gain", min: 0.05, max: 5, step: 0.05, default: 1 },
    { key: "contrast", label: "Contrast", min: 0.15, max: 10, step: 0.05, default: 1 }
  ],
  colors: [
    { key: "lit", label: "Lit tile", default: "#fff2dd" },
    { key: "wall", label: "Wall", default: "#161616" }
  ],
  /*
    Each state animates DIFFERENTLY — its own motion, same palette and
    composition throughout (no stateColors on purpose).
  */
  /*
    Coverage is staged DOWN in the active states on purpose: the synthesized
    volumes push it up, and without the counterweight thinking and speaking
    flood the wall with light — the composition lives on its big dark
    voids, so every state keeps them.
  */
  statePresets: {
    // idle FLOWS: blobs streaming and curling slowly, lava-lamp pace
    idle: {
      drift: 0.45,
      churn: 0.5,
      shuffle: 0.6,
      pulse: 0,
      spin: 0.1,
      coverage: 0.52,
      gain: 1
    },
    // thinking BOILS: the stream stops but the fluid warp churns hard in
    // place while the confetti races — blobs kneading among the voids
    thinking: {
      drift: 0.1,
      churn: 1.9,
      shuffle: 5,
      pulse: 0,
      spin: 0.03,
      coverage: 0.42,
      gain: 0.95
    },
    // speaking PULSES: rings radiate through the flowing wall as the dome
    // rolls — the rings carve dark bands as much as they light bright ones
    speaking: {
      drift: 0.5,
      churn: 0.9,
      shuffle: 1.2,
      pulse: 0.55,
      spin: 0.45,
      coverage: 0.44,
      gain: 1.15
    }
  }
};

export type Shdr29Props = Omit<ShaderOrbProps, "variant">;

export function Shdr29({ size = 280, ...rest }: Shdr29Props) {
  return <ShaderOrb variant={shdr29Orb} size={size} {...rest} />;
}

export default Shdr29;

32. components/ui/shdr-30.tsx

/*
 * Deliberately not a `"use client"` module — see the note in `shdr-11.tsx`.
 *
 * Note for editors: the shader lives in a template literal, so its comments
 * must not contain backticks.
 */
import { ShaderOrb, type OrbVariant, type ShaderOrbProps } from "@/components/ui/orbkit-core";

/* ----------------------------------------------------------------------------
   SHDR-30 — a meadow folding into itself, falling forever toward a blue
   vanishing point.

   Not a port of a golfed listing like its neighbours: built to a reference
   picture — a flower meadow whose sky peels up and inward through nested
   rectangular frames, each one the same landscape a size smaller, until
   the recursion closes on a scrap of blue sky and cloud.

   How it is put together:

   - THE RECURSION IS A LOGARITHM. Take the CHEBYSHEV norm of the point,
     max(|x|,|y|), and the level sets are SQUARES rather than circles —
     which is the whole difference between this and a spiral tunnel.
     log of that norm, base K, is a continuous frame index; its fractional
     part says where inside one frame the fragment sits, and raising K back
     to that fraction gives the radius the fragment maps to in the BASE
     frame. Every fragment therefore reads one small picture, and the
     picture contains itself.
   - THE CLOCK GOES INTO THE LOG, not into a scale. Adding to the frame
     index slides the whole mapping, so the tunnel flies inward forever
     with no seam to hide and no accumulating error — an integrated clock
     makes the rate tunable without jumping the fall.
   - THE PICTURE is painted in the base frame's own coordinates: sky and
     cloud above the horizon line, canopy and meadow below it, and flowers
     hashed into the low ground. Because every frame reads the SAME base
     picture, the clouds and flowers repeat at every scale, which is what
     the reference does and what makes the recursion legible.
   - THE SMEAR ON THE WALLS is the reference's most distinctive texture,
     and it comes for free from the geometry: the land noise is sampled on
     (distance around the frame, frame index), with a low frequency on the
     second axis, so its features run long in the direction the recursion
     stretches them.
   - AERIAL PERSPECTIVE IS LOAD-BEARING. Depth has to come from the SCREEN
     radius, not from the position inside a frame — every frame has the
     same fractional part, so that number carries no depth at all. Fading
     toward the sky colour as the screen radius goes to zero is what makes
     the middle read as far away rather than as small, and it doubles as
     the guard on the log at the exact centre.

   Orb decisions, each one a rule in the README:

   - THE ORB IS THE OBJECT: the tunnel is sampled through a stereographic
     projection of the dome, so the frames compress toward the limb the
     way a texture on a real sphere does, and the vanishing point sits at
     the ball's centre. With the fresnel sheen over it the ball reads as
     glass with a world falling away inside it, not as a disc cut out of a
     picture.
   - The frame index runs continuously across frames while the picture
     coordinate resets at every boundary. That reset is not a defect to
     smooth away: it IS the edge of the picture, and it draws the nested
     frame borders the reference is made of.
   - Distance around the frame is a real arc length around the square, not
     atan and not a swap between the two axes at the corners. Both of those
     put a seam on the wall; the arc length puts its one wrap at a corner,
     where a frame corner already is.
   - Surface-lit and mask-bounded, so alpha IS coverage — premultiplied
     output, as in shdr-17.
---------------------------------------------------------------------------- */

/*
 * AA is a `#define`: ES 1.0 requires constant loop bounds. The recursion
 * crowds an unbounded number of frames into the last few pixels before the
 * limb, so supersampling here is not polish — it is the only thing standing
 * between the rim and a band of noise.
 */
const DROSTE_FRAG = `
#define AA 2

// Volume-reactive values, resolved once per fragment in main().
float drosteHaze;
float drosteCloud;
float drosteBloom;

/*
  Arc length around the unit square, in [0,8), counter-clockwise from the
  bottom-right corner — two units per face. Continuous everywhere except
  its single wrap, which lands on a corner.
*/
float squareArc(vec2 q) {
  if (abs(q.x) >= abs(q.y)) {
    if (q.x > 0.0) return q.y + 1.0;
    return 5.0 - q.y;
  }
  if (q.y > 0.0) return 3.0 - q.x;
  return 7.0 + q.x;
}

/*
  Flower colour by hash: mostly white daisies, then the planted warm, then
  the cornflower blues — which reuse the SKY colour rather than adding a
  sixth stop, because that is what keeps them reading as part of the same
  picture instead of as confetti thrown over it.
*/
vec3 drosteFlower(float h) {
  vec3 c = uC_cloud;
  c = mix(c, uC_bloom, step(0.52, h));
  c = mix(c, uC_sky, step(0.86, h));
  return c;
}

vec3 drosteRender(vec2 fragCoord) {
  vec2 uv = (2.0 * fragCoord - uRes) / min(uRes.x, uRes.y);
  float R = max(uP_radius, 0.001);

  // the dome: the front hemisphere of a unit ball, in screen space
  vec2 pl = uv / R;
  float z = sqrt(max(1.0 - dot(pl, pl), 0.0));

  float fall = uP_fall;   // integrated clock: the flight inward
  float drift = uP_drift; // integrated clock: weather

  /*
    The frame tilt is a STATIC angle, not an integrated clock like the roll
    every other orb here gets. Those clocks seed at a random phase per
    mount, which is exactly right for a field with no preferred direction
    and exactly wrong for a picture: it lands the sky down one side of the
    ball and the meadow up the other. This one has an up.
  */
  float sw = uP_tilt;

  // stereographic wrap — the tunnel is inside the ball, and compresses
  // toward the limb the way a texture on a sphere does
  vec2 p = pl / (z + 1.0 + uP_bulge) * uP_scale;
  p = mat2(cos(sw), -sin(sw), sin(sw), cos(sw)) * p;

  /*
    The Chebyshev norm makes the level sets SQUARES. The floor on it is
    what keeps the logarithm finite at the dead centre; the haze below
    covers that last pixel anyway.
  */
  float m = max(max(abs(p.x), abs(p.y)), 0.002);

  float K = max(uP_ratio, 1.05);
  float L = log2(m) / log2(K) + fall;

  vec2 q = p / m;                 // direction, on the unit square boundary
  float sm = pow(K, fract(L));    // this fragment's radius in base-frame units
  vec2 P = q * sm;                // where it lands in the base picture

  float Yn = P.y / K;             // picture height, about -1 at the bottom edge
  float arc = squareArc(q);       // distance around the frame

  // ---- sky -----------------------------------------------------------
  vec3 col = mix(uC_sky * 0.72, uC_sky, clamp(Yn * 1.3, 0.0, 1.0));

  /*
    Cloud and land are both read in BASE-PICTURE coordinates, so every
    frame carries the same weather at its own scale — which is the whole
    point of a picture that contains itself.
  */
  float skyMask = smoothstep(uP_horizon - 0.3, uP_horizon + 0.2, Yn);
  float cl = fbm(P * uP_cloudScale + vec2(drift, drift * 0.3));
  cl = smoothstep(drosteCloud, drosteCloud + 0.16, cl);
  col = mix(col, uC_cloud, cl * (0.2 + 0.8 * skyMask));

  // ---- land ----------------------------------------------------------
  /*
    The smear. Sampled on (distance around the frame, frame index) with a
    low frequency on the second axis, so features run LONG in the
    direction the recursion stretches them — the streaked walls of the
    reference, straight out of the geometry.
  */
  float streak = fbm(vec2(arc * uP_streakFreq, L * uP_streakRad));

  vec3 land = mix(uC_canopy, uC_meadow, smoothstep(0.02, -0.62, Yn));
  land *= 0.42 + 1.25 * streak;

  // water: the low ground holds it where the streak field pools
  float water = smoothstep(0.42, 0.16, streak) * smoothstep(0.05, -0.3, Yn);
  land = mix(land, uC_water, water * uP_water);

  /*
    Flowers, hashed one to a cell on the same (around, index) grid, jittered
    inside it. Densest low in the picture and gone by the horizon.
  */
  vec2 fg = vec2(arc * uP_flowerScale, L * uP_flowerScale * 0.3);
  vec2 fc = floor(fg);
  vec2 ff = fract(fg) - 0.5;
  vec2 dcv = ff - (vec2(hash(fc + 3.7), hash(fc + 19.1)) - 0.5) * 0.6;
  float petal = smoothstep(uP_flowerSize, uP_flowerSize * 0.35, length(dcv));
  float present = step(1.0 - drosteBloom, hash(fc + 51.3));
  float meadow = smoothstep(0.13, -0.38, Yn);
  land = mix(land, drosteFlower(hash(fc + 7.9)), petal * present * meadow);

  float landMask = 1.0 - smoothstep(uP_horizon - 0.12, uP_horizon + 0.16, Yn);
  col = mix(col, land, landMask);

  /*
    The picture's own edge. Darkening across the frame and resetting hard
    at its boundary is not an artefact to smooth away — it draws the
    nested borders the reference is built out of.
  */
  col *= mix(1.0, uP_frameShade, fract(L));

  /*
    Aerial perspective, from the SCREEN radius. Every frame has the same
    fractional part, so depth cannot come from inside a frame — it has to
    come from how far in the fragment sits. This is what makes the middle
    read as far away instead of merely small.
  */
  float deep = 1.0 - smoothstep(0.0, uP_hazeRange, m);
  col = mix(col, uC_sky, deep * drosteHaze);

  col = pow(max(col, vec3(0.0)), vec3(uP_contrast)) * uP_gain;

  float lum = dot(col, vec3(0.299, 0.587, 0.114));
  col = mix(vec3(lum), col, uP_saturation);

  // dome shading, kept light — this is a window, not a lit surface
  vec3 n = vec3(pl, z);
  float lambert = clamp(dot(n, normalize(vec3(-0.45, 0.55, 0.72))), 0.0, 1.0);
  col *= 0.72 + uP_light * lambert;

  // the glass: a strong fresnel is what turns a picture into a sphere
  // with a world inside it
  float fres = 1.0 - z;
  fres = fres * fres * fres;
  col += uC_sheen * uP_rim * fres;

  return col;
}

void main() {
  // Volume coupling: the user's voice thickens the weather, the agent's
  // clears the haze and brings the meadow into flower.
  drosteCloud = clamp(uP_cloudCover - 0.12 * uInput, 0.02, 0.98);
  drosteHaze = uP_haze * (1.0 - 0.25 * uOutput);
  drosteBloom = clamp(uP_flowerDensity * (1.0 + 0.5 * uOutput), 0.0, 1.0);

  vec2 uv = orbUV();
  float mask = smoothstep(0.012, -0.012, length(uv) - max(uP_radius, 0.001));

  // Two fbm evaluations and a flower grid per sample — none of it worth
  // paying for outside the silhouette.
  if (mask <= 0.0) {
    gl_FragColor = vec4(0.0);
    return;
  }

  vec3 col = vec3(0.0);
#if AA > 1
  for (int mx = 0; mx < AA; mx++) {
    for (int my = 0; my < AA; my++) {
      vec2 off = (vec2(float(mx), float(my)) + 0.5) / float(AA) - 0.5;
      col += drosteRender(gl_FragCoord.xy + off);
    }
  }
  col /= float(AA * AA);
#else
  col = drosteRender(gl_FragCoord.xy);
#endif

  // Surface orb bounded by a mask: alpha IS coverage, so premultiply — the
  // opposite convention from the emissive orbs (see shdr-31).
  float a = mask;
  gl_FragColor = vec4(max(col, vec3(0.0)) * a, a);
}
`;

export const shdr30Orb: OrbVariant = {
  key: "shdr-30",
  label: "SHDR-30",
  note: "a meadow folding into itself toward a blue vanishing point",
  frag: DROSTE_FRAG,
  params: [
    { key: "fall", label: "Fall speed", min: 0, max: 4, step: 0.01, default: 0.12, integrate: true },
    { key: "tilt", label: "Frame tilt", min: -1.6, max: 1.6, step: 0.01, default: 0 },
    { key: "drift", label: "Weather drift", min: 0, max: 4, step: 0.02, default: 0.2, integrate: true },
    { key: "radius", label: "Radius", min: 0.15, max: 3, step: 0.015, default: 0.9 },
    { key: "scale", label: "Tunnel scale", min: 0.3, max: 20, step: 0.1, default: 4.5 },
    { key: "bulge", label: "Dome bulge", min: 0, max: 4, step: 0.02, default: 0.25 },
    { key: "ratio", label: "Frame ratio", min: 1.1, max: 6, step: 0.02, default: 1.8 },
    { key: "horizon", label: "Horizon", min: -0.9, max: 0.9, step: 0.01, default: 0.14 },
    { key: "cloudScale", label: "Cloud scale", min: 0.1, max: 8, step: 0.05, default: 1.6 },
    { key: "cloudCover", label: "Cloud cover", min: 0.02, max: 0.98, step: 0.01, default: 0.46 },
    { key: "streakFreq", label: "Wall detail", min: 0.2, max: 20, step: 0.1, default: 5 },
    { key: "streakRad", label: "Smear", min: 0.02, max: 4, step: 0.02, default: 0.5 },
    { key: "water", label: "Water", min: 0, max: 1, step: 0.01, default: 0.7 },
    { key: "flowerScale", label: "Flower scale", min: 2, max: 120, step: 1, default: 44 },
    { key: "flowerDensity", label: "Flower density", min: 0, max: 1, step: 0.01, default: 0.55 },
    { key: "flowerSize", label: "Flower size", min: 0.05, max: 0.6, step: 0.01, default: 0.26 },
    { key: "frameShade", label: "Frame shading", min: 0.2, max: 1.4, step: 0.01, default: 0.62 },
    { key: "haze", label: "Aerial haze", min: 0, max: 1, step: 0.01, default: 0.85 },
    { key: "hazeRange", label: "Haze reach", min: 0.005, max: 1.5, step: 0.005, default: 0.09 },
    { key: "gain", label: "Brightness", min: 0.05, max: 4, step: 0.02, default: 1.05 },
    { key: "contrast", label: "Contrast", min: 0.15, max: 10, step: 0.05, default: 1.05 },
    { key: "saturation", label: "Saturation", min: 0, max: 4, step: 0.02, default: 1.1 },
    { key: "light", label: "Key light", min: 0, max: 3, step: 0.015, default: 0.35 },
    { key: "rim", label: "Rim sheen", min: 0, max: 3, step: 0.015, default: 0.55 }
  ],
  /*
   * Six stops, and they are the picture rather than a palette: the sky the
   * recursion closes on, its cloud, the dark canopy, the meadow under the
   * flowers, the planted warm colour, and the glass.
   */
  colors: [
    { key: "sky", label: "Sky", default: "#4a92e0" },
    { key: "cloud", label: "Cloud", default: "#f7fbff" },
    { key: "canopy", label: "Canopy", default: "#12401f" },
    { key: "meadow", label: "Meadow", default: "#5aa63a" },
    { key: "water", label: "Water", default: "#156f6a" },
    { key: "bloom", label: "Bloom", default: "#ff6a3a" },
    { key: "sheen", label: "Sheen", default: "#cfe6ff" }
  ],
  /*
    Staged on the fall, which is the orb's whole subject, and on the haze,
    which decides how far into the recursion the eye can see. Frame ratio
    sets how many frames land on the ball; it differs only for idle, and
    the glide out of rest reads as the tunnel breathing once.
  */
  statePresets: {
    /*
      at rest: a slow fall, deep haze, weather barely moving — on a frame
      ratio nearly double the working states, so fewer, larger frames land
      on the ball, with the horizon dropped below centre. The wall detail
      is coarsened to a broad smear, the water drained entirely, and the
      meadow thinned to sparse, oversized flowers; brighter, and more
      saturated, than the states it falls into.
    */
    idle: {
      fall: 0.12,
      drift: 0.2,
      bulge: 0.2,
      ratio: 3.06,
      horizon: -0.11,
      cloudScale: 1.2,
      cloudCover: 0.42,
      streakFreq: 2.9,
      streakRad: 0.54,
      water: 0,
      flowerScale: 24,
      flowerDensity: 0.27,
      flowerSize: 0.43,
      haze: 0.8,
      hazeRange: 0.09,
      gain: 1.38,
      saturation: 1.56,
      light: 0.345,
      rim: 0.555
    },
    /*
      searching: the fall QUINTUPLES and the haze closes in over three
      times as far, so the recursion is swallowed within a frame or two of
      the middle — the eye is pulled down a tunnel it cannot see the end
      of. The weather thickens and the meadow goes out of flower. Lit hard
      against that: the key light nearly triples, and the gain, contrast
      and saturation all come up, so what the haze leaves is vivid.
    */
    thinking: {
      fall: 0.6,
      drift: 0.5,
      haze: 1,
      hazeRange: 0.3,
      cloudCover: 0.3,
      flowerDensity: 0.28,
      gain: 1.42,
      contrast: 1.45,
      saturation: 2,
      light: 0.96
    },
    /*
      answering: the fall goes FASTEST of the three — twelve times idle —
      on a drift five times as quick and a tunnel nearly doubled in scale,
      with the frames given a slight tilt. The haze lifts to less than half
      idle over a longer reach, opening the recursion to the vanishing
      point; the walls go to fine, wide-smeared detail, the clouds scale up
      threefold, and the meadow comes fully into flower on larger blooms.
      Lit and saturated hardest of the three.
    */
    speaking: {
      fall: 1.47,
      tilt: 0.03,
      drift: 1.54,
      scale: 8.7,
      horizon: 0.03,
      cloudScale: 3.65,
      cloudCover: 0.44,
      streakFreq: 11,
      streakRad: 1.48,
      flowerScale: 32,
      flowerDensity: 0.95,
      haze: 0.38,
      hazeRange: 0.315,
      gain: 1.36,
      contrast: 1.45,
      saturation: 2.32,
      light: 0.57
    }
  },
  // the picture keeps its own colours; the states move the weather and the
  // light, cooling toward overcast while searching and warming while
  // answering
  stateColors: {
    idle: {
      sky: "#4a92e0",
      cloud: "#f7fbff",
      canopy: "#12401f",
      meadow: "#5aa63a",
      water: "#156f6a",
      bloom: "#ff6a3a",
      sheen: "#cfe6ff"
    },
    thinking: {
      sky: "#3f6fa8",
      cloud: "#dde8f4",
      canopy: "#0e2c2e",
      meadow: "#3f7f5c",
      water: "#12525f",
      bloom: "#7d8cff",
      sheen: "#b6cdf0"
    },
    speaking: {
      sky: "#6fb0ec",
      cloud: "#fff6e8",
      canopy: "#204a16",
      meadow: "#7cc23f",
      water: "#1d8a76",
      bloom: "#ffb02e",
      sheen: "#ffe3c4"
    }
  }
};

export type Shdr30Props = Omit<ShaderOrbProps, "variant">;

export function Shdr30({ size = 280, ...rest }: Shdr30Props) {
  return <ShaderOrb variant={shdr30Orb} size={size} {...rest} />;
}

export default Shdr30;

33. components/ui/shdr-31.tsx

/*
 * Deliberately not a `"use client"` module — see the note in `shdr-11.tsx`.
 */
import { ShaderOrb, type OrbVariant, type ShaderOrbProps } from "@/components/ui/orbkit-core";

/* ----------------------------------------------------------------------------
   SHDR-31 — a raymarched SDF shell with volumetric godrays.

   The distance field is the space between a sine-warped sphere and a plain one
   (`-smin(warped, inverted, k)`), so the camera looks into a glowing hollow
   rather than at a solid surface. Light is accumulated along every ray as an
   inverse-square falloff gated by the shell, which is what produces the
   god-ray bleed — there is no light source, only the integral.

   Ported to WebGL 1 / GLSL ES 1.0, which needed four fixes:
     1. `transpose()` is ES 3.0 only — hand-written as `transpose3` below.
     2. `fragColor` was assigned in main() but never declared; ES 1.0 writes to
        the built-in `gl_FragColor`.
     3. ES 1.0 restricts a for-loop condition to comparing the index against a
        constant, so the march exits via `break` instead.
     4. `u_time` / `u_resolution` are the runtime's `uP_speed` clock and `uRes`.

   The original returned an opaque frame. Orbs composite onto the page, so the
   accumulated luminance drives alpha instead and the orb glows over whatever
   is behind it.
---------------------------------------------------------------------------- */

/*
 * Supersampling factor and march ceiling are `#define`s, not params: ES 1.0
 * needs constant loop bounds. AA=2 means 4 full marches per pixel — at DPR 2
 * that is 16 marches per CSS pixel, which is why this ships at 1.
 */
const CORONA_FRAG = `
#define AA 1
#define MAX_STEPS 256

mat3 transpose3(mat3 m) {
  return mat3(
    m[0][0], m[1][0], m[2][0],
    m[0][1], m[1][1], m[2][1],
    m[0][2], m[1][2], m[2][2]
  );
}

// An artistic tumble, not an orthonormal rotation — the axes shear against each
// other so the shell never repeats a clean spin.
mat3 coronaRot(float a) {
  return mat3(
    cos(a), sin(a / 2.0) * sin(a), sin(a) * cos(a / 2.0),
    0.0, cos(a / 2.0), -sin(a / 2.0),
    -sin(a), sin(a / 2.0) * cos(a), cos(a / 2.0) * cos(a)
  );
}

mat3 globalRot;
mat3 globalInvRot;

// Volume-reactive values, resolved once per fragment in main().
float shellRadius;
float warpAmount;
float rayGain;
float warpFreqNow;
float smoothKNow;

float smin(float a, float b, float k) {
  float h = clamp(0.5 + 0.5 * (b - a) / k, 0.0, 1.0);
  return mix(b, a, h) - k * h * (1.0 - h);
}

float coronaSDF(vec3 p) {
  vec3 p1 = p;
  p1.zyx += sin(p.xzy * warpFreqNow) / max(warpAmount, 0.001);
  return -smin(length(p1) - shellRadius, shellRadius - length(p), smoothKNow);
}

vec3 shellColor(vec3 p) {
  float eps = 0.001;
  vec3 normal = globalInvRot * normalize(vec3(
    coronaSDF(p + vec3(eps, 0.0, 0.0)) - coronaSDF(p - vec3(eps, 0.0, 0.0)),
    coronaSDF(p + vec3(0.0, eps, 0.0)) - coronaSDF(p - vec3(0.0, eps, 0.0)),
    coronaSDF(p + vec3(0.0, 0.0, eps)) - coronaSDF(p - vec3(0.0, 0.0, eps))
  ));

  vec3 next = 1.0 - (normal * 0.5 + 0.5);
  next = vec3(dot(next, vec3(1.0)) / 3.0);
  return 1.025 - next * next;
}

vec4 coronaRender(vec2 fragCoord) {
  vec2 uv = (fragCoord * 2.0 - uRes) / min(uRes.x, uRes.y);

  vec3 ro = vec3(0.0, 0.0, -uP_camDist);
  vec3 rd = normalize(vec3(uv, uP_fov));

  ro = globalRot * ro;
  rd = globalRot * rd;

  vec3 p = ro;
  float d = 1.0;
  float t = 0.0;
  float godrays = 0.0;

  for (int i = 0; i < MAX_STEPS; i++) {
    if (d <= 0.005 || t >= uP_maxDist) break;
    p = ro + rd * t;
    d = coronaSDF(p) / max(uP_stepScale, 0.5);

    // Gate the accumulation on the shell so light bleeds out of the hollow
    // instead of glowing uniformly through empty space.
    float fog = length(p) > shellRadius
      ? smoothstep(0.0, 0.5, coronaSDF(normalize(p) * shellRadius))
      : 1.0;
    godrays += (rayGain / (1.0 + dot(p, p) * uP_rayFalloff)) * fog;

    t += d;
  }

  vec3 col = vec3(uP_ambient);
  if (t < uP_maxDist) col = shellColor(p) * uP_surfaceLit;
  col += godrays;

  return vec4(col, 1.0);
}

void main() {
  float animTime = uP_speed; // integrated clock
  globalRot = coronaRot(animTime);
  globalInvRot = transpose3(coronaRot(animTime));

  /*
    One shared BACK-AND-FORTH phase for the swept values. sin() of an
    integrated clock is a true round trip — it eases through both ends
    instead of snapping at a wrap, which fract() or mod() would do.

    Every swept value is resolved HERE, once per fragment, and never read
    straight from its uniform inside coronaSDF: the normal estimate calls
    that SDF six more times, and a value that moved between those calls
    would corrupt the finite difference and pit the shading.

    uP_sweepRate is its own integrated clock, so the breathing rate tunes
    without jumping the phase, and it only ever enters through sin() —
    safe for an unbounded clock. All three swings share the phase, so the
    shell breathes as one motion rather than three unrelated wobbles.
  */
  float sweepPhase = sin(uP_sweepRate);

  // Louder agent output pushes the godrays; user input roughens the shell and
  // swells it slightly, so the silhouette breathes with speech.
  shellRadius = uP_radius + uP_swell * uInput;
  warpAmount = (uP_warp + uP_warpSwing * sweepPhase) * (1.0 - 0.25 * uInput - 0.15 * uOutput);
  warpAmount = max(warpAmount, 0.05);
  rayGain = uP_rayGain * (0.7 + 0.8 * uOutput + 0.3 * uInput);

  warpFreqNow = max(uP_warpFreq + uP_freqSwing * sweepPhase, 0.05);

  /*
    smoothK reaches EXACTLY zero at the bottom of the speaking sweep
    (0.25 +/- 0.25), and smin() divides by it — an unguarded zero is a
    NaN across the whole SDF. The floor keeps the blend hard but finite.
  */
  smoothKNow = max(uP_smoothK + uP_smoothSwing * sweepPhase, 0.005);

  vec4 acc = vec4(0.0);
#if AA > 1
  for (int mx = 0; mx < AA; mx++) {
    for (int my = 0; my < AA; my++) {
      vec2 offset = vec2(float(mx), float(my)) / float(AA) - 0.5;
      acc += coronaRender(gl_FragCoord.xy + offset);
    }
  }
  acc /= float(AA * AA);
#else
  acc = coronaRender(gl_FragCoord.xy);
#endif

  // Luminance becomes alpha so the orb composites onto the page instead of
  // painting an opaque square.
  //
  // The colour is emitted light, so it is already premultiplied: rgb is what
  // the orb adds, alpha is only how much background it hides. Multiplying rgb
  // by alpha again (the usual move for a lit surface) would darken the glow
  // quadratically and wash the godrays out.
  vec3 col = clamp(acc.rgb, 0.0, 1.0);
  float lum = dot(col, vec3(0.2126, 0.7152, 0.0722));
  float a = clamp(lum * uP_alphaGain, 0.0, 1.0);

  // The godrays are volumetric, so they reach the frame boundary and would
  // otherwise show the canvas as a hard-edged glowing square. Taper radially to
  // let the halo fall off into the page instead — colour as well as alpha,
  // since premultiplied output would otherwise keep emitting at full brightness
  // right up to the cutoff and leave a visible rim.
  float fade = 1.0 - smoothstep(uP_edgeFade, 1.0, length(orbUV()));
  col *= fade;
  a *= fade;

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

export const shdr31Orb: OrbVariant = {
  key: "shdr-31",
  label: "SHDR-31",
  note: "raymarched shell, volumetric godrays",
  frag: CORONA_FRAG,
  params: [
    { key: "speed", label: "Anim speed", min: 0.015, max: 10, step: 0.05, default: 1, integrate: true },
    { key: "sweepRate", label: "Sweep rate", min: 0, max: 6, step: 0.02, default: 0.5, integrate: true },
    { key: "radius", label: "Shell radius", min: 0.4, max: 10, step: 0.05, default: 2.6 },
    { key: "swell", label: "Input swell", min: 0, max: 3, step: 0.015, default: 0.18 },
    { key: "warp", label: "Warp divisor", min: 0.3, max: 10, step: 0.05, default: 0.9 },
    { key: "warpFreq", label: "Warp frequency", min: 0.15, max: 30, step: 0.15, default: 5.25 },
    { key: "warpSwing", label: "Warp swing", min: 0, max: 8, step: 0.05, default: 0 },
    { key: "freqSwing", label: "Frequency swing", min: 0, max: 15, step: 0.05, default: 0 },
    { key: "smoothSwing", label: "Softness swing", min: 0, max: 2, step: 0.005, default: 0 },
    { key: "smoothK", label: "Blend softness", min: 0.015, max: 4, step: 0.02, default: 0.42 },
    { key: "rayGain", label: "Godray gain", min: 0, max: 4, step: 0.02, default: 0.8 },
    { key: "rayFalloff", label: "Godray falloff", min: 0.3, max: 100, step: 0.5, default: 10.5 },
    { key: "surfaceLit", label: "Surface light", min: 0, max: 3, step: 0.015, default: 0.105 },
    { key: "ambient", label: "Ambient", min: 0, max: 1, step: 0.005, default: 0 },
    { key: "alphaGain", label: "Alpha gain", min: 0.05, max: 15, step: 0.1, default: 3 },
    { key: "edgeFade", label: "Halo falloff", min: 0.1, max: 3, step: 0.015, default: 0.45 },
    { key: "camDist", label: "Camera distance", min: 1.5, max: 100, step: 0.5, default: 12 },
    { key: "fov", label: "Lens", min: 0.3, max: 20, step: 0.1, default: 3.5 },
    { key: "stepScale", label: "Step safety", min: 0.3, max: 30, step: 0.5, default: 9 },
    { key: "maxDist", label: "Max distance", min: 3, max: 100, step: 1, default: 22 }
  ],
  colors: [],
  statePresets: {
    /*
      idle is the reference look, dialled in by hand: every swing at zero,
      so the shell holds still and only the tumble moves.
    */
    idle: {
      speed: 1,
      swell: 0.18,
      warp: 0.9,
      warpFreq: 5.25,
      smoothK: 0.42,
      rayGain: 0.8,
      rayFalloff: 10.5,
      alphaGain: 3,
      warpSwing: 0,
      freqSwing: 0,
      smoothSwing: 0
    },
    // thinking: warp frequency breathes 5 <-> 9 (7 +/- 2), everything else held
    thinking: {
      speed: 0.9,
      swell: 0.66,
      warp: 1,
      warpFreq: 7,
      smoothK: 0.58,
      rayGain: 0.52,
      rayFalloff: 8,
      alphaGain: 1.8,
      sweepRate: 0.5,
      freqSwing: 2,
      warpSwing: 0,
      smoothSwing: 0
    },
    /*
      speaking: three values sweep together, all of them round trips —
        warp       1.5 <-> 6     (3.75 +/- 2.25)
        warpFreq   15  <-> 24    (19.5 +/- 4.5)
        smoothK    0   <-> 0.5   (0.25 +/- 0.25, floored in the shader)
    */
    speaking: {
      speed: 0.9,
      swell: 0.66,
      warp: 3.75,
      warpSwing: 2.25,
      warpFreq: 19.5,
      freqSwing: 4.5,
      smoothK: 0.25,
      smoothSwing: 0.25,
      sweepRate: 0.8,
      rayGain: 0.52,
      rayFalloff: 8,
      alphaGain: 1.8
    }
  }
};

export type Shdr31Props = Omit<ShaderOrbProps, "variant">;

export function Shdr31({ size = 280, ...rest }: Shdr31Props) {
  return <ShaderOrb variant={shdr31Orb} size={size} {...rest} />;
}

export default Shdr31;

34. components/ui/shdr-32.tsx

/*
 * Deliberately not a `"use client"` module — see the note in `shdr-11.tsx`.
 *
 * Note for editors: the shader lives in a template literal, so its comments
 * must not contain backticks.
 */
import { ShaderOrb, type OrbVariant, type ShaderOrbProps } from "@/components/ui/orbkit-core";

/* ----------------------------------------------------------------------------
   SHDR-32 — a galaxy, marched as a volume of gas and dust inside the ball.

   Built the way the raymarched orbs here are built (shdr-18, shdr-22,
   shdr-21), not as a picture: the ray walks through the ball and at every
   step reads a DENSITY, accumulates its emission front-to-back and carries
   a transmittance so the near gas veils the far. The density is a galaxy:

   - A FLARED DISC. Density falls exponentially in the cylindrical radius
     and exponentially in height above the plane, with a scale height that
     grows outward, so the disc is razor-thin at the core and puffs toward
     the rim the way real discs do. A Gaussian BULGE sits on the centre.
   - LOG-SPIRAL ARMS modulate it: cos(N*phi - k*log(rho)), N arms wound by
     k, raised to a power for lanes. N multiplies phi so it stays an integer
     (continuity across the branch cut) and, with k, never moves between
     states — a gliding winding rakes the arms across the disc.
   - TURBULENCE breaks everything into filaments: the same feedback curl the
     family uses (q += cos(q.yzx * f + t) / f, four octaves, each reading the
     last one's result with its components rolled), with the clock entering
     as a shared phase so the gas boils coherently. Its sum is thresholded
     into clumps, and it also wobbles the arm phase so the arms fray.
   - DUST is the absorption. The transmittance is a vec3, and the extinction
     is weighted toward blue, so light seen through thick gas comes out
     reddened — the brown lanes of a galaxy photograph, for free, from the
     physics rather than a painted-on colour.
   - The colour is a ramp keyed to the cylindrical radius: three stops, the
     core, the inner arms and the outer arms, so the hue changes with
     distance from the centre; the bulge whitens toward the core colour.
   - STARS are not marched — a volumetric point smears along the ray into a
     streak. They are a hashed lattice evaluated at the ray's analytic hit
     with the galactic plane, denser in the arms, then dimmed by the
     transmittance the march left, so dust in front of them veils them.

   The galaxy plane is tilted toward the viewer and turns on its own
   integrated clock. Orb-family conventions throughout: the analytic
   silhouette from shdr-01, tanh tone map with a tunable knee, alpha from
   the peak channel for emitted light, a night fill so the ball reads as a
   solid sphere behind the gas. Uninitialised locals are explicit; loop
   bounds are defines.
---------------------------------------------------------------------------- */

const GALAXY_FRAG = `
#define STEPS 56
#define TURB_OCT 4

// Volume-reactive values, resolved once per fragment in main().
float galDensity;
float galCore;
float galFalloff;

/*
  The galactic density at a point in the galaxy's own frame: the disc lies
  in xz, the normal is y. Returns the density; writes the arm weight and the
  cylindrical radius for the colour.
*/
float galaxy(vec3 p, float t, out float arm, out float rho) {
  rho = length(p.xz);
  float h = p.y;
  // atan(0, 0) is undefined; the exact axis is all bulge anyway
  float phi = rho > 1e-4 ? atan(p.z, p.x) : 0.0;
  float lr = log(max(rho, 0.02));
  float armPhase = phi * uP_arms - uP_wind * lr;

  // feedback curl turbulence, shared phase
  vec3 q = p * uP_turbScale;
  float f = 1.0;
  for (int k = 0; k < TURB_OCT; k++) {
    q += cos(q.yzx * f + t) / f;
    f *= 1.9;
  }
  float n = (sin(q.x) + sin(q.y) + sin(q.z)) / 3.0 * 0.5 + 0.5;
  float clump = smoothstep(uP_threshold, 1.0, n);

  arm = 0.5 + 0.5 * cos(armPhase + (n - 0.5) * uP_ragged);
  arm = pow(arm, uP_armSharp);

  float scaleH = uP_thick * (0.12 + rho);
  float disc = exp(-rho * galFalloff) * exp(-abs(h) / scaleH);
  float bulge = exp(-dot(p, p) * uP_bulge);

  float dens = disc * (0.08 + 1.6 * arm) * (0.25 + 0.75 * clump) + bulge * galCore;
  return dens * galDensity;
}

/*
  One lattice of hashed stars. Each cell either carries a star or not, at a
  hashed position, with its own twinkle rate; the 3x3 neighbourhood is
  gathered so a star near a cell wall is not clipped.
*/
float starField(vec2 p, float density, float size, float twinkleT) {
  vec2 id = floor(p);
  vec2 f = fract(p);
  float acc = 0.0;
  for (int j = -1; j <= 1; j++) {
    for (int i = -1; i <= 1; i++) {
      vec2 o = vec2(float(i), float(j));
      vec2 cid = id + o;
      float h = hash(cid);
      if (h > density) continue;
      vec2 sp = o + vec2(hash(cid + 1.3), hash(cid + 2.7));
      float dd = length(f - sp);
      float tw = 0.55 + 0.45 * sin(twinkleT * (1.5 + 5.0 * hash(cid + 5.1)) + h * 40.0);
      float sz = size * (0.5 + 1.2 * hash(cid + 8.9) * hash(cid + 8.9));
      acc += tw * exp(-dd * dd / (sz * sz)) * (0.4 + 0.6 * h / max(density, 0.001));
    }
  }
  return acc;
}

vec4 galaxyRender(vec2 fragCoord) {
  vec2 uv = (2.0 * fragCoord - uRes) / min(uRes.x, uRes.y);
  vec3 ro = vec3(0.0, 0.0, uP_camDist);
  vec3 rd = normalize(vec3(uv, -uP_focal));

  float t = uP_churn; // integrated clock: the turbulence boils
  float spin = uP_spin; // integrated clock: the disc turns

  // the galaxy frame: tip about x by the tilt, then turn about the disc's
  // own normal
  float ct = cos(uP_tilt);
  float st = sin(uP_tilt);
  float cs = cos(spin);
  float sn = sin(spin);

  vec3 acc = vec3(0.0);
  vec3 T = vec3(1.0);
  // extinction weighted to blue, so thick gas reddens what is behind it
  vec3 absorb = vec3(0.7, 1.0, 1.5) * uP_absorb;

  // march only the span the envelope can light
  float z = max(uP_camDist - uP_envRadius * 1.05, 0.0);
  float zEnd = uP_camDist + uP_envRadius * 1.05;
  float dt = (zEnd - z) / float(STEPS);
  // a hashed start offset per pixel hides the step banding
  z += dt * hash(fragCoord * 0.37);

  for (int i = 0; i < STEPS; i++) {
    vec3 p = ro + rd * z;

    // envelope: nothing outside the ball contributes
    float env = 1.0 - smoothstep(uP_envRadius * 0.92, uP_envRadius, length(p));
    if (env > 0.001) {
      // into the galaxy frame
      vec3 g = vec3(p.x, p.y * ct - p.z * st, p.y * st + p.z * ct);
      g = vec3(g.x * cs - g.z * sn, g.y, g.x * sn + g.z * cs);

      float arm = 0.0;
      float rho = 0.0;
      float d = galaxy(g / uP_envRadius, t, arm, rho) * env;

      // the colour ramp, keyed to radius from the core
      vec3 ramp = mix(uC_inner, uC_outer, smoothstep(0.12, uP_hueReach, rho));
      float coreW = exp(-rho * rho * uP_bulge * 0.6);
      vec3 emit = mix(ramp, uC_core, coreW) * (0.6 + 0.6 * arm);

      acc += T * d * emit * dt;
      T *= exp(-d * absorb * dt);
    }

    z += dt;
    if (T.g < 0.004 || z > zEnd) break;
  }

  return vec4(acc, 1.0 - T.g);
}

void main() {
  // The beat: one wave on the core-beat clock, shared by the core flare and
  // the disc's breathing. Both depths are amplitudes, so they stage cleanly.
  float wave = 0.5 + 0.5 * cos(uP_beat);
  galDensity = uP_density * (1.0 + 0.35 * uInput);
  galCore = uP_core * (1.0 + 0.7 * uOutput) * (1.0 + uP_pulse * wave);
  // breathing: the disc's falloff relaxes on the wave, so the whole disc
  // swells outward and draws back — a smooth exponential, safe to sweep
  galFalloff = uP_falloff / (1.0 + uP_breathe * wave);

  vec4 acc = galaxyRender(gl_FragCoord.xy);

  /*
    Stars, at the ray's exact hit with the galactic plane. The plane is the
    tilted xz-plane through the origin; its world normal is the tilted y.
    The lattice lives in the disc's own turning frame, so the stars turn
    with the gas, and the march's transmittance dims them through the dust.
  */
  {
    vec3 ro = vec3(0.0, 0.0, uP_camDist);
    vec3 rd = normalize(vec3(orbUV(), -uP_focal));
    float ct = cos(uP_tilt);
    float st = sin(uP_tilt);
    vec3 N = vec3(0.0, ct, st);
    float denom = dot(N, rd);
    if (abs(denom) > 1e-4) {
      float th = -dot(N, ro) / denom;
      vec3 q = ro + rd * th;
      if (th > 0.0 && dot(q, q) < uP_envRadius * uP_envRadius * 0.9) {
        vec3 g = vec3(q.x, q.y * ct - q.z * st, q.y * st + q.z * ct) / uP_envRadius;
        float cs = cos(uP_spin);
        float sn = sin(uP_spin);
        vec2 gp = vec2(g.x * cs - g.z * sn, g.x * sn + g.z * cs);
        float rho = length(gp);
        float phi = rho > 1e-4 ? atan(gp.y, gp.x) : 0.0;
        float armW = 0.5 + 0.5 * cos(phi * uP_arms - uP_wind * log(max(rho, 0.02)));
        float sf = starField(gp * uP_starScale, uP_starDensity * (0.3 + 0.7 * armW), 0.12, uP_twinkle);
        float veil = 1.0 - acc.a; // what the march let through
        acc.rgb += vec3(1.0, 0.97, 0.9) * sf * uP_stars * exp(-rho * 1.5) * (0.25 + 0.75 * veil);
      }
    }
  }

  // tanh tone map per channel, tunable knee
  vec3 col = tanh3(acc.rgb / max(uP_exposure, 0.01));
  col = pow(clamp(col, 0.0, 1.0), vec3(uP_contrast));

  // saturation about luminance, then the tint
  float lum = dot(col, vec3(0.299, 0.587, 0.114));
  col = mix(vec3(lum), col, uP_saturation);
  col *= uC_tint;

  // alpha from the brightest channel — emitted light (see shdr-18)
  float peak = max(col.r, max(col.g, col.b));
  float a = clamp(peak * uP_alphaGain, 0.0, 1.0);

  // the night behind: a fill so the ball is a solid sphere, not a cut-out
  col += uC_deep * uP_fill;
  a = max(a, uP_fill);

  // Analytic silhouette — identical construction to shdr-01: exact
  // ray-to-centre distance against the radius, colour AND alpha.
  vec3 mrd = normalize(vec3(orbUV(), -uP_focal));
  float closest = length(cross(vec3(0.0, 0.0, uP_camDist), mrd));
  float band = mix(0.35, 0.012, clamp(uP_edge, 0.0, 1.0));
  float mask = 1.0 - smoothstep(uP_envRadius * (1.0 - band), uP_envRadius * 1.005, closest);
  col *= mask;
  a *= mask;

  // a fresnel rim on the glass, inside the mask
  float fres = smoothstep(uP_envRadius * 0.7, uP_envRadius, closest);
  col += uC_rim * uP_rim * fres * fres * mask;

  // safety taper at the frame boundary — colour as well as alpha
  float r2d = length(orbUV());
  float fade = 1.0 - smoothstep(uP_edgeFade, 1.0, r2d);
  col *= fade;
  a *= fade;

  // Emitted light, so rgb is already premultiplied — do NOT scale by alpha
  // again (see the same note in shdr-31).
  gl_FragColor = vec4(col, a);
}
`;

export const shdr32Orb: OrbVariant = {
  key: "shdr-32",
  label: "SHDR-32",
  note: "a galaxy marched as gas and dust inside the ball",
  frag: GALAXY_FRAG,
  params: [
    { key: "spin", label: "Disc turn", min: 0, max: 3, step: 0.01, default: 0.06, integrate: true },
    { key: "churn", label: "Gas churn", min: 0, max: 5, step: 0.02, default: 0.25, integrate: true },
    { key: "beat", label: "Core beat", min: 0, max: 12, step: 0.05, default: 0.8, integrate: true },
    { key: "twinkle", label: "Twinkle rate", min: 0, max: 12, step: 0.05, default: 1.2, integrate: true },
    { key: "camDist", label: "Camera distance", min: 1, max: 50, step: 0.3, default: 7 },
    { key: "focal", label: "Lens", min: 0.15, max: 15, step: 0.05, default: 2.25 },
    { key: "envRadius", label: "Envelope radius", min: 0.15, max: 15, step: 0.1, default: 2.6 },
    { key: "tilt", label: "Tilt (0 edge-on)", min: 0, max: 1.5, step: 0.01, default: 0.85 },
    { key: "arms", label: "Arm count", min: 1, max: 6, step: 1, default: 2 },
    { key: "wind", label: "Arm winding", min: 0, max: 8, step: 0.05, default: 3.4 },
    { key: "ragged", label: "Arm fray", min: 0, max: 12, step: 0.05, default: 3 },
    { key: "armSharp", label: "Arm sharpness", min: 0.3, max: 8, step: 0.05, default: 2.2 },
    { key: "falloff", label: "Disc falloff", min: 0.3, max: 12, step: 0.05, default: 1.7 },
    { key: "thick", label: "Disc thickness", min: 0.01, max: 1, step: 0.005, default: 0.035 },
    { key: "bulge", label: "Core tightness", min: 2, max: 200, step: 1, default: 40 },
    { key: "core", label: "Core density", min: 0, max: 20, step: 0.1, default: 5 },
    { key: "turbScale", label: "Turbulence scale", min: 0.5, max: 30, step: 0.1, default: 9 },
    { key: "threshold", label: "Clumping", min: 0, max: 1, step: 0.01, default: 0.35 },
    { key: "density", label: "Gas density", min: 0.1, max: 40, step: 0.1, default: 14 },
    { key: "absorb", label: "Dust absorption", min: 0, max: 20, step: 0.1, default: 3.5 },
    { key: "stars", label: "Stars", min: 0, max: 10, step: 0.05, default: 1.2 },
    { key: "starDensity", label: "Star density", min: 0, max: 1, step: 0.01, default: 0.5 },
    { key: "starScale", label: "Star scale", min: 5, max: 200, step: 1, default: 48 },
    { key: "hueReach", label: "Hue reach", min: 0.15, max: 1.5, step: 0.01, default: 0.6 },
    { key: "pulse", label: "Beat depth", min: 0, max: 3, step: 0.01, default: 0.2 },
    { key: "breathe", label: "Disc breathing", min: 0, max: 2, step: 0.01, default: 0 },
    { key: "exposure", label: "Exposure", min: 0.05, max: 50, step: 0.05, default: 1.1 },
    { key: "contrast", label: "Contrast", min: 0.15, max: 6, step: 0.05, default: 1.15 },
    { key: "saturation", label: "Saturation", min: 0, max: 4, step: 0.02, default: 1.35 },
    { key: "alphaGain", label: "Alpha gain", min: 0.05, max: 15, step: 0.1, default: 2 },
    { key: "fill", label: "Night fill", min: 0, max: 1, step: 0.01, default: 0.85 },
    { key: "rim", label: "Rim light", min: 0, max: 3, step: 0.015, default: 0.35 },
    { key: "edge", label: "Edge sharpness", min: 0, max: 1, step: 0.01, default: 1 },
    { key: "edgeFade", label: "Halo falloff", min: 0.1, max: 3, step: 0.015, default: 0.98 }
  ],
  /*
   * Six stops: the overall tint, the core the bulge whitens toward, the
   * inner and outer arm colours the ramp runs between, the night the ball
   * is filled with, and the glass rim.
   */
  colors: [
    { key: "tint", label: "Tint", default: "#ffffff" },
    { key: "core", label: "Core", default: "#fff3d6" },
    { key: "inner", label: "Inner arms", default: "#7fb4ff" },
    { key: "outer", label: "Outer arms", default: "#c46bff" },
    { key: "deep", label: "Night", default: "#04050f" },
    { key: "rim", label: "Rim", default: "#8fb0ff" }
  ],
  /*
    Staged on the TILT first — each state is a different view of the disc —
    and then on the clocks and amplitudes. The tilt glides, and a tipping
    disc is the biggest, most legible motion this orb has, so the state
    change itself is the tell. The arm count and winding, the turbulence
    scale and the star scale all multiply a coordinate and are pinned.
  */
  statePresets: {
    /*
      at rest: the spiral seen about halfway between edge-on and face-on.
      A slow turn, the gas barely boiling, a lazy shallow beat on the core.
    */
    idle: {
      tilt: 0.85,
      spin: 0.06,
      churn: 0.25,
      beat: 0.8,
      twinkle: 1.2,
      pulse: 0.2,
      breathe: 0,
      ragged: 3,
      armSharp: 2.2,
      thick: 0.035,
      threshold: 0.35,
      density: 14,
      core: 5,
      absorb: 3.5,
      stars: 1.2,
      exposure: 1.1,
      contrast: 1.15,
      saturation: 1.35
    },
    /*
      searching: the disc swings FACE-ON and becomes a whirlpool. The arms
      fray to nothing and the gas boils at six times rest on a thicker
      disc, a sparser clumping and a heavier absorption, so what is left is
      filaments and shadow spinning at seven times rest — face-on, the turn
      is fully visible — with the core held down. Cold.
    */
    thinking: {
      tilt: 1.45,
      spin: 0.45,
      churn: 1.6,
      beat: 2.4,
      twinkle: 4.5,
      pulse: 0.25,
      breathe: 0,
      ragged: 8,
      armSharp: 1,
      thick: 0.07,
      threshold: 0.5,
      density: 20,
      core: 3,
      absorb: 6,
      stars: 1.8,
      exposure: 1.05,
      contrast: 1.3,
      saturation: 1.2
    },
    /*
      answering: the disc swings FACE-ON and lights up — the full spiral,
      arms sharp and wide, the core flaring on a hard beat (depth five
      times rest on a clock six times as fast) and the whole disc swelling
      outward and drawing back on the same wave. The gas is dense but the
      dust is cleared, so all of it glows, at a lower knee. Hot.
    */
    speaking: {
      tilt: 1.3,
      spin: 0.2,
      churn: 0.6,
      beat: 4.8,
      twinkle: 2.4,
      pulse: 1,
      breathe: 0.45,
      ragged: 2,
      armSharp: 1.8,
      thick: 0.04,
      threshold: 0.25,
      density: 18,
      core: 12,
      absorb: 1.6,
      stars: 2,
      exposure: 0.75,
      contrast: 1.05,
      saturation: 1.6
    }
  },
  // blue into violet at rest, ice into cyan while searching, gold into rose
  // while answering
  stateColors: {
    idle: { tint: "#ffffff", core: "#fff3d6", inner: "#7fb4ff", outer: "#c46bff", deep: "#04050f", rim: "#8fb0ff" },
    thinking: { tint: "#ffffff", core: "#e6f0ff", inner: "#6fb0ff", outer: "#4fe3ff", deep: "#030614", rim: "#7fa8ff" },
    speaking: { tint: "#ffffff", core: "#fff4c8", inner: "#ffa63c", outer: "#ff3f8e", deep: "#0a0508", rim: "#ffb98a" }
  }
};

export type Shdr32Props = Omit<ShaderOrbProps, "variant">;

export function Shdr32({ size = 280, ...rest }: Shdr32Props) {
  return <ShaderOrb variant={shdr32Orb} size={size} {...rest} />;
}

export default Shdr32;

35. components/ui/shdr-33.tsx

/*
 * Deliberately not a `"use client"` module — see the note in `shdr-11.tsx`.
 *
 * Note for editors: the shader lives in a template literal, so its comments
 * must not contain backticks.
 */
import { ShaderOrb, type OrbVariant, type ShaderOrbProps } from "@/components/ui/orbkit-core";

/* ----------------------------------------------------------------------------
   SHDR-33 — a thermal image, risograph-printed, wrapped on the ball.

   The reference is a heat map put through a cheap colour print: blocky
   sources glowing white-hot inside nested rectangular contours that cool
   from yellow through orange and violet to black, all of it rendered as a
   coarse square-dot halftone whose three ink screens do not quite line up,
   on grainy paper. Three stages reproduce it:

   - THE HEAT. A noise field, not placed sources: three octaves of value
     noise, domain-warped by a coarser noise so the pools bend and pinch,
     with a threshold window cut out of it — everything below the window
     is the cold field, everything above is white-hot, and the window in
     between is where the ramp lives. Value noise at low frequency sits on
     a lattice, which is what gives the pools their blocky, rectangular
     lean without anything being drawn as a rectangle, and the warp is
     what keeps them from looking like a grid. The field DRIFTS under the
     sample point, and the warp drifts at a different rate, so the pools
     travel, merge and split rather than sit. The field is then QUANTIZED
     into bands and blended back with the smooth field, which draws the
     nested contours: a thermal camera's palette is a lookup with visible
     steps, and the steps are the contours.
   - THE PALETTE. Five stops climb the heat — near-black, violet-blue,
     red-orange, yellow, white — the classic false-colour thermal ramp.
   - THE PRINT. The palette colour is separated into cyan, magenta and
     yellow ink coverage (1 - channel), and each ink is laid down as a
     screen of round dots whose size carries the coverage, mixed in lightly
     over the smooth palette so it reads as texture rather than a grid. The three screens sit at
     slightly different angles and offsets, so where two overlap you get
     the violet (cyan over magenta) and the orange (magenta over yellow) of
     the reference, and the misregistration gives the moire that makes it
     look printed rather than rendered. Paper white shows through where the
     dots are small, which is why the hot cores come out pale.
   - GRAIN. Two taps of animated noise as in shdr-17: one folded into the
     heat before quantizing, so the band edges dither; one over the final
     print, as paper.

   The plane is sampled through a stereographic wrap of a rotating dome, so
   the print compresses toward the limb and rolls around the ball rather
   than sitting flat on a disc. Surface-lit and mask-bounded, so alpha IS
   coverage — premultiplied output, as in shdr-14.
---------------------------------------------------------------------------- */

const HEAT_FRAG = `
const float PI = 3.14159265359;

// Volume-reactive values, resolved once per fragment in main().
float heatGainNow;
float heatJitterNow;

float grainNoise(vec2 gpix, float frame, float seed) {
  return hash(gpix + vec2(frame * 13.71 + seed, frame * 7.37 - seed));
}

mat2 rot2(float a) {
  float c = cos(a);
  float s = sin(a);
  return mat2(c, -s, s, c);
}

/*
  One ink screen. Square dots on a grid at angle a, offset o (the
  misregistration), sized by the coverage: coverage 0 is paper, coverage 1
  is a solid. Returns how much of this pixel the ink covers.

  The grid is laid in SCREEN space, not on the wrapped plane: a print is
  flat, and it is the picture that curves round the ball. A screen on the
  wrapped coordinates changes pitch toward the limb and beats against the
  other two into moire rings.
*/
float screen(vec2 uv, float a, vec2 o, float coverage, float soft) {
  vec2 cell = rot2(a) * uv * uP_dots + o;
  vec2 f = fract(cell) - 0.5;
  float d = length(f); // a round dot: reads as tone, not as a grid
  // dot half-size from coverage; sqrt so mid-tones read as mid-tones the
  // way a real screen's area does
  float size = 0.5 * sqrt(clamp(coverage * uP_dotGain, 0.0, 1.0));
  return 1.0 - smoothstep(size - soft, size + soft, d);
}

void main() {
  heatGainNow = uP_gain * (1.0 + 0.6 * uOutput);
  heatJitterNow = uP_jitter * (1.0 + 1.5 * uInput);

  vec2 uv = orbUV();
  float rd = length(uv);
  float R = uP_radius;
  float mask = smoothstep(0.012, -0.012, rd - R);

  if (mask <= 0.0) {
    gl_FragColor = vec4(0.0);
    return;
  }

  vec2 pl = uv / R;
  float r2 = dot(pl, pl);
  float z = sqrt(max(1.0 - r2, 0.0));
  vec3 n = vec3(pl, z);

  // roll the dome about Y on its own integrated clock
  float cr = cos(uP_spin);
  float sr = sin(uP_spin);
  vec3 sp = vec3(n.x * cr - n.z * sr, n.y, n.x * sr + n.z * cr);

  float t = uP_speed; // integrated clock: the sources drift

  // stereographic wrap of the plane onto the ball
  vec2 st = sp.xy / (1.3 + sp.z) * uP_scale;

  /*
    The heat: a drifting, domain-warped noise field with a threshold window
    cut out of it. Two drifts at different rates so the pools travel and
    change shape rather than slide as one sheet; the input jitter is a fast
    wobble on top.
  */
  vec2 p = st * uP_freq + vec2(t * 0.11, -t * 0.07);
  vec2 wp = st * uP_freq * 0.55 + vec2(-t * 0.05, t * 0.08);
  vec2 warp = vec2(noise(wp + 3.1), noise(wp + 9.4)) - 0.5;
  p += warp * uP_warp;
  p += vec2(sin(t * 3.7), cos(t * 4.3)) * heatJitterNow;
  float field = noise(p) * 0.62 + noise(p * 2.1 + 5.3) * 0.26 + noise(p * 4.2 + 1.7) * 0.12;
  float heat = clamp((field - uP_lo) * heatGainNow / max(uP_hi - uP_lo, 0.01), 0.0, 1.0);

  // grain tap 1: dither the field before it is banded, so the contour
  // edges break up into speckle instead of clean steps
  vec2 gpix = floor(gl_FragCoord.xy / max(uP_grainSize, 1.0));
  float frame = floor(uTime * 48.0);
  heat += (grainNoise(gpix, frame, 3.1) - 0.5) * uP_dither;

  // the contours: quantize into bands, blend back with the smooth field
  float banded = floor(heat * uP_bands + 0.5) / uP_bands;
  heat = clamp(mix(heat, banded, uP_banding), 0.0, 1.0);
  heat = pow(heat, uP_contrast);

  // the thermal ramp
  vec3 base = mix(uC_cold, uC_cool, smoothstep(0.0, 0.3, heat));
  base = mix(base, uC_warm, smoothstep(0.3, 0.55, heat));
  base = mix(base, uC_hot, smoothstep(0.55, 0.78, heat));
  base = mix(base, uC_core, smoothstep(0.78, 0.97, heat));

  /*
    The print. Separate the palette into CMY coverage and lay each ink down
    as its own screen; the paper shows through the gaps. The angles are the
    classic offsets, scaled by the misregistration, plus a per-ink shift.
  */
  float soft = uP_dotSoft;
  float mis = uP_misregister;
  float cC = screen(uv, 0.035 * mis, vec2(0.22, 0.12) * mis, 1.0 - base.r, soft);
  float cM = screen(uv, -0.03 * mis, vec2(-0.14, 0.2) * mis, 1.0 - base.g, soft);
  float cY = screen(uv, 0.0, vec2(0.0), 1.0 - base.b, soft);

  vec3 print = uC_paper;
  print *= mix(vec3(1.0), vec3(0.05, 0.62, 0.92), cC * uP_ink);
  print *= mix(vec3(1.0), vec3(0.92, 0.08, 0.48), cM * uP_ink);
  print *= mix(vec3(1.0), vec3(0.98, 0.86, 0.02), cY * uP_ink);

  // the unprinted palette is mixed back a little so the blacks stay black
  // and the screens never wash the whole ball to paper
  vec3 col = mix(base, print, uP_printMix);

  // grain tap 2: paper
  col *= 1.0 + (grainNoise(gpix, frame, 27.9) - 0.5) * uP_grain;

  // dome shading keeps the ball a ball under the print
  float lambert = clamp(dot(n, normalize(vec3(-0.45, 0.55, 0.7))), 0.0, 1.0);
  col *= 1.0 - uP_light * (1.0 - lambert);
  float fres = pow(1.0 - z, 2.5);
  col += uC_paper * uP_rim * fres * 0.5;

  // Surface orb bounded by a mask: alpha IS coverage, so premultiply — the
  // opposite convention from the emissive orbs (see shdr-31).
  float a = mask;
  gl_FragColor = vec4(max(col, vec3(0.0)) * a, a);
}
`;

// the rest look: warm pools drifting slowly, contours soft
const HEAT_REST = {
  speed: 0.5,
  spin: 0.05,
  gain: 1,
  warp: 0.6,
  lo: 0.42,
  hi: 0.74,
  jitter: 0.015,
  banding: 0.85,
  grain: 0.35,
  contrast: 1
};

const HEAT_PALETTE = {
  cold: "#0b0a1e",
  cool: "#3b2a9a",
  warm: "#f05a28",
  hot: "#f6b53a",
  core: "#fff1e6"
};

export const shdr33Orb: OrbVariant = {
  key: "shdr-33",
  label: "SHDR-33",
  note: "a thermal image, risograph-printed on the ball",
  frag: HEAT_FRAG,
  params: [
    { key: "speed", label: "Drift", min: 0.015, max: 10, step: 0.05, default: 0.5, integrate: true },
    { key: "spin", label: "Roll", min: 0, max: 5, step: 0.03, default: 0.05, integrate: true },
    { key: "radius", label: "Radius", min: 0.15, max: 3, step: 0.015, default: 0.9 },
    { key: "scale", label: "Zoom", min: 0.3, max: 8, step: 0.05, default: 3 },
    { key: "freq", label: "Pool scale", min: 0.2, max: 6, step: 0.05, default: 1.4 },
    { key: "warp", label: "Warp", min: 0, max: 3, step: 0.02, default: 0.6 },
    { key: "lo", label: "Cold threshold", min: 0, max: 1, step: 0.005, default: 0.42 },
    { key: "hi", label: "Hot threshold", min: 0, max: 1, step: 0.005, default: 0.74 },
    { key: "gain", label: "Heat gain", min: 0.1, max: 6, step: 0.02, default: 1 },
    { key: "jitter", label: "Heat jitter", min: 0, max: 0.5, step: 0.005, default: 0.015 },
    { key: "bands", label: "Contour bands", min: 2, max: 24, step: 1, default: 7 },
    { key: "banding", label: "Contour strength", min: 0, max: 1, step: 0.01, default: 0.85 },
    { key: "contrast", label: "Contrast", min: 0.3, max: 3, step: 0.02, default: 1 },
    { key: "dither", label: "Dither", min: 0, max: 0.6, step: 0.005, default: 0.05 },
    { key: "dots", label: "Screen pitch", min: 4, max: 120, step: 1, default: 46 },
    { key: "dotGain", label: "Dot gain", min: 0.2, max: 2, step: 0.01, default: 1 },
    { key: "dotSoft", label: "Dot softness", min: 0.01, max: 0.3, step: 0.005, default: 0.12 },
    { key: "misregister", label: "Misregistration", min: 0, max: 3, step: 0.02, default: 0.5 },
    { key: "ink", label: "Ink density", min: 0, max: 1, step: 0.01, default: 0.92 },
    { key: "printMix", label: "Print mix", min: 0, max: 1, step: 0.01, default: 0.28 },
    { key: "grain", label: "Paper grain", min: 0, max: 2, step: 0.01, default: 0.35 },
    { key: "grainSize", label: "Grain size", min: 1, max: 8, step: 1, default: 2 },
    { key: "light", label: "Key light", min: 0, max: 1, step: 0.01, default: 0.25 },
    { key: "rim", label: "Rim light", min: 0, max: 3, step: 0.015, default: 0.25 }
  ],
  /*
   * Six stops: five up the thermal ramp, and the paper the screens are
   * printed on.
   */
  colors: [
    { key: "cold", label: "Cold", default: "#0b0a1e" },
    { key: "cool", label: "Cool", default: "#3b2a9a" },
    { key: "warm", label: "Warm", default: "#f05a28" },
    { key: "hot", label: "Hot", default: "#f6b53a" },
    { key: "core", label: "Core", default: "#fff1e6" },
    { key: "paper", label: "Paper", default: "#f4ecdf" }
  ],
  /*
    All three states share the rest palette; idle is the rest preset.
    Speaking is the rest look set RACING in place — the drift at eighteen
    times rest on a roll forty times as fast, the pools slightly finer and
    the warp more than doubled, with the window dropped so more of it
    reads as hot — without the rescale thinking makes. Thinking is the
    rest look zoomed out and set racing: the plane at four times the
    zoom with the pools three times finer and the warp tripled, the drift
    at twenty times rest on a roll ten times as fast, the window dropped
    so more of it reads as hot, on fewer, softer bands and a finer screen.
    Note the zoom, the pool scale, the band count and the screen pitch all
    multiply a coordinate, so the transition into and out of thinking
    glides through a rescale — chosen deliberately.
  */
  statePresets: {
    idle: HEAT_REST,
    thinking: {
      ...HEAT_REST,
      speed: 10,
      spin: 0.51,
      scale: 4.6,
      freq: 3.1,
      warp: 1.82,
      lo: 0.3,
      hi: 0.66,
      jitter: 0,
      bands: 6,
      banding: 0.75,
      dots: 42
    },
    speaking: {
      ...HEAT_REST,
      speed: 8.8,
      spin: 2.01,
      freq: 1.3,
      warp: 1.42,
      lo: 0.345,
      hi: 0.72,
      jitter: 0
    }
  },
  stateColors: {
    idle: HEAT_PALETTE,
    thinking: HEAT_PALETTE,
    speaking: HEAT_PALETTE
  }
};

export type Shdr33Props = Omit<ShaderOrbProps, "variant">;

export function Shdr33({ size = 280, ...rest }: Shdr33Props) {
  return <ShaderOrb variant={shdr33Orb} size={size} {...rest} />;
}

export default Shdr33;

36. Use it

import { Shdr01 } from "@/components/ui/shdr-01";

<Shdr01 size={280} state="idle" />