// GL Transitions v1 param parser — ByteTinker, MIT
//
// Single source of truth for shader parameters. The `.glsl` files declare their own
// params using the GL Transitions comment convention, extended with an optional range:
//
//     uniform float bounce; // = 0.5 [0.0..1.5]
//
// This parser is consumed by:
//   - the web player renderer   (uniform defaults + values from snapshot config)
//   - the Tizen player renderer (same)
//   - the dashboard picker      (slider min/max/default/step)
//
// There is deliberately no separate param schema. If you find yourself adding one,
// the shader and the UI have already drifted.

const PARAM_RE =
  /uniform\s+float\s+(\w+)\s*;\s*\/\/\s*=\s*(-?[\d.]+)\s*(?:\[\s*(-?[\d.]+)\s*\.\.\s*(-?[\d.]+)\s*\])?/g;

/**
 * Parse param declarations out of a GLSL source string.
 * @param {string} src
 * @returns {Array<{name:string, default:number, min:number, max:number, step:number}>}
 */
function parseParams(src) {
  const out = [];
  let m;
  PARAM_RE.lastIndex = 0;
  while ((m = PARAM_RE.exec(src))) {
    const def = parseFloat(m[2]);
    const min = m[3] !== undefined ? parseFloat(m[3]) : Math.min(0, def);
    const max = m[4] !== undefined ? parseFloat(m[4]) : (def === 0 ? 1 : def * 2);
    out.push({ name: m[1], default: def, min, max, step: (max - min) / 200 });
  }
  return out;
}

/**
 * Merge stored values over defaults, dropping unknown keys and clamping to range.
 * Never trust the snapshot blob — a shader may have been edited since the playlist
 * was configured, and an out-of-range uniform can produce a black frame.
 * @param {Array} params result of parseParams
 * @param {Object} stored values from snapshot.transition.params
 */
function resolveParams(params, stored) {
  const out = {};
  for (const p of params) {
    const v = stored && typeof stored[p.name] === 'number' ? stored[p.name] : p.default;
    out[p.name] = Math.min(p.max, Math.max(p.min, v));
  }
  return out;
}

// The renderer wraps each .glsl with this. Keep it identical across web, Tizen, and
// Android — the shader sources assume exactly these names and nothing else.
const PREAMBLE = `precision highp float;
varying vec2 vUv;
uniform sampler2D uFrom;
uniform sampler2D uTo;
uniform float progress;
uniform float ratio;
vec4 getFromColor(vec2 uv){ return texture2D(uFrom, uv); }
vec4 getToColor(vec2 uv){ return texture2D(uTo, uv); }
`;

const EPILOGUE = `
void main(){ gl_FragColor = transition(vUv); }`;

const VERTEX = `attribute vec2 aPos;
varying vec2 vUv;
void main(){ vUv = aPos * 0.5 + 0.5; gl_Position = vec4(aPos, 0.0, 1.0); }`;

// Hand-committed copy of shared/Transitions — keep the both-targets export in step with it.
if (typeof module !== 'undefined' && module.exports) {
  module.exports = { parseParams, resolveParams, PREAMBLE, EPILOGUE, VERTEX };
}
if (typeof self !== 'undefined') {
  self.TransitionParams = { parseParams, resolveParams, PREAMBLE, EPILOGUE, VERTEX }; // browser (player/demo)
}

;
// Portable WebGL transition compositor (GL Transitions v1).
//
// ONE implementation, consumed by both the web player (inlined at build) and the Tizen player.
// No DOM dependency beyond the <canvas> handed in. The .glsl shaders + params.js are the single
// source of truth; this module only wraps (PREAMBLE + shader + EPILOGUE, shared VERTEX) and runs
// them. It composites TWO textures (from -> to) across `progress` 0..1; the outgoing frame stays
// live in `uFrom` for the whole transition, so there is never a blank seam.
//
// Never-blank teeth live here too: on `webglcontextlost` the renderer flips `lost` and calls
// opts.onContextLost so the player can hard-cut to a plain <img> instead of showing black.
(function (root, factory) {
  var api = factory();
  if (typeof module !== 'undefined' && module.exports) module.exports = api;
  if (root) root.TransitionRenderer = api;
})(typeof self !== 'undefined' ? self : this, function () {
  'use strict';

  function compile(gl, type, src) {
    const s = gl.createShader(type);
    gl.shaderSource(s, src);
    gl.compileShader(s);
    if (!gl.getShaderParameter(s, gl.COMPILE_STATUS)) {
      const log = gl.getShaderInfoLog(s);
      gl.deleteShader(s);
      throw new Error('shader compile failed: ' + log);
    }
    return s;
  }

  // wrap = { PREAMBLE, EPILOGUE, VERTEX } from params.js
  function createRenderer(canvas, wrap, opts) {
    opts = opts || {};
    const attrs = {
      alpha: false, antialias: false, depth: false, stencil: false,
      premultipliedAlpha: false, preserveDrawingBuffer: !!opts.preserveDrawingBuffer,
    };
    const gl = canvas.getContext('webgl', attrs) || canvas.getContext('experimental-webgl', attrs);
    if (!gl) throw new Error('no-webgl');

    let lost = false;
    const onLost = (e) => { e.preventDefault(); lost = true; if (opts.onContextLost) opts.onContextLost(); };
    const onRestored = () => { lost = false; programs = {}; buildQuad(); if (opts.onContextRestored) opts.onContextRestored(); };
    canvas.addEventListener('webglcontextlost', onLost, false);
    canvas.addEventListener('webglcontextrestored', onRestored, false);

    let quadBuf, vShader;
    function buildQuad() {
      quadBuf = gl.createBuffer();
      gl.bindBuffer(gl.ARRAY_BUFFER, quadBuf);
      gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([-1, -1, 1, -1, -1, 1, 1, 1]), gl.STATIC_DRAW);
      vShader = compile(gl, gl.VERTEX_SHADER, wrap.VERTEX);
    }
    buildQuad();

    let programs = {}; // shaderSrc -> { program, uni:{} }
    function programFor(src) {
      if (programs[src]) return programs[src];
      const f = compile(gl, gl.FRAGMENT_SHADER, wrap.PREAMBLE + '\n' + src + '\n' + wrap.EPILOGUE);
      const p = gl.createProgram();
      gl.attachShader(p, vShader);
      gl.attachShader(p, f);
      gl.bindAttribLocation(p, 0, 'aPos');
      gl.linkProgram(p);
      if (!gl.getProgramParameter(p, gl.LINK_STATUS)) {
        const log = gl.getProgramInfoLog(p);
        gl.deleteProgram(p); gl.deleteShader(f);
        throw new Error('program link failed: ' + log);
      }
      gl.deleteShader(f);
      programs[src] = { program: p, uni: {} };
      return programs[src];
    }
    function uniLoc(rec, name) {
      if (!(name in rec.uni)) rec.uni[name] = gl.getUniformLocation(rec.program, name);
      return rec.uni[name];
    }

    function makeTex() {
      const t = gl.createTexture();
      gl.bindTexture(gl.TEXTURE_2D, t);
      gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
      gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
      gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
      gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
      return t;
    }
    let texFrom = makeTex(), texTo = makeTex();
    let curShader = null;

    function upload(tex, source) {
      gl.bindTexture(gl.TEXTURE_2D, tex);
      gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true);            // match getFromColor/getToColor uv convention
      gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, false);
      gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, source);
    }

    return {
      get lost() { return lost; },
      gl,
      // Compile eagerly so a bad shader throws HERE (caller hard-cuts) rather than mid-transition.
      setShader(src) { curShader = src; programFor(src); },
      setFrom(img) { upload(texFrom, img); },
      setTo(img) { upload(texTo, img); },
      resize(w, h) { canvas.width = w; canvas.height = h; gl.viewport(0, 0, w, h); },
      render(progress, params) {
        if (lost || !curShader) return false;
        const rec = programFor(curShader);
        gl.useProgram(rec.program);
        gl.viewport(0, 0, canvas.width, canvas.height);
        gl.activeTexture(gl.TEXTURE0); gl.bindTexture(gl.TEXTURE_2D, texFrom); gl.uniform1i(uniLoc(rec, 'uFrom'), 0);
        gl.activeTexture(gl.TEXTURE1); gl.bindTexture(gl.TEXTURE_2D, texTo); gl.uniform1i(uniLoc(rec, 'uTo'), 1);
        gl.uniform1f(uniLoc(rec, 'progress'), Math.max(0, Math.min(1, progress)));
        gl.uniform1f(uniLoc(rec, 'ratio'), canvas.width / Math.max(1, canvas.height));
        if (params) for (const k in params) { const loc = uniLoc(rec, k); if (loc) gl.uniform1f(loc, params[k]); }
        gl.bindBuffer(gl.ARRAY_BUFFER, quadBuf);
        gl.enableVertexAttribArray(0);
        gl.vertexAttribPointer(0, 2, gl.FLOAT, false, 0, 0);
        gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
        return true;
      },
      destroy() {
        canvas.removeEventListener('webglcontextlost', onLost);
        canvas.removeEventListener('webglcontextrestored', onRestored);
        try {
          gl.deleteTexture(texFrom); gl.deleteTexture(texTo); gl.deleteBuffer(quadBuf);
          for (const k in programs) gl.deleteProgram(programs[k].program);
        } catch (e) { /* context already gone */ }
        programs = {};
      },
    };
  }

  return { createRenderer, compile };
});

;
window.__TRANSITION_SHADERS={"CRTCollapse":"// CRT Collapse\n// blurb: Frame crushes to a line, then a dot, then the next image blooms back out. Power-cycle drama.\n// Author: Dan (ByteTinker)\n// License: MIT\n// GL Transitions v1 — GLSL ES 1.0, runs unmodified in WebGL1 and Android GLES2.\nuniform float lineHold; // = 0.16 [0.0..0.45]\nuniform float flashGain;// = 1.6 [0.0..4.0]\nuniform float bloom;    // = 14.0 [4.0..40.0]\n\nvec4 transition(vec2 uv){\n  vec2 c = uv - 0.5;\n  float half_ = 0.5 * lineHold + 0.5;   // unused shaping guard\n  float pa = clamp(progress / 0.45, 0.0, 1.0);\n  float pb = clamp((progress - 0.55) / 0.45, 0.0, 1.0);\n\n  float vs  = 1.0 - smoothstep(0.0, 0.70, pa);\n  float hs  = 1.0 - smoothstep(0.62, 1.0, pa);\n  float vs2 = smoothstep(0.0, 0.38, pb);\n  float hs2 = smoothstep(0.30, 1.0, pb);\n\n  vec3 col = vec3(0.0);\n  if(progress < 0.5){\n    if(abs(c.y) < vs * 0.5 + 0.0016 && abs(c.x) < hs * 0.5 + 0.0016){\n      vec2 s = vec2(c.x / max(hs, 0.003), c.y / max(vs, 0.003)) + 0.5;\n      col = getFromColor(clamp(s, 0.0, 1.0)).rgb;\n    }\n  } else {\n    if(abs(c.y) < vs2 * 0.5 + 0.0016 && abs(c.x) < hs2 * 0.5 + 0.0016){\n      vec2 s = vec2(c.x / max(hs2, 0.003), c.y / max(vs2, 0.003)) + 0.5;\n      col = getToColor(clamp(s, 0.0, 1.0)).rgb;\n    }\n  }\n\n  float d = length(vec2(c.x * ratio, c.y));\n  float flash = exp(-pow(abs(progress - 0.5) * 8.0, 2.0));\n  col += vec3(1.0, 0.96, 0.88) * flash * exp(-d * bloom) * flashGain;\n  return vec4(col, 1.0);\n}\n","Datamosh":"// Datamosh\n// blurb: P-frame corruption — the old frame’s gradients smear the new one until the blocks give up.\n// Author: Dan (ByteTinker)\n// License: MIT\n// GL Transitions v1 — GLSL ES 1.0, runs unmodified in WebGL1 and Android GLES2.\nuniform float blockSize; // = 30.0 [6.0..90.0]\nuniform float bleed;     // = 0.45 [0.0..1.5]\nuniform float chroma;    // = 0.5 [0.0..2.0]\n\nfloat bt_hash(vec2 p){ return fract(sin(dot(p, vec2(127.1, 311.7))) * 43758.5453123); }\n\nvec4 transition(vec2 uv){\n  float env = sin(3.14159265 * progress);\n  vec2 bs = vec2(blockSize * ratio, blockSize);\n  vec2 blk = floor(uv * bs);\n\n  vec3 c0 = getFromColor((blk + 0.5) / bs).rgb;\n  vec3 c1 = getFromColor((blk + vec2(1.5, 0.5)) / bs).rgb;\n  vec3 c2 = getFromColor((blk + vec2(0.5, 1.5)) / bs).rgb;\n  vec2 mv = vec2(dot(c1 - c0, vec3(0.333)), dot(c2 - c0, vec3(0.333)));\n  mv *= bleed * env * 5.0;\n\n  float keep = step(progress, bt_hash(blk) * 0.85 + 0.10);\n\n  vec3 A = getFromColor(fract(uv + mv)).rgb;\n  vec2 ub = fract(uv + mv * 0.5);\n  vec3 B;\n  float ch = chroma * env * 0.01;\n  B.r = getToColor(fract(ub + vec2(ch, 0.0))).r;\n  B.g = getToColor(ub).g;\n  B.b = getToColor(fract(ub - vec2(ch, 0.0))).b;\n\n  vec3 res = mix(B, A, keep);\n  float q = mix(255.0, 10.0, env);\n  res = floor(res * q) / q;\n  return vec4(res, 1.0);\n}\n","Etch":"// Etch\n// blurb: Photomask reveal — the frame develops in on a stepper field, with a hot exposure edge.\n// Author: Dan (ByteTinker)\n// License: MIT\n// GL Transitions v1 — GLSL ES 1.0, runs unmodified in WebGL1 and Android GLES2.\nuniform float cellSize;  // = 26.0 [6.0..90.0]\nuniform float edgeGlow;  // = 1.0 [0.0..3.0]\nuniform float randomness;// = 0.55 [0.0..1.0]\nuniform float softness;  // = 0.09 [0.005..0.3]\n\nfloat bt_hash(vec2 p){ return fract(sin(dot(p, vec2(127.1, 311.7))) * 43758.5453123); }\n\nvec4 transition(vec2 uv){\n  vec2 g = floor(uv * vec2(cellSize * ratio, cellSize));\n  float m = bt_hash(g);\n  float sweep = (uv.x + uv.y) * 0.5;\n  float mask = clamp(mix(sweep, m, randomness), 0.0, 1.0);\n\n  float e = softness;\n  float pp = progress * (1.0 + e);\n  float t = smoothstep(mask, mask + e, pp);\n\n  vec3 a = getFromColor(uv).rgb;\n  vec3 b = getToColor(uv).rgb;\n  vec3 res = mix(a, b, t);\n\n  float edge = exp(-abs(pp - mask) / max(e, 0.005) * 1.6);\n  edge *= (1.0 - smoothstep(0.92, 1.0, progress)) * smoothstep(0.0, 0.05, progress);\n  res += vec3(1.0, 0.70, 0.22) * edge * edgeGlow * 0.7;\n\n  return vec4(res, 1.0);\n}\n","FiberSplice":"// Fiber Splice\n// blurb: Two ends draw apart, the arc fires, and the new frame fuses in from the seam.\n// Author: Dan (ByteTinker)\n// License: MIT\n// GL Transitions v1 — GLSL ES 1.0, runs unmodified in WebGL1 and Android GLES2.\nuniform float separation; // = 0.30 [0.0..0.5]\nuniform float arcGain;    // = 1.8 [0.0..4.0]\nuniform float arcTight;   // = 26.0 [4.0..80.0]\nuniform float flicker;    // = 0.5 [0.0..1.0]\n\nfloat bt_hash(vec2 p){ return fract(sin(dot(p, vec2(127.1, 311.7))) * 43758.5453123); }\n\nvec4 transition(vec2 uv){\n  float x = uv.x;\n  float ph = clamp(progress / 0.46, 0.0, 1.0);\n  float pb = clamp((progress - 0.54) / 0.46, 0.0, 1.0);\n\n  vec3 col = vec3(0.0);\n  if(progress < 0.5){\n    float off = ph * separation;\n    if(x < 0.5 - off)       col = getFromColor(vec2(x + off, uv.y)).rgb;\n    else if(x >= 0.5 + off) col = getFromColor(vec2(x - off, uv.y)).rgb;\n  } else {\n    float off = (1.0 - pb) * separation;\n    if(x < 0.5 - off)       col = getToColor(vec2(x + off, uv.y)).rgb;\n    else if(x >= 0.5 + off) col = getToColor(vec2(x - off, uv.y)).rgb;\n  }\n\n  float fl = 1.0 + flicker * (bt_hash(vec2(floor(uv.y * 90.0), floor(progress * 120.0))) - 0.5);\n  float arc = exp(-abs(x - 0.5) * arcTight) * exp(-pow((progress - 0.5) * 8.5, 2.0)) * fl;\n  col += vec3(0.72, 0.88, 1.0) * arc * arcGain;\n  col += vec3(1.0) * arc * arc * 0.6;\n\n  return vec4(col, 1.0);\n}\n","FilmAdvance":"// Film Advance\n// blurb: The strip pulls through the gate — frame bar and sprockets sweep past, shutter flickers, next frame registers with a bounce.\n// Author: Dan (ByteTinker)\n// License: MIT\n// GL Transitions v1 — GLSL ES 1.0, runs unmodified in WebGL1 and Android GLES2.\nuniform float frameBar;  // = 0.11 [0.02..0.35]\nuniform float bounce;    // = 0.5 [0.0..1.5]\nuniform float shutter;   // = 0.55 [0.0..1.0]\nuniform float sprockets; // = 1.0 [0.0..1.0]\nuniform float grainAmt;  // = 0.5 [0.0..1.0]\n\nfloat bt_hash(vec2 p){ return fract(sin(dot(p, vec2(127.1, 311.7))) * 43758.5453123); }\n\nvec4 transition(vec2 uv){\n  float e = progress;\n  float env = sin(3.14159265 * e);\n\n  // pull with a little registration overshoot at the gate\n  float ease = smoothstep(0.0, 1.0, e);\n  ease += bounce * 0.045 * sin(e * 18.85) * e * (1.0 - e) * 4.0;\n  ease = clamp(ease, 0.0, 1.0);\n\n  float o = ease * (1.0 + frameBar);\n  float t = uv.y - o;\n\n  vec3 col;\n  if(t >= 0.0){\n    col = getFromColor(vec2(uv.x, min(t, 1.0))).rgb;\n  } else if(t <= -frameBar){\n    col = getToColor(vec2(uv.x, clamp(t + 1.0 + frameBar, 0.0, 1.0))).rgb;\n  } else {\n    float scr = bt_hash(vec2(floor(uv.x * 400.0), floor(e * 12.0)));\n    col = vec3(0.035, 0.028, 0.022) + vec3(0.10, 0.08, 0.06) * step(0.985, scr);\n  }\n\n  // edge perforations, present only while the strip is moving\n  float edge = min(uv.x, 1.0 - uv.x);\n  float band = 1.0 - smoothstep(0.030, 0.038, edge);\n  float sy = fract((uv.y - o) * (1.0 / (1.0 + frameBar)) * 4.0);\n  float holeY = 1.0 - smoothstep(0.26, 0.34, abs(sy - 0.5));\n  float holeX = 1.0 - smoothstep(0.008, 0.014, abs(edge - 0.019));\n  float perf = holeY * step(0.006, edge) * (1.0 - holeX * 0.0);\n  vec3 strip = mix(vec3(0.02), vec3(0.86, 0.83, 0.75), perf);\n  col = mix(col, strip, band * env * sprockets);\n\n  // shutter blade\n  float blade = 1.0 - shutter * env * (0.75 + 0.25 * sin(e * 62.8));\n  col *= blade;\n\n  // gate grain\n  float g = bt_hash(uv * vec2(720.0, 405.0) + floor(e * 48.0)) - 0.5;\n  col += vec3(g) * grainAmt * env * 0.20;\n\n  return vec4(col, 1.0);\n}\n","PacketLoss":"// Packet Loss\n// blurb: Blocks drop out of the stream, rows tear, and the new frame retransmits block by block.\n// Author: Dan (ByteTinker)\n// License: MIT\n// GL Transitions v1 — GLSL ES 1.0, runs unmodified in WebGL1 and Android GLES2.\nuniform float cols;     // = 26.0 [4.0..80.0]\nuniform float rows;     // = 15.0 [3.0..48.0]\nuniform float rowTear;  // = 0.06 [0.0..0.3]\nuniform float garbage;  // = 0.5 [0.0..1.0]\n\nfloat bt_hash(vec2 p){ return fract(sin(dot(p, vec2(127.1, 311.7))) * 43758.5453123); }\n\nvec4 transition(vec2 uv){\n  float env = sin(3.14159265 * progress);\n  vec2 bs = vec2(cols, rows);\n\n  float rowId = floor(uv.y * rows);\n  float tear = (bt_hash(vec2(rowId, floor(progress * 24.0))) - 0.5) * rowTear * env;\n  vec2 uvt = vec2(fract(uv.x + tear), uv.y);\n\n  vec2 blk = floor(uvt * bs);\n  float dropT = 0.05 + bt_hash(blk) * 0.40;\n  float backT = 0.55 + bt_hash(blk + 11.3) * 0.40;\n\n  float gone = step(dropT, progress);\n  float back = step(backT, progress);\n\n  vec3 a = getFromColor(uvt).rgb;\n  vec3 b = getToColor(uvt).rgb;\n\n  vec3 junk = getFromColor(fract(uvt + vec2(bt_hash(blk + 3.1) * 0.5, bt_hash(blk + 5.7) * 0.5))).rgb;\n  junk = junk.gbr * (0.4 + bt_hash(blk + 9.0) * 0.6);\n  vec3 hole = mix(vec3(0.0), junk, garbage * step(0.55, bt_hash(blk + 2.2)));\n\n  vec3 col = mix(a, hole, gone * (1.0 - back));\n  col = mix(col, b, back);\n  return vec4(col, 1.0);\n}\n","PixelSort":"// Pixel Sort\n// blurb: Columns tear and quantize like glitch art, then resolve on a staggered per-column threshold.\n// Author: Dan (ByteTinker)\n// License: MIT\n// GL Transitions v1 — GLSL ES 1.0, runs unmodified in WebGL1 and Android GLES2.\nuniform float columns;  // = 200.0 [20.0..600.0]\nuniform float strength; // = 0.45 [0.0..1.0]\nuniform float split;    // = 0.012 [0.0..0.06]\nuniform float density;  // = 0.35 [0.0..0.9]\n\nfloat bt_hash(vec2 p){ return fract(sin(dot(p, vec2(127.1, 311.7))) * 43758.5453123); }\n\nvec4 transition(vec2 uv){\n  float env = sin(3.14159265 * progress);\n  float col = floor(uv.x * columns);\n  float seed = bt_hash(vec2(col, 3.0));\n  float active = step(density, seed);\n\n  float amt = env * strength * (seed * 2.0 - 1.0) * active;\n  vec3 a = getFromColor(vec2(uv.x, fract(uv.y + amt))).rgb;\n\n  float s = split * env * active;\n  vec2 ub = vec2(uv.x, fract(uv.y - amt));\n  vec3 b;\n  b.r = getToColor(vec2(fract(ub.x + s), ub.y)).r;\n  b.g = getToColor(ub).g;\n  b.b = getToColor(vec2(fract(ub.x - s), ub.y)).b;\n\n  float q = mix(255.0, 6.0, env);\n  a = floor(a * q) / q;\n  b = floor(b * q) / q;\n\n  float t = smoothstep(seed * 0.5, seed * 0.5 + 0.5, progress);\n  return vec4(mix(a, b, t), 1.0);\n}\n","QuantumDither":"// Quantum Dither\n// blurb: Pixels stay undecided, shimmering between both frames, then collapse on an ordered threshold.\n// Author: Dan (ByteTinker)\n// License: MIT\n// GL Transitions v1 — GLSL ES 1.0, runs unmodified in WebGL1 and Android GLES2.\nuniform float grain;     // = 180.0 [20.0..600.0]\nuniform float coherence; // = 0.6 [0.0..1.0]\nuniform float shimmer;   // = 0.5 [0.0..1.0]\nuniform float edgeSoft;  // = 0.10 [0.01..0.4]\n\nfloat bt_hash(vec2 p){ return fract(sin(dot(p, vec2(127.1, 311.7))) * 43758.5453123); }\nfloat bt_bayer2(vec2 a){ a = floor(a); return fract(a.x * 0.5 + a.y * a.y * 0.75); }\nfloat bt_bayer4(vec2 a){ return (bt_bayer2(a * 0.5) * 0.25 + bt_bayer2(a)) / 0.9375; }\n\nvec4 transition(vec2 uv){\n  vec2 g = floor(uv * vec2(grain * ratio, grain));\n  float n  = bt_hash(g);\n  float bo = bt_bayer4(g);\n  float thr = mix(n, bo, coherence);\n\n  float w = edgeSoft;\n  float front = abs(progress - thr);\n  float undecided = exp(-front / max(w, 0.01) * 1.4);\n  thr += (bt_hash(g + floor(progress * 45.0) * 17.0) - 0.5) * shimmer * undecided * 0.5;\n\n  float t = smoothstep(thr - w, thr + w, progress * (1.0 + 2.0 * w) - w);\n\n  vec3 a = getFromColor(uv).rgb;\n  vec3 b = getToColor(uv).rgb;\n  vec3 col = mix(a, b, t);\n  col += vec3(0.18, 0.55, 0.62) * undecided * shimmer * 0.5;\n  return vec4(col, 1.0);\n}\n","ReelChange":"// Reel Change\n// blurb: Cue mark burns in the corner, the splice jumps the frame, dust settles on the new reel.\n// Author: Dan (ByteTinker)\n// License: MIT\n// GL Transitions v1 — GLSL ES 1.0, runs unmodified in WebGL1 and Android GLES2.\nuniform float cueSize;  // = 0.045 [0.0..0.12]\nuniform float jumpAmt;  // = 0.16 [0.0..0.5]\nuniform float dust;     // = 0.6 [0.0..1.0]\nuniform float scratch;  // = 0.5 [0.0..1.0]\n\nfloat bt_hash(vec2 p){ return fract(sin(dot(p, vec2(127.1, 311.7))) * 43758.5453123); }\n\nvec4 transition(vec2 uv){\n  float e = progress;\n  float splice = clamp((e - 0.46) / 0.16, 0.0, 1.0);\n  float active = step(0.46, e) * step(e, 0.62);\n\n  // stepped misregistration through the splice\n  float k = floor(splice * 4.0);\n  float off = (bt_hash(vec2(k, 2.0)) - 0.5) * jumpAmt * (1.0 - splice) * active;\n\n  vec2 su = vec2(uv.x, fract(uv.y + off));\n  vec3 a = getFromColor(su).rgb;\n  vec3 b = getToColor(su).rgb;\n  vec3 col = mix(a, b, step(0.54, e));\n\n  // black frame bar riding through during the splice\n  float barY = fract(uv.y + off * 3.0 + k * 0.37);\n  float bar = (1.0 - smoothstep(0.0, 0.045, abs(barY - 0.5))) * active;\n  col = mix(col, vec3(0.02), bar);\n\n  // cue mark, upper right, four-frame flicker\n  vec2 cp = (uv - vec2(0.865, 0.855)) * vec2(ratio, 1.0);\n  float ring = 1.0 - smoothstep(cueSize * 0.72, cueSize, length(cp));\n  float cueWin = step(0.20, e) * step(e, 0.47);\n  float flick = 0.55 + 0.45 * step(0.5, fract(e * 26.0));\n  col += vec3(1.0, 0.94, 0.80) * ring * cueWin * flick * 0.85;\n\n  // dust and scratches, decaying after the change\n  float decay = exp(-max(e - 0.54, 0.0) * 9.0) * step(0.46, e);\n  float sp = bt_hash(uv * vec2(300.0, 170.0) + floor(e * 40.0));\n  col += vec3(0.9) * step(0.9975, sp) * dust * decay;\n  float sc = bt_hash(vec2(floor(uv.x * 260.0), floor(e * 20.0)));\n  col += vec3(0.85, 0.82, 0.74) * step(0.995, sc) * scratch * decay * 0.7;\n\n  return vec4(col, 1.0);\n}\n","SignalLock":"// Signal Lock\n// blurb: Static, rolling sync bars, then the picture snaps in like a tuner acquiring.\n// Author: Dan (ByteTinker)\n// License: MIT\n// GL Transitions v1 — GLSL ES 1.0, runs unmodified in WebGL1 and Android GLES2.\nuniform float noiseAmount; // = 0.85 [0.0..1.0]\nuniform float rollSpeed;   // = 2.4 [0.0..8.0]\nuniform float barCount;    // = 3.0 [0.0..8.0]\nuniform float tearAmount;  // = 0.12 [0.0..0.5]\n\nfloat bt_hash(vec2 p){ return fract(sin(dot(p, vec2(127.1, 311.7))) * 43758.5453123); }\n\nvec4 transition(vec2 uv){\n  float env = sin(3.14159265 * progress);          // 0 at both ends\n  float settle = 1.0 - smoothstep(0.55, 1.0, progress);\n\n  vec2 uvr = uv;\n  uvr.y = fract(uv.y + settle * progress * rollSpeed);\n  float line = floor(uv.y * 240.0);\n  float tear = (bt_hash(vec2(line, floor(progress * 30.0))) - 0.5) * tearAmount * env;\n  uvr.x = fract(uv.x + tear);\n\n  vec4 a = getFromColor(uv);\n  vec4 b = getToColor(uvr);\n  float sig = smoothstep(0.42, 0.58, progress);\n  vec4 img = mix(a, b, sig);\n\n  float bp = fract(uv.y + progress * barCount);\n  float bar = smoothstep(0.0, 0.05, bp) * (1.0 - smoothstep(0.05, 0.11, bp));\n  img.rgb += bar * 0.40 * env;\n\n  float st = bt_hash(uv * vec2(640.0, 360.0) + floor(progress * 60.0));\n  img.rgb = mix(img.rgb, vec3(st), pow(env, 0.55) * noiseAmount * 0.7);\n\n  return vec4(img.rgb, 1.0);\n}\n","SpectrumSweep":"// Spectrum Sweep\n// blurb: An analyzer band crosses the frame, drawing the incoming image as bars before it resolves.\n// Author: Dan (ByteTinker)\n// License: MIT\n// GL Transitions v1 — GLSL ES 1.0, runs unmodified in WebGL1 and Android GLES2.\nuniform float bandWidth; // = 0.22 [0.05..0.6]\nuniform float barCount;  // = 64.0 [8.0..200.0]\nuniform float glow;      // = 1.2 [0.0..3.0]\nuniform float floorLift; // = 0.12 [0.0..0.5]\n\nvec4 transition(vec2 uv){\n  float front = progress * (1.0 + bandWidth) - bandWidth;\n  vec3 a = getFromColor(uv).rgb;\n  vec3 b = getToColor(uv).rgb;\n\n  if(uv.x < front)               return vec4(b, 1.0);\n  if(uv.x > front + bandWidth)   return vec4(a, 1.0);\n\n  float k = (uv.x - front) / bandWidth;          // 0 at trailing edge, 1 at leading\n  float bx = (floor(uv.x * barCount) + 0.5) / barCount;\n\n  vec3 s = b;\n  float lum = dot(getToColor(vec2(bx, uv.y)).rgb, vec3(0.299, 0.587, 0.114));\n  float h = clamp(lum + floorLift, 0.0, 1.0);\n\n  float bar = step(uv.y, h) * (0.35 + 0.65 * step(h - 0.02, uv.y));\n  float gap = smoothstep(0.0, 0.06, fract(uv.x * barCount)) * (1.0 - smoothstep(0.94, 1.0, fract(uv.x * barCount)));\n  vec3 analyzer = vec3(0.25, 1.0, 0.72) * bar * gap;\n\n  vec3 col = mix(s, analyzer, smoothstep(0.15, 0.85, k));\n  col = mix(col, a, smoothstep(0.75, 1.0, k));\n  col += vec3(0.4, 1.0, 0.8) * exp(-abs(k - 1.0) * 22.0) * glow;\n  return vec4(col, 1.0);\n}\n","ThermalBloom":"// Thermal Bloom\n// blurb: The frame falls into false colour, blooms hot, and the next image cools back out of it.\n// Author: Dan (ByteTinker)\n// License: MIT\n// GL Transitions v1 — GLSL ES 1.0, runs unmodified in WebGL1 and Android GLES2.\nuniform float heat;   // = 1.0 [0.0..1.0]\nuniform float blur;   // = 0.010 [0.0..0.05]\nuniform float gain;   // = 0.35 [0.0..1.2]\n\nvec3 bt_iron(float t){\n  t = clamp(t, 0.0, 1.0);\n  return clamp(vec3(\n    smoothstep(0.05, 0.55, t) * 1.15,\n    smoothstep(0.40, 0.98, t),\n    smoothstep(0.0, 0.22, t) * 0.62 - smoothstep(0.22, 0.62, t) * 0.60 + smoothstep(0.78, 1.0, t)\n  ), 0.0, 1.0);\n}\n\nvec4 transition(vec2 uv){\n  float env = sin(3.14159265 * progress);\n  float t = smoothstep(0.34, 0.66, progress);\n\n  float r = blur * env;\n  vec3 a = vec3(0.0), b = vec3(0.0);\n  for(int i = 0; i < 5; i++){\n    float f = (float(i) - 2.0) * 0.5;\n    a += getFromColor(clamp(uv + vec2(f * r * ratio, f * r), 0.0, 1.0)).rgb;\n    b += getToColor(clamp(uv + vec2(f * r * ratio, -f * r), 0.0, 1.0)).rgb;\n  }\n  a /= 5.0; b /= 5.0;\n\n  vec3 real = mix(a, b, t);\n  float lum = dot(real, vec3(0.299, 0.587, 0.114));\n  vec3 thermal = bt_iron(lum + env * gain);\n\n  vec3 col = mix(real, thermal, env * heat);\n  return vec4(col, 1.0);\n}\n","TraceRoute":"// Trace Route\n// blurb: Copper traces route across the board and the new frame fills in behind them.\n// Author: Dan (ByteTinker)\n// License: MIT\n// GL Transitions v1 — GLSL ES 1.0, runs unmodified in WebGL1 and Android GLES2.\nuniform float pitch;    // = 30.0 [6.0..90.0]\nuniform float wander;   // = 0.45 [0.0..1.0]\nuniform float traceGlow;// = 1.4 [0.0..3.0]\nuniform float traceW;   // = 0.10 [0.02..0.35]\n\nfloat bt_hash(vec2 p){ return fract(sin(dot(p, vec2(127.1, 311.7))) * 43758.5453123); }\n\nvec4 transition(vec2 uv){\n  vec2 gv = vec2(pitch * ratio, pitch);\n  vec2 cell = floor(uv * gv);\n  vec2 f = fract(uv * gv);\n\n  float march = (cell.x / gv.x) * 0.65 + abs(cell.y / gv.y - 0.5) * 0.35;\n  float d = clamp(mix(march, bt_hash(cell), wander), 0.0, 0.92);\n\n  float e = 0.07;\n  float pp = progress * (1.0 + e);\n  float fill = smoothstep(d, d + e, pp);\n\n  vec3 a = getFromColor(uv).rgb;\n  vec3 b = getToColor(uv).rgb;\n  vec3 col = mix(a, b, fill);\n\n  float horiz = step(0.5, bt_hash(cell + 4.7));\n  float line = mix(\n    1.0 - smoothstep(0.0, traceW, abs(f.x - 0.5)),\n    1.0 - smoothstep(0.0, traceW, abs(f.y - 0.5)),\n    horiz);\n  float pad = 1.0 - smoothstep(0.0, traceW * 1.6, length(f - 0.5));\n  float front = exp(-abs(pp - d) / max(e, 0.005) * 1.5);\n  front *= smoothstep(0.0, 0.05, progress) * (1.0 - smoothstep(0.92, 1.0, progress));\n\n  col += vec3(1.0, 0.58, 0.20) * max(line, pad) * front * traceGlow;\n  return vec4(col, 1.0);\n}\n","VanEck":"// Van Eck\n// blurb: The next frame reconstructs from raster noise, scanline by scanline, behind an acquisition beam.\n// Author: Dan (ByteTinker)\n// License: MIT\n// GL Transitions v1 — GLSL ES 1.0, runs unmodified in WebGL1 and Android GLES2.\nuniform float lineCount; // = 300.0 [60.0..720.0]\nuniform float smear;     // = 0.07 [0.0..0.3]\nuniform float jitter;    // = 0.35 [0.0..1.0]\nuniform float phosphor;  // = 1.0 [0.0..1.0]\n\nfloat bt_hash(vec2 p){ return fract(sin(dot(p, vec2(127.1, 311.7))) * 43758.5453123); }\n\nvec4 transition(vec2 uv){\n  float gate = smoothstep(0.0, 0.04, progress);\n  float line = floor(uv.y * lineCount);\n  float ly   = line / lineCount;\n\n  float j = bt_hash(vec2(line, 7.0)) * jitter;\n  float thresh = clamp(ly * (1.0 - jitter) + j, 0.0, 0.8);\n  float acq = smoothstep(thresh, thresh + 0.18, progress);\n\n  vec3 b = vec3(0.0);\n  float sm = smear * (1.0 - acq);\n  for(int i = 0; i < 6; i++){\n    float f = float(i) / 5.0;\n    b += getToColor(vec2(fract(uv.x + f * sm), uv.y)).rgb;\n  }\n  b /= 6.0;\n\n  float lum = dot(b, vec3(0.299, 0.587, 0.114));\n  vec3 phos = vec3(lum) * mix(vec3(1.0), vec3(1.0, 0.72, 0.25), phosphor);\n  b = mix(phos, b, acq);\n\n  float n = bt_hash(uv * vec2(900.0, 500.0) + floor(progress * 90.0));\n  b = mix(b, vec3(n) * mix(vec3(1.0), vec3(1.0, 0.72, 0.25), phosphor), (1.0 - acq) * 0.55);\n\n  vec3 a = getFromColor(uv).rgb;\n  float wipe = smoothstep(thresh - 0.25, thresh, progress) * gate;\n  vec3 res = mix(a, b, wipe);\n\n  float beam = exp(-abs(progress - thresh) * 55.0) * gate;\n  res += beam * vec3(1.0, 0.78, 0.34) * 0.55;\n\n  res = mix(res, getToColor(uv).rgb, smoothstep(0.96, 1.0, progress));\n  return vec4(res, 1.0);\n}\n"};
window.__TRANSITION_MANIFEST=[{"id":"CRTCollapse","name":"CRT Collapse","blurb":"Frame crushes to a line, then a dot, then the next image blooms back out. Power-cycle drama.","file":"CRTCollapse.glsl","params":[{"name":"lineHold","default":0.16,"min":0,"max":0.45},{"name":"flashGain","default":1.6,"min":0,"max":4},{"name":"bloom","default":14,"min":4,"max":40}]},{"id":"Datamosh","name":"Datamosh","blurb":"P-frame corruption — the old frame’s gradients smear the new one until the blocks give up.","file":"Datamosh.glsl","params":[{"name":"blockSize","default":30,"min":6,"max":90},{"name":"bleed","default":0.45,"min":0,"max":1.5},{"name":"chroma","default":0.5,"min":0,"max":2}]},{"id":"Etch","name":"Etch","blurb":"Photomask reveal — the frame develops in on a stepper field, with a hot exposure edge.","file":"Etch.glsl","params":[{"name":"cellSize","default":26,"min":6,"max":90},{"name":"edgeGlow","default":1,"min":0,"max":3},{"name":"randomness","default":0.55,"min":0,"max":1},{"name":"softness","default":0.09,"min":0.005,"max":0.3}]},{"id":"FiberSplice","name":"Fiber Splice","blurb":"Two ends draw apart, the arc fires, and the new frame fuses in from the seam.","file":"FiberSplice.glsl","params":[{"name":"separation","default":0.3,"min":0,"max":0.5},{"name":"arcGain","default":1.8,"min":0,"max":4},{"name":"arcTight","default":26,"min":4,"max":80},{"name":"flicker","default":0.5,"min":0,"max":1}]},{"id":"FilmAdvance","name":"Film Advance","blurb":"The strip pulls through the gate — frame bar and sprockets sweep past, shutter flickers, next frame registers with a bounce.","file":"FilmAdvance.glsl","params":[{"name":"frameBar","default":0.11,"min":0.02,"max":0.35},{"name":"bounce","default":0.5,"min":0,"max":1.5},{"name":"shutter","default":0.55,"min":0,"max":1},{"name":"sprockets","default":1,"min":0,"max":1},{"name":"grainAmt","default":0.5,"min":0,"max":1}]},{"id":"PacketLoss","name":"Packet Loss","blurb":"Blocks drop out of the stream, rows tear, and the new frame retransmits block by block.","file":"PacketLoss.glsl","params":[{"name":"cols","default":26,"min":4,"max":80},{"name":"rows","default":15,"min":3,"max":48},{"name":"rowTear","default":0.06,"min":0,"max":0.3},{"name":"garbage","default":0.5,"min":0,"max":1}]},{"id":"PixelSort","name":"Pixel Sort","blurb":"Columns tear and quantize like glitch art, then resolve on a staggered per-column threshold.","file":"PixelSort.glsl","params":[{"name":"columns","default":200,"min":20,"max":600},{"name":"strength","default":0.45,"min":0,"max":1},{"name":"split","default":0.012,"min":0,"max":0.06},{"name":"density","default":0.35,"min":0,"max":0.9}]},{"id":"QuantumDither","name":"Quantum Dither","blurb":"Pixels stay undecided, shimmering between both frames, then collapse on an ordered threshold.","file":"QuantumDither.glsl","params":[{"name":"grain","default":180,"min":20,"max":600},{"name":"coherence","default":0.6,"min":0,"max":1},{"name":"shimmer","default":0.5,"min":0,"max":1},{"name":"edgeSoft","default":0.1,"min":0.01,"max":0.4}]},{"id":"ReelChange","name":"Reel Change","blurb":"Cue mark burns in the corner, the splice jumps the frame, dust settles on the new reel.","file":"ReelChange.glsl","params":[{"name":"cueSize","default":0.045,"min":0,"max":0.12},{"name":"jumpAmt","default":0.16,"min":0,"max":0.5},{"name":"dust","default":0.6,"min":0,"max":1},{"name":"scratch","default":0.5,"min":0,"max":1}]},{"id":"SignalLock","name":"Signal Lock","blurb":"Static, rolling sync bars, then the picture snaps in like a tuner acquiring.","file":"SignalLock.glsl","params":[{"name":"noiseAmount","default":0.85,"min":0,"max":1},{"name":"rollSpeed","default":2.4,"min":0,"max":8},{"name":"barCount","default":3,"min":0,"max":8},{"name":"tearAmount","default":0.12,"min":0,"max":0.5}]},{"id":"SpectrumSweep","name":"Spectrum Sweep","blurb":"An analyzer band crosses the frame, drawing the incoming image as bars before it resolves.","file":"SpectrumSweep.glsl","params":[{"name":"bandWidth","default":0.22,"min":0.05,"max":0.6},{"name":"barCount","default":64,"min":8,"max":200},{"name":"glow","default":1.2,"min":0,"max":3},{"name":"floorLift","default":0.12,"min":0,"max":0.5}]},{"id":"ThermalBloom","name":"Thermal Bloom","blurb":"The frame falls into false colour, blooms hot, and the next image cools back out of it.","file":"ThermalBloom.glsl","params":[{"name":"heat","default":1,"min":0,"max":1},{"name":"blur","default":0.01,"min":0,"max":0.05},{"name":"gain","default":0.35,"min":0,"max":1.2}]},{"id":"TraceRoute","name":"Trace Route","blurb":"Copper traces route across the board and the new frame fills in behind them.","file":"TraceRoute.glsl","params":[{"name":"pitch","default":30,"min":6,"max":90},{"name":"wander","default":0.45,"min":0,"max":1},{"name":"traceGlow","default":1.4,"min":0,"max":3},{"name":"traceW","default":0.1,"min":0.02,"max":0.35}]},{"id":"VanEck","name":"Van Eck","blurb":"The next frame reconstructs from raster noise, scanline by scanline, behind an acquisition beam.","file":"VanEck.glsl","params":[{"name":"lineCount","default":300,"min":60,"max":720},{"name":"smear","default":0.07,"min":0,"max":0.3},{"name":"jitter","default":0.35,"min":0,"max":1},{"name":"phosphor","default":1,"min":0,"max":1}]}];
