{
  "schemaVersion": 1,
  "id": "torus-wave",
  "version": "1.1.0",
  "status": "published",
  "kind": "effect",
  "title": "Torus Wave Loop",
  "summary": "Glowing rings around a slowly turning doughnut, with a wave rippling through them. Loops seamlessly.",
  "description": "Dozens of thin neon rings sit around a doughnut shape. A wave swells through them while the whole form turns, creating that calm, mesmerising wireframe look. Pick two colours, the number of rings and waves, and how much they swell. Everything is drawn as simple lines, so it stays crisp at any size.",
  "role": "Hero loop or background (Canvas 2D layer).",
  "collections": [
    "hypnotic-loops",
    "satisfying",
    "backgrounds"
  ],
  "tags": [
    "torus",
    "doughnut",
    "donut",
    "rings",
    "wave",
    "wireframe",
    "loop",
    "neon",
    "lines",
    "geometric",
    "2d",
    "canvas",
    "hypnotic"
  ],
  "synonyms": [
    "ribbon",
    "ripple",
    "torus wave",
    "line art",
    "wireframe donut",
    "satisfying loop",
    "geometric loop",
    "rings rippling"
  ],
  "useCases": [
    "Seamless looping background",
    "Satisfying geometric loop",
    "Album or podcast visual"
  ],
  "featured": 8,
  "publishedAt": "2026-10-03",
  "implementation": {
    "target": "canvas2d",
    "runtime": "browser",
    "language": "javascript",
    "module": "esm",
    "entry": "src/torus-wave.js",
    "exports": [
      {
        "name": "createTorusWave",
        "signature": "createTorusWave({ rings?, waves?, cycles?, spins?, amplitude?, tilt?, colorA?, colorB?, lineWidth?, scale? }) => { draw(ctx, info, width, height) }",
        "description": "Canvas 2D layer; draws additively."
      }
    ],
    "reusable": [
      "src/torus-wave.js"
    ],
    "demo": "demo.js",
    "harness": "index.html"
  },
  "dependencies": [
    {
      "id": "apw-runtime",
      "version": "1.0.0",
      "scope": "demo",
      "reason": "Demo harness. src/torus-wave.js has no dependencies."
    }
  ],
  "params": [
    {
      "key": "rings",
      "label": "Rings",
      "type": "integer",
      "min": 12,
      "max": 140,
      "step": 1,
      "default": 44
    },
    {
      "key": "waves",
      "label": "Waves",
      "help": "Number of swells around the doughnut.",
      "type": "integer",
      "min": 1,
      "max": 8,
      "step": 1,
      "default": 3
    },
    {
      "key": "speed",
      "label": "Wave speed",
      "help": "Wave cycles per loop.",
      "type": "integer",
      "min": 1,
      "max": 4,
      "step": 1,
      "default": 1
    },
    {
      "key": "spins",
      "label": "Turns",
      "help": "Turns of the doughnut per loop.",
      "type": "integer",
      "min": 0,
      "max": 2,
      "step": 1,
      "default": 1
    },
    {
      "key": "swell",
      "label": "Swell",
      "type": "number",
      "min": 0,
      "max": 0.9,
      "step": 0.05,
      "default": 0.32
    },
    {
      "key": "tilt",
      "label": "Tilt",
      "type": "number",
      "min": 0,
      "max": 1.5,
      "step": 0.05,
      "default": 1.15
    },
    {
      "key": "colorA",
      "label": "Colour A",
      "type": "color",
      "default": "#ff4fd8"
    },
    {
      "key": "colorB",
      "label": "Colour B",
      "type": "color",
      "default": "#4fd6ff"
    },
    {
      "key": "lineWidth",
      "label": "Line weight",
      "type": "number",
      "min": 0.3,
      "max": 3,
      "step": 0.1,
      "default": 1.6
    },
    {
      "key": "background",
      "label": "Background",
      "type": "color",
      "default": "#07060f"
    }
  ],
  "presets": [
    {
      "id": "default",
      "label": "Neon rings",
      "params": {}
    },
    {
      "id": "gold",
      "label": "Fine gold",
      "params": {
        "colorA": "#ffcf73",
        "colorB": "#fff3d6",
        "rings": 72,
        "swell": 0.2,
        "lineWidth": 0.9
      }
    },
    {
      "id": "pulse",
      "label": "Pulse",
      "params": {
        "waves": 6,
        "speed": 3,
        "swell": 0.6,
        "spins": 0,
        "tilt": 1.4
      }
    }
  ],
  "output": {
    "width": 1080,
    "height": 1080,
    "fps": 30,
    "durationSeconds": 8,
    "posterSeconds": 1,
    "alpha": false,
    "motion": "procedural",
    "loop": {
      "seamless": true,
      "cycleSeconds": 8,
      "method": "Whole wave cycles and whole turns per clip."
    },
    "seed": null
  },
  "composition": {
    "inputs": "Canvas 2D context, info.progress, width, height.",
    "outputs": "Additive line drawing centred on the canvas. Does not clear the canvas.",
    "coordinates": "Centred; size is 27% of the shorter side × scale.",
    "lighting": "Additive blending; use a dark background.",
    "timing": "Pure function of progress.",
    "compatibleWith": [
      "gradient-background",
      "kinetic-title"
    ],
    "notes": []
  },
  "reuse": [
    "Draw it small (scale 0.4) in a corner as an animated logo mark.",
    "Use one colour and spins 0 as a calm breathing loader.",
    "Layer it over Soft Gradient Background for a richer backdrop."
  ],
  "integration": {
    "newProject": "Create a 2D canvas; each frame fill the background and call torus.draw(ctx, { progress }, width, height).",
    "existingProject": "Copy src/torus-wave.js and call draw from your existing frame loop between your background and foreground."
  },
  "performance": {
    "notes": "64 rings × 56 segments of lines per frame; light.",
    "limits": "Above ~140 rings lines start to merge visually."
  },
  "limitations": [
    "Lines are not depth-sorted; additive blending hides most of this."
  ],
  "license": {
    "spdx": "MIT",
    "notice": "Copyright (c) 2026 AI Pixel Wizard. Released under the MIT License. See LICENSE in this folder.",
    "thirdParty": []
  },
  "provenance": "Original code written for AI Pixel Wizard in October 2026. Demo rebuilt in October 2026 to the catalog quality bar (docs/QUALITY.md).",
  "changelog": [
    {
      "version": "1.1.0",
      "date": "2026-10-03",
      "notes": "Fewer, heavier rings so the cross-section reads as a torus, a glow pass under the lines and a vignette. The ring code is unchanged."
    },
    {
      "version": "1.0.0",
      "date": "2026-10-02",
      "notes": "Initial release."
    }
  ]
}
