{
  "schemaVersion": 1,
  "id": "faraday-wave",
  "version": "1.0.1",
  "status": "published",
  "kind": "background",
  "title": "Faraday Wave",
  "summary": "A pool of liquid shaken into standing patterns that flow between squares, hexagons and stars as the surface heaves. Loops perfectly.",
  "description": "A shallow pool of liquid shaken from below, the way Michael Faraday did it: above a certain shaking the surface breaks into a standing pattern that pulses with the shaking, as squares, hexagons or ten-pointed stars. Here the three patterns flow into one another around the loop while the surface heaves up and down, lit like a pool of mercury, ink, honey or lagoon water. Choose the liquid, the number of cells across the picture, how many heaves per loop, how steep the surface looks and the brightness of its reflections.",
  "role": "Shader background (three.js): a hypnotic physics pattern for science, music and ambient content.",
  "collections": [
    "backgrounds",
    "hypnotic-loops",
    "satisfying"
  ],
  "tags": [
    "faraday wave",
    "standing wave",
    "liquid",
    "pattern",
    "hexagons",
    "squares",
    "stars",
    "physics",
    "ripples",
    "mercury",
    "loop"
  ],
  "synonyms": [
    "faraday instability",
    "standing waves",
    "vibrated fluid",
    "cymatics",
    "liquid pattern",
    "wave interference"
  ],
  "useCases": [
    "A physics or cymatics explainer",
    "A hypnotic ambient background",
    "A liquid-metal or music visual"
  ],
  "featured": null,
  "implementation": {
    "target": "three",
    "runtime": "browser",
    "language": "javascript",
    "module": "esm",
    "entry": "src/faraday-wave.js",
    "exports": [
      {
        "name": "createFaradayWave",
        "signature": "createFaradayWave({ liquid?, cells?, cycles?, heave?, gain? }) => { scene, camera, setSize(width, height), update(time, loopSeconds), dispose() }",
        "description": "The pool as one full-screen shader; render its scene 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 tone mapping and a fixed grain."
      },
      {
        "name": "LIQUIDS",
        "signature": "LIQUIDS: { mercury, ink, honey, lagoon }",
        "description": "Base, sky and light colours for each liquid."
      }
    ],
    "reusable": [
      "src/faraday-wave.js"
    ],
    "demo": "demo.js",
    "harness": "index.html"
  },
  "dependencies": [
    {
      "id": "three",
      "version": "0.185.1",
      "reason": "Renderer and a full-screen shader material."
    },
    {
      "id": "apw-runtime",
      "version": "1.0.0",
      "scope": "demo",
      "reason": "Demo harness."
    }
  ],
  "params": [
    {
      "key": "liquid",
      "label": "Liquid",
      "type": "select",
      "default": "mercury",
      "options": [
        {
          "value": "mercury",
          "label": "Mercury"
        },
        {
          "value": "ink",
          "label": "Ink"
        },
        {
          "value": "honey",
          "label": "Honey"
        },
        {
          "value": "lagoon",
          "label": "Lagoon"
        }
      ]
    },
    {
      "key": "cells",
      "label": "Cells across",
      "type": "number",
      "min": 4,
      "max": 10,
      "step": 0.5,
      "default": 5
    },
    {
      "key": "cycles",
      "label": "Heaves per loop",
      "type": "integer",
      "min": 2,
      "max": 8,
      "step": 1,
      "default": 4
    },
    {
      "key": "heave",
      "label": "Steepness",
      "type": "number",
      "min": 0.2,
      "max": 1.2,
      "step": 0.05,
      "default": 0.55
    },
    {
      "key": "gain",
      "label": "Reflections",
      "type": "number",
      "min": 0.5,
      "max": 1.6,
      "step": 0.1,
      "default": 1
    }
  ],
  "presets": [
    {
      "id": "default",
      "label": "Mercury, five cells",
      "params": {}
    },
    {
      "id": "ink",
      "label": "Ink, fine pattern",
      "params": {
        "liquid": "ink",
        "cells": 8,
        "heave": 0.7
      }
    },
    {
      "id": "honey",
      "label": "Honey, slow heave",
      "params": {
        "liquid": "honey",
        "cycles": 2,
        "cells": 4.5
      }
    },
    {
      "id": "lagoon",
      "label": "Lagoon, steep",
      "params": {
        "liquid": "lagoon",
        "heave": 1,
        "cycles": 6
      }
    }
  ],
  "output": {
    "width": 1080,
    "height": 1080,
    "fps": 30,
    "durationSeconds": 12,
    "posterSeconds": 0.1,
    "alpha": false,
    "motion": "procedural",
    "loop": {
      "seamless": true,
      "cycleSeconds": 12,
      "method": "The three patterns are crossfaded with weights of whole cycles of the loop, the pool heaves a whole number of times per loop and the pattern's small rotation is a sine of the loop, so the last frame meets the first."
    },
    "seed": null
  },
  "composition": {
    "inputs": "Time within the loop; the liquid, cells, heaves, steepness and reflections.",
    "outputs": "A full-frame shader image through a bloom pass.",
    "coordinates": "Screen-centred, ±1 vertically; waves are plane waves of wavelength 2 / cells at fixed angles.",
    "lighting": "A fixed light from the upper left lights the slope of the liquid, with a sky reflection by slope and a Fresnel rim.",
    "timing": "update(time, loopSeconds) sets the loop phase; frames render in any order.",
    "compatibleWith": [
      "kinetic-title",
      "number-reveal"
    ],
    "notes": [
      "Heights and slopes are computed analytically from the sum of plane waves, so there are no texture lookups."
    ]
  },
  "reuse": [
    "Science backdrop: Mercury, five cells.",
    "Dark ambient: Ink, fine pattern.",
    "Warm ambient: Honey, slow heave."
  ],
  "integration": {
    "newProject": "Create a WebGLRenderer, createFaradayWave() and createBloomPass(renderer, { width, height }); call pool.setSize(w, h) and each frame pool.update(time, loopSeconds) then bloom.render(pool.scene, pool.camera).",
    "existingProject": "Copy src/faraday-wave.js and render pool.scene with pool.camera into your own target."
  },
  "performance": {
    "notes": "One full-screen fragment shader of about ten cosines per pixel, plus the bloom: under 2 ms a frame at 1080² on an integrated Intel GPU.",
    "limits": "Software WebGL renders in about two seconds a frame."
  },
  "limitations": [
    "The surface is a closed-form sum of waves, not a fluid simulation; the patterns are idealised.",
    "Reflections use a fixed light and sky rather than real environment mapping."
  ],
  "license": {
    "spdx": "MIT",
    "notice": "Copyright (c) 2026 AI Pixel Wizard. Released under the MIT License. See LICENSE in this folder.",
    "thirdParty": []
  },
  "provenance": "Adapted from the owner's threejs-sketches/physics-forces/faraday-wave (MIT) and rebuilt for AI Pixel Wizard in October 2026 as an original shader: hexagon, square and ten-fold standing-wave patterns crossfaded on an exact loop and lit as a liquid surface.",
  "changelog": [
    {
      "version": "1.0.1",
      "date": "2026-10-08",
      "notes": "New poster frame, taken when the pattern is crisp; nothing else changes."
    },
    {
      "version": "1.0.0",
      "date": "2026-10-05",
      "notes": "Initial release."
    }
  ],
  "publishedAt": "2026-10-08"
}
