/*:
 * @target MZ
 * @plugindesc Opening animé 60s — Persona 5 × Shōnen × Néon — Skippable après la première fois
 * @author Bertran
 * @help
 * Placez vos images dans /img/pictures/ :
 * - op_bg.png
 * - op_slash.png
 * - op_glitch.png
 * - op_particle.png
 * - op_kou.png
 * - op_sakura.png
 * - op_ken.png
 * - op_asuka.png
 * - op_villain.png
 * - op_logo.png
 */

(() => {

    // -------------------------------------------------------------
    // CONFIG
    // -------------------------------------------------------------

    const OP = {
        duration: 60 * 60, // 60 secondes × 60 FPS
        skipAfterFirst: true,
        storageKey: "TLF_OpeningSeen",

        images: {
            bg: "op_bg",
            slash: "op_slash",
            glitch: "op_glitch",
            particle: "op_particle",
            kou: "op_kou",
            sakura: "op_sakura",
            ken: "op_ken",
            asuka: "op_asuka",
            villain: "op_villain",
            logo: "op_logo"
        }
    };

    // -------------------------------------------------------------
    // Override du boot : lancer l’opening avant le titre
    // -------------------------------------------------------------

    const _Scene_Boot_start = Scene_Boot.prototype.start;
    Scene_Boot.prototype.start = function() {
        if (!localStorage.getItem(OP.storageKey)) {
            SceneManager.goto(Scene_Opening);
        } else {
            _Scene_Boot_start.call(this);
        }
    };

    // -------------------------------------------------------------
    // SCENE OPENING
    // -------------------------------------------------------------

    function Scene_Opening() {
        this.initialize(...arguments);
    }

    Scene_Opening.prototype = Object.create(Scene_Base.prototype);
    Scene_Opening.prototype.constructor = Scene_Opening;

    Scene_Opening.prototype.initialize = function() {
        Scene_Base.prototype.initialize.call(this);
        this._timer = 0;
        this._phase = 0;
        this._sprites = {};
    };

    Scene_Opening.prototype.create = function() {
        Scene_Base.prototype.create.call(this);
        this.createBackground();
        this.createParticles();
        this.createSlash();
        this.createGlitch();
        this.createCharacters();
        this.createLogo();
    };

    // -------------------------------------------------------------
    // Création des éléments
    // -------------------------------------------------------------

    Scene_Opening.prototype.createBackground = function() {
        const s = new Sprite(ImageManager.loadPicture(OP.images.bg));
        s.anchor.x = 0.5;
        s.anchor.y = 0.5;
        s.x = Graphics.width / 2;
        s.y = Graphics.height / 2;
        s.opacity = 0;
        this.addChild(s);
        this._sprites.bg = s;
    };

    Scene_Opening.prototype.createParticles = function() {
        this._sprites.particles = [];
        for (let i = 0; i < 40; i++) {
            const p = new Sprite(ImageManager.loadPicture(OP.images.particle));
            p.anchor.x = 0.5;
            p.anchor.y = 0.5;
            p.x = Math.random() * Graphics.width;
            p.y = Math.random() * Graphics.height;
            p.opacity = 0;
            p._speed = 0.5 + Math.random() * 1.5;
            this.addChild(p);
            this._sprites.particles.push(p);
        }
    };

    Scene_Opening.prototype.createSlash = function() {
        const s = new Sprite(ImageManager.loadPicture(OP.images.slash));
        s.anchor.x = 0.5;
        s.anchor.y = 0.5;
        s.x = Graphics.width / 2;
        s.y = Graphics.height / 2;
        s.opacity = 0;
        s.scale.x = 0;
        this.addChild(s);
        this._sprites.slash = s;
    };

    Scene_Opening.prototype.createGlitch = function() {
        const s = new Sprite(ImageManager.loadPicture(OP.images.glitch));
        s.opacity = 0;
        this.addChild(s);
        this._sprites.glitch = s;
    };

    Scene_Opening.prototype.createCharacters = function() {
        const names = ["kou", "sakura", "ken", "asuka", "villain"];
        this._sprites.chars = {};

        for (const n of names) {
            const s = new Sprite(ImageManager.loadPicture(OP.images[n]));
            s.anchor.x = 0.5;
            s.anchor.y = 0.5;
            s.x = Graphics.width / 2;
            s.y = Graphics.height / 2;
            s.opacity = 0;
            s.scale.x = 1.2;
            s.scale.y = 1.2;
            this.addChild(s);
            this._sprites.chars[n] = s;
        }
    };

    Scene_Opening.prototype.createLogo = function() {
        const s = new Sprite(ImageManager.loadPicture(OP.images.logo));
        s.anchor.x = 0.5;
        s.anchor.y = 0.5;
        s.x = Graphics.width / 2;
        s.y = Graphics.height / 2;
        s.opacity = 0;
        this.addChild(s);
        this._sprites.logo = s;
    };

    // -------------------------------------------------------------
    // UPDATE
    // -------------------------------------------------------------

    Scene_Opening.prototype.update = function() {
        Scene_Base.prototype.update.call(this);
        this._timer++;

        this.updateBackground();
        this.updateParticles();
        this.updateTimeline();
        this.updateSkip();
    };

    // -------------------------------------------------------------
    // Effets
    // -------------------------------------------------------------

    Scene_Opening.prototype.updateBackground = function() {
        const bg = this._sprites.bg;
        if (this._timer < 60) bg.opacity += 4;
        bg.rotation = Math.sin(this._timer / 120) * 0.02;
    };

    Scene_Opening.prototype.updateParticles = function() {
        for (const p of this._sprites.particles) {
            if (this._timer > 30) p.opacity = Math.min(255, p.opacity + 3);
            p.y -= p._speed;
            if (p.y < -10) {
                p.y = Graphics.height + 10;
                p.x = Math.random() * Graphics.width;
            }
        }
    };

    // -------------------------------------------------------------
    // TIMELINE 60 SECONDES
    // -------------------------------------------------------------

    Scene_Opening.prototype.updateTimeline = function() {
        const t = this._timer;

        // 0–60 : intro néon
        if (t === 30) this.playSlash();

        // 60–120 : KOU
        if (t === 60) this.reveal("kou");

        // 120–180 : Sakura
        if (t === 120) this.reveal("sakura");

        // 180–240 : Ken
        if (t === 180) this.reveal("ken");

        // 240–300 : Asuka
        if (t === 240) this.reveal("asuka");

        // 300–360 : Méchant (sourire)
        if (t === 300) this.reveal("villain");

        // 360–420 : glitch + tension
        if (t > 360 && t < 420) this._sprites.glitch.opacity = Math.random() * 150;

        // 420–480 : Logo final
        if (t === 420) this.revealLogo();

        // Fin → titre
        if (t >= OP.duration) {
            localStorage.setItem(OP.storageKey, "true");
            SceneManager.goto(Scene_Title);
        }
    };

    Scene_Opening.prototype.playSlash = function() {
        const s = this._sprites.slash;
        s.opacity = 255;
        s.scale.x = 0;
        s.scale.y = 1;

        this._slashAnim = 20;
    };

    Scene_Opening.prototype.reveal = function(name) {
        const s = this._sprites.chars[name];
        s.opacity = 0;
        s.scale.x = 1.3;
        s.scale.y = 1.3;
        this._revealTarget = s;
        this._revealTimer = 0;
    };

    Scene_Opening.prototype.revealLogo = function() {
        const s = this._sprites.logo;
        s.opacity = 0;
        s.scale.x = 1.4;
        s.scale.y = 1.4;
        this._logoTimer = 0;
    };

    Scene_Opening.prototype.updateSkip = function() {
        if (!localStorage.getItem(OP.storageKey)) return;

        if (Input.isTriggered("ok") || Input.isTriggered("cancel") || TouchInput.isTriggered()) {
            SceneManager.goto(Scene_Title);
        }
    };

})();

Embed on website

To embed this project on your website, copy the following code and paste it into your website's HTML: