Home

28. Illustration · 2026 · Modern experiments

Spaghetti vortex

Demos are animating. Following your system setting.
Colors and knobs

These rewrite the live file. Copy HTML picks up whatever you set.

About

Parametric helices scaled by the 2026 OpenAI paper's leading laws: radius follows tau^(1/2), height tau^(1/2−h), with illustrative h = 0.005 and tau = 1−t/T*. Both shrink; radius shrinks faster. Drag to orbit, scrub toward T*, or follow the core with a labeled uniform zoom. Color shows normalized radius. Oscillatory corrections are omitted. Illustration of the proposed blowup construction; not a numerical reproduction of the proof.

Drag to orbit; auto-rotation stops after the first drag. Play, pause, scrub the approach, or follow the core.

Reference

Interpretation: this evokes a period look and does not reproduce one specific original.

Browser APIs

  • WebGL 1
  • GLSL ES 1.0
  • Pointer Events
  • requestAnimationFrame
  • matchMedia

If WebGL is missing, the demo draws a message on the canvas instead of a blank frame. By default a demo holds a still frame when your system asks for reduced motion; the Motion buttons override that, and a standalone copy animates with #motion=play on its URL.

Source

(function () {
  const PARAMS = {
    color: "#6ee7b7",
    core: "#f2b45c",
    trail: 0.35,
    speed: 1
  };
  // Leading scales: OpenAI (2026), section 2.1. h is illustrative.
  // These scales do not define a velocity field or simulate its corrections.
  const H = 0.005, MAX_S = 12, COUNT = 960, SAMPLES = 24;
  function tau(s) { return Math.pow(10, -s); }
  function scales(s) {
    const t = tau(s);
    return { radial: Math.pow(t, 0.5), axial: Math.pow(t, 0.5 - H), speed: Math.pow(t, -0.5 - H) };
  }
  function seed(i) {
    // Fixed low-discrepancy seeds: reset and reverse seeking need no RNG state.
    return { phase: (i * 0.61803398875) % 1, angle: (i * 2.60258057) % (2 * Math.PI), side: i % 2 ? 1 : -1 };
  }
  function position(p, s) {
    const k = scales(s), cycle = p.phase + s * 0.65;
    const q = cycle - Math.floor(cycle);
    // An inward helix bends into two axial exits. Recycling is a schematic
    // seeding device; history segments across a recycle boundary are omitted.
    const r = 0.14 + 1.36 * Math.exp(-3.6 * q);
    const angle = p.angle + s * 6 + q * 10;
    return [k.radial * r * Math.cos(angle), k.axial * p.side * (0.08 + 2.1 * q * q), k.radial * r * Math.sin(angle), r / 1.5, Math.floor(cycle)];
  }
  function rgb(hex) {
    const n = parseInt(hex.slice(1), 16);
    return [(n >> 16 & 255) / 255, (n >> 8 & 255) / 255, (n & 255) / 255];
  }
  const canvas = document.getElementById("c"), fallback = document.getElementById("fallback");
  const play = document.getElementById("play"), slider = document.getElementById("approach");
  const follow = document.getElementById("follow"), reset = document.getElementById("reset");
  const timeText = document.getElementById("time"), zoomText = document.getElementById("zoom"), ruler = document.getElementById("ruler");
  document.getElementById("ramp").style.background = "linear-gradient(90deg," + PARAMS.core + "," + PARAMS.color + ")";
  let gl;
  try { gl = canvas.getContext("webgl", { alpha: false, antialias: true }); } catch (_) { /* Fallback below. */ }
  function fail(message) {
    fallback.hidden = false;
    fallback.textContent = message;
    for (const el of [play, slider, follow, reset]) el.disabled = true;
    // A failed WebGL creation still allows a Canvas 2D explanation.
    if (!gl) {
      const ctx = canvas.getContext("2d");
      if (ctx) {
        canvas.width = Math.max(1, canvas.clientWidth); canvas.height = Math.max(1, canvas.clientHeight);
        ctx.fillStyle = "#000"; ctx.fillRect(0, 0, canvas.width, canvas.height);
        ctx.fillStyle = "#c8d7d0"; ctx.font = "12px monospace"; ctx.textAlign = "center";
        ctx.fillText("WebGL unavailable", canvas.width / 2, canvas.height / 2);
      }
    }
  }
  if (!gl) { fail("WebGL is unavailable. Enable WebGL to view this illustration."); return; }
  const VERT = `attribute vec3 a_pos;
attribute vec2 a_style;
uniform mat4 u_mvp;
uniform float u_zoom;
uniform float u_point;
varying vec2 v_style;
varying float v_depth;
void main() {
  gl_Position = u_mvp * vec4(a_pos * u_zoom, 1.0);
  gl_PointSize = u_point;
  v_style = a_style;
  v_depth = clamp(1.5 - gl_Position.w * 0.1, 0.25, 1.0);
}`;
  const FRAG = `precision mediump float;
uniform vec3 u_outer;
uniform vec3 u_core;
varying vec2 v_style;
varying float v_depth;
void main() {
  vec3 color = mix(u_core, u_outer, smoothstep(0.08, 0.85, v_style.x));
  gl_FragColor = vec4(color, v_style.y * v_depth);
}`;
  function compile(type, source) {
    const shader = gl.createShader(type);
    gl.shaderSource(shader, source); gl.compileShader(shader);
    if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) throw new Error("Could not compile the vortex shader.");
    return shader;
  }
  // Column-major orbit matrices shared with the gallery's lowpoly example.
  function mul(a, b) {
    const out = new Float32Array(16);
    for (let c = 0; c < 4; c++) for (let r = 0; r < 4; r++) {
      out[c * 4 + r] = a[r] * b[c * 4] + a[4 + r] * b[c * 4 + 1] + a[8 + r] * b[c * 4 + 2] + a[12 + r] * b[c * 4 + 3];
    }
    return out;
  }
  function camera(aspect, pitch, yaw) {
    const f = 1 / Math.tan(Math.PI / 8), near = 0.1, far = 50;
    const proj = new Float32Array([f/aspect,0,0,0, 0,f,0,0, 0,0,(far+near)/(near-far),-1, 0,0,2*far*near/(near-far),0]);
    const cx = Math.cos(pitch), sx = Math.sin(pitch), cy = Math.cos(yaw), sy = Math.sin(yaw);
    const rx = new Float32Array([1,0,0,0, 0,cx,sx,0, 0,-sx,cx,0, 0,0,0,1]);
    const ry = new Float32Array([cy,0,-sy,0, 0,1,0,0, sy,0,cy,0, 0,0,0,1]);
    const view = new Float32Array([1,0,0,0, 0,1,0,0, 0,0,1,0, 0,0,-8.8*Math.max(1,0.8/aspect),1]);
    return mul(proj, mul(view, mul(rx, ry)));
  }
  let prog, buffer, loc;
  function setup() {
    prog = gl.createProgram();
    gl.attachShader(prog, compile(gl.VERTEX_SHADER, VERT));
    gl.attachShader(prog, compile(gl.FRAGMENT_SHADER, FRAG)); gl.linkProgram(prog);
    if (!gl.getProgramParameter(prog, gl.LINK_STATUS)) throw new Error("Could not link the vortex shader.");
    buffer = gl.createBuffer();
    loc = {};
    for (const key of ["mvp", "zoom", "point", "outer", "core"]) loc[key] = gl.getUniformLocation(prog, "u_" + key);
    gl.useProgram(prog); gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
    for (const [name, size, offset] of [["a_pos", 3, 0], ["a_style", 2, 12]]) {
      const at = gl.getAttribLocation(prog, name);
      gl.enableVertexAttribArray(at); gl.vertexAttribPointer(at, size, gl.FLOAT, false, 20, offset);
    }
    gl.uniform3fv(loc.outer, rgb(PARAMS.color)); gl.uniform3fv(loc.core, rgb(PARAMS.core));
    gl.enable(gl.BLEND); gl.blendFunc(gl.SRC_ALPHA, gl.ONE);
    gl.clearColor(0, 0, 0, 1);
  }
  try { setup(); } catch (err) { fail(err.message); return; }
  const motion = matchMedia("(prefers-reduced-motion: reduce)");
  let s = motion.matches ? 6 : 0.6, playing = !motion.matches, auto = !motion.matches;
  let yaw = 0.7, pitch = 0.34, pointer = null, lastX = 0, lastY = 0;
  let raf = 0, last = 0, lost = false, dirty = true;
  let count = COUNT, slow = 0;
  const seeds = Array.from({ length: COUNT }, (_, i) => seed(i));
  // Bounded world-space history ring, regenerated analytically on every seek.
  // Fixed sample spacing makes history independent of playback frame rate.
  const history = new Float32Array(COUNT * SAMPLES * 5);
  const vertices = new Float32Array(COUNT * (SAMPLES - 1) * 2 * 5 + COUNT * 5);
  let lineCount = 0, vertexCount = 0;
  function rebuild() {
    const step = PARAMS.trail / (SAMPLES - 1), tick = Math.floor(s / step), head = tick % SAMPLES;
    let n = 0;
    for (let i = 0; i < count; i++) {
      for (let age = 0; age < SAMPLES; age++) {
        const slot = (head - age + SAMPLES) % SAMPLES;
        const t = age === 0 ? s : Math.max(0, (tick - age + 1) * step);
        history.set(position(seeds[i], t), (i * SAMPLES + slot) * 5);
      }
      for (let age = SAMPLES - 1; age > 0; age--) {
        const a = (i * SAMPLES + (head - age + SAMPLES) % SAMPLES) * 5;
        const b = (i * SAMPLES + (head - age + 1 + SAMPLES) % SAMPLES) * 5;
        if (history[a + 4] !== history[b + 4]) continue;
        for (const at of [a, b]) {
          vertices[n++] = history[at]; vertices[n++] = history[at+1]; vertices[n++] = history[at+2];
          vertices[n++] = history[at+3]; vertices[n++] = 0.16 * (1 - age / SAMPLES);
        }
      }
    }
    lineCount = n / 5;
    for (let i = 0; i < count; i++) {
      const at = (i * SAMPLES + head) * 5;
      vertices[n++] = history[at]; vertices[n++] = history[at+1]; vertices[n++] = history[at+2];
      vertices[n++] = history[at+3]; vertices[n++] = 0.65;
    }
    vertexCount = n / 5;
    dirty = false;
  }
  function draw() {
    const dpr = Math.min(devicePixelRatio || 1, 2);
    const w = Math.max(1, Math.round(canvas.clientWidth * dpr)), 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, canvas.width, canvas.height); gl.clear(gl.COLOR_BUFFER_BIT);
    gl.uniformMatrix4fv(loc.mvp, false, camera(canvas.width / canvas.height, pitch, yaw));
    const mag = follow.checked ? 1 / scales(s).radial : 1;
    gl.uniform1f(loc.zoom, 1); gl.uniform1f(loc.point, Math.min(2, dpr * 1.3));
    // Reference ticks are in view units; the label converts back to world units.
    const axes = [];
    function line(a, b) { for (const p of [a, b]) axes.push(...p, 1, 0.13); }
    line([0,-2.7,0], [0,2.7,0]);
    for (let i = -3; i <= 3; i++) {
      line([i*0.5,-2.5,-1.5], [i*0.5,-2.5,1.5]);
      line([-1.5,-2.5,i*0.5], [1.5,-2.5,i*0.5]);
    }
    gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(axes), gl.DYNAMIC_DRAW);
    gl.drawArrays(gl.LINES, 0, axes.length / 5);
    if (dirty) rebuild();
    gl.uniform1f(loc.zoom, mag);
    gl.bufferData(gl.ARRAY_BUFFER, vertices.subarray(0, vertexCount * 5), gl.DYNAMIC_DRAW);
    gl.drawArrays(gl.LINES, 0, lineCount);
    gl.drawArrays(gl.POINTS, lineCount, vertexCount - lineCount);
    timeText.textContent = "1−t/T* = " + tau(s).toExponential(2);
    zoomText.textContent = (follow.checked ? "FOLLOW · " : "FIXED · ") + mag.toExponential(2) + "×";
    ruler.textContent = "grid Δ = " + (0.5 / mag).toExponential(1) + " initial units";
    slider.value = String(s);
    slider.setAttribute("aria-valuetext", "s " + s.toFixed(2) + ", remaining time " + tau(s).toExponential(2));
    play.textContent = s >= MAX_S ? "Replay" : playing ? "Pause" : "Play";
    play.setAttribute("aria-pressed", String(playing));
  }
  function wake() { if (!raf && !lost && !document.hidden) raf = requestAnimationFrame(frame); }
  function frame(now) {
    raf = 0;
    const dt = last ? Math.min((now - last) / 1000, 0.05) : 0;
    last = now;
    if (playing) { s = Math.min(MAX_S, s + dt * 0.24 * PARAMS.speed); dirty = true; if (s >= MAX_S) playing = false; }
    if (auto) yaw += dt * 0.09;
    const start = performance.now();
    draw();
    // Reduce geometry only after sustained expensive frames, never change seeds.
    slow = performance.now() - start > 24 ? slow + 1 : 0;
    if (slow > 45 && count > 480) { count = 480; dirty = true; slow = 0; }
    if (playing || auto) wake(); else last = 0;
  }
  play.addEventListener("click", function () {
    if (motionMode === "still") return; // an explicit "still" override wins over the in-demo button
    if (s >= MAX_S) { s = 0.6; dirty = true; }
    playing = !playing; last = 0; wake();
  });
  slider.addEventListener("input", function () {
    s = Math.max(0, Math.min(MAX_S, Number(slider.value))); playing = false; dirty = true; last = 0; wake();
  });
  follow.addEventListener("change", wake);
  reset.addEventListener("click", function () {
    const still = stillNow();
    s = still ? 6 : 0.6; yaw = 0.7; pitch = 0.34; count = COUNT; slow = 0;
    playing = !still; auto = !still; follow.checked = true; pointer = null; dirty = true; last = 0; wake();
  });
  canvas.addEventListener("pointerdown", function (e) {
    if (pointer !== null) return;
    pointer = e.pointerId; lastX = e.clientX; lastY = e.clientY;
    canvas.setPointerCapture(e.pointerId);
  });
  canvas.addEventListener("pointermove", function (e) {
    if (e.pointerId !== pointer) return;
    auto = false; yaw += (e.clientX - lastX) * 0.008;
    pitch = Math.max(-1.2, Math.min(1.2, pitch + (e.clientY - lastY) * 0.008));
    lastX = e.clientX; lastY = e.clientY; wake();
  });
  for (const name of ["pointerup", "pointercancel", "lostpointercapture"]) canvas.addEventListener(name, function () { pointer = null; });
  // Motion. The system's reduced-motion preference decides by default. A
  // standalone copy animates anyway with "#motion=play" in its URL (or holds a
  // frame with "#motion=still"); an embedding page can post {motion: "play"}.
  let motionMode = "auto";
  function stillNow() { return motionMode === "still" || (motionMode === "auto" && motion.matches); }
  function applyMotion() {
    const still = stillNow();
    play.disabled = motionMode === "still";
    if (still) { playing = false; auto = false; }
    else if (!playing && !auto) { if (s >= MAX_S) s = 0.6; playing = true; auto = true; dirty = true; }
    last = 0; wake();
  }
  function setMotion(mode) {
    if (mode === "play" || mode === "still" || mode === "auto") { motionMode = mode; applyMotion(); }
  }
  function readMotionHash() {
    const m = /motion=(play|still|auto)/.exec(location.hash);
    let mode = m ? m[1] : "auto";
    // Embedded in the gallery, the page sets window.__demoMotion before any frame loads.
    try { if (!m && window.parent !== window && typeof window.parent.__demoMotion === "string") mode = window.parent.__demoMotion; } catch (e) { /* cross-origin parent */ }
    setMotion(mode);
  }
  motion.addEventListener("change", applyMotion);
  window.addEventListener("hashchange", readMotionHash);
  window.addEventListener("message", function (e) { if (e.data && typeof e.data.motion === "string") setMotion(e.data.motion); });
  document.addEventListener("visibilitychange", function () {
    cancelAnimationFrame(raf); raf = 0; last = 0; if (!document.hidden) wake();
  });
  new ResizeObserver(wake).observe(document.getElementById("scene"));
  canvas.addEventListener("webglcontextlost", function (e) {
    e.preventDefault(); lost = true; cancelAnimationFrame(raf); raf = 0;
    fail("WebGL context lost. Waiting for the browser to restore it.");
  });
  canvas.addEventListener("webglcontextrestored", function () {
    try {
      setup(); lost = false; dirty = true; last = 0; fallback.hidden = true;
      for (const el of [play, slider, follow, reset]) el.disabled = false;
      wake();
    } catch (err) { fail(err.message); }
  });
  window.__vortexTest = {
    getS: function () { return s; },
    isPlaying: function () { return playing; },
    isAuto: function () { return auto; }
  };

  wake();
  readMotionHash();
})();