{
  "schemaVersion": 1,
  "id": "black-hole",
  "version": "1.0.0",
  "status": "published",
  "kind": "background",
  "title": "Black Hole",
  "summary": "A black hole bending the light of the stars around it, a glowing accretion disk wrapping over and under its shadow. Loops perfectly.",
  "description": "A black hole ray-traced in curved space: every pixel's ray is bent by gravity, so the disk of glowing gas is wrapped over the top and under the bottom of the black shadow, the stars are stretched into arcs and a thin halo rings the edge of the shadow. The disk is hotter and brighter on the side moving towards you and turns in rings, each ring a whole number of turns per loop, while the camera sways slowly round. Choose the colours (gold, ice blue, red ember or violet), the viewing angle from edge-on to nearly face-on, how fast the disk spins, how bright it is, how much brighter the approaching side is, how strongly gravity bends light, the stars and the Milky Way.",
  "role": "Background (WebGL shader, full frame): an awe-inspiring space scene for science, sci-fi, music and title content.",
  "collections": [
    "backgrounds",
    "hypnotic-loops"
  ],
  "tags": [
    "black hole",
    "accretion disk",
    "gravitational lensing",
    "space",
    "astronomy",
    "sci-fi",
    "interstellar",
    "event horizon",
    "stars",
    "loop",
    "background"
  ],
  "synonyms": [
    "gargantua",
    "space background",
    "cosmic loop",
    "quasar",
    "singularity",
    "event horizon animation",
    "gravity lens",
    "galaxy core"
  ],
  "useCases": [
    "A title or intro backdrop for science, sci-fi and music videos",
    "A cosmic loop behind explainer and podcast visuals",
    "A game or app space scene"
  ],
  "featured": null,
  "publishedAt": "2026-10-05",
  "implementation": {
    "target": "three",
    "runtime": "browser",
    "language": "javascript",
    "module": "esm",
    "entry": "src/black-hole.js",
    "exports": [
      {
        "name": "createBlackHole",
        "signature": "createBlackHole({ palette?, tilt?, spin?, brightness?, doppler?, gravity?, stars?, milkyWay?, seed? }) => { scene, camera, material, setSize(width, height), update(time, loopSeconds), dispose() }",
        "description": "The whole scene as one full-screen shader: render its scene with its camera after setSize and update, ideally through createBloomPass."
      },
      {
        "name": "createBloomPass",
        "signature": "createBloomPass(renderer, { width, height, glow?, exposure?, grain? }) => { render(scene, camera, output?), dispose() }",
        "description": "A small bloom with filmic tone mapping and a fixed grain: draws a scene to the canvas with its glow."
      },
      {
        "name": "PALETTES",
        "signature": "PALETTES: { gold, ice, ember, violet }",
        "description": "The temperature ramps of the disk: five colours from cool to white-hot."
      }
    ],
    "reusable": [
      "src/black-hole.js"
    ],
    "demo": "demo.js",
    "harness": "index.html"
  },
  "dependencies": [
    {
      "id": "three",
      "version": "0.185.1",
      "reason": "Renderer, the full-screen shader and the bloom targets."
    },
    {
      "id": "apw-runtime",
      "version": "1.0.0",
      "scope": "demo",
      "reason": "Demo harness."
    }
  ],
  "params": [
    {
      "key": "palette",
      "label": "Colours",
      "type": "select",
      "default": "gold",
      "options": [
        {
          "value": "gold",
          "label": "Gold"
        },
        {
          "value": "ice",
          "label": "Ice blue"
        },
        {
          "value": "ember",
          "label": "Red ember"
        },
        {
          "value": "violet",
          "label": "Violet"
        }
      ]
    },
    {
      "key": "tilt",
      "label": "Viewing angle",
      "help": "Degrees above the plane of the disk: low is edge-on, high looks down on it.",
      "type": "number",
      "min": 3,
      "max": 70,
      "step": 1,
      "default": 8
    },
    {
      "key": "spin",
      "label": "Disk spin",
      "help": "Turns the innermost ring makes in one loop; outer rings turn slower.",
      "type": "integer",
      "min": 3,
      "max": 16,
      "step": 1,
      "default": 5
    },
    {
      "key": "brightness",
      "label": "Brightness",
      "type": "number",
      "min": 0.5,
      "max": 1.8,
      "step": 0.05,
      "default": 1
    },
    {
      "key": "doppler",
      "label": "Bright side",
      "help": "How much brighter the side of the disk coming towards you is.",
      "type": "number",
      "min": 0,
      "max": 1,
      "step": 0.05,
      "default": 0.7
    },
    {
      "key": "gravity",
      "label": "Bending of light",
      "help": "1 is the real thing; less or more changes the size of the shadow and the rings.",
      "type": "number",
      "min": 0.7,
      "max": 1.3,
      "step": 0.05,
      "default": 1
    },
    {
      "key": "stars",
      "label": "Stars",
      "type": "number",
      "min": 0.3,
      "max": 1.6,
      "step": 0.1,
      "default": 1
    },
    {
      "key": "milkyWay",
      "label": "Milky Way",
      "type": "number",
      "min": 0,
      "max": 1.5,
      "step": 0.1,
      "default": 1
    },
    {
      "key": "seed",
      "label": "Seed",
      "help": "Changes the stars, the Milky Way and the streaks in the disk.",
      "type": "integer",
      "min": 1,
      "max": 999,
      "step": 1,
      "default": 5
    }
  ],
  "presets": [
    {
      "id": "default",
      "label": "Gold disk, seen edge-on",
      "params": {}
    },
    {
      "id": "quasar",
      "label": "Blue quasar, tilted",
      "params": {
        "palette": "ice",
        "tilt": 24,
        "spin": 8,
        "brightness": 1.15,
        "doppler": 0.9,
        "seed": 17
      }
    },
    {
      "id": "ember",
      "label": "Red ember, slow",
      "params": {
        "palette": "ember",
        "tilt": 14,
        "spin": 3,
        "doppler": 0.5,
        "milkyWay": 1.3,
        "seed": 8
      }
    },
    {
      "id": "faceon",
      "label": "Violet, from above",
      "params": {
        "palette": "violet",
        "tilt": 62,
        "spin": 4,
        "doppler": 0.4,
        "seed": 3
      }
    }
  ],
  "output": {
    "width": 1080,
    "height": 1080,
    "fps": 30,
    "durationSeconds": 12,
    "posterSeconds": 3,
    "alpha": false,
    "motion": "procedural",
    "loop": {
      "seamless": true,
      "cycleSeconds": 12,
      "method": "The gas turns in rings, each a whole number of turns per loop (the dark lanes between them hide the seams), the turbulence in the disk is noise read round a circle in time, and the camera's sway is a sine of the loop, so the last frame meets the first."
    },
    "seed": {
      "param": "seed",
      "behavior": "Seed places the stars and the Milky Way and shapes the streaks in the disk: same seed, same sky."
    }
  },
  "composition": {
    "inputs": "Time within the loop; the colours, viewing angle, spin, brightness, bright side, bending, stars and Milky Way.",
    "outputs": "A full frame, drawn by one ray-tracing shader on a full-screen quad.",
    "coordinates": "Units of the hole's mass M: the horizon is at 2, the photon sphere at 3, the disk from 6 to 18; the camera sits 36 away, looking at the centre.",
    "lighting": "Self-lit: the disk glows, with the Doppler and gravitational shifts of its colour and brightness.",
    "timing": "update(time, loopSeconds) sets the loop phase; frames render in any order.",
    "compatibleWith": [
      "kinetic-title",
      "number-reveal"
    ],
    "notes": [
      "Light is bent with the acceleration a = -(3/2) r_s h² p / |p|^5, the shape of a photon's orbit in Schwarzschild space, stepped in 170 steps at most.",
      "The brightness and colour of the disk follow the photon's redshift factor g = sqrt(1 - 3M/r) / (1 - Omega lambda) for gas on circular orbits."
    ]
  },
  "reuse": [
    "Sci-fi title: Gold disk behind a kinetic title.",
    "Music or podcast loop: Red ember, slow.",
    "Science explainer: Blue quasar, tilted, with a narrator."
  ],
  "integration": {
    "newProject": "Create a WebGLRenderer, createBlackHole() and createBloomPass(renderer, { width, height }); call hole.setSize(width, height), then each frame hole.update(time, loopSeconds) and bloom.render(hole.scene, hole.camera).",
    "existingProject": "Copy src/black-hole.js; render hole.scene with hole.camera into a render target (or through the bloom pass) as the backdrop of your scene."
  },
  "performance": {
    "notes": "One full-screen pass: up to 170 ray steps per pixel and a small baked noise texture, plus a small bloom. About 12 ms a frame at 1080p on an integrated Intel GPU.",
    "limits": "Software WebGL (no GPU) renders slowly, several seconds a frame."
  },
  "limitations": [
    "A non-spinning (Schwarzschild) hole: the shadow is a circle; the disk is a thin sheet with no vertical thickness or jets.",
    "Rays that orbit the photon sphere many times are cut off after 170 steps, so the very thinnest higher-order rings are missing."
  ],
  "license": {
    "spdx": "MIT",
    "notice": "Copyright (c) 2026 AI Pixel Wizard. Released under the MIT License. See LICENSE in this folder.",
    "thirdParty": [
      {
        "name": "three.js",
        "version": "0.185.1",
        "license": "MIT",
        "url": "https://github.com/mrdoob/three.js"
      }
    ]
  },
  "provenance": "Adapted from the owner's threejs-sketches/space-cosmic/black-hole (with webgpu-black-hole, accretion-disk and gravitational-lens, MIT) and rebuilt for AI Pixel Wizard in October 2026 as an original ray-tracer: light bent by the Schwarzschild photon orbit, a thin accretion disk with Doppler and gravitational shifts, lensed stars and Milky Way, rings that turn whole times a loop, an exact loop and the frame/time contract.",
  "changelog": [
    {
      "version": "1.0.0",
      "date": "2026-10-05",
      "notes": "Initial release."
    }
  ]
}
