/**
 * Transition Effects Library
 * Each transition has: name, category, render(ctx, srcA, srcB, progress, w, h, drawFit)
 * progress: 0 = full A, 1 = full B (same as crossfader)
 * drawFit is passed in so transitions can use it.
 */

const TransitionRegistry = {
    _transitions: {},
    _order: [],

    register(t) {
        this._transitions[t.id] = t;
        if (!this._order.includes(t.id)) this._order.push(t.id);
    },

    get(id) {
        return this._transitions[id] || this._transitions['crossfade'];
    },

    getAll() {
        return this._order.map(id => this._transitions[id]);
    },

    getByCategory() {
        const cats = {};
        for (const t of this.getAll()) {
            if (!cats[t.category]) cats[t.category] = [];
            cats[t.category].push(t);
        }
        return cats;
    }
};

// ============================================================
// BASIC
// ============================================================

TransitionRegistry.register({
    id: 'crossfade',
    name: 'Crossfade',
    category: 'Basic',
    render(ctx, srcA, srcB, p, w, h, fit) {
        if (srcA) { ctx.globalAlpha = 1 - p; fit(ctx, srcA, w, h); }
        if (srcB) { ctx.globalAlpha = p; fit(ctx, srcB, w, h); }
        ctx.globalAlpha = 1;
    }
});

TransitionRegistry.register({
    id: 'cut',
    name: 'Hard Cut',
    category: 'Basic',
    render(ctx, srcA, srcB, p, w, h, fit) {
        const src = p < 0.5 ? srcA : srcB;
        if (src) { ctx.globalAlpha = 1; fit(ctx, src, w, h); }
    }
});

TransitionRegistry.register({
    id: 'fade-black',
    name: 'Fade to Black',
    category: 'Basic',
    render(ctx, srcA, srcB, p, w, h, fit) {
        if (p < 0.5) {
            if (srcA) { ctx.globalAlpha = 1 - p * 2; fit(ctx, srcA, w, h); }
        } else {
            if (srcB) { ctx.globalAlpha = (p - 0.5) * 2; fit(ctx, srcB, w, h); }
        }
        ctx.globalAlpha = 1;
    }
});

TransitionRegistry.register({
    id: 'fade-white',
    name: 'Fade to White',
    category: 'Basic',
    render(ctx, srcA, srcB, p, w, h, fit) {
        if (p < 0.5) {
            if (srcA) { ctx.globalAlpha = 1; fit(ctx, srcA, w, h); }
            ctx.globalAlpha = p * 2;
            ctx.fillStyle = '#fff';
            ctx.fillRect(0, 0, w, h);
        } else {
            ctx.globalAlpha = 1;
            ctx.fillStyle = '#fff';
            ctx.fillRect(0, 0, w, h);
            if (srcB) { ctx.globalAlpha = (p - 0.5) * 2; fit(ctx, srcB, w, h); }
        }
        ctx.globalAlpha = 1;
    }
});

// ============================================================
// WIPES
// ============================================================

TransitionRegistry.register({
    id: 'wipe-left',
    name: 'Wipe Left',
    category: 'Wipes',
    render(ctx, srcA, srcB, p, w, h, fit) {
        const split = Math.round(w * (1 - p));
        if (srcA && split > 0) {
            ctx.save(); ctx.beginPath(); ctx.rect(0, 0, split, h); ctx.clip();
            ctx.globalAlpha = 1; fit(ctx, srcA, w, h); ctx.restore();
        }
        if (srcB && split < w) {
            ctx.save(); ctx.beginPath(); ctx.rect(split, 0, w - split, h); ctx.clip();
            ctx.globalAlpha = 1; fit(ctx, srcB, w, h); ctx.restore();
        }
    }
});

TransitionRegistry.register({
    id: 'wipe-right',
    name: 'Wipe Right',
    category: 'Wipes',
    render(ctx, srcA, srcB, p, w, h, fit) {
        const split = Math.round(w * p);
        if (srcB && split > 0) {
            ctx.save(); ctx.beginPath(); ctx.rect(0, 0, split, h); ctx.clip();
            ctx.globalAlpha = 1; fit(ctx, srcB, w, h); ctx.restore();
        }
        if (srcA && split < w) {
            ctx.save(); ctx.beginPath(); ctx.rect(split, 0, w - split, h); ctx.clip();
            ctx.globalAlpha = 1; fit(ctx, srcA, w, h); ctx.restore();
        }
    }
});

TransitionRegistry.register({
    id: 'wipe-down',
    name: 'Wipe Down',
    category: 'Wipes',
    render(ctx, srcA, srcB, p, w, h, fit) {
        const split = Math.round(h * p);
        if (srcB && split > 0) {
            ctx.save(); ctx.beginPath(); ctx.rect(0, 0, w, split); ctx.clip();
            ctx.globalAlpha = 1; fit(ctx, srcB, w, h); ctx.restore();
        }
        if (srcA && split < h) {
            ctx.save(); ctx.beginPath(); ctx.rect(0, split, w, h - split); ctx.clip();
            ctx.globalAlpha = 1; fit(ctx, srcA, w, h); ctx.restore();
        }
    }
});

TransitionRegistry.register({
    id: 'wipe-up',
    name: 'Wipe Up',
    category: 'Wipes',
    render(ctx, srcA, srcB, p, w, h, fit) {
        const split = Math.round(h * (1 - p));
        if (srcA && split > 0) {
            ctx.save(); ctx.beginPath(); ctx.rect(0, 0, w, split); ctx.clip();
            ctx.globalAlpha = 1; fit(ctx, srcA, w, h); ctx.restore();
        }
        if (srcB && split < h) {
            ctx.save(); ctx.beginPath(); ctx.rect(0, split, w, h - split); ctx.clip();
            ctx.globalAlpha = 1; fit(ctx, srcB, w, h); ctx.restore();
        }
    }
});

TransitionRegistry.register({
    id: 'wipe-diagonal',
    name: 'Diagonal Wipe',
    category: 'Wipes',
    render(ctx, srcA, srcB, p, w, h, fit) {
        // Diagonal line sweeps from top-left to bottom-right
        const offset = (w + h) * p;
        if (srcA) { ctx.globalAlpha = 1; fit(ctx, srcA, w, h); }
        if (srcB) {
            ctx.save();
            ctx.beginPath();
            ctx.moveTo(offset - h, 0);
            ctx.lineTo(offset, h);
            ctx.lineTo(w + h, h);
            ctx.lineTo(w + h, 0);
            ctx.closePath();
            ctx.clip();
            ctx.globalAlpha = 1;
            fit(ctx, srcB, w, h);
            ctx.restore();
        }
    }
});

// ============================================================
// GEOMETRIC
// ============================================================

TransitionRegistry.register({
    id: 'iris',
    name: 'Iris (Circle)',
    category: 'Geometric',
    render(ctx, srcA, srcB, p, w, h, fit) {
        const maxR = Math.sqrt(w * w + h * h) / 2;
        const r = maxR * p;
        if (srcA) { ctx.globalAlpha = 1; fit(ctx, srcA, w, h); }
        if (srcB && r > 0) {
            ctx.save();
            ctx.beginPath();
            ctx.arc(w / 2, h / 2, r, 0, Math.PI * 2);
            ctx.clip();
            ctx.globalAlpha = 1;
            fit(ctx, srcB, w, h);
            ctx.restore();
        }
    }
});

TransitionRegistry.register({
    id: 'diamond',
    name: 'Diamond',
    category: 'Geometric',
    render(ctx, srcA, srcB, p, w, h, fit) {
        const cx = w / 2, cy = h / 2;
        const maxD = Math.max(w, h);
        const d = maxD * p;
        if (srcA) { ctx.globalAlpha = 1; fit(ctx, srcA, w, h); }
        if (srcB && d > 0) {
            ctx.save();
            ctx.beginPath();
            ctx.moveTo(cx, cy - d);
            ctx.lineTo(cx + d, cy);
            ctx.lineTo(cx, cy + d);
            ctx.lineTo(cx - d, cy);
            ctx.closePath();
            ctx.clip();
            ctx.globalAlpha = 1;
            fit(ctx, srcB, w, h);
            ctx.restore();
        }
    }
});

TransitionRegistry.register({
    id: 'clock',
    name: 'Clock Wipe',
    category: 'Geometric',
    render(ctx, srcA, srcB, p, w, h, fit) {
        const cx = w / 2, cy = h / 2;
        const maxR = Math.sqrt(w * w + h * h);
        const angle = Math.PI * 2 * p - Math.PI / 2;
        if (srcA) { ctx.globalAlpha = 1; fit(ctx, srcA, w, h); }
        if (srcB && p > 0) {
            ctx.save();
            ctx.beginPath();
            ctx.moveTo(cx, cy);
            ctx.lineTo(cx, cy - maxR);
            ctx.arc(cx, cy, maxR, -Math.PI / 2, angle, false);
            ctx.closePath();
            ctx.clip();
            ctx.globalAlpha = 1;
            fit(ctx, srcB, w, h);
            ctx.restore();
        }
    }
});

TransitionRegistry.register({
    id: 'box',
    name: 'Box Wipe',
    category: 'Geometric',
    render(ctx, srcA, srcB, p, w, h, fit) {
        const bw = w * p, bh = h * p;
        const x = (w - bw) / 2, y = (h - bh) / 2;
        if (srcA) { ctx.globalAlpha = 1; fit(ctx, srcA, w, h); }
        if (srcB && p > 0) {
            ctx.save();
            ctx.beginPath();
            ctx.rect(x, y, bw, bh);
            ctx.clip();
            ctx.globalAlpha = 1;
            fit(ctx, srcB, w, h);
            ctx.restore();
        }
    }
});

// ============================================================
// MOTION
// ============================================================

TransitionRegistry.register({
    id: 'slide-left',
    name: 'Slide Left',
    category: 'Motion',
    render(ctx, srcA, srcB, p, w, h, fit) {
        const offset = w * p;
        ctx.save();
        if (srcA) { ctx.save(); ctx.translate(-offset, 0); fit(ctx, srcA, w, h); ctx.restore(); }
        if (srcB) { ctx.save(); ctx.translate(w - offset, 0); fit(ctx, srcB, w, h); ctx.restore(); }
        ctx.restore();
    }
});

TransitionRegistry.register({
    id: 'slide-right',
    name: 'Slide Right',
    category: 'Motion',
    render(ctx, srcA, srcB, p, w, h, fit) {
        const offset = w * p;
        ctx.save();
        if (srcA) { ctx.save(); ctx.translate(offset, 0); fit(ctx, srcA, w, h); ctx.restore(); }
        if (srcB) { ctx.save(); ctx.translate(-w + offset, 0); fit(ctx, srcB, w, h); ctx.restore(); }
        ctx.restore();
    }
});

TransitionRegistry.register({
    id: 'zoom-in',
    name: 'Zoom In',
    category: 'Motion',
    render(ctx, srcA, srcB, p, w, h, fit) {
        if (srcA) {
            ctx.save();
            const s = 1 + p * 2;
            ctx.globalAlpha = 1 - p;
            ctx.translate(w / 2, h / 2);
            ctx.scale(s, s);
            ctx.translate(-w / 2, -h / 2);
            fit(ctx, srcA, w, h);
            ctx.restore();
        }
        if (srcB) {
            ctx.globalAlpha = p;
            fit(ctx, srcB, w, h);
        }
        ctx.globalAlpha = 1;
    }
});

TransitionRegistry.register({
    id: 'zoom-out',
    name: 'Zoom Out',
    category: 'Motion',
    render(ctx, srcA, srcB, p, w, h, fit) {
        if (srcA) { ctx.globalAlpha = 1 - p; fit(ctx, srcA, w, h); }
        if (srcB) {
            ctx.save();
            const s = p;
            ctx.globalAlpha = p;
            ctx.translate(w / 2, h / 2);
            ctx.scale(s, s);
            ctx.translate(-w / 2, -h / 2);
            fit(ctx, srcB, w, h);
            ctx.restore();
        }
        ctx.globalAlpha = 1;
    }
});

TransitionRegistry.register({
    id: 'spin',
    name: 'Spin',
    category: 'Motion',
    render(ctx, srcA, srcB, p, w, h, fit) {
        ctx.save();
        ctx.translate(w / 2, h / 2);
        if (p < 0.5) {
            const t = p * 2;
            ctx.rotate(t * Math.PI);
            ctx.scale(1 - t, 1 - t);
            ctx.translate(-w / 2, -h / 2);
            if (srcA) { ctx.globalAlpha = 1; fit(ctx, srcA, w, h); }
        } else {
            const t = (p - 0.5) * 2;
            ctx.rotate((1 - t) * Math.PI);
            ctx.scale(t, t);
            ctx.translate(-w / 2, -h / 2);
            if (srcB) { ctx.globalAlpha = 1; fit(ctx, srcB, w, h); }
        }
        ctx.restore();
        ctx.globalAlpha = 1;
    }
});

// ============================================================
// STYLIZED
// ============================================================

TransitionRegistry.register({
    id: 'pixelate',
    name: 'Pixelate',
    category: 'Stylized',
    render(ctx, srcA, srcB, p, w, h, fit) {
        // Draw A pixelated as it fades, B sharpens
        if (p < 0.5) {
            const blockSize = Math.max(1, Math.floor(p * 2 * 60));
            if (srcA) {
                fit(ctx, srcA, w, h);
                if (blockSize > 1) {
                    ctx.imageSmoothingEnabled = false;
                    const tw = Math.ceil(w / blockSize);
                    const th = Math.ceil(h / blockSize);
                    const tempCanvas = document.createElement('canvas');
                    tempCanvas.width = tw;
                    tempCanvas.height = th;
                    const tc = tempCanvas.getContext('2d');
                    tc.drawImage(ctx.canvas, 0, 0, tw, th);
                    ctx.clearRect(0, 0, w, h);
                    ctx.drawImage(tempCanvas, 0, 0, w, h);
                    ctx.imageSmoothingEnabled = true;
                }
            }
        } else {
            const blockSize = Math.max(1, Math.floor((1 - p) * 2 * 60));
            if (srcB) {
                fit(ctx, srcB, w, h);
                if (blockSize > 1) {
                    ctx.imageSmoothingEnabled = false;
                    const tw = Math.ceil(w / blockSize);
                    const th = Math.ceil(h / blockSize);
                    const tempCanvas = document.createElement('canvas');
                    tempCanvas.width = tw;
                    tempCanvas.height = th;
                    const tc = tempCanvas.getContext('2d');
                    tc.drawImage(ctx.canvas, 0, 0, tw, th);
                    ctx.clearRect(0, 0, w, h);
                    ctx.drawImage(tempCanvas, 0, 0, w, h);
                    ctx.imageSmoothingEnabled = true;
                }
            }
        }
    }
});

TransitionRegistry.register({
    id: 'dissolve',
    name: 'Dissolve',
    category: 'Stylized',
    _pattern: null,
    render(ctx, srcA, srcB, p, w, h, fit) {
        // Random block dissolve
        const blockSize = 16;
        const cols = Math.ceil(w / blockSize);
        const rows = Math.ceil(h / blockSize);
        const total = cols * rows;
        const threshold = Math.floor(total * p);

        if (srcA) { ctx.globalAlpha = 1; fit(ctx, srcA, w, h); }
        if (srcB && p > 0) {
            // Draw B to a temp canvas
            const tmp = document.createElement('canvas');
            tmp.width = w; tmp.height = h;
            const tc = tmp.getContext('2d');
            fit(tc, srcB, w, h);

            // Use deterministic random to decide which blocks show B
            for (let i = 0; i < cols; i++) {
                for (let j = 0; j < rows; j++) {
                    // Simple hash for deterministic randomness
                    const hash = ((i * 7919 + j * 104729) % total);
                    if (hash < threshold) {
                        const sx = i * blockSize, sy = j * blockSize;
                        const sw = Math.min(blockSize, w - sx);
                        const sh = Math.min(blockSize, h - sy);
                        ctx.drawImage(tmp, sx, sy, sw, sh, sx, sy, sw, sh);
                    }
                }
            }
        }
        ctx.globalAlpha = 1;
    }
});

TransitionRegistry.register({
    id: 'burn',
    name: 'Burn',
    category: 'Stylized',
    render(ctx, srcA, srcB, p, w, h, fit) {
        if (srcA) {
            ctx.globalAlpha = 1;
            fit(ctx, srcA, w, h);
        }
        // Orange/red overlay grows
        if (p > 0 && p < 1) {
            const intensity = p < 0.5 ? p * 2 : (1 - p) * 2;
            ctx.globalAlpha = intensity * 0.6;
            ctx.fillStyle = p < 0.5 ? '#ff4400' : '#ff8800';
            ctx.fillRect(0, 0, w, h);
        }
        if (srcB) {
            ctx.globalAlpha = p;
            fit(ctx, srcB, w, h);
        }
        ctx.globalAlpha = 1;
    }
});

TransitionRegistry.register({
    id: 'luma-fade',
    name: 'Luma Fade',
    category: 'Stylized',
    render(ctx, srcA, srcB, p, w, h, fit) {
        // Bright areas of B appear first
        if (srcA) { ctx.globalAlpha = 1 - p * 0.5; fit(ctx, srcA, w, h); }
        if (srcB) {
            ctx.globalCompositeOperation = 'lighter';
            ctx.globalAlpha = p;
            fit(ctx, srcB, w, h);
            ctx.globalCompositeOperation = 'source-over';
        }
        ctx.globalAlpha = 1;
    }
});

TransitionRegistry.register({
    id: 'blinds-h',
    name: 'Blinds Horizontal',
    category: 'Stylized',
    render(ctx, srcA, srcB, p, w, h, fit) {
        const numBlinds = 10;
        const blindH = h / numBlinds;
        const openH = blindH * p;

        if (srcA) { ctx.globalAlpha = 1; fit(ctx, srcA, w, h); }
        if (srcB && p > 0) {
            for (let i = 0; i < numBlinds; i++) {
                ctx.save();
                ctx.beginPath();
                ctx.rect(0, i * blindH, w, openH);
                ctx.clip();
                ctx.globalAlpha = 1;
                fit(ctx, srcB, w, h);
                ctx.restore();
            }
        }
    }
});

TransitionRegistry.register({
    id: 'blinds-v',
    name: 'Blinds Vertical',
    category: 'Stylized',
    render(ctx, srcA, srcB, p, w, h, fit) {
        const numBlinds = 12;
        const blindW = w / numBlinds;
        const openW = blindW * p;

        if (srcA) { ctx.globalAlpha = 1; fit(ctx, srcA, w, h); }
        if (srcB && p > 0) {
            for (let i = 0; i < numBlinds; i++) {
                ctx.save();
                ctx.beginPath();
                ctx.rect(i * blindW, 0, openW, h);
                ctx.clip();
                ctx.globalAlpha = 1;
                fit(ctx, srcB, w, h);
                ctx.restore();
            }
        }
    }
});

// ============================================================
// COMPOSITE
// ============================================================

TransitionRegistry.register({
    id: 'additive',
    name: 'Additive',
    category: 'Composite',
    render(ctx, srcA, srcB, p, w, h, fit) {
        ctx.globalCompositeOperation = 'source-over';
        if (srcA) { ctx.globalAlpha = 1 - p; fit(ctx, srcA, w, h); }
        ctx.globalCompositeOperation = 'lighter';
        if (srcB) { ctx.globalAlpha = p; fit(ctx, srcB, w, h); }
        ctx.globalCompositeOperation = 'source-over';
        ctx.globalAlpha = 1;
    }
});

TransitionRegistry.register({
    id: 'multiply',
    name: 'Multiply',
    category: 'Composite',
    render(ctx, srcA, srcB, p, w, h, fit) {
        if (srcA) { ctx.globalAlpha = 1; fit(ctx, srcA, w, h); }
        ctx.globalCompositeOperation = 'multiply';
        if (srcB) { ctx.globalAlpha = p; fit(ctx, srcB, w, h); }
        ctx.globalCompositeOperation = 'source-over';
        // Fade from A to result
        if (p > 0.5 && srcB) {
            ctx.globalAlpha = (p - 0.5) * 2;
            fit(ctx, srcB, w, h);
        }
        ctx.globalAlpha = 1;
    }
});

TransitionRegistry.register({
    id: 'screen',
    name: 'Screen',
    category: 'Composite',
    render(ctx, srcA, srcB, p, w, h, fit) {
        if (srcA) { ctx.globalAlpha = 1 - p; fit(ctx, srcA, w, h); }
        ctx.globalCompositeOperation = 'screen';
        if (srcB) { ctx.globalAlpha = p; fit(ctx, srcB, w, h); }
        ctx.globalCompositeOperation = 'source-over';
        ctx.globalAlpha = 1;
    }
});

TransitionRegistry.register({
    id: 'difference',
    name: 'Difference',
    category: 'Composite',
    render(ctx, srcA, srcB, p, w, h, fit) {
        if (p < 0.5) {
            if (srcA) { ctx.globalAlpha = 1; fit(ctx, srcA, w, h); }
            ctx.globalCompositeOperation = 'difference';
            if (srcB) { ctx.globalAlpha = p * 2; fit(ctx, srcB, w, h); }
            ctx.globalCompositeOperation = 'source-over';
        } else {
            if (srcB) { ctx.globalAlpha = 1; fit(ctx, srcB, w, h); }
            ctx.globalCompositeOperation = 'difference';
            if (srcA) { ctx.globalAlpha = (1 - p) * 2; fit(ctx, srcA, w, h); }
            ctx.globalCompositeOperation = 'source-over';
        }
        ctx.globalAlpha = 1;
    }
});

// ============================================================
// SIGNATURE FX — polished, scrub-safe show transitions
// ============================================================

TransitionRegistry.register({
    id: 'prism-split',
    name: 'Prism Split',
    category: 'Signature FX',
    render(ctx, srcA, srcB, p, w, h, fit) {
        const intensity = Math.sin(Math.PI * p);
        ctx.save();
        if (srcA) {
            ctx.globalAlpha = 1 - p;
            fit(ctx, srcA, w, h);
        }
        if (srcB) {
            ctx.globalAlpha = p;
            fit(ctx, srcB, w, h);
        }

        if (intensity > .001) {
            const shift = Math.max(2, w * .018 * intensity);
            ctx.globalCompositeOperation = 'screen';
            if (srcA) {
                ctx.save();
                ctx.globalAlpha = (1 - p) * intensity * .2;
                ctx.filter = 'sepia(1) saturate(8) hue-rotate(305deg) brightness(1.15)';
                ctx.translate(-shift, 0);
                fit(ctx, srcA, w, h);
                ctx.restore();
            }
            if (srcB) {
                ctx.save();
                ctx.globalAlpha = p * intensity * .22;
                ctx.filter = 'sepia(1) saturate(8) hue-rotate(145deg) brightness(1.2)';
                ctx.translate(shift, 0);
                fit(ctx, srcB, w, h);
                ctx.restore();
            }
            ctx.globalCompositeOperation = 'source-over';
            const flare = ctx.createLinearGradient(0, 0, w, h);
            flare.addColorStop(0, 'rgba(88,214,255,0)');
            flare.addColorStop(.48, `rgba(255,255,255,${intensity * .15})`);
            flare.addColorStop(.52, `rgba(255,139,231,${intensity * .12})`);
            flare.addColorStop(1, 'rgba(130,84,255,0)');
            ctx.fillStyle = flare;
            ctx.fillRect(0, 0, w, h);
        }
        ctx.restore();
    },
});

TransitionRegistry.register({
    id: 'fluid-wave',
    name: 'Fluid Wave',
    category: 'Signature FX',
    render(ctx, srcA, srcB, p, w, h, fit) {
        if (srcA) {
            ctx.globalAlpha = 1;
            fit(ctx, srcA, w, h);
        }
        if (!srcB || p <= 0) return;
        if (p >= 1) {
            ctx.globalAlpha = 1;
            fit(ctx, srcB, w, h);
            return;
        }

        const strips = 22;
        const stripH = h / strips + 1;
        const feather = w * .035;
        for (let i = 0; i < strips; i++) {
            const wave = Math.sin(i * .72 + p * Math.PI * 2) * .055
                + Math.sin(i * .23 - p * Math.PI) * .025;
            const edge = Math.max(0, Math.min(1, p + wave));
            const revealW = w * edge;
            if (revealW <= 0) continue;
            ctx.save();
            ctx.beginPath();
            ctx.rect(0, i * h / strips, revealW, stripH);
            ctx.clip();
            ctx.globalAlpha = 1;
            fit(ctx, srcB, w, h);
            ctx.restore();

            const glow = ctx.createLinearGradient(Math.max(0, revealW - feather), 0, revealW, 0);
            glow.addColorStop(0, 'rgba(72,118,255,0)');
            glow.addColorStop(.72, 'rgba(95,224,255,.08)');
            glow.addColorStop(1, 'rgba(255,255,255,.32)');
            ctx.fillStyle = glow;
            ctx.fillRect(Math.max(0, revealW - feather), i * h / strips, feather, stripH);
        }
        ctx.globalAlpha = 1;
    },
});

TransitionRegistry.register({
    id: 'film-flash',
    name: 'Film Flash',
    category: 'Signature FX',
    render(ctx, srcA, srcB, p, w, h, fit) {
        const cut = p < .5 ? srcA : srcB;
        if (cut) {
            ctx.globalAlpha = 1;
            fit(ctx, cut, w, h);
        }
        const flash = Math.pow(Math.max(0, 1 - Math.abs(p - .5) * 2), 3);
        if (flash > 0) {
            const leak = ctx.createRadialGradient(w * .18, h * .72, 0, w * .18, h * .72, w * .9);
            leak.addColorStop(0, `rgba(255,248,220,${flash * .98})`);
            leak.addColorStop(.18, `rgba(255,192,104,${flash * .8})`);
            leak.addColorStop(.5, `rgba(255,62,104,${flash * .46})`);
            leak.addColorStop(1, 'rgba(70,20,90,0)');
            ctx.save();
            ctx.globalCompositeOperation = 'screen';
            ctx.fillStyle = leak;
            ctx.fillRect(0, 0, w, h);
            ctx.globalAlpha = flash * .58;
            ctx.fillStyle = '#fff7e8';
            ctx.fillRect(0, 0, w, h);
            ctx.restore();
        }
    },
});

// Make available globally
window.TransitionRegistry = TransitionRegistry;
