> ## Documentation Index
> Fetch the complete documentation index at: /llms.txt
> Use this file to discover all available pages before exploring further.

# Cosmic Orb

> An iridescent soap-bubble planet: a slowly spinning sphere of nebula bands, dust lanes and twinkling stars wrapped in a refracting glass limb with chromatic aberration at the edge.

# Cosmic Orb

An iridescent soap-bubble planet: a slowly spinning sphere of nebula bands, dust lanes and twinkling stars wrapped in a refracting glass limb with chromatic aberration at the edge.

**Cosmic Orb**

```html cosmic-orb.html
<!doctype html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=1920, height=1080" />
    <title>Cosmic Orb</title>
    <script src="https://cdn.jsdelivr.net/npm/gsap@3.14.2/dist/gsap.min.js"></script>
    <style>
      *,
      *::before,
      *::after {
        margin: 0;
        padding: 0;
        box-sizing: border-box;
      }
      body {
        background: #000;
        overflow: hidden;
      }
      #co-root {
        position: relative;
        width: 1920px;
        height: 1080px;
        overflow: hidden;
      }
      #co-backdrop {
        position: absolute;
        inset: 0;
        background: #04040a;
      }
      #co-canvas {
        position: absolute;
        top: 0;
        left: 0;
        width: 1920px;
        height: 1080px;
      }
    </style>
  </head>
  <body>
    <div
      id="co-root"
      data-composition-id="cosmic-orb"
      data-root="true"
      data-width="1920"
      data-height="1080"
      data-start="0"
      data-duration="10"
      data-composition-variables='[
        {"id":"hue","type":"number","label":"Nebula hue","default":268,"min":0,"max":360,"step":1,"unit":"deg"},
        {"id":"accent","type":"number","label":"Accent hue","default":196,"min":0,"max":360,"step":1,"unit":"deg"},
        {"id":"spin","type":"number","label":"Spin rate","default":0.035,"min":0,"max":0.5,"step":0.005,"unit":"turns/s"},
        {"id":"stars","type":"number","label":"Star density","default":1,"min":0,"max":2,"step":0.05},
        {"id":"glow","type":"number","label":"Glow intensity","default":1,"min":0,"max":2,"step":0.05},
        {"id":"size","type":"number","label":"Orb size","default":0.86,"min":0.2,"max":1.2,"step":0.01},
        {"id":"pulse","type":"number","label":"Beat pulse","default":0,"min":0,"max":1,"step":0.01},
        {"id":"pulseEnvelope","type":"string","label":"Beat pulse envelope (comma-separated 0-1 samples)","default":"","placeholder":"0,0.2,1,0.4,0"},
        {"id":"backdrop","type":"color","label":"Backdrop","default":"#04040a"}
      ]'
    >
      <div id="co-backdrop"></div>
      <canvas id="co-canvas" width="1920" height="1080"></canvas>

      <!-- Driver clip: gives HyperFrames a timed element to own on track 0. -->
      <div
        id="co-drv"
        class="clip"
        data-start="0"
        data-duration="10"
        data-track-index="0"
        style="position: absolute; width: 1px; height: 1px; opacity: 0; pointer-events: none"
      ></div>
    </div>

    <script>
      (function () {
        var DUR = 10;
        var W = 1920;
        var H = 1080;

        var ROOT = document.getElementById("co-root");
        var CS = getComputedStyle(ROOT);

        // `data-composition-variables` is the single owner of every declared
        // default — parse it rather than repeating each default a second time
        // in JS. `window.__hyperframes` is NOT guaranteed to exist yet when this
        // inline script runs, so it can only ever be an override, never the
        // source. Precedence: host CSS custom property, runtime variable bag,
        // declared default.
        var DECL = {};
        JSON.parse(ROOT.getAttribute("data-composition-variables") || "[]").forEach(function (v) {
          DECL[v.id] = v.default;
        });
        var HF = window.__hyperframes;
        var V = (HF && HF.getVariables && HF.getVariables()) || {};

        function raw(id) {
          var css = CS.getPropertyValue("--" + id.toLowerCase()).trim();
          if (css !== "") return css;
          if (V[id] !== undefined && V[id] !== "") return V[id];
          return DECL[id];
        }
        function num(id) {
          var n = parseFloat(raw(id));
          return isFinite(n) ? n : 0;
        }
        function str(id) {
          var s = raw(id);
          return typeof s === "string" ? s.trim() : "";
        }

        var HUE = num("hue");
        var ACCENT = num("accent");
        var SPIN = num("spin");
        var STARS = num("stars");
        var GLOW = num("glow");
        var SIZE = num("size");
        var PULSE = num("pulse");
        var BACKDROP = str("backdrop");

        document.getElementById("co-backdrop").style.background = BACKDROP;

        // Beat hook. A precomputed envelope (comma-separated 0-1 samples spread
        // evenly across the clip) is sampled closed-form at time t — no audio
        // analysis, no accumulator. Empty envelope => the flat `pulse` value.
        var ENV = str("pulseEnvelope")
          .split(",")
          .map(function (s) {
            return parseFloat(s);
          })
          .filter(function (n) {
            return isFinite(n);
          });

        function pulseAt(t) {
          if (ENV.length === 0) return PULSE;
          if (ENV.length === 1) return ENV[0];
          var x = Math.min(1, Math.max(0, t / DUR)) * (ENV.length - 1);
          var i = Math.floor(x);
          var j = Math.min(i + 1, ENV.length - 1);
          return ENV[i] + (ENV[j] - ENV[i]) * (x - i);
        }

        var canvas = document.getElementById("co-canvas");
        var gl =
          canvas.getContext("webgl", {
            alpha: true,
            antialias: false,
            depth: false,
            stencil: false,
            preserveDrawingBuffer: true,
            powerPreference: "high-performance",
          }) ||
          canvas.getContext("experimental-webgl", {
            alpha: true,
            preserveDrawingBuffer: true,
          });

        var VERT = [
          "attribute vec2 aPos;",
          "void main() { gl_Position = vec4(aPos, 0.0, 1.0); }",
        ].join("\n");

        var FRAG = [
          "precision highp float;",
          "uniform vec2 uRes;",
          "uniform float uTime;",
          "uniform float uRadius;",
          "uniform float uSpin;",
          "uniform float uHue;",
          "uniform float uAccent;",
          "uniform float uStars;",
          "uniform float uGlow;",
          "uniform float uPulse;",
          "const float TAU = 6.28318531;",
          "",
          "// One cheap hash for every random-looking thing in here.",
          "float h1(float x) { return fract(sin(x * 127.1) * 43758.5453); }",
          "float h2(vec2 p) { return h1(dot(p, vec2(1.0, 157.31))); }",
          "",
          "vec3 hue2rgb(float h) {",
          "  return clamp(abs(mod(h * 6.0 + vec3(0.0, 4.0, 2.0), 6.0) - 3.0) - 1.0, 0.0, 1.0);",
          "}",
          "",
          "vec3 rotX(vec3 p, float a) { float c = cos(a), s = sin(a); return vec3(p.x, c * p.y - s * p.z, s * p.y + c * p.z); }",
          "vec3 rotY(vec3 p, float a) { float c = cos(a), s = sin(a); return vec3(c * p.x + s * p.z, p.y, -s * p.x + c * p.z); }",
          "vec3 rotZ(vec3 p, float a) { float c = cos(a), s = sin(a); return vec3(c * p.x - s * p.y, s * p.x + c * p.y, p.z); }",
          "",
          "vec3 safeDir(vec3 v) {",
          "  float l = dot(v, v);",
          "  if (l < 1e-6) return vec3(0.0, 0.0, -1.0);",
          "  return v * inversesqrt(l);",
          "}",
          "",
          "// Fixed axial tilt, then spin about the tilted polar axis.",
          "vec3 toBody(vec3 n, float spin) { return rotY(rotZ(rotX(n, -0.34), 0.42), -spin); }",
          "vec2 lonlat(vec3 d) { return vec2(atan(d.x, d.z), asin(clamp(d.y, -1.0, 1.0))); }",
          "",
          "float dens(float base) { return clamp(1.0 - (1.0 - base) * uStars, 0.0, 1.0); }",
          "",
          "// One star size-class: cell grid, seeded jitter, soft round core.",
          "float starField(vec2 q, float freq, float base, float rad) {",
          "  vec2 g = q * freq;",
          "  vec2 id = floor(g);",
          "  float h = h2(id);",
          "  if (h < dens(base)) return 0.0;",
          "  vec2 c = vec2(h2(id + vec2(3.7, 1.3)), h2(id + vec2(9.1, 5.5)));",
          "  float d = length(fract(g) - c);",
          "  float core = smoothstep(rad, 0.0, d);",
          "  // Twinkle: a per-star phase driven straight off uTime, no accumulator.",
          "  float tw = 0.62 + 0.38 * sin(uTime * 2.4 + h * 53.7);",
          "  return core * core * (0.45 + 0.55 * h1(h * 91.7)) * tw;",
          "}",
          "",
          "// 4-point diffraction glare, only worth paying for on the brightest class.",
          "float starGlare(vec2 q, float freq, float base) {",
          "  vec2 g = q * freq;",
          "  vec2 id = floor(g);",
          "  float h = h2(id);",
          "  if (h < dens(base)) return 0.0;",
          "  vec2 c = vec2(h2(id + vec2(3.7, 1.3)), h2(id + vec2(9.1, 5.5)));",
          "  vec2 dv = abs(fract(g) - c);",
          "  float hx = exp(-dv.x * 70.0) * exp(-dv.y * 9.0);",
          "  float hy = exp(-dv.y * 70.0) * exp(-dv.x * 9.0);",
          "  return max(hx, hy) * 0.45;",
          "}",
          "",
          "// Three size-classes at ~6 / 11 / 19 cell frequency.",
          "float starsAll(vec2 q, float gain) {",
          "  float s = starField(q, 6.0, 0.958, 0.20);",
          "  s += starGlare(q, 6.0, 0.958);",
          "  s += starField(q, 11.0, 0.920, 0.18) * 0.95;",
          "  s += starField(q, 19.0, 0.865, 0.17) * 0.75;",
          "  return s * gain;",
          "}",
          "",
          "vec3 nebula(vec2 q) {",
          "  // Two sin-field turbulence layers.",
          "  float t1 = sin(q.x * 3.1 + sin(q.y * 2.7) * 1.9) * sin(q.y * 4.3 + sin(q.x * 1.7) * 2.3);",
          "  float t2 = sin(q.x * 7.9 + sin(q.y * 5.1) * 1.3) * sin(q.y * 9.7 + sin(q.x * 4.4) * 1.1);",
          "  float turb = t1 * 0.66 + t2 * 0.34;",
          "",
          "  // Galaxy bands, warped by the turbulence.",
          "  float band = sin(q.y * 3.2 + turb * 1.7 + q.x * 0.55);",
          "  float bands = pow(max(0.0, band * 0.5 + 0.5), 3.2);",
          "",
          "  // Dust lanes: broad darkened swathes, not a thin curve.",
          "  float lane = sin(q.y * 2.9 + q.x * 1.3 + t1 * 1.6);",
          "  float dust = 0.22 + 0.78 * smoothstep(-0.45, 0.75, lane);",
          "",
          "  // Core bulge.",
          "  float dc = length(vec2((q.x - 0.55) * 0.5, (q.y + 0.12) * 1.1));",
          "  float core = exp(-dc * dc * 2.6);",
          "",
          "  // Two pocket glows.",
          "  float dA = length(vec2((q.x + 1.95) * 0.62, (q.y - 0.52) * 1.45));",
          "  float dB = length(vec2((q.x - 2.35) * 0.55, (q.y + 0.78) * 1.30));",
          "  float pA = exp(-dA * dA * 4.0);",
          "  float pB = exp(-dB * dB * 5.0);",
          "",
          "  vec3 cNeb = hue2rgb(uHue / 360.0);",
          "  vec3 cAcc = hue2rgb(uAccent / 360.0);",
          "  vec3 cWarm = hue2rgb(uHue / 360.0 + 0.07);",
          "",
          "  vec3 col = mix(cNeb, cWarm, 0.5 + 0.5 * turb) * bands * dust * 0.17;",
          "  col += mix(cNeb, vec3(1.0), 0.50) * core * dust * 0.13;",
          "  col += mix(cAcc, vec3(1.0), 0.60) * pA * 0.30 * uGlow;",
          "  col += mix(cNeb, vec3(1.0), 0.50) * pB * 0.24 * uGlow;",
          "  return col;",
          "}",
          "",
          "void main() {",
          "  vec2 p = (gl_FragCoord.xy - uRes * 0.5) / uRadius;",
          "  float r = length(p);",
          "  float aa = 1.4 / uRadius;",
          "  float spin = TAU * uSpin * uTime;",
          "",
          "  vec3 cNeb = hue2rgb(uHue / 360.0);",
          "  vec3 cAcc = hue2rgb(uAccent / 360.0);",
          "",
          "  float mask = smoothstep(1.0 + aa, 1.0 - aa, r);",
          "  float halo = exp(-max(0.0, r - 1.0) * 13.0) * (1.0 - mask);",
          "",
          "  vec3 body = vec3(0.0);",
          "  if (mask > 0.001) {",
          "    float rc = min(r, 1.0);",
          "    // Analytic sphere: the normal falls straight out of z = sqrt(1 - r^2).",
          "    float z = sqrt(max(0.0, 1.0 - rc * rc));",
          "    vec3 N = vec3(p.x, p.y, z);",
          "    vec2 q = lonlat(toBody(safeDir(N), spin));",
          "",
          "    float gain = 0.9 + 2.6 * uPulse;",
          "    body = nebula(q);",
          "    body += vec3(1.0) * starsAll(q, gain) * (0.95 + 0.15 * uGlow);",
          "",
          "    // Limb darkening.",
          "    body *= mix(0.32, 1.0, pow(z, 0.5));",
          "",
          "    // Refract-sampled back layer, split per channel: the chromatic",
          "    // aberration that reads as glass at the limb.",
          "    vec3 I = vec3(0.0, 0.0, -1.0);",
          "    vec3 dR = safeDir(refract(I, N, 1.0 / 1.09));",
          "    vec3 dG = safeDir(refract(I, N, 1.0 / 1.13));",
          "    vec3 dB = safeDir(refract(I, N, 1.0 / 1.17));",
          "    vec2 qR = lonlat(toBody(dR, spin));",
          "    vec2 qG = lonlat(toBody(dG, spin));",
          "    vec2 qB = lonlat(toBody(dB, spin));",
          "    vec3 back = vec3(starsAll(qR, gain), starsAll(qG, gain), starsAll(qB, gain));",
          "    float fres = pow(1.0 - z, 1.8);",
          "    body += back * fres * 2.2;",
          "    body += nebula(qG) * fres * 0.8;",
          "",
          "    // Aurora sheen riding the glass shell.",
          "    float ang = atan(p.y, p.x);",
          "    float sheen = smoothstep(0.72, 0.99, r) * smoothstep(1.0, 0.90, r);",
          "    float w = 0.5 + 0.5 * sin(ang * 2.0 + spin * 1.6);",
          "    body += (mix(cNeb, cAcc, w) * 0.55 + vec3(0.16)) * sheen * 0.30 * uGlow;",
          "",
          "    // Three speculars, fixed in screen space: the key light does not spin.",
          "    vec2 a1 = p - vec2(-0.34, 0.62);",
          "    vec2 a2 = p - vec2(0.58, 0.60);",
          "    vec2 a3 = p - vec2(0.24, -0.80);",
          "    float sp = exp(-dot(a1, a1) * 30.0) * 0.30;",
          "    sp += exp(-dot(a2, a2) * 150.0) * 0.80;",
          "    sp += exp(-dot(a3, a3) * 70.0) * 0.30;",
          "    body += vec3(1.0) * sp * (0.30 + 0.70 * pow(1.0 - z, 1.5)) * uGlow;",
          "",
          "    // Fresnel void glow hugging the inside of the rim.",
          "    body += mix(cAcc, vec3(1.0), 0.4) * pow(1.0 - z, 6.0) * 0.30 * uGlow;",
          "  }",
          "",
          "  vec3 haloCol = mix(cNeb, cAcc, 0.35);",
          "  vec3 outCol = body * mask + haloCol * halo * 0.22 * uGlow;",
          "  float outA = clamp(mask + halo * 0.16 * uGlow, 0.0, 1.0);",
          "  gl_FragColor = vec4(clamp(outCol, 0.0, 4.0), outA);",
          "}",
        ].join("\n");

        var uni = {};
        var ready = false;

        function compile(type, src) {
          var sh = gl.createShader(type);
          gl.shaderSource(sh, src);
          gl.compileShader(sh);
          if (!gl.getShaderParameter(sh, gl.COMPILE_STATUS)) {
            throw new Error("cosmic-orb shader: " + gl.getShaderInfoLog(sh));
          }
          return sh;
        }

        if (gl) {
          var 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("cosmic-orb link: " + gl.getProgramInfoLog(prog));
          }
          gl.useProgram(prog);

          var 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);
          var loc = gl.getAttribLocation(prog, "aPos");
          gl.enableVertexAttribArray(loc);
          gl.vertexAttribPointer(loc, 2, gl.FLOAT, false, 0, 0);

          [
            "uRes",
            "uTime",
            "uRadius",
            "uSpin",
            "uHue",
            "uAccent",
            "uStars",
            "uGlow",
            "uPulse",
          ].forEach(function (n) {
            uni[n] = gl.getUniformLocation(prog, n);
          });

          gl.viewport(0, 0, W, H);
          gl.uniform2f(uni.uRes, W, H);
          gl.uniform1f(uni.uRadius, (SIZE * H) / 2);
          gl.uniform1f(uni.uSpin, SPIN);
          gl.uniform1f(uni.uHue, HUE);
          gl.uniform1f(uni.uAccent, ACCENT);
          gl.uniform1f(uni.uStars, STARS);
          gl.uniform1f(uni.uGlow, GLOW);
          ready = true;
        }

        // Every frame is computed from t alone: spin is spin_rate * t, the
        // pulse is sampled from the envelope at t. No accumulators, no clocks.
        function draw(t) {
          if (!ready) return;
          gl.uniform1f(uni.uTime, t);
          gl.uniform1f(uni.uPulse, pulseAt(t));
          gl.clearColor(0, 0, 0, 0);
          gl.clear(gl.COLOR_BUFFER_BIT);
          gl.drawArrays(gl.TRIANGLES, 0, 3);
          gl.flush();
        }

        window.__timelines = window.__timelines || {};
        var tl = gsap.timeline({ paused: true });

        // The canvas is repainted from a property SETTER, not from onUpdate:
        // gsap's seek(t) suppresses events by default, so an onUpdate callback
        // silently never fires on a scrub and the canvas freezes on frame 0.
        // Tweened values are always written during render, suppressed or not,
        // so this fires on every seek — and hands us the frame time directly.
        var driver = { _t: 0 };
        Object.defineProperty(driver, "t", {
          get: function () {
            return this._t;
          },
          set: function (v) {
            this._t = v;
            draw(v);
          },
        });
        tl.to(driver, { t: DUR, duration: DUR, ease: "none" }, 0);
        window.__timelines["cosmic-orb"] = tl;

        draw(0);
      })();
    </script>
  </body>
</html>
```

Copy npx hyperframes add cosmic-orb to the clipboard

That writes one file: `compositions/cosmic-orb.html`.

## Add it to your video

It runs for 10 seconds at 1920×1080. Paste this into your composition:

```html index.html
<div
  data-composition-id="cosmic-orb"
  data-composition-src="compositions/cosmic-orb.html"
  data-start="0"
  data-duration="10"
  data-track-index="1"
  data-width="1920"
  data-height="1080"
></div>
```

Move it in time with `data-start`. Put it on a different timeline row with
`data-track-index`. See [data attributes](/concepts/data-attributes.md) for the rest.

## Change how it looks

Set these CSS variables on the block:

* `--hue` — Nebula hue. Defaults to `268`.
* `--accent` — Accent hue. Defaults to `196`.
* `--spin` — Spin rate (turns per second). Defaults to `0.035`.
* `--stars` — Star density. Defaults to `1`.
* `--glow` — Glow intensity. Defaults to `1`.
* `--size` — Orb size (fraction of frame height). Defaults to `0.86`.
* `--pulse` — Beat pulse (0-1, flat value when no envelope is set). Defaults to `0`.
* `--pulseenvelope` — Beat pulse envelope: comma-separated 0-1 samples spread evenly across the clip. Defaults to \`\`.
* `--backdrop` — Backdrop. Defaults to `#04040a`.

## Variables

Every one of these has a default, so the piece works untouched. Set the ones you
want to change on the element:

| Variable | Default | Accepts | What it does |
| - | - | - | - |
| `hue` | `268` | 0deg to 360deg, step 1deg | |
| `accent` | `196` | 0deg to 360deg, step 1deg | |
| `spin` | `0.035` | 0turns/s to 0.5turns/s, step 0.005turns/s | |
| `stars` | `1` | 0 to 2, step 0.05 | |
| `glow` | `1` | 0 to 2, step 0.05 | |
| `size` | `0.86` | 0.2 to 1.2, step 0.01 | |
| `pulse` | `0` | 0 to 1, step 0.01 | |
| `pulseEnvelope` | \`\` | string | |
| `backdrop` | `#04040a` | color | |

Set them with `data-variable-values` on the element that mounts it. These are the
defaults, so this behaves exactly like the preview above until you change one:

```html wrap
<div
  data-composition-id="cosmic-orb"
  data-composition-src="compositions/cosmic-orb.html"
  data-variable-values='{"hue":268,"accent":196,"spin":0.035,"stars":1,"glow":1,"size":0.86,"pulse":0,"pulseEnvelope":"","backdrop":"#04040a"}'
></div>
```

{/* hf:generated-footer */}

Tagged `webgl` `shader` `background` `showcase` `3d`.

## Related topics

* [Browse the complete Catalog](/catalog.md)
* [Add assets and Catalog items in the browser editor](/studio/assets-and-blocks.md)
* [Put blocks together into one video](/concepts/compositions.md)
