{
  "schemaVersion": 1,
  "id": "string-art",
  "version": "1.0.0",
  "status": "published",
  "kind": "background",
  "title": "String Art",
  "summary": "Threads stretched across glowing hoops weave cardioids, nephroids and lace as the multiplier slides up and down. Loops perfectly.",
  "description": "The classic times-table string art: every point of a hoop is joined by a straight thread to the point m times as far round, and the threads together weave curves that are not there: a cardioid when m is 2, a nephroid at 3, a lace of ever more cusps as m grows. Here m slides smoothly up through all the whole numbers to a maximum and back down, so the weave swells, folds and blooms while up to three hoops of different sizes hang in space, tipping slowly, their colours running round. Choose the colours (rainbow, sunset, ocean or gold), how many threads a hoop has, how far the multiplier climbs, the number of hoops, their tilt, the thickness of the threads and the glow.",
  "role": "Background (WebGL, full frame): a hypnotic, geometric motion loop for music, meditation, maths and title content.",
  "collections": [
    "backgrounds",
    "hypnotic-loops"
  ],
  "tags": [
    "string art",
    "times table",
    "cardioid",
    "nephroid",
    "geometry",
    "mathematics",
    "hypnotic",
    "glow",
    "lines",
    "pattern",
    "loop",
    "background"
  ],
  "synonyms": [
    "times table circle",
    "mandala",
    "modular multiplication",
    "cardioid animation",
    "geometric lines",
    "thread art",
    "spirograph style",
    "math art"
  ],
  "useCases": [
    "A hypnotic backdrop for music, meditation and visualiser videos",
    "A maths explainer on cardioids and modular arithmetic",
    "A technology or science title scene"
  ],
  "featured": null,
  "publishedAt": "2026-10-06",
  "implementation": {
    "target": "three",
    "runtime": "browser",
    "language": "javascript",
    "module": "esm",
    "entry": "src/string-art.js",
    "exports": [
      {
        "name": "createStringArt",
        "signature": "createStringArt({ palette?, lines?, maxMultiplier?, rings?, tilt?, thickness?, glow?, aspect? }) => { scene, camera, material, setSize(width, height), update(time, loopSeconds), dispose() }",
        "description": "The whole piece: a backdrop and every thread of every hoop as instanced ribbons. Render it with its camera, 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: draws a scene to the canvas with its glow."
      },
      {
        "name": "PALETTES",
        "signature": "PALETTES: { rainbow, sunset, ocean, gold }",
        "description": "Cosine gradients: colour = a + b·cos(2π(c·h + d))."
      }
    ],
    "reusable": [
      "src/string-art.js"
    ],
    "demo": "demo.js",
    "harness": "index.html"
  },
  "dependencies": [
    {
      "id": "three",
      "version": "0.185.1",
      "reason": "Renderer, instanced thread ribbons and the bloom targets."
    },
    {
      "id": "apw-runtime",
      "version": "1.0.0",
      "scope": "demo",
      "reason": "Demo harness."
    }
  ],
  "params": [
    {
      "key": "palette",
      "label": "Colours",
      "type": "select",
      "default": "rainbow",
      "options": [
        {
          "value": "rainbow",
          "label": "Rainbow"
        },
        {
          "value": "sunset",
          "label": "Sunset"
        },
        {
          "value": "ocean",
          "label": "Ocean"
        },
        {
          "value": "gold",
          "label": "Gold"
        }
      ]
    },
    {
      "key": "lines",
      "label": "Threads per hoop",
      "type": "integer",
      "min": 60,
      "max": 480,
      "step": 10,
      "default": 150
    },
    {
      "key": "maxMultiplier",
      "label": "Most cusps",
      "help": "How far the multiplier climbs from 2 (a cardioid) before it falls back.",
      "type": "integer",
      "min": 3,
      "max": 14,
      "step": 1,
      "default": 5
    },
    {
      "key": "rings",
      "label": "Hoops",
      "type": "integer",
      "min": 1,
      "max": 3,
      "step": 1,
      "default": 1
    },
    {
      "key": "tilt",
      "label": "Tilt",
      "type": "number",
      "min": 0,
      "max": 0.9,
      "step": 0.05,
      "default": 0.45
    },
    {
      "key": "thickness",
      "label": "Thread thickness",
      "type": "number",
      "min": 0.6,
      "max": 2,
      "step": 0.05,
      "default": 1
    },
    {
      "key": "glow",
      "label": "Glow",
      "type": "number",
      "min": 0.5,
      "max": 1.8,
      "step": 0.05,
      "default": 1
    }
  ],
  "presets": [
    {
      "id": "default",
      "label": "Rainbow hoop",
      "params": {}
    },
    {
      "id": "twin",
      "label": "Twin hoops",
      "params": {
        "rings": 2,
        "lines": 110,
        "maxMultiplier": 6,
        "tilt": 0.5
      }
    },
    {
      "id": "sunset",
      "label": "Sunset lace",
      "params": {
        "palette": "sunset",
        "maxMultiplier": 11,
        "rings": 3,
        "lines": 200,
        "tilt": 0.55
      }
    },
    {
      "id": "gold",
      "label": "Golden filigree",
      "params": {
        "palette": "gold",
        "maxMultiplier": 14,
        "rings": 2,
        "lines": 300,
        "tilt": 0.7,
        "glow": 1.2
      }
    }
  ],
  "output": {
    "width": 1080,
    "height": 1080,
    "fps": 30,
    "durationSeconds": 12,
    "posterSeconds": 3,
    "alpha": false,
    "motion": "procedural",
    "loop": {
      "seamless": true,
      "cycleSeconds": 12,
      "method": "The multiplier is a raised cosine of the loop phase (so it climbs and falls once a loop), the colours run round the hoop a whole number of cycles, the thread shimmer has whole cycles and the tilt of the hoops is a sine of the loop, so the last frame meets the first."
    },
    "seed": null
  },
  "composition": {
    "inputs": "Time within the loop; the colours, number of threads, largest multiplier, number of hoops, tilt, thickness and glow.",
    "outputs": "A full frame: a dark backdrop and thousands of glowing thread ribbons drawn additively.",
    "coordinates": "Perspective, 30° field of view, the camera 5.5 units from the origin; the hoops have radius 1, 0.66 and 0.32 and hang 0.35 apart in depth.",
    "lighting": "Self-lit: every thread adds its colour; overlaps glow, and the bloom softens them.",
    "timing": "update(time, loopSeconds) sets the loop time; each thread end is a closed-form function of it, so frames render in any order.",
    "compatibleWith": [
      "kinetic-title",
      "number-reveal"
    ],
    "notes": [
      "Thread i joins the angle 2πi/N to the angle m·2πi/N, m between 2 and the maximum: the envelope of those lines is an epicycloid with m − 1 cusps.",
      "Each thread is a ribbon of constant pixel width whose ends are projected in the vertex shader."
    ]
  },
  "reuse": [
    "Music visualiser or meditation loop: Rainbow hoop, slow.",
    "Maths explainer on cardioids and modular arithmetic: Rainbow hoop with the most cusps lowered to 3.",
    "Elegant title backdrop: Golden filigree behind a kinetic title."
  ],
  "integration": {
    "newProject": "Create a WebGLRenderer, createStringArt({ aspect }) and createBloomPass(renderer, { width, height }); call art.setSize(width, height), then each frame art.update(time, loopSeconds) and bloom.render(art.scene, art.camera).",
    "existingProject": "Copy src/string-art.js; render art.scene with art.camera into a render target (or through the bloom pass) as the backdrop of your scene."
  },
  "performance": {
    "notes": "Up to about 1,400 thread ribbons and 540 hoop segments, positioned in the vertex shader, and a small bloom: trivial for any GPU, a fraction of a second a frame in software.",
    "limits": "At most 480 threads a hoop and three hoops."
  },
  "limitations": [
    "The multiplier is continuous, so between whole values the thread ends do not land on a fixed set of nails: there are no nails drawn.",
    "Every thread is a straight line of the same brightness; there is no wood, cotton or shadow."
  ],
  "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/abstract-artistic/string-art (with webgpu-string-art, MIT) and rebuilt for AI Pixel Wizard in October 2026 as an original piece: a smoothly sliding times-table multiplier, hoops hanging in space, thread ribbons projected in the vertex shader, an exact loop and the frame/time contract.",
  "changelog": [
    {
      "version": "1.0.0",
      "date": "2026-10-05",
      "notes": "Initial release."
    }
  ]
}
