> ## Documentation Index
> Fetch the complete documentation index at: https://hyperframes.heygen.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Anamorphic Streak Flare

> A drifting field of bright points, each one throwing the long horizontal blue-violet streak a real anamorphic cinema lens produces. Built from the measured filter constants: a 0.3 high-pass, 80 horizontal taps at quarter resolution, a samples/3 gain and a 0x7a8aff tint.

export const InstallCommand = ({command}) => {
  const [copied, setCopied] = React.useState(false);
  const copy = async () => {
    try {
      if (navigator.clipboard && window.isSecureContext) {
        await navigator.clipboard.writeText(command);
      } else {
        const previous = document.activeElement;
        const scratch = document.createElement("textarea");
        scratch.value = command;
        scratch.setAttribute("readonly", "");
        scratch.style.position = "fixed";
        scratch.style.opacity = "0";
        document.body.appendChild(scratch);
        scratch.select();
        document.execCommand("copy");
        document.body.removeChild(scratch);
        previous?.focus?.();
      }
      setCopied(true);
      setTimeout(() => setCopied(false), 2000);
    } catch {}
  };
  return <div className="hf-install-command not-prose my-4 flex items-stretch overflow-hidden rounded-xl border border-zinc-200 bg-zinc-50 dark:border-zinc-800 dark:bg-zinc-900">
      <code className="flex-1 overflow-x-auto whitespace-nowrap border-r border-zinc-200 px-4 py-3 font-mono text-sm text-zinc-800 dark:border-zinc-800 dark:text-zinc-100">
        {command}
      </code>
      <button type="button" onClick={copy} data-copied={copied ? "true" : "false"} aria-label={`Copy ${command} to the clipboard`} className="hf-install-copy">
        <svg className="hf-install-copy-clipboard" xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 18 18" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
          <path d="M14.25 5.25H7.25C6.14543 5.25 5.25 6.14543 5.25 7.25V14.25C5.25 15.3546 6.14543 16.25 7.25 16.25H14.25C15.3546 16.25 16.25 15.3546 16.25 14.25V7.25C16.25 6.14543 15.3546 5.25 14.25 5.25Z" />
          <path d="M2.80103 11.998L1.77203 5.07397C1.61003 3.98097 2.36403 2.96397 3.45603 2.80197L10.38 1.77297C11.313 1.63397 12.19 2.16297 12.528 3.00097" />
        </svg>
        <svg className="hf-install-copy-check" xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 18 18" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
          <path d="M2.75 9.5L6.5 13.25L15.25 4.5" />
        </svg>
      </button>
      <span className="hf-install-copy-status" role="status" aria-live="polite">
        {copied ? "Copied" : ""}
      </span>
    </div>;
};

<iframe className="w-full aspect-video rounded-xl border-0 bg-zinc-100 dark:bg-zinc-800" title="vfx-anamorphic-flare preview" loading="lazy" srcDoc={`<!doctype html><html><head><meta charset="utf-8"><style>html,body{margin:0;height:100%;overflow:hidden;background:transparent}hyperframes-player{display:block;width:100%;height:100%}</style><script src="https://cdn.jsdelivr.net/npm/@hyperframes/player@0.7/dist/hyperframes-player.global.js"><\/script></head><body><script>fetch("/public/catalog/blocks/vfx-anamorphic-flare.json").then(function(r){return r.json()}).then(function(d){var p=document.createElement("hyperframes-player");p.setAttribute("srcdoc",d.html);p.setAttribute("controls","");p.setAttribute("autoplay","");p.setAttribute("loop","");p.setAttribute("muted","");p.setAttribute("poster","https://static.heygen.ai/hyperframes-oss/docs/images/catalog/blocks/vfx-anamorphic-flare.png");document.body.appendChild(p)});<\/script></body></html>`} />

## Install

<InstallCommand command="npx hyperframes add vfx-anamorphic-flare" />

That writes one file: `compositions/vfx-anamorphic-flare.html`.

## Add it to your video

It runs for 10 seconds at 1920×1080. Paste this into your composition:

```html index.html theme={null}
<div
  data-composition-id="vfx-anamorphic-flare"
  data-composition-src="compositions/vfx-anamorphic-flare.html"
  data-start="0"
  data-duration="10"
  data-track-index="1"
  data-width="1920"
  data-height="1080"
></div>
```

Move it in time with `data-start`. Put it on a different timeline row with
`data-track-index`. See [data attributes](/concepts/data-attributes) for the rest.

## Change how it looks

Set these CSS variables on the block:

* `--intensity` — Streak intensity. Defaults to `5`.
* `--threshold` — High-pass threshold. Defaults to `0.3`.
* `--streaklength` — Streak length gain. Defaults to `1`.
* `--tint` — Streak tint. Defaults to `#7a8aff`.
* `--bloomradius` — Bloom radius. Defaults to `0`.
* `--timescale` — Drift rate. Defaults to `0.5`.
* `--emitters` — Emitter count. Defaults to `140`.
* `--backdrop` — Backdrop. Defaults to `#04040a`.

## Variables

Every one of these has a default, so the piece works untouched. Set the ones you
want to change on the element:

| Variable       | Default   | Accepts               | What it does |
| -------------- | --------- | --------------------- | ------------ |
| `intensity`    | `5`       | 0 to 20, step 0.1     |              |
| `threshold`    | `0.3`     | 0 to 1, step 0.01     |              |
| `streakLength` | `1`       | 0.1 to 6, step 0.05   |              |
| `tint`         | `#7a8aff` | color                 |              |
| `bloomRadius`  | `0`       | 0px to 80px, step 1px |              |
| `timeScale`    | `0.5`     | 0 to 3, step 0.05     |              |
| `emitters`     | `140`     | 1 to 400, step 1      |              |
| `backdrop`     | `#04040a` | color                 |              |

Set them with `data-variable-values` on the element that mounts it. These are the
defaults, so this behaves exactly like the preview above until you change one:

```html wrap theme={null}
<div
  data-composition-id="vfx-anamorphic-flare"
  data-composition-src="compositions/vfx-anamorphic-flare.html"
  data-variable-values='{"intensity":5,"threshold":0.3,"streakLength":1,"tint":"#7a8aff","bloomRadius":0,"timeScale":0.5,"emitters":140,"backdrop":"#04040a"}'
></div>
```

## Source

<Accordion title={`vfx-anamorphic-flare.html`}>
  ```html theme={null}
  <!doctype html>
  <html lang="en">
    <head>
      <meta charset="utf-8" />
      <meta name="viewport" content="width=1920, height=1080" />
      <title>Anamorphic Streak Flare</title>
      <script src="https://cdn.jsdelivr.net/npm/gsap@3.14.2/dist/gsap.min.js"></script>
      <style>
        *,
        *::before,
        *::after {
          margin: 0;
          padding: 0;
          box-sizing: border-box;
        }
        body {
          background: #000;
          overflow: hidden;
        }
        #af-root {
          position: relative;
          width: 1920px;
          height: 1080px;
          overflow: hidden;
        }
        #af-canvas {
          position: absolute;
          top: 0;
          left: 0;
          width: 1920px;
          height: 1080px;
        }
      </style>
    </head>
    <body>
      <div
        id="af-root"
        data-composition-id="vfx-anamorphic-flare"
        data-root="true"
        data-width="1920"
        data-height="1080"
        data-start="0"
        data-duration="10"
        data-composition-variables='[
          {"id":"intensity","type":"number","label":"Streak intensity","default":5,"min":0,"max":20,"step":0.1},
          {"id":"threshold","type":"number","label":"High-pass threshold","default":0.3,"min":0,"max":1,"step":0.01},
          {"id":"streakLength","type":"number","label":"Streak length gain","default":1,"min":0.1,"max":6,"step":0.05},
          {"id":"tint","type":"color","label":"Streak tint","default":"#7a8aff"},
          {"id":"bloomRadius","type":"number","label":"Bloom radius","default":0,"min":0,"max":80,"step":1,"unit":"px"},
          {"id":"timeScale","type":"number","label":"Drift rate","default":0.5,"min":0,"max":3,"step":0.05},
          {"id":"emitters","type":"number","label":"Emitter count","default":140,"min":1,"max":400,"step":1},
          {"id":"backdrop","type":"color","label":"Backdrop","default":"#04040a"}
        ]'
      >
        <canvas id="af-canvas" width="1920" height="1080"></canvas>

        <!-- Driver clip: gives HyperFrames a timed element to own on track 0. -->
        <div
          id="af-drv"
          class="clip"
          data-start="0"
          data-duration="10"
          data-track-index="0"
          style="position: absolute; width: 1px; height: 1px; opacity: 0; pointer-events: none"
        ></div>
      </div>

      <script>
        (function () {
          var DUR = 10;
          var W = 1920;
          var H = 1080;

          var root = document.getElementById("af-root");
          var V = (window.__hyperframes && window.__hyperframes.getVariables()) || {};
          var CS = getComputedStyle(root);

          // A declared variable reaches CSS as a custom property on the root.
          // Read that first so a host stylesheet can retheme the block, then
          // fall back to the declared value. Both the namespaced and the bare
          // property name are probed: which one the runtime emits has changed
          // across versions, and the block should not care.
          function raw(id) {
            var slug = id.toLowerCase();
            var css =
              CS.getPropertyValue("--hf-var-" + slug).trim() ||
              CS.getPropertyValue("--" + slug).trim();
            return css !== "" ? css : V[id];
          }
          function num(id, fallback) {
            var n = parseFloat(raw(id));
            return isFinite(n) ? n : fallback;
          }
          function str(id, fallback) {
            var s = raw(id);
            return typeof s === "string" && s !== "" ? s : fallback;
          }

          // ---------------------------------------------------------------
          // Measured constants.
          //
          // Read from the three.js anamorphic post-processing example (MIT),
          // cross-checked against a live capture of that demo's own GUI:
          //
          //   tint 0x7a8aff, threshold 0.3, intensity 5, bloom radius 0,
          //   samples 80, time scale 0.5, resolution scale 0.25.
          //
          // The filter walks samples/2 taps in each direction HORIZONTALLY
          // only, weights each tap by 1 - |i| / halfSamples (a tent), and
          // normalises the sum by samples / 3.0. Dividing by samples/3 rather
          // than by the weight sum is deliberate: it is a gain above 1, not
          // an average, which is why a dim highlight still throws a readable
          // streak.
          //
          // This implementation evaluates that convolution in closed form
          // instead of running the 80-tap loop. For a small bright source the
          // tent-weighted horizontal blur has an exact analytic result: the
          // tent itself, centred on the source. So one horizontal gradient
          // per highlight is the same arithmetic the loop would perform, at
          // a fraction of the cost, and with no dependence on WebGL surviving
          // seek capture.
          // ---------------------------------------------------------------
          var SAMPLES = 80;
          var HALF_SAMPLES = SAMPLES / 2; // 40 taps each side
          var RES_SCALE = 0.25; // the streak buffer runs at quarter resolution
          var NORM = SAMPLES / 3; // total / (samples / 3.0)
          var REF_W = 1280; // width the constants were measured at

          // 40 taps of a 1/(0.25 * 1280) texel step = 12.5% of frame width per
          // side. Held as a fraction rather than as absolute pixels so the look
          // matches the reference at any output size.
          var HALF_FRAC = HALF_SAMPLES / (RES_SCALE * REF_W);

          var INTENSITY = num("intensity", 5);
          var THRESHOLD = num("threshold", 0.3);
          var STREAK_LEN = num("streakLength", 1);
          var BLOOM_R = num("bloomRadius", 0);
          var TIME_SCALE = num("timeScale", 0.5);
          var COUNT = Math.max(1, Math.round(num("emitters", 140)));
          var BACKDROP = str("backdrop", "#04040a");
          var TINT = hexRgb(str("tint", "#7a8aff"), [0.478, 0.541, 1.0]);

          function hexRgb(hex, fallback) {
            var m = /^#?([0-9a-f]{6})$/i.exec(String(hex).trim());
            if (!m) return fallback;
            var n = parseInt(m[1], 16);
            return [((n >> 16) & 255) / 255, ((n >> 8) & 255) / 255, (n & 255) / 255];
          }

          function mulberry32(seed) {
            return function () {
              seed |= 0;
              seed = (seed + 0x6d2b79f5) | 0;
              var t = Math.imul(seed ^ (seed >>> 15), 1 | seed);
              t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t;
              return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
            };
          }

          function hsv(h, s, v) {
            var hh = ((((h % 360) + 360) % 360) / 60) % 6;
            var c = v * s;
            var x = c * (1 - Math.abs((hh % 2) - 1));
            var m = v - c;
            var r = 0;
            var g = 0;
            var b = 0;
            if (hh < 1) {
              r = c;
              g = x;
            } else if (hh < 2) {
              r = x;
              g = c;
            } else if (hh < 3) {
              g = c;
              b = x;
            } else if (hh < 4) {
              g = x;
              b = c;
            } else if (hh < 5) {
              r = x;
              b = c;
            } else {
              r = c;
              b = x;
            }
            return [r + m, g + m, b + m];
          }

          // ---------------------------------------------------------------
          // The emitter field. Every emitter is a closed-form function of t:
          // two incommensurate sinusoids for position and one for brightness.
          // The seeded PRNG runs once, at setup, so the field is identical on
          // every load and every seek.
          // ---------------------------------------------------------------
          var rng = mulberry32(1337);
          var EM = [];
          for (var i = 0; i < COUNT; i++) {
            EM.push({
              x0: rng() * W,
              y0: rng() * H,
              ax: 24 + rng() * 90,
              ay: 12 + rng() * 48,
              fx: 0.018 + rng() * 0.05,
              fy: 0.015 + rng() * 0.045,
              px: rng(),
              py: rng(),
              r: 3 + Math.pow(rng(), 1.6) * 10,
              h: rng() * 360,
              s: 0.62 + rng() * 0.38,
              v: 0.7 + rng() * 0.3,
              fb: 0.03 + rng() * 0.09,
              pb: rng(),
            });
          }

          var TAU = Math.PI * 2;
          var canvas = document.getElementById("af-canvas");
          var ctx = canvas.getContext("2d");

          // Streaks are accumulated in a quarter-resolution buffer, matching
          // the reference's setResolutionScale(0.25). Upscaling it bilinearly
          // is what gives the streak its soft vertical falloff, and it is the
          // reason the streak reads as smooth rather than as a hard bar.
          var SW = Math.round(W * RES_SCALE);
          var SH = Math.round(H * RES_SCALE);
          var sbuf = document.createElement("canvas");
          sbuf.width = SW;
          sbuf.height = SH;
          var sctx = sbuf.getContext("2d");

          function rgba(c, a) {
            return (
              "rgba(" +
              Math.round(Math.min(1, Math.max(0, c[0])) * 255) +
              "," +
              Math.round(Math.min(1, Math.max(0, c[1])) * 255) +
              "," +
              Math.round(Math.min(1, Math.max(0, c[2])) * 255) +
              "," +
              Math.min(1, Math.max(0, a)) +
              ")"
            );
          }

          // A slow swell across the clip so the field reads as a shot rather
          // than as a loop. Closed form in t, like everything else here.
          function gainAt(t) {
            var u = Math.min(1, Math.max(0, t / DUR));
            return 0.75 + 0.35 * Math.sin(Math.PI * u);
          }

          function draw(t) {
            var tau = t * TIME_SCALE;
            var gain = gainAt(t);
            var halfLen = HALF_FRAC * W * STREAK_LEN;

            sctx.setTransform(1, 0, 0, 1, 0, 0);
            sctx.clearRect(0, 0, SW, SH);
            sctx.globalCompositeOperation = "lighter";

            var e;
            var k;
            var col;
            var bright;
            var x;
            var y;
            for (k = 0; k < EM.length; k++) {
              e = EM[k];
              x = e.x0 + e.ax * Math.sin(TAU * (e.fx * tau + e.px));
              y = e.y0 + e.ay * Math.sin(TAU * (e.fy * tau + e.py));
              bright = e.v * (0.62 + 0.38 * Math.sin(TAU * (e.fb * tau + e.pb)));
              col = hsv(e.h, e.s, Math.max(0, bright));

              // High pass, then tint, exactly as the reference filter does:
              // max(colour - threshold, 0) * tint. Below threshold there is no
              // streak at all, which is what keeps the field from smearing.
              var hp = [
                Math.max(0, col[0] - THRESHOLD) * TINT[0],
                Math.max(0, col[1] - THRESHOLD) * TINT[1],
                Math.max(0, col[2] - THRESHOLD) * TINT[2],
              ];
              var peak = Math.max(hp[0], Math.max(hp[1], hp[2]));
              if (peak <= 0.0001) continue;

              // Peak of the tent equals the source value times intensity over
              // the samples/3 normaliser. Magnitude is carried in alpha so the
              // hue keeps full 8-bit precision at low brightness.
              var alpha = (peak * INTENSITY * gain) / NORM;
              if (alpha < 0.003) continue;
              var hue = [hp[0] / peak, hp[1] / peak, hp[2] / peak];

              var bx = x * RES_SCALE;
              var by = y * RES_SCALE;
              var bh = Math.max(1, e.r * 2 * RES_SCALE);
              var bl = halfLen * RES_SCALE;

              var g = sctx.createLinearGradient(bx - bl, 0, bx + bl, 0);
              g.addColorStop(0, rgba(hue, 0));
              g.addColorStop(0.5, rgba(hue, alpha));
              g.addColorStop(1, rgba(hue, 0));
              sctx.fillStyle = g;
              sctx.fillRect(bx - bl, by - bh / 2, bl * 2, bh);
            }

            ctx.setTransform(1, 0, 0, 1, 0, 0);
            ctx.globalCompositeOperation = "source-over";
            ctx.fillStyle = BACKDROP;
            ctx.fillRect(0, 0, W, H);

            ctx.globalCompositeOperation = "lighter";
            ctx.imageSmoothingEnabled = true;
            ctx.imageSmoothingQuality = "high";
            ctx.drawImage(sbuf, 0, 0, SW, SH, 0, 0, W, H);

            // The highlights themselves, drawn at full resolution on top.
            for (k = 0; k < EM.length; k++) {
              e = EM[k];
              x = e.x0 + e.ax * Math.sin(TAU * (e.fx * tau + e.px));
              y = e.y0 + e.ay * Math.sin(TAU * (e.fy * tau + e.py));
              bright = e.v * (0.62 + 0.38 * Math.sin(TAU * (e.fb * tau + e.pb)));
              col = hsv(e.h, e.s, Math.max(0, bright));

              if (BLOOM_R > 0) {
                var br = e.r + BLOOM_R;
                var gb = ctx.createRadialGradient(x, y, 0, x, y, br);
                gb.addColorStop(0, rgba(col, 0.5 * gain));
                gb.addColorStop(0.35, rgba(col, 0.16 * gain));
                gb.addColorStop(1, rgba(col, 0));
                ctx.fillStyle = gb;
                ctx.fillRect(x - br, y - br, br * 2, br * 2);
              }

              var core = ctx.createRadialGradient(x, y, 0, x, y, e.r);
              core.addColorStop(
                0,
                rgba([col[0] * 0.88 + 0.12, col[1] * 0.88 + 0.12, col[2] * 0.88 + 0.12], 1),
              );
              core.addColorStop(0.72, rgba(col, 0.96));
              core.addColorStop(1, rgba(col, 0));
              ctx.fillStyle = core;
              ctx.fillRect(x - e.r, y - e.r, e.r * 2, e.r * 2);
            }
          }

          window.__timelines = window.__timelines || {};
          var tl = gsap.timeline({ paused: true });

          // The canvas is repainted from a property SETTER, not from a
          // timeline onUpdate callback: gsap suppresses events on seek(), so a
          // callback-driven canvas silently freezes on frame 0 under any
          // consumer that scrubs. Tweened values are written on every render,
          // suppressed or not, so the setter fires on seek and hands us the
          // frame time directly.
          var driver = { _t: 0 };
          Object.defineProperty(driver, "t", {
            get: function () {
              return this._t;
            },
            set: function (v) {
              this._t = v;
              draw(v);
            },
          });
          tl.to(driver, { t: DUR, duration: DUR, ease: "none" }, 0);
          window.__timelines["vfx-anamorphic-flare"] = tl;

          draw(0);
        })();
      </script>
    </body>
  </html>
  ```
</Accordion>

Tagged `vfx` `background` `showcase` `optical` `canvas`.

## Related topics

* [Browse the complete Catalog](/catalog)
* [Add assets and Catalog items in Studio](/studio/assets-and-blocks)
* [Build a richer composition](/go-further)


## Related topics

- [Verified example prompts](/prompting/examples.md)
- [Camera Follow Captions](/catalog/components/caption-camera-follow.md)
- [@hyperframes/shader-transitions](/packages/shader-transitions.md)
