{
  "schemaVersion": 1,
  "id": "lens-flare",
  "version": "1.0.0",
  "status": "published",
  "kind": "effect",
  "title": "Cinematic Lens Flare",
  "summary": "A cinematic lens flare drawn over any frame: glow, starburst, anamorphic streak, rainbow halo and ghosts that move with the light.",
  "description": "A lens flare as a finishing layer. Place a light anywhere in the frame (or just outside it) and the flare follows: a bright core and glow, a starburst from the aperture blades, an anamorphic streak, a rainbow halo, a chain of ghosts reflected through the centre of the frame, lens dirt that lights up near the source and a veil of glare. Four styles (cinematic, anamorphic blue streak, sun, soft), any light colour, and a light that drifts on a loop, sweeps across or stays put. On black it is ready to drop on a clip with Screen blending.",
  "role": "Full-frame finishing effect (Canvas 2D): a lens flare layered over a scene.",
  "collections": [
    "backgrounds",
    "product-reveals",
    "hypnotic-loops"
  ],
  "tags": [
    "lens flare",
    "flare",
    "anamorphic",
    "light leak",
    "starburst",
    "sun",
    "glow",
    "cinematic",
    "overlay",
    "golden hour"
  ],
  "synonyms": [
    "j j abrams flare",
    "anamorphic streak",
    "sun flare",
    "light flare",
    "camera flare",
    "flare overlay",
    "optical flare",
    "lens glare",
    "sun burst"
  ],
  "useCases": [
    "A golden-hour or hero shot finish",
    "A light sweep across a title or product",
    "A flare overlay for editors (on black, Screen blend)"
  ],
  "featured": null,
  "publishedAt": "2026-10-05",
  "implementation": {
    "target": "canvas2d",
    "runtime": "browser",
    "language": "javascript",
    "module": "esm",
    "entry": "src/lens-flare.js",
    "exports": [
      {
        "name": "createLensFlare",
        "signature": "createLensFlare({ style?, tint?, ghosts?, rays?, size?, dirt?, grain?, seed? }) => { style, draw(ctx, { x, y, width, height, intensity? }) }",
        "description": "Draws the flare over whatever is on the canvas, for a light at (x, y) in pixels. Uses additive blending: draw it last."
      },
      {
        "name": "lightPath",
        "signature": "lightPath(progress, kind = 'drift') => { x, y }",
        "description": "Light position in fractions of the frame: 'drift' (a figure-of-eight that loops exactly), 'sweep' (left to right) or 'static'."
      },
      {
        "name": "STYLES",
        "signature": "STYLES: Record<'cinematic' | 'anamorphic' | 'sun' | 'soft', object>",
        "description": "Which flare elements each style uses and how strong they are."
      }
    ],
    "reusable": [
      "src/lens-flare.js"
    ],
    "demo": "demo.js",
    "harness": "index.html"
  },
  "dependencies": [
    {
      "id": "apw-runtime",
      "version": "1.0.0",
      "scope": "demo",
      "reason": "Demo harness."
    }
  ],
  "params": [
    {
      "key": "style",
      "label": "Flare style",
      "type": "select",
      "default": "cinematic",
      "options": [
        {
          "value": "cinematic",
          "label": "Cinematic"
        },
        {
          "value": "anamorphic",
          "label": "Anamorphic streak"
        },
        {
          "value": "sun",
          "label": "Sun burst"
        },
        {
          "value": "soft",
          "label": "Soft glow"
        }
      ]
    },
    {
      "key": "tint",
      "label": "Light colour",
      "type": "color",
      "default": "#ffd29a"
    },
    {
      "key": "path",
      "label": "Light movement",
      "help": "Drift loops seamlessly; Sweep crosses the frame once.",
      "type": "select",
      "default": "drift",
      "options": [
        {
          "value": "drift",
          "label": "Drift (loops)"
        },
        {
          "value": "sweep",
          "label": "Sweep across"
        },
        {
          "value": "static",
          "label": "Still"
        }
      ]
    },
    {
      "key": "scene",
      "label": "Backdrop",
      "help": "Black is for laying the flare over your own footage with Screen blending.",
      "type": "select",
      "default": "dusk",
      "options": [
        {
          "value": "dusk",
          "label": "Golden hour hills"
        },
        {
          "value": "space",
          "label": "Night sky"
        },
        {
          "value": "black",
          "label": "Black (for Screen blending)"
        }
      ]
    },
    {
      "key": "intensity",
      "label": "Brightness",
      "type": "number",
      "min": 0.3,
      "max": 1.5,
      "step": 0.05,
      "default": 1
    },
    {
      "key": "size",
      "label": "Size",
      "type": "number",
      "min": 0.5,
      "max": 1.6,
      "step": 0.05,
      "default": 1
    },
    {
      "key": "ghosts",
      "label": "Ghosts",
      "help": "Reflections along the line through the centre of the frame.",
      "type": "integer",
      "min": 0,
      "max": 12,
      "step": 1,
      "default": 7
    },
    {
      "key": "rays",
      "label": "Starburst rays",
      "type": "integer",
      "min": 6,
      "max": 24,
      "step": 2,
      "default": 12
    },
    {
      "key": "dirt",
      "label": "Lens dirt",
      "type": "number",
      "min": 0,
      "max": 1,
      "step": 0.05,
      "default": 0.5
    },
    {
      "key": "seed",
      "label": "Variation",
      "type": "integer",
      "min": 1,
      "max": 9999,
      "step": 1,
      "default": 7
    }
  ],
  "presets": [
    {
      "id": "default",
      "label": "Golden hour",
      "params": {}
    },
    {
      "id": "anamorphic",
      "label": "Anamorphic night",
      "params": {
        "style": "anamorphic",
        "tint": "#bfe0ff",
        "scene": "space",
        "ghosts": 6
      }
    },
    {
      "id": "sun-overlay",
      "label": "Sun burst on black",
      "params": {
        "style": "sun",
        "tint": "#fff1d6",
        "scene": "black",
        "path": "static",
        "rays": 16
      }
    },
    {
      "id": "dream",
      "label": "Soft rose glow",
      "params": {
        "style": "soft",
        "tint": "#ffb3c4",
        "ghosts": 9,
        "seed": 21
      }
    }
  ],
  "output": {
    "width": 1080,
    "height": 1080,
    "fps": 30,
    "durationSeconds": 8,
    "posterSeconds": 1.4,
    "alpha": false,
    "motion": "procedural",
    "loop": {
      "seamless": true,
      "cycleSeconds": 8,
      "method": "With Drift (the default) the light follows a closed figure-of-eight with whole-number frequencies, and the flare is a pure function of the light position, so the last frame meets the first. Sweep and Still do not loop."
    },
    "seed": {
      "param": "seed",
      "behavior": "Seed shapes the ghosts (where, how big, what colour), the starburst rays and the lens dirt. Same seed, same flare."
    }
  },
  "composition": {
    "inputs": "A canvas with your scene already drawn, and the light position in pixels (it may be off-frame).",
    "outputs": "Adds light onto the canvas ('lighter' compositing) plus a faint dither grain over the frame.",
    "coordinates": "Pixels of the canvas you draw on; ghosts mirror the light through the canvas centre.",
    "lighting": "Additive; on black the result can be blended over footage with Screen or Add.",
    "timing": "draw() is a pure function of the light position; lightPath(progress) gives a position per frame.",
    "compatibleWith": [
      "gradient-background",
      "kinetic-title",
      "particle-tunnel",
      "number-reveal"
    ],
    "notes": [
      "To flare a 3D scene, project your light's 3D position to the screen (vector.project(camera)) and draw onto a 2D canvas laid over the WebGL one.",
      "Fade intensity down when the light is hidden behind something."
    ]
  },
  "reuse": [
    "Title reveal: sweep the light behind Kinetic Title Reveal for a cinematic title card.",
    "Overlay for footage: render it on black and blend it over a clip with Screen in any editor.",
    "Product hero: pin the light to a product's highlight in a 3D scene by projecting its position each frame."
  ],
  "integration": {
    "newProject": "Draw your background on a canvas, then call createLensFlare().draw(ctx, { x, y, width, height }) every frame, with x and y from lightPath(frame / totalFrames).",
    "existingProject": "Copy src/lens-flare.js. After your scene is drawn each frame, draw the flare onto a 2D canvas over it at the light's screen position."
  },
  "performance": {
    "notes": "About 120 gradient fills per frame plus one full-frame dirt mask; light work at 1080p.",
    "limits": "The dirt buffer is a full-size offscreen canvas; pass dirt: 0 to skip it on low memory devices."
  },
  "limitations": [
    "A drawn approximation of a lens, not optical ray tracing: ghosts follow the classic axis through the frame centre.",
    "It does not detect occlusion: lower intensity yourself when the light goes behind something."
  ],
  "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/light-atmosphere/webgpu-lens-flare (MIT), rebuilt for AI Pixel Wizard in October 2026 as a Canvas 2D finishing layer (no WebGPU needed) with flare styles, lens dirt, dithering and the frame/time contract.",
  "changelog": [
    {
      "version": "1.0.0",
      "date": "2026-10-05",
      "notes": "Initial release."
    }
  ]
}
