// Nouvelle logique : chaque phase a une séquence d'actions jouées dans l'ordre.
  // Chaque action a une durée (ACTION_MS), pendant laquelle :
  //  - dribble/cut/drive/curl : le joueur glisse vers la fin
  //  - pass : le ballon vole en arc vers le receveur, owner change à la fin
  //  - screen : le joueur glisse vers le point d'écran
  //  - shoot : le ballon vole vers la cible
  //  - handoff : ballon transmis
  // À la fin de toutes les actions d'une phase, on enchaîne sur la phase suivante.

  const ACTION_MS_DEFAULT = 900;     // durée d'une action
  const PHASE_TRANSITION_MS = 600;   // transition entre phases (si pas d'actions)

  function togglePlay(){
    if(editor.isPlaying){ pauseAnim(); } else { startAnim(); }
  }
  function startAnim(){
    if(editor.phases.length < 1){ toast('Aucune phase à jouer', 'error'); return; }
    // Vérifier qu'il y a au moins UNE action ou UNE phase suivante
    const hasAnyAction = editor.phases.some(p => p.actions.length > 0);
    if(!hasAnyAction && editor.phases.length < 2){
      toast('Crée des actions ou des phases pour animer', 'error');
      return;
    }
    editor.isPlaying = true;
    editor.animStart = performance.now();
    // Repart de la phase courante (si on est à la fin, repart du début)
    editor.animFromPhase = editor.currentPhase >= editor.phases.length - 1 && editor.currentPhase > 0 ? 0 : editor.currentPhase;
    editor.animSequence = buildAnimSequence();
    editor.animSeqIdx = findStartIndexForPhase(editor.animFromPhase);
    editor.animSeqStartTime = editor.animStart;
    // État dynamique des joueurs/ballon pendant l'anim
    editor.animState = cloneAnimState(editor.phases[editor.animFromPhase]);
    document.getElementById('tlPlay').textContent = '⏸';
    editor.selectedId = null;
    editor.selectedKind = null;
    requestAnimationFrame(animTick);
  }
  function pauseAnim(){
    editor.isPlaying = false;
    document.getElementById('tlPlay').textContent = '▶';
  }
  function stopAnim(){
    editor.isPlaying = false;
    editor.animProgress = 0;
    document.getElementById('tlPlay').textContent = '▶';
    document.getElementById('tlBar').style.width = '0%';
    editor.animState = null;
    render();
  }

  // Construit la séquence d'animation
  // Chaque entrée = { phaseIdx, actions: [...] | null, duration, isTransition }
  // - actions = null : transition entre 2 phases sans actions
  // - actions = [a1] : 1 action seule
  // - actions = [a1, a2, ...] : plusieurs actions jouées en PARALLÈLE
  //   (les actions consécutives avec startWith=true sont groupées avec la précédente)
  function buildAnimSequence(){
    const seq = [];
    for(let i=0; i<editor.phases.length; i++){
      const phase = editor.phases[i];
      if(phase.actions.length === 0){
        if(i < editor.phases.length - 1){
          seq.push({ phaseIdx:i, actions:null, duration:PHASE_TRANSITION_MS / editor.speed, isTransition:true });
        }
      } else {
        // Grouper les actions consécutives avec startWith=true
        let currentGroup = null;
        for(const a of phase.actions){
          if(a.startWith && currentGroup){
            // Ajouter à l'étape précédente (= jouer en parallèle)
            currentGroup.actions.push(a);
          } else {
            // Nouvelle étape
            if(currentGroup) seq.push(currentGroup);
            currentGroup = { phaseIdx:i, actions:[a], duration:ACTION_MS_DEFAULT / editor.speed, isTransition:false };
          }
        }
        if(currentGroup) seq.push(currentGroup);
      }
    }
    return seq;
  }

  function findStartIndexForPhase(phaseIdx){
    for(let i=0; i<editor.animSequence.length; i++){
      if(editor.animSequence[i].phaseIdx === phaseIdx) return i;
    }
    return 0;
  }

  function cloneAnimState(phase){
    return {
      players: phase.players.map(p => ({ ...p })),
      balls: getPhaseBalls(phase).map(b => ({ ...b })),
      ballFlying: null  // tableau [{ ballId, x, y }] pendant les passes/tirs
    };
  }

  function animTick(now){
    if(!editor.isPlaying) return;
    const seq = editor.animSequence;
    if(!seq || seq.length === 0){ pauseAnim(); return; }

    const elapsed = now - editor.animSeqStartTime;
    const current = seq[editor.animSeqIdx];
    if(!current){ finishAnim(); return; }

    const totalMs = seq.reduce((s,e) => s + e.duration, 0);
    const elapsedTotal = now - editor.animStart;
    document.getElementById('tlBar').style.width = (Math.min(1, elapsedTotal/totalMs) * 100) + '%';

    const t = Math.min(1, elapsed / current.duration);
    const tEased = ease(t);

    // Mettre à jour l'état pour cette frame
    applyActionAtProgress(current, tEased);

    // Synchroniser currentPhase pour les vignettes
    if(editor.currentPhase !== current.phaseIdx){
      editor.currentPhase = current.phaseIdx;
      renderPhasesList();
    }

    render();

    if(t >= 1){
      // Action terminée : appliquer l'effet définitif sur animState
      commitAction(current);
      editor.animSeqIdx++;
      editor.animSeqStartTime = now;
      if(editor.animSeqIdx >= seq.length){
        finishAnim();
        return;
      }
      // Si nouvelle phase, snapshot animState depuis la phase
      const newCurrent = seq[editor.animSeqIdx];
      if(newCurrent.phaseIdx !== current.phaseIdx){
        // Snap : on récupère l'état de la nouvelle phase, mais en gardant
        // les modifs (positions finales) appliquées via commitAction
        editor.animState = cloneAnimState(editor.phases[newCurrent.phaseIdx]);
      }
    }
    requestAnimationFrame(animTick);
  }

  function finishAnim(){
    // Aller à la dernière phase
    editor.currentPhase = editor.phases.length - 1;
    pauseAnim();
    editor.animState = null;
    document.getElementById('tlBar').style.width = '100%';
    renderPhasesList();
    render();
  }

  // Applique l'effet visuel d'une étape (= 1 ou plusieurs actions en parallèle) à un certain progrès
  function applyActionAtProgress(entry, t){
    if(!editor.animState) return;
    const phase = editor.phases[entry.phaseIdx];
    if(!entry.actions){
      // Transition entre 2 phases sans actions
      if(entry.phaseIdx < editor.phases.length - 1){
        const next = editor.phases[entry.phaseIdx + 1];
        editor.animState.players = phase.players.map(p => {
          const np = next.players.find(x => x.id === p.id);
          if(!np) return { ...p };
          return { ...p, x: p.x + (np.x - p.x) * t, y: p.y + (np.y - p.y) * t };
        });
        if(next.balls){ editor.animState.balls = next.balls.map(b => ({ ...b })); }
        editor.animState.ballFlying = null;
      }
      return;
    }
    // Cumul des ballons en vol pour cette étape (1 par passe/tir/handoff parallèle)
    editor.animState.ballFlying = [];
    // 1) D'abord traiter TOUS les mouvements de joueurs (dribble/cut/drive/curl/screen)
    //    pour que leurs positions soient à jour avant qu'on calcule les trajectoires de ballons
    for(const a of entry.actions){
      if(a.kind === 'dribble' || a.kind === 'cut' || a.kind === 'drive' || a.kind === 'curl' || a.kind === 'screen'){
        applySingleActionAtProgress(a, t, phase);
      }
    }
    // 2) Puis les passes/tirs/handoffs (qui utilisent les positions à jour)
    for(const a of entry.actions){
      if(a.kind === 'pass' || a.kind === 'shoot' || a.kind === 'handoff'){
        applySingleActionAtProgress(a, t, phase);
      }
    }
    if(editor.animState.ballFlying.length === 0) editor.animState.ballFlying = null;
  }

  // Applique l'effet d'UNE action à un certain progrès (ajoute à ballFlying si besoin)
  function applySingleActionAtProgress(a, t, phase){
    const stateP = editor.animState.players;
    const stateBalls = editor.animState.balls || [];

    if(a.kind === 'dribble' || a.kind === 'cut' || a.kind === 'drive' || a.kind === 'curl' || a.kind === 'screen'){
      const player = stateP.find(p => p.id === a.fromPlayerId);
      if(player){
        if(!a._animStart){
          a._animStart = { x: player.x, y: player.y };
        }
        const endPos = a.endPos || (a.toPlayerId ? phase.players.find(x => x.id === a.toPlayerId) : null);
        if(endPos){
          const ctrls = getActionCtrls(a);
          const pos = positionOnPath(a._animStart, endPos, ctrls, t);
          player.x = pos.x;
          player.y = pos.y;
        }
      }
    } else if(a.kind === 'pass'){
      const fromP = stateP.find(p => p.id === a.fromPlayerId);
      const toP = a.toPlayerId ? stateP.find(p => p.id === a.toPlayerId) : null;
      if(fromP){
        const ball = stateBalls.find(b => b.ownerId === a.fromPlayerId);
        const targetX = toP ? toP.x : (a.endPos ? a.endPos.x : fromP.x);
        const targetY = toP ? toP.y : (a.endPos ? a.endPos.y : fromP.y);
        const arcHeight = 40;
        editor.animState.ballFlying.push({
          ballId: ball ? ball.id : null,
          x: fromP.x + (targetX - fromP.x) * t,
          y: fromP.y + (targetY - fromP.y) * t - Math.sin(t * Math.PI) * arcHeight
        });
        if(ball) ball.ownerId = null;
      }
    } else if(a.kind === 'shoot'){
      const fromP = stateP.find(p => p.id === a.fromPlayerId);
      if(fromP && a.endPos){
        const ball = stateBalls.find(b => b.ownerId === a.fromPlayerId);
        const arcHeight = 60;
        editor.animState.ballFlying.push({
          ballId: ball ? ball.id : null,
          x: fromP.x + (a.endPos.x - fromP.x) * t,
          y: fromP.y + (a.endPos.y - fromP.y) * t - Math.sin(t * Math.PI) * arcHeight
        });
        if(ball) ball.ownerId = null;
      }
    } else if(a.kind === 'handoff'){
      const fromP = stateP.find(p => p.id === a.fromPlayerId);
      const toP = a.toPlayerId ? stateP.find(p => p.id === a.toPlayerId) : null;
      if(fromP){
        const ball = stateBalls.find(b => b.ownerId === a.fromPlayerId);
        const targetX = toP ? toP.x : (a.endPos ? a.endPos.x : fromP.x);
        const targetY = toP ? toP.y : (a.endPos ? a.endPos.y : fromP.y);
        editor.animState.ballFlying.push({
          ballId: ball ? ball.id : null,
          x: fromP.x + (targetX - fromP.x) * t,
          y: fromP.y + (targetY - fromP.y) * t
        });
        if(ball) ball.ownerId = null;
      }
    }
  }

  function commitAction(entry){
    if(!entry.actions || !editor.animState) return;
    for(const a of entry.actions){
      commitSingleAction(a, entry);
    }
    editor.animState.ballFlying = null;
  }

  function commitSingleAction(a, entry){
    const stateP = editor.animState.players;
    if(a.kind === 'dribble' || a.kind === 'cut' || a.kind === 'drive' || a.kind === 'curl' || a.kind === 'screen'){
      const player = stateP.find(p => p.id === a.fromPlayerId);
      if(player){
        const endPos = a.endPos || (a.toPlayerId ? editor.phases[entry.phaseIdx].players.find(x => x.id === a.toPlayerId) : null);
        if(endPos){
          player.x = endPos.x;
          player.y = endPos.y;
        }
      }
      delete a._animStart;
    } else if(a.kind === 'pass' || a.kind === 'handoff'){
      const stateBalls = editor.animState.balls || [];
      if(a.toPlayerId){
        // Trouver un ballon "sans owner" (en vol)
        let ball = stateBalls.find(b => !b.ownerId);
        if(!ball){
          ball = { id:'b_'+Date.now()+'_'+Math.floor(Math.random()*9999), ownerId: a.toPlayerId };
          stateBalls.push(ball);
        } else {
          ball.ownerId = a.toPlayerId;
        }
      }
    } else if(a.kind === 'shoot'){
      const stateBalls = editor.animState.balls || [];
      if(a.fromPlayerId){
        let ball = stateBalls.find(b => !b.ownerId);
        if(ball) ball.ownerId = a.fromPlayerId;
      }
    }
  }

  function ease(t){ return t<0.5 ? 2*t*t : 1 - Math.pow(-2*t+2, 2)/2; }

  // === RENDER ===
  function render(){
    if(!ctx) return;
    ctx.clearRect(0, 0, canvas.width, canvas.height);
    drawCourt();

    const phase = editor.phases[editor.currentPhase];
    let players, balls, ballFlying;

    if(editor.isPlaying && editor.animState){
      players = editor.animState.players;
      balls = editor.animState.balls || [];
      ballFlying = editor.animState.ballFlying;  // tableau { ballId, x, y } pendant les passes
    } else {
      players = phase.players;
      balls = getPhaseBalls(phase);
      ballFlying = null;
    }

    phase.drawings.forEach(d => drawFreedraw(d));
    phase.objects.forEach(o => drawObject(o, o.id === editor.selectedId && !editor.isPlaying));

    if(!editor.isPlaying){
      // État statique : toutes les flèches visibles avec numéro d'ordre
      phase.actions.forEach((a, i) => drawAction(a, phase, a.id === editor.selectedId, i+1));
    } else if(editor.animSequence && editor.animState){
      const seqEntry = editor.animSequence[editor.animSeqIdx];
      if(seqEntry && seqEntry.phaseIdx === editor.currentPhase){
        const phaseActions = phase.actions;
        const currentActions = seqEntry.actions || [];
        // Indices des actions en cours (qui peuvent être plusieurs si parallèles)
        const currentIndices = new Set();
        let minCurrentIdx = -1;
        currentActions.forEach(ca => {
          const idx = phaseActions.indexOf(ca);
          if(idx >= 0){
            currentIndices.add(idx);
            if(minCurrentIdx < 0 || idx < minCurrentIdx) minCurrentIdx = idx;
          }
        });
        const elapsed = performance.now() - editor.animSeqStartTime;
        const progress = Math.min(1, elapsed / seqEntry.duration);
        phaseActions.forEach((a, i) => {
          if(minCurrentIdx >= 0 && i < minCurrentIdx) return; // déjà jouée
          if(currentIndices.has(i)){
            const alpha = Math.max(0, 1 - progress);
            drawAction(a, phase, false, null, alpha);
          } else {
            drawAction(a, phase, false, null, 0.55);
          }
        });
      }
    }

    // Dessiner TOUS les ballons :
    // - Ballons en vol (ballFlying) : à la position interpolée
    // - Ballons sans propriétaire (au sol) : à leur x,y
    // - Ballons avec propriétaire : halo autour du porteur
    const flyingIds = new Set();
    if(ballFlying && Array.isArray(ballFlying)){
      ballFlying.forEach(bf => {
        drawBallHalo(bf.x, bf.y, 1);
        if(bf.ballId) flyingIds.add(bf.ballId);
      });
    }
    balls.forEach(b => {
      if(b.id && flyingIds.has(b.id)) return; // déjà dessiné en vol
      if(b.ownerId){
        const owner = players.find(p => p.id === b.ownerId);
        if(owner) drawBallHalo(owner.x, owner.y, 1);
      } else if(b.x !== undefined && b.y !== undefined){
        // Ballon au sol
        drawGroundBall(b.x, b.y, b.id === editor.selectedId && !editor.isPlaying);
      }
    });

    // Joueurs : le 3e paramètre dit si le joueur porte un ballon (n'importe lequel)
    players.forEach(p => {
      const hasBall = balls.some(b => b.ownerId === p.id);
      drawPlayer(p, p.id === editor.selectedId && !editor.isPlaying, hasBall);
    });
  }

  // Dessine un ballon au sol (sans porteur)
  function drawGroundBall(x, y, selected){
    ctx.save();
    // Cercle orange avec lignes du ballon (réduit en terrain complet)
    const r = 18 /* V7.53: rayon hitbox identique */;
    ctx.shadowColor = 'rgba(0,0,0,.4)';
    ctx.shadowBlur = 6;
    ctx.shadowOffsetY = 3;
    ctx.fillStyle = '#E8742C';
    ctx.beginPath();
    ctx.arc(x, y, r, 0, Math.PI*2);
    ctx.fill();
    ctx.shadowBlur = 0; ctx.shadowOffsetY = 0;
    // Lignes du ballon (style basket)
    ctx.strokeStyle = '#3A1A0A';
    ctx.lineWidth = 1.5;
    ctx.beginPath();
    ctx.arc(x, y, r, 0, Math.PI*2);
    ctx.stroke();
    ctx.beginPath();
    ctx.moveTo(x - r, y); ctx.lineTo(x + r, y);
    ctx.moveTo(x, y - r); ctx.lineTo(x, y + r);
    ctx.stroke();
    // Sélection
    if(selected){
      ctx.strokeStyle = '#D4A24C';
      ctx.lineWidth = 2.5;
      ctx.setLineDash([4, 3]);
      ctx.beginPath();
      ctx.arc(x, y, r + 6, 0, Math.PI*2);
      ctx.stroke();
      ctx.setLineDash([]);
    }
    ctx.restore();
  }

  function drawCourt(){
    const img = editor.courtType === 'half' ? demiImg : fullImg;
    const ready = editor.courtType === 'half' ? imgsReady.demi : imgsReady.full;
    if(img && ready){
      if(editor.courtType === 'full'){
        // V7.53 : terrain entier paysage, dessiné centré à sa taille naturelle
        // Fond transparent autour (effacer le canvas d'abord)
        ctx.clearRect(0, 0, canvas.width, canvas.height);
        // Calculer la taille pour rentrer dans le canvas en conservant le ratio (contain)
        const imgRatio = img.naturalWidth / img.naturalHeight;
        const canvasRatio = canvas.width / canvas.height;
        let dw, dh;
        if(imgRatio > canvasRatio){
          // Image plus large : limiter par largeur canvas
          dw = canvas.width;
          dh = dw / imgRatio;
        } else {
          dh = canvas.height;
          dw = dh * imgRatio;
        }
        const dx = (canvas.width - dw) / 2;
        const dy = (canvas.height - dh) / 2;
        // Stocker pour les conversions de coordonnées des joueurs
        editor._fullCourtRect = { x: dx, y: dy, w: dw, h: dh };
        ctx.drawImage(img, dx, dy, dw, dh);
      } else {
        // Demi-terrain : remplit le canvas (ratio adapté)
        ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
        editor._fullCourtRect = null;
      }
    } else {
      ctx.fillStyle = '#6B1A2C';
      ctx.fillRect(0, 0, canvas.width, canvas.height);
    }
  }


  // Cercle d'ancrage ballon = "halo orange autour du porteur"
  function drawBallHalo(x, y, alpha){
    ctx.save();
    const scale = 1 /* V7.53: joueurs taille identique en full et half */;
    const R = 40 * scale;
    const BR = 10 * scale;  // rayon petit ballon
    const BD = 28 * scale;  // décalage petit ballon
    // Halo lumineux orange en arrière
    ctx.shadowColor = 'rgba(232, 116, 60, 0.85)';
    ctx.shadowBlur = 20 * scale;
    ctx.strokeStyle = `rgba(232, 116, 60, ${alpha || 1})`;
    ctx.lineWidth = 6 * scale;
    ctx.beginPath();
    ctx.arc(x, y, R, 0, Math.PI*2);
    ctx.stroke();
    ctx.shadowBlur = 0;
    // Anneau or vif intérieur
    ctx.strokeStyle = '#FFB570';
    ctx.lineWidth = 3 * scale;
    ctx.beginPath();
    ctx.arc(x, y, R, 0, Math.PI*2);
    ctx.stroke();
    // Petit ballon orange en haut à droite
    ctx.shadowColor = 'rgba(0,0,0,0.5)';
    ctx.shadowBlur = 4;
    ctx.shadowOffsetY = 2;
    const bx = x + BD, by = y - BD;
    const grad = ctx.createRadialGradient(bx-3*scale, by-3*scale, 1, bx, by, BR);
    grad.addColorStop(0, '#FFB570');
    grad.addColorStop(1, '#C0501A');
    ctx.fillStyle = grad;
    ctx.beginPath();
    ctx.arc(bx, by, BR, 0, Math.PI*2);
    ctx.fill();
    ctx.shadowBlur = 0; ctx.shadowOffsetY = 0;
    ctx.strokeStyle = '#0F0F12';
    ctx.lineWidth = 1.2;
    ctx.beginPath();
    ctx.arc(bx, by, BR, 0, Math.PI*2);
    ctx.moveTo(bx-BR, by); ctx.lineTo(bx+BR, by);
    ctx.moveTo(bx, by-BR); ctx.lineTo(bx, by+BR);
    ctx.stroke();
    ctx.restore();
  }

  // === CACHE D'IMAGES POUR LES PHOTOS DE JOUEURS ===
  // Évite de re-décoder l'image data: à chaque frame
  const _imageCache = {};
  function getCachedImage(dataUrl){
    if(!dataUrl) return null;
    // Utiliser une clé courte (hash naïf) pour le cache
    const key = dataUrl.length + '_' + dataUrl.slice(-32);
    if(_imageCache[key]){
      return _imageCache[key];
    }
    const img = new Image();
    img.src = dataUrl;
    img.onload = () => {
      // Re-rendre quand l'image est chargée
      if(typeof render === 'function') render();
    };
    _imageCache[key] = img;
    return img;
  }

  function drawPlayer(p, selected, hasBall){
    // Taille des joueurs : réduite pour terrain complet (plus grand espace)
    const SIZE = 28 /* V7.53: taille joueur identique */;
    ctx.save();
    ctx.shadowColor = 'rgba(0,0,0,0.45)';
    ctx.shadowBlur = 6;
    ctx.shadowOffsetY = 3;
    const customColor = p.color;
    const fillCol = customColor || (p.kind === 'attack-sq' ? '#D4A24C' : '#6B1A2C');

    if(p.kind === 'attack' || p.kind === 'attack-ball'){
      ctx.fillStyle = fillCol;
      ctx.strokeStyle = '#D4A24C';
      ctx.lineWidth = 3.5;
      ctx.beginPath();
      ctx.arc(p.x, p.y, SIZE, 0, Math.PI*2);
      ctx.fill();
      ctx.stroke();
      ctx.shadowBlur = 0; ctx.shadowOffsetY = 0;
      // Si le joueur a une photo liée, on l'affiche à la place du numéro
      const photoImg = p.photo ? getCachedImage(p.photo) : null;
      if(photoImg && photoImg.complete && photoImg.naturalWidth > 0){
        // Clipper en cercle pour que la photo épouse la forme du pion
        ctx.save();
        ctx.beginPath();
        ctx.arc(p.x, p.y, SIZE - 2, 0, Math.PI*2);
        ctx.clip();
        ctx.drawImage(photoImg, p.x - SIZE, p.y - SIZE, SIZE * 2, SIZE * 2);
        ctx.restore();
        // Bord doré par-dessus pour cacher les pixels du bord
        ctx.strokeStyle = '#D4A24C';
        ctx.lineWidth = 3;
        ctx.beginPath();
        ctx.arc(p.x, p.y, SIZE, 0, Math.PI*2);
        ctx.stroke();
      } else {
        // Numéro classique
        ctx.fillStyle = '#FFFFFF';
        ctx.strokeStyle = '#0F0F12';
        ctx.lineWidth = 2;
        ctx.font = `bold ${Math.round(SIZE * 0.86)}px Arial, sans-serif`;
        ctx.textAlign = 'center'; ctx.textBaseline = 'middle';
        ctx.strokeText(p.num, p.x, p.y + 1);
        ctx.fillText(p.num, p.x, p.y + 1);
      }
    } else if(p.kind === 'attack-sq'){
      ctx.fillStyle = fillCol;
      ctx.strokeStyle = '#0F0F12';
      ctx.lineWidth = 3.5;
      ctx.fillRect(p.x-SIZE, p.y-SIZE, SIZE*2, SIZE*2);
      ctx.strokeRect(p.x-SIZE, p.y-SIZE, SIZE*2, SIZE*2);
      ctx.shadowBlur = 0; ctx.shadowOffsetY = 0;
      // Photo possible pour carré aussi
      const photoImg = p.photo ? getCachedImage(p.photo) : null;
      if(photoImg && photoImg.complete && photoImg.naturalWidth > 0){
        ctx.save();
        ctx.beginPath();
        ctx.rect(p.x-SIZE+2, p.y-SIZE+2, SIZE*2-4, SIZE*2-4);
        ctx.clip();
        ctx.drawImage(photoImg, p.x-SIZE, p.y-SIZE, SIZE*2, SIZE*2);
        ctx.restore();
        ctx.strokeStyle = '#0F0F12';
        ctx.lineWidth = 3;
        ctx.strokeRect(p.x-SIZE, p.y-SIZE, SIZE*2, SIZE*2);
      } else {
        ctx.fillStyle = '#0F0F12';
        ctx.font = `bold ${Math.round(SIZE * 0.86)}px Arial, sans-serif`;
        ctx.textAlign = 'center'; ctx.textBaseline = 'middle';
        ctx.fillText(p.num, p.x, p.y + 1);
      }
    } else if(p.kind === 'defense'){
      // Défenseur : cercle rouge avec numéro + 2 "moustaches" arquées de chaque côté
      // Pivotable via p.rotation (angle en radians)
      const scale = 1 /* V7.53: joueurs taille identique en full et half */;
      const R = 22 * scale;       // rayon du cercle central
      const W = 36 * scale;       // largeur des moustaches
      const HOFF = 4 * scale;
      const col = customColor || '#E63946';
      const angle = p.rotation || 0;

      ctx.save();
      ctx.translate(p.x, p.y);
      ctx.rotate(angle);

      // Moustaches : 2 arcs symétriques (dans le repère tourné, origine = p)
      ctx.strokeStyle = col;
      ctx.lineWidth = 6 * scale;
      ctx.lineCap = 'round';
      ctx.beginPath();
      ctx.moveTo(-R - W, HOFF + 6*scale);
      ctx.quadraticCurveTo(-R - W/2, -14*scale, -R - 2, HOFF);
      ctx.stroke();
      ctx.beginPath();
      ctx.moveTo(R + 2, HOFF);
      ctx.quadraticCurveTo(R + W/2, -14*scale, R + W, HOFF + 6*scale);
      ctx.stroke();

      // Cercle central plein rouge
      ctx.shadowBlur = 6;
      ctx.fillStyle = col;
      ctx.beginPath();
      ctx.arc(0, 0, R, 0, Math.PI*2);
      ctx.fill();
      ctx.restore();

      // Numéro blanc au centre — TOUJOURS droit (pas tourné)
      ctx.shadowBlur = 0; ctx.shadowOffsetY = 0;
      ctx.fillStyle = '#FFFFFF';
      ctx.font = `bold ${Math.round(20 * scale)}px Arial, sans-serif`;
      ctx.textAlign = 'center'; ctx.textBaseline = 'middle';
      ctx.fillText(p.num, p.x, p.y + 1);

      // Poignée de rotation : visible quand le défenseur est sélectionné
      if(selected){
        // Position dans le sens "haut" du défenseur (suit la rotation)
        const handleDist = R + 22 * scale;
        const hx = p.x + Math.sin(angle) * (-handleDist);
        const hy = p.y - Math.cos(angle) * handleDist;
        // Trait reliant le pion à la poignée
        ctx.strokeStyle = '#D4A24C';
        ctx.lineWidth = 2;
        ctx.setLineDash([3, 3]);
        ctx.beginPath();
        ctx.moveTo(p.x, p.y);
        ctx.lineTo(hx, hy);
        ctx.stroke();
        ctx.setLineDash([]);
        // Cercle poignée or
        ctx.shadowColor = 'rgba(0,0,0,.35)';
        ctx.shadowBlur = 4;
        ctx.fillStyle = '#D4A24C';
        ctx.strokeStyle = '#FFFFFF';
        ctx.lineWidth = 2.5;
        ctx.beginPath();
        ctx.arc(hx, hy, 9, 0, Math.PI*2);
        ctx.fill();
        ctx.stroke();
        ctx.shadowBlur = 0;
        // Icône ↻ dedans
        ctx.fillStyle = '#0F0F12';
        ctx.font = 'bold 11px Arial';
        ctx.textAlign = 'center'; ctx.textBaseline = 'middle';
        ctx.fillText('↻', hx, hy + 1);
      }
    } else if(p.kind === 'coach'){
      // Coach : cercle blanc avec C rouge (comme dans la maquette)
      ctx.fillStyle = '#FFFFFF';
      ctx.strokeStyle = '#E63946';
      ctx.lineWidth = 4;
      ctx.beginPath();
      ctx.arc(p.x, p.y, SIZE, 0, Math.PI*2);
      ctx.fill();
      ctx.stroke();
      ctx.shadowBlur = 0; ctx.shadowOffsetY = 0;
      ctx.fillStyle = '#E63946';
      ctx.font = `bold ${Math.round(SIZE)}px Arial, sans-serif`;
      ctx.textAlign = 'center'; ctx.textBaseline = 'middle';
      ctx.fillText('C', p.x, p.y + 1);
    }
    ctx.restore();

    if(selected){
      ctx.save();
      ctx.strokeStyle = '#D4A24C';
      ctx.lineWidth = 4;
      ctx.setLineDash([8, 5]);
      ctx.beginPath();
      const r = p.kind === 'defense' ? 38 : SIZE + 10;
      ctx.arc(p.x, p.y, r, 0, Math.PI*2);
      ctx.stroke();
      ctx.restore();
    }
  }

  // Récupère le point de contrôle (pour Bézier quadratique) si l'action est courbée,
  // sinon null (= ligne droite)
  // Note : seuls dribble et screen supportent la courbure
  // Position le long d'un chemin (0=start, 1=end), supporte les courbes
  function positionOnPath(start, end, ctrls, t){
    const C = ctrls || [];
    if(C.length === 0){
      return {
        x: start.x + (end.x - start.x) * t,
        y: start.y + (end.y - start.y) * t
      };
    }
    if(C.length === 1){
      const mt = 1 - t;
      return {
        x: mt*mt*start.x + 2*mt*t*C[0].x + t*t*end.x,
        y: mt*mt*start.y + 2*mt*t*C[0].y + t*t*end.y
      };
    }
    // Catmull-Rom : on échantillonne et on indexe linéairement
    const samples = samplePath(start, end, C, 100);
    const idx = Math.min(samples.length - 1, Math.max(0, Math.floor(t * (samples.length - 1))));
    return samples[idx];
  }

  // Types d'action qui supportent la courbure (= peuvent suivre un chemin avec ctrlPoints)
  function canCurveAction(a){
    return a.kind === 'dribble' || a.kind === 'screen' || a.kind === 'cut' || a.kind === 'drive' || a.kind === 'curl';
  }

  // Récupère les points de contrôle (tableau, peut être vide)
  // Compatibilité : si `ctrlPos` existe (ancien format), on le retourne en single-element
  function getActionCtrls(a){
    if(!canCurveAction(a)) return [];
    if(a.ctrlPoints && a.ctrlPoints.length) return a.ctrlPoints;
    if(a.ctrlPos) return [a.ctrlPos];
    return [];
  }
  // Conserver l'ancienne fonction pour compat
  function getActionCtrl(a){
    const c = getActionCtrls(a);
    return c.length === 1 ? c[0] : null;
  }

  // Échantillonne des points le long du chemin
  // - 0 ctrl : ligne droite start → end
  // - 1 ctrl : Bézier quadratique (start, ctrl, end)
  // - 2+ ctrls : spline Catmull-Rom passant par start, ctrls..., end
  function samplePath(start, end, ctrls, n){
    const C = ctrls || [];
    const pts = [];
    if(C.length === 0){
      for(let i=0; i<=n; i++){
        const t = i/n;
        pts.push({
          x: start.x + (end.x - start.x) * t,
          y: start.y + (end.y - start.y) * t
        });
      }
    } else if(C.length === 1){
      const ctrl = C[0];
      for(let i=0; i<=n; i++){
        const t = i/n;
        const mt = 1 - t;
        pts.push({
          x: mt*mt*start.x + 2*mt*t*ctrl.x + t*t*end.x,
          y: mt*mt*start.y + 2*mt*t*ctrl.y + t*t*end.y
        });
      }
    } else {
      // Catmull-Rom spline passant par [start, ...ctrls, end]
      // On échantillonne sur chaque segment et on concatène
      const allPts = [start, ...C, end];
      const segments = allPts.length - 1;
      const ptsPerSeg = Math.max(8, Math.floor(n / segments));
      // Pour Catmull-Rom on a besoin de "points fantômes" aux extrémités
      for(let s = 0; s < segments; s++){
        const p0 = s === 0 ? allPts[0] : allPts[s-1];
        const p1 = allPts[s];
        const p2 = allPts[s+1];
        const p3 = s+2 >= allPts.length ? allPts[s+1] : allPts[s+2];
        for(let i=0; i<ptsPerSeg; i++){
          const t = i / ptsPerSeg;
          const t2 = t*t, t3 = t2*t;
          // Catmull-Rom (tension 0.5)
          const x = 0.5 * (
            (2 * p1.x) +
            (-p0.x + p2.x) * t +
            (2*p0.x - 5*p1.x + 4*p2.x - p3.x) * t2 +
            (-p0.x + 3*p1.x - 3*p2.x + p3.x) * t3
          );
          const y = 0.5 * (
            (2 * p1.y) +
            (-p0.y + p2.y) * t +
            (2*p0.y - 5*p1.y + 4*p2.y - p3.y) * t2 +
            (-p0.y + 3*p1.y - 3*p2.y + p3.y) * t3
          );
          pts.push({ x, y });
        }
      }
      pts.push({ x: end.x, y: end.y });
    }
    return pts;
  }

  // Tangente (direction) en t=1 sur le chemin (pour orienter la flèche)
  function tangentAtEnd(start, end, ctrls){
    const C = ctrls || [];
    if(C.length === 0){
      return { dx: end.x - start.x, dy: end.y - start.y };
    }
    if(C.length === 1){
      // Dérivée Bézier en t=1 : 2·(E - C)
      return { dx: end.x - C[0].x, dy: end.y - C[0].y };
    }
    // Plusieurs ctrls : tangente = end - dernier ctrl (approximation correcte)
    const lastCtrl = C[C.length - 1];
    return { dx: end.x - lastCtrl.x, dy: end.y - lastCtrl.y };
  }

  function drawAction(a, phase, selected, orderNum, alpha){
    const start = getActionStart(a, phase);
    const end = getActionEnd(a, phase);
    const ctrls = getActionCtrls(a);
    const color = a.color || '#0F0F12';
    const A = (alpha === undefined ? 1 : alpha);
    if(A <= 0.02) return; // invisible, on saute
    ctx.save();
    ctx.globalAlpha = A;
    ctx.strokeStyle = color;
    ctx.fillStyle = color;
    ctx.lineWidth = 3.5;
    ctx.lineCap = 'round';

    if(a.kind === 'dribble'){
      drawWavyPath(start, end, ctrls, color);
      drawArrowheadOriented(end, tangentAtEnd(start, end, ctrls), color);
    } else if(a.kind === 'pass'){
      ctx.setLineDash([10, 6]);
      drawPathLine(start, end, ctrls);
      ctx.setLineDash([]);
      drawArrowheadOriented(end, tangentAtEnd(start, end, ctrls), color);
    } else if(a.kind === 'cut' || a.kind === 'drive' || a.kind === 'curl'){
      drawPathLine(start, end, ctrls);
      drawArrowheadOriented(end, tangentAtEnd(start, end, ctrls), color);
    } else if(a.kind === 'screen'){
      drawPathLine(start, end, ctrls);
      // Barre perpendiculaire à la tangente en fin
      const t = tangentAtEnd(start, end, ctrls);
      const len = Math.hypot(t.dx, t.dy) || 1;
      const nx = -t.dy/len, ny = t.dx/len;
      ctx.lineWidth = 6;
      ctx.beginPath();
      ctx.moveTo(end.x + nx*14, end.y + ny*14);
      ctx.lineTo(end.x - nx*14, end.y - ny*14);
      ctx.stroke();
    } else if(a.kind === 'shoot'){
      ctx.setLineDash([10, 5]);
      drawPathLine(start, end, ctrls);
      ctx.setLineDash([]);
      ctx.lineWidth = 3;
      ctx.beginPath();
      ctx.arc(end.x, end.y, 10, 0, Math.PI*2);
      ctx.stroke();
      ctx.beginPath();
      ctx.moveTo(end.x-10, end.y); ctx.lineTo(end.x+10, end.y);
      ctx.moveTo(end.x, end.y-10); ctx.lineTo(end.x, end.y+10);
      ctx.stroke();
    } else if(a.kind === 'handoff'){
      drawPathLine(start, end, ctrls);
      drawArrowheadOriented(end, tangentAtEnd(start, end, ctrls), color);
      // Petit trait perpendiculaire au milieu
      const mid = ctrls.length ?
        { x:(start.x+ctrls[0].x+end.x)/3, y:(start.y+ctrls[0].y+end.y)/3 } :
        { x:(start.x+end.x)/2, y:(start.y+end.y)/2 };
      const dx = end.x - start.x, dy = end.y - start.y;
      const len = Math.hypot(dx, dy) || 1;
      const nx = -dy/len, ny = dx/len;
      ctx.beginPath();
      ctx.moveTo(mid.x + nx*7, mid.y + ny*7);
      ctx.lineTo(mid.x - nx*7, mid.y - ny*7);
      ctx.stroke();
    }
    ctx.restore();

    // Numéro d'ordre (1, 2, 3...) près du milieu pour montrer la séquence
    if(orderNum && !selected){
      let midX, midY;
      // Échantillonner le chemin et prendre le point au milieu
      const pts = samplePath(start, end, ctrls, 20);
      const midPt = pts[Math.floor(pts.length/2)];
      midX = midPt.x; midY = midPt.y;
      const dx = end.x - start.x, dy = end.y - start.y;
      const len = Math.hypot(dx, dy) || 1;
      const nx = -dy/len, ny = dx/len;
      const ox = midX + nx * 16;
      const oy = midY + ny * 16;
      ctx.save();
      ctx.fillStyle = '#D4A24C';
      ctx.strokeStyle = '#0F0F12';
      ctx.lineWidth = 2;
      ctx.beginPath();
      ctx.arc(ox, oy, 11, 0, Math.PI*2);
      ctx.fill();
      ctx.stroke();
      ctx.fillStyle = '#0F0F12';
      ctx.font = 'bold 13px Arial';
      ctx.textAlign = 'center';
      ctx.textBaseline = 'middle';
      ctx.fillText(orderNum, ox, oy + 1);
      // Si l'action est en parallèle avec la précédente, ajouter une indication "↔"
      if(a.startWith){
        ctx.fillStyle = '#1B5E9C';
        ctx.font = 'bold 11px Arial';
        ctx.fillText('↔', ox + 16, oy + 1);
      }
      ctx.restore();
    }

    // Poignées si sélectionné
    if(selected){
      drawHandle(start.x, start.y, '#16A34A');
      drawHandle(end.x, end.y, '#E63946');
      // Poignée de courbure SEULEMENT pour dribble et screen
      if(canCurveAction(a)){
        if(ctrls.length === 0){
          // Aucun ctrl : on dessine une poignée "fantôme" au milieu pour pouvoir commencer à courber
          const ctrlX = (start.x + end.x) / 2;
          const ctrlY = (start.y + end.y) / 2;
          drawHandle(ctrlX, ctrlY, '#D4A24C', true); // semi-transparente
        } else {
          // Une poignée or par point de contrôle
          ctrls.forEach(c => drawHandle(c.x, c.y, '#D4A24C'));
        }
      }
    }
  }

  // Dessine une ligne droite OU une courbe (suivant le nombre de ctrls)
  function drawPathLine(start, end, ctrls){
    const C = ctrls || [];
    ctx.beginPath();
    ctx.moveTo(start.x, start.y);
    if(C.length === 0){
      ctx.lineTo(end.x, end.y);
    } else if(C.length === 1){
      // Bézier quadratique
      ctx.quadraticCurveTo(C[0].x, C[0].y, end.x, end.y);
    } else {
      // Catmull-Rom : on échantillonne et on trace une polyligne
      const pts = samplePath(start, end, C, 60);
      for(let i=1; i<pts.length; i++) ctx.lineTo(pts[i].x, pts[i].y);
    }
    ctx.stroke();
  }

  // Flèche orientée selon une tangente (utile pour les courbes)
  function drawArrowheadOriented(point, tangent, color){
    const len = Math.hypot(tangent.dx, tangent.dy);
    if(len < 5) return;
    const angle = Math.atan2(tangent.dy, tangent.dx);
    const headLen = 14;
    ctx.save();
    ctx.fillStyle = color;
    ctx.beginPath();
    ctx.moveTo(point.x, point.y);
    ctx.lineTo(point.x - headLen*Math.cos(angle - Math.PI/7), point.y - headLen*Math.sin(angle - Math.PI/7));
    ctx.lineTo(point.x - headLen*Math.cos(angle + Math.PI/7), point.y - headLen*Math.sin(angle + Math.PI/7));
    ctx.closePath();
    ctx.fill();
    ctx.restore();
  }

  function drawHandle(x, y, color, ghost){
    ctx.save();
    if(ghost){
      ctx.globalAlpha = 0.5;
    }
    ctx.shadowColor = 'rgba(0,0,0,.3)';
    ctx.shadowBlur = 4;
    ctx.fillStyle = color;
    ctx.strokeStyle = '#FFFFFF';
    ctx.lineWidth = 2;
    ctx.beginPath();
    ctx.arc(x, y, ghost ? 7 : 9, 0, Math.PI*2);
    ctx.fill();
    ctx.stroke();
    ctx.restore();
  }

  // Dessine un zigzag (dribble) le long d'une ligne droite, d'une Bézier ou d'une spline
  function drawWavyPath(start, end, ctrls, color){
    const N = 200;  // bonne résolution
    let totalLen = 0;
    const samples = samplePath(start, end, ctrls, N);
    for(let i=1; i<samples.length; i++){
      totalLen += Math.hypot(samples[i].x - samples[i-1].x, samples[i].y - samples[i-1].y);
    }
    if(totalLen < 10) return;
    const headSpace = 14;  // place pour la tête de flèche
    const useLen = Math.max(0, totalLen - headSpace);
    if(useLen <= 0) return;
    // Nombre de vagues : ~1 vague tous les 22px, minimum 3
    const waves = Math.max(3, Math.round(useLen / 22));
    const amplitude = 9;
    // Reconstruire en s'arrêtant à useLen
    ctx.beginPath();
    let acc = 0;
    let prevPt = samples[0];
    ctx.moveTo(prevPt.x, prevPt.y);
    for(let i=1; i<samples.length; i++){
      const seg = Math.hypot(samples[i].x - prevPt.x, samples[i].y - prevPt.y);
      acc += seg;
      if(acc > useLen){
        // Couper ici (fin du zigzag, à 14px avant l'arrivée)
        break;
      }
      const t = acc / useLen;
      // Tangente locale : direction entre prevPt et samples[i]
      const tdx = samples[i].x - prevPt.x;
      const tdy = samples[i].y - prevPt.y;
      const tlen = Math.hypot(tdx, tdy) || 1;
      // Normale perpendiculaire
      const nx = -tdy/tlen, ny = tdx/tlen;
      // Atténuation près de la fin
      const fade = t > 0.92 ? (1 - (t - 0.92)/0.08) : 1;
      const wave = Math.sin(t * Math.PI * 2 * waves) * amplitude * fade;
      const px = samples[i].x + nx*wave;
      const py = samples[i].y + ny*wave;
      ctx.lineTo(px, py);
      prevPt = samples[i];
    }
    ctx.stroke();
  }

  // Ancien drawWavyLine kept for compatibility
  function drawWavyLine(start, end, color){
    drawWavyPath(start, end, [], color);
  }

  function drawArrowhead(start, end, color){
    const dx = end.x - start.x, dy = end.y - start.y;
    const len = Math.hypot(dx, dy);
    if(len < 5) return;
    const angle = Math.atan2(dy, dx);
    const headLen = 14;
    ctx.save();
    ctx.fillStyle = color;
    ctx.beginPath();
    ctx.moveTo(end.x, end.y);
    ctx.lineTo(end.x - headLen*Math.cos(angle - Math.PI/7), end.y - headLen*Math.sin(angle - Math.PI/7));
    ctx.lineTo(end.x - headLen*Math.cos(angle + Math.PI/7), end.y - headLen*Math.sin(angle + Math.PI/7));
    ctx.closePath();
    ctx.fill();
    ctx.restore();
  }

  function drawFreedraw(d){
    if(d.points.length < 2) return;
    ctx.save();
    ctx.strokeStyle = d.color || '#0F0F12';
    ctx.lineWidth = d.width || 2;
    ctx.lineCap = 'round';
    ctx.lineJoin = 'round';
    ctx.beginPath();
    ctx.moveTo(d.points[0].x, d.points[0].y);
    for(let i=1; i<d.points.length; i++) ctx.lineTo(d.points[i].x, d.points[i].y);
    ctx.stroke();
    ctx.restore();
  }

  function drawObject(o, selected){
    ctx.save();
    const fillCol = o.color || '#FFA500';
    const sx = (o.sizeX !== undefined ? o.sizeX : (o.size || 1));
    const sy = (o.sizeY !== undefined ? o.sizeY : (o.size || 1));
    const angle = o.rotation || 0;
    ctx.globalAlpha = o.opacity === undefined ? 1 : o.opacity;

    // Translation + rotation : tout est dessiné autour de (0,0)
    ctx.translate(o.x, o.y);
    ctx.rotate(angle);

    if(o.kind === 'cone'){
      ctx.fillStyle = fillCol;
      ctx.strokeStyle = '#0F0F12';
      ctx.lineWidth = 2;
      ctx.beginPath();
      ctx.moveTo(0, -16*sy);
      ctx.lineTo(13*sx, 11*sy);
      ctx.lineTo(-13*sx, 11*sy);
      ctx.closePath();
      ctx.fill();
      ctx.stroke();
    } else if(o.kind === 'triangle'){
      ctx.strokeStyle = o.color || '#0F0F12';
      ctx.lineWidth = 3;
      ctx.beginPath();
      ctx.moveTo(0, -18*sy);
      ctx.lineTo(16*sx, 12*sy);
      ctx.lineTo(-16*sx, 12*sy);
      ctx.closePath();
      ctx.stroke();
    } else if(o.kind === 'square'){
      ctx.fillStyle = fillCol;
      ctx.strokeStyle = '#0F0F12';
      ctx.lineWidth = 2;
      ctx.fillRect(-14*sx, -14*sy, 28*sx, 28*sy);
      ctx.strokeRect(-14*sx, -14*sy, 28*sx, 28*sy);
    } else if(o.kind === 'circle'){
      ctx.fillStyle = fillCol;
      ctx.strokeStyle = '#0F0F12';
      ctx.lineWidth = 2;
      ctx.beginPath();
      ctx.ellipse(0, 0, 14*sx, 14*sy, 0, 0, Math.PI*2);
      ctx.fill();
      ctx.stroke();
    } else if(o.kind === 'text'){
      ctx.fillStyle = o.color || '#0F0F12';
      ctx.strokeStyle = '#FFFFFF';
      ctx.lineWidth = 3;
      const sAvg = (sx + sy) / 2;
      ctx.font = `bold ${Math.round(18*sAvg)}px Arial`;
      ctx.textAlign = 'center';
      ctx.textBaseline = 'middle';
      ctx.strokeText(o.text, 0, 0);
      ctx.fillText(o.text, 0, 0);
    } else if(o.kind === 'handoff'){
      const col = o.color || '#0F0F12';
      const height = 26 * sy;
      const halfW = 8 * sx;
      const overshoot = 5 * sx;
      const thick = Math.max(3, 4 * Math.min(sx, sy));
      ctx.strokeStyle = col;
      ctx.lineWidth = thick;
      ctx.lineCap = 'round';
      ctx.beginPath();
      ctx.moveTo(-halfW, -height/2);
      ctx.lineTo(-halfW, height/2);
      ctx.stroke();
      ctx.beginPath();
      ctx.moveTo(halfW, -height/2);
      ctx.lineTo(halfW, height/2);
      ctx.stroke();
      ctx.beginPath();
      ctx.moveTo(-halfW - overshoot, 0);
      ctx.lineTo(halfW + overshoot, 0);
      ctx.stroke();
    }

    if(selected){
      // Cadre pointillé + poignées dans le repère TOURNÉ (la sélection suit la rotation)
      ctx.globalAlpha = 1;
      const local = getObjectLocalBBox(o);  // bbox dans le repère local (sans translation)
      ctx.strokeStyle = '#D4A24C';
      ctx.lineWidth = 1.5;
      ctx.setLineDash([4, 3]);
      ctx.strokeRect(local.x, local.y, local.w, local.h);
      ctx.setLineDash([]);
      // 8 poignées en coordonnées locales
      const handles = [
        { x: local.x, y: local.y },                               // tl
        { x: local.x + local.w, y: local.y },                     // tr
        { x: local.x, y: local.y + local.h },                     // bl
        { x: local.x + local.w, y: local.y + local.h },           // br
        { x: local.x + local.w/2, y: local.y },                   // t
        { x: local.x + local.w/2, y: local.y + local.h },         // b
        { x: local.x, y: local.y + local.h/2 },                   // l
        { x: local.x + local.w, y: local.y + local.h/2 },         // r
      ];
      handles.forEach(h => {
        ctx.fillStyle = '#FFFFFF';
        ctx.strokeStyle = '#D4A24C';
        ctx.lineWidth = 2;
        ctx.fillRect(h.x - 5, h.y - 5, 10, 10);
        ctx.strokeRect(h.x - 5, h.y - 5, 10, 10);
      });
      // Poignée de rotation : au-dessus du cadre, reliée par un trait pointillé
      const rotY = local.y - 25;
      ctx.strokeStyle = '#D4A24C';
      ctx.lineWidth = 1.5;
      ctx.setLineDash([3, 3]);
      ctx.beginPath();
      ctx.moveTo(local.x + local.w/2, local.y);
      ctx.lineTo(local.x + local.w/2, rotY);
      ctx.stroke();
      ctx.setLineDash([]);
      // Cercle or de rotation
      ctx.fillStyle = '#D4A24C';
      ctx.strokeStyle = '#FFFFFF';
      ctx.lineWidth = 2.5;
      ctx.beginPath();
      ctx.arc(local.x + local.w/2, rotY, 9, 0, Math.PI*2);
      ctx.fill();
      ctx.stroke();
      // Icône ↻ (toujours droite, donc on dé-tourne juste pour ce dessin)
      ctx.save();
      ctx.translate(local.x + local.w/2, rotY);
      ctx.rotate(-angle);  // contre-rotation
      ctx.fillStyle = '#0F0F12';
      ctx.font = 'bold 11px Arial';
      ctx.textAlign = 'center';
      ctx.textBaseline = 'middle';
      ctx.fillText('↻', 0, 1);
      ctx.restore();
    }
    ctx.restore();
  }

  // BBox dans le repère LOCAL (autour de (0,0), sans translation, sans rotation)
  function getObjectLocalBBox(o){
    const sx = (o.sizeX !== undefined ? o.sizeX : (o.size || 1));
    const sy = (o.sizeY !== undefined ? o.sizeY : (o.size || 1));
    let halfW, halfH;
    if(o.kind === 'cone'){ halfW = 13*sx; halfH = 13.5*sy; }
    else if(o.kind === 'triangle'){ halfW = 16*sx; halfH = 15*sy; }
    else if(o.kind === 'square'){ halfW = 14*sx; halfH = 14*sy; }
    else if(o.kind === 'circle'){ halfW = 14*sx; halfH = 14*sy; }
    else if(o.kind === 'text'){
      const sAvg = (sx+sy)/2;
      halfW = (o.text||'').length * 5 * sAvg + 5;
      halfH = 10 * sAvg;
    }
    else if(o.kind === 'handoff'){ halfW = 13*sx + 5*sx; halfH = 13*sy; }
    else { halfW = 15*sx; halfH = 15*sy; }
    return { x: -halfW, y: -halfH, w: halfW*2, h: halfH*2 };
  }

  // BBox dans le repère MONDE (rectangle englobant après rotation — pour hitbox)
  function getObjectBBox(o){
    const local = getObjectLocalBBox(o);
    const angle = o.rotation || 0;
    if(!angle){
      return { x: o.x + local.x, y: o.y + local.y, w: local.w, h: local.h };
    }
    // Calculer la bbox englobante du rect tourné
    const corners = [
      { x: local.x, y: local.y },
      { x: local.x + local.w, y: local.y },
      { x: local.x, y: local.y + local.h },
      { x: local.x + local.w, y: local.y + local.h }
    ];
    const cos = Math.cos(angle), sin = Math.sin(angle);
    const rotated = corners.map(c => ({
      x: o.x + c.x * cos - c.y * sin,
      y: o.y + c.x * sin + c.y * cos
    }));
    const xs = rotated.map(p => p.x);
    const ys = rotated.map(p => p.y);
    const xMin = Math.min(...xs), xMax = Math.max(...xs);
    const yMin = Math.min(...ys), yMax = Math.max(...ys);
    return { x: xMin, y: yMin, w: xMax - xMin, h: yMax - yMin };
  }

  // Helper : convertir un point monde en point local (inverse de translate+rotate)
  function worldToLocal(o, wx, wy){
    const angle = o.rotation || 0;
    const dx = wx - o.x, dy = wy - o.y;
    const cos = Math.cos(-angle), sin = Math.sin(-angle);
    return { x: dx * cos - dy * sin, y: dx * sin + dy * cos };
  }

  // Helper : convertir un point local en point monde (translate+rotate appliqué)
  function localToWorld(o, lx, ly){
    const angle = o.rotation || 0;
    const cos = Math.cos(angle), sin = Math.sin(angle);
    return { x: o.x + lx * cos - ly * sin, y: o.y + lx * sin + ly * cos };
  }

  function drawArrowPreview(start, end, kind){
    ctx.save();
    ctx.strokeStyle = editor.currentColor;
    ctx.fillStyle = editor.currentColor;
    ctx.lineWidth = 3;
    ctx.setLineDash([6, 4]);
    ctx.beginPath();
    ctx.moveTo(start.x, start.y);
    ctx.lineTo(end.x, end.y);
    ctx.stroke();
    ctx.setLineDash([]);
    drawArrowhead(start, end, editor.currentColor);
    ctx.restore();
  }

  // === UNDO / REDO ===
  function snapshot(){
    return JSON.stringify({ phases: editor.phases, currentPhase: editor.currentPhase });
  }
  function pushUndo(){
    editor.undoStack.push(snapshot());
    if(editor.undoStack.length > 30) editor.undoStack.shift();
    editor.redoStack = [];
  }
  function undo(){
    if(editor.undoStack.length === 0){ toast('Rien à annuler'); return; }
    editor.redoStack.push(snapshot());
    const s = JSON.parse(editor.undoStack.pop());
    editor.phases = s.phases;
    editor.currentPhase = Math.min(s.currentPhase, editor.phases.length-1);
    editor.selectedId = null;
    render(); renderPhasesList();
  }
  function redo(){
    if(editor.redoStack.length === 0){ toast('Rien à refaire'); return; }
    editor.undoStack.push(snapshot());
    const s = JSON.parse(editor.redoStack.pop());
    editor.phases = s.phases;
    editor.currentPhase = Math.min(s.currentPhase, editor.phases.length-1);
    render(); renderPhasesList();
  }

  // === EXPORT / SAVE ===
  function exportPng(){
    render();
    // Le canvas a maintenant le ratio adapté au type de terrain → pas besoin de cropper
    const url = canvas.toDataURL('image/png');
    download(url, (document.getElementById('edTitle').value || 'play')+'.png');
    toast('PNG téléchargé ✓');
  }
  function exportJson(){
    const data = { title: document.getElementById('edTitle').value, courtType: editor.courtType, phases: editor.phases };
    const blob = new Blob([JSON.stringify(data, null, 2)], { type:'application/json' });
    download(URL.createObjectURL(blob), (data.title || 'play')+'.json');
    toast('JSON téléchargé ✓');
  }
  function importJson(e){
    const f = e.target.files[0]; if(!f) return;
    const r = new FileReader();
    r.onload = ev => {
      try {
        const data = JSON.parse(ev.target.result);
        pushUndo();
        editor.phases = (data.phases && data.phases.length) ? data.phases : [emptyPhase()];
        editor.currentPhase = 0;
        if(data.courtType){
          editor.courtType = data.courtType;
          // Ajuster le canvas aux dimensions du mode
          const dims = COURT_DIMS[editor.courtType];
          canvas.width = dims.w; canvas.height = dims.h; canvas.setAttribute('data-court', editor.courtType);
        }
        if(data.title) document.getElementById('edTitle').value = data.title;
        const btn = document.getElementById('courtToggleBtn');
        if(btn) btn.textContent = editor.courtType === 'half' ? '🏟 Terrain : Demi' : '🏟 Terrain : Complet';
        render(); renderPhasesList();
        toast('JSON importé ✓');
      } catch(err){ toast('JSON invalide', 'error'); }
    };
    r.readAsText(f);
  }
  // Charge ffmpeg.wasm dynamiquement la première fois
  let _ffmpegInstance = null;
  let _ffmpegLoading = null;
  async function loadFFmpeg(){
    if(_ffmpegInstance) return _ffmpegInstance;
    if(_ffmpegLoading) return _ffmpegLoading;
    _ffmpegLoading = (async () => {
      // Charger le script ffmpeg via <script>
      const scriptUrl = 'https://unpkg.com/@ffmpeg/ffmpeg@0.12.10/dist/umd/ffmpeg.js';
      const utilUrl = 'https://unpkg.com/@ffmpeg/util@0.12.1/dist/umd/index.js';
      await new Promise((resolve, reject) => {
        const s = document.createElement('script');
        s.src = utilUrl;
        s.onload = resolve;
        s.onerror = () => reject(new Error('Impossible de charger ffmpeg/util'));
        document.head.appendChild(s);
      });
      await new Promise((resolve, reject) => {
        const s = document.createElement('script');
        s.src = scriptUrl;
        s.onload = resolve;
        s.onerror = () => reject(new Error('Impossible de charger ffmpeg.js'));
        document.head.appendChild(s);
      });
      const { FFmpeg } = window.FFmpegWASM || {};
      if(!FFmpeg) throw new Error('ffmpeg.wasm indisponible');
      const ffmpeg = new FFmpeg();
      const baseURL = 'https://unpkg.com/@ffmpeg/core@0.12.6/dist/umd';
      await ffmpeg.load({
        coreURL: await window.FFmpegUtil.toBlobURL(`${baseURL}/ffmpeg-core.js`, 'text/javascript'),
        wasmURL: await window.FFmpegUtil.toBlobURL(`${baseURL}/ffmpeg-core.wasm`, 'application/wasm')
      });
      _ffmpegInstance = ffmpeg;
      return ffmpeg;
    })();
    return _ffmpegLoading;
  }

  // Calcule la durée totale de l'animation en ms (selon buildAnimSequence)
  function computeTotalAnimMs(){
    const seq = buildAnimSequence();
    if(!seq || seq.length === 0){
      // Fallback : transition entre phases
      return Math.max(1000, (editor.phases.length - 1) * 600);
    }
    return seq.reduce((s, e) => s + e.duration, 0);
  }

  async function exportVideo(){
    if(editor.phases.length < 1){ toast('Aucune phase à exporter', 'error'); return; }
    if(!canvas.captureStream || !window.MediaRecorder){ toast('Navigateur incompatible', 'error'); return; }

    // Modale de progression
    const progressHtml = `
      <h2 style="margin-top:0">🎬 Export vidéo</h2>
      <div id="vidProgress" style="text-align:center;padding:1rem 0">
        <div style="font-size:1.1rem;font-weight:600;margin-bottom:.8rem" id="vidStatus">Préparation...</div>
        <div style="background:#eee;border-radius:8px;height:14px;overflow:hidden;margin:.8rem 0">
          <div id="vidBar" style="background:linear-gradient(90deg,#D4A24C,#C0501A);height:100%;width:0%;transition:width .3s"></div>
        </div>
        <div style="font-size:.8rem;color:#666;margin-top:.5rem" id="vidDetail">Ne ferme pas cette fenêtre</div>
      </div>
    `;
    openModal(progressHtml);
    const setStatus = (msg, pct, detail) => {
      const s = document.getElementById('vidStatus');
      const b = document.getElementById('vidBar');
      const d = document.getElementById('vidDetail');
      if(s) s.textContent = msg;
      if(b) b.style.width = pct + '%';
      if(d && detail) d.textContent = detail;
    };

    try {
      // Le canvas est déjà au bon ratio selon le mode terrain → pas besoin de cropper
      const stream = canvas.captureStream(30);
      const chunks = [];
      const rec = new MediaRecorder(stream, { mimeType: 'video/webm;codecs=vp9' });
      rec.ondataavailable = e => { if(e.data.size > 0) chunks.push(e.data); };

      const baseFilename = (document.getElementById('edTitle').value || 'play').replace(/[^a-z0-9_-]/gi, '_');

      const onStopPromise = new Promise(resolve => {
        rec.onstop = () => {
          const blob = new Blob(chunks, { type:'video/webm' });
          resolve(blob);
        };
      });

      // Étape 1 : enregistrement
      setStatus('Enregistrement de l\'animation...', 5);
      rec.start();
      // FIX V7.48 : forcer le démarrage à la phase 0 pour capturer TOUTES les phases dans l'export vidéo
      editor.currentPhase = 0;
      // Rendre la phase 0 immédiatement (le canvas doit afficher la phase 0 avant que rec démarre la capture)
      if(typeof render === 'function') render();
      if(editor.isPlaying) pauseAnim();
      editor.phases.forEach(ph => ph.actions.forEach(a => delete a._animStart));
      // Petite pause pour que la phase 0 soit visible 1 frame avant le démarrage anim
      await new Promise(r => setTimeout(r, 100));
      startAnim();

      const totalMs = computeTotalAnimMs() + 600;
      // Avancée pendant l'enregistrement (jusqu'à 40%)
      const recStart = performance.now();
      const recInterval = setInterval(() => {
        const elapsed = performance.now() - recStart;
        const pct = 5 + Math.min(35, (elapsed / totalMs) * 35);
        setStatus('Enregistrement de l\'animation...', pct, `${Math.round(elapsed/1000)}s / ${Math.round(totalMs/1000)}s`);
      }, 200);

      await new Promise(r => setTimeout(r, totalMs));
      clearInterval(recInterval);
      if(editor.isPlaying) pauseAnim();
      await new Promise(r => setTimeout(r, 200));
      rec.stop();

      const webmBlob = await onStopPromise;
      setStatus('Chargement de l\'outil de conversion...', 45, '~10 Mo à télécharger la 1ère fois');

      // Étape 2 : charger ffmpeg avec timeout 60s
      let ffmpeg;
      try {
        const ffmpegPromise = loadFFmpeg();
        const timeoutPromise = new Promise((_, rej) => setTimeout(() => rej(new Error('Timeout')), 60000));
        ffmpeg = await Promise.race([ffmpegPromise, timeoutPromise]);
      } catch(loadErr){
        // Fallback WebM
        setStatus('MP4 indisponible — Téléchargement WebM', 90);
        const blobUrl = URL.createObjectURL(webmBlob);
        download(blobUrl, baseFilename + '.webm');
        await new Promise(r => setTimeout(r, 1500));
        closeModal();
        toast('⚠ ' + (loadErr.message === 'Timeout' ? 'Conversion trop lente, fichier WebM téléchargé' : 'MP4 indisponible, WebM téléchargé'), 'error');
        return;
      }

      // Étape 3 : conversion
      setStatus('Conversion en MP4...', 60, 'Cela peut prendre 10-30 secondes');
      const inputName = 'input.webm';
      const outputName = 'output.mp4';
      const arrayBuffer = await webmBlob.arrayBuffer();
      await ffmpeg.writeFile(inputName, new Uint8Array(arrayBuffer));
      await ffmpeg.exec([
        '-i', inputName,
        '-c:v', 'libx264',
        '-preset', 'fast',
        '-crf', '23',
        '-pix_fmt', 'yuv420p',
        '-movflags', '+faststart',
        outputName
      ]);
      setStatus('Téléchargement du fichier...', 95);
      const mp4Data = await ffmpeg.readFile(outputName);
      const mp4Blob = new Blob([mp4Data.buffer], { type: 'video/mp4' });

      // Téléchargement avec délai pour laisser le navigateur déclencher la sauvegarde
      const url = URL.createObjectURL(mp4Blob);
      const a = document.createElement('a');
      a.href = url;
      a.download = baseFilename + '.mp4';
      a.style.display = 'none';
      document.body.appendChild(a);
      a.click();
      // Garder l'URL vivante plus longtemps (Safari/iOS notamment)
      setTimeout(() => {
        a.remove();
        URL.revokeObjectURL(url);
      }, 5000);

      setStatus('✓ Vidéo téléchargée !', 100, `Fichier : ${baseFilename}.mp4`);
      await new Promise(r => setTimeout(r, 1500));
      closeModal();
      toast('🎬 Vidéo MP4 téléchargée ✓');

      try { await ffmpeg.deleteFile(inputName); } catch(e){}
      try { await ffmpeg.deleteFile(outputName); } catch(e){}
    } catch(err){
      console.error(err);
      closeModal();
      toast('Erreur vidéo : '+err.message, 'error');
    }
  }
  function savePlay(){
    const title = document.getElementById('edTitle').value.trim() || 'Play sans titre';
    render();
    const thumbnail = canvas.toDataURL('image/png');
    state.plays.push({
      id: 'pl_'+Date.now(), title,
      phases: JSON.parse(JSON.stringify(editor.phases)),
      courtType: editor.courtType,
      thumbnail,
      date: Date.now()
    });
    saveState();
    toast('Play sauvegardé ✓');
    renderSavedPlays();
  }
  function renderSavedPlays(){
    const list = document.getElementById('savedPlaysList');
    const msg = document.getElementById('noPlaysMsg');
    if(!list) return;
    msg.style.display = state.plays.length ? 'none' : 'block';
    list.innerHTML = state.plays.map(p => `
      <div style="padding:.5rem;background:var(--gris-bg);border-radius:6px;cursor:pointer;display:flex;gap:.4rem;align-items:center" data-load="${p.id}">
        ${p.thumbnail ? `<img src="${p.thumbnail}" style="width:42px;height:30px;object-fit:cover;border-radius:3px">` : ''}
        <div style="flex:1;min-width:0">
          <strong style="font-size:.82rem;display:block;overflow:hidden;text-overflow:ellipsis;white-space:nowrap">${p.title}</strong>
          <small style="color:var(--gris-text)">${p.phases.length} phases</small>
        </div>
        <span style="color:var(--rouge);padding:0 .25rem;cursor:pointer" data-pdel="${p.id}">✕</span>
      </div>
    `).join('');
    list.querySelectorAll('[data-pdel]').forEach(b => b.addEventListener('click', e => {
      e.stopPropagation();
      state.plays = state.plays.filter(p => p.id !== b.dataset.pdel);
      saveState(); renderSavedPlays();
    }));
    list.querySelectorAll('[data-load]').forEach(el => el.addEventListener('click', () => {
      const p = state.plays.find(x => x.id === el.dataset.load);
      if(p){
        pushUndo();
        editor.phases = JSON.parse(JSON.stringify(p.phases));
        editor.currentPhase = 0;
        editor.courtType = p.courtType || 'half';
        // Ajuster le canvas selon le mode
        const dims = COURT_DIMS[editor.courtType];
        canvas.width = dims.w; canvas.height = dims.h; canvas.setAttribute('data-court', editor.courtType);
        const btn = document.getElementById('courtToggleBtn');
        if(btn) btn.textContent = editor.courtType === 'half' ? '🏟 Terrain : Demi' : '🏟 Terrain : Complet';
        document.getElementById('edTitle').value = p.title;
        render(); renderPhasesList();
        toast('Play chargé ✓');
      }
    }));
  }
  function download(url, filename){
    const a = document.createElement('a');
    a.href = url; a.download = filename;
    document.body.appendChild(a); a.click();
    setTimeout(() => { a.remove(); if(url.startsWith('blob:')) URL.revokeObjectURL(url); }, 100);
  }

  // Dimensions du canvas selon le mode terrain
  // Demi : 900×704 (ratio image demi 746/584 ≈ 1.277, ratio canvas 900/704 ≈ 1.278)
  // Entier : 440×688 (vertical, ratio image entier inversé 658/1024 ≈ 0.642, ratio canvas 440/688 ≈ 0.640)
  // Convention : le demi-terrain représente la MOITIÉ HAUTE du terrain entier (zone d'attaque côté panier).
  // Donc dans le terrain entier, la zone d'attaque haute correspond à y ∈ [0, H_full/2].
  const COURT_DIMS = {
    half: { w: 900, h: 704 },
    full: { w: 1100, h: 704 }   // V7.53 : canvas paysage assez large pour l'image 1024x658 + marges transparentes
  };

  // Conversion : transforme les coordonnées (x,y) d'un mode à l'autre
  // - half → full : la zone demi-terrain devient la moitié haute du terrain entier
  // - full → half : seuls les éléments dans la moitié haute du terrain entier sont conservés
  //                 (les éléments en moitié basse sont conservés mais ramenés dans le visible avec un facteur)
  function convertCoord(x, y, fromMode, toMode){
    if(fromMode === toMode) return { x, y };
    const from = COURT_DIMS[fromMode];
    const to = COURT_DIMS[toMode];
    if(fromMode === 'half' && toMode === 'full'){
      // V7.53 : demi → full : centrer le demi dans la moitié gauche du full (zone d'attaque)
      // Le canvas full est plus large que demi, on garde la position relative
      return { x: x * to.w / from.w, y: y * to.h / from.h };
    } else {
      // full → demi : étire le full pour remplir le demi
      return { x: x * to.w / from.w, y: y * to.h / from.h };
    }
  }

  // Convertit toutes les coordonnées d'une phase (joueurs, objets, actions, drawings, ballons)
  function convertPhaseCoords(phase, fromMode, toMode){
    if(fromMode === toMode) return;
    const conv = (pt) => convertCoord(pt.x, pt.y, fromMode, toMode);
    // Joueurs
    phase.players.forEach(p => {
      const c = conv(p);
      p.x = c.x; p.y = c.y;
    });
    // Ballons (s'ils ont position au sol)
    (phase.balls || []).forEach(b => {
      if(b.x !== undefined && b.y !== undefined){
        const c = conv(b);
        b.x = c.x; b.y = c.y;
      }
    });
    // Objets
    (phase.objects || []).forEach(o => {
      const c = conv(o);
      o.x = c.x; o.y = c.y;
    });
    // Actions : startPos, endPos, ctrlPoints
    (phase.actions || []).forEach(a => {
      if(a.startPos){ const c = conv(a.startPos); a.startPos.x = c.x; a.startPos.y = c.y; }
      if(a.endPos){ const c = conv(a.endPos); a.endPos.x = c.x; a.endPos.y = c.y; }
      if(a.ctrlPoints){
        a.ctrlPoints.forEach(cp => {
          const c = conv(cp);
          cp.x = c.x; cp.y = c.y;
        });
      }
    });
    // Drawings (chemins libres)
    (phase.drawings || []).forEach(d => {
      if(d.points){
        d.points.forEach(pt => {
          const c = conv(pt);
          pt.x = c.x; pt.y = c.y;
        });
      }
    });
  }

  function toggleCourtType(){
    const oldMode = editor.courtType;
    const newMode = oldMode === 'half' ? 'full' : 'half';
    // Convertir toutes les coordonnées dans toutes les phases
    editor.phases.forEach(phase => convertPhaseCoords(phase, oldMode, newMode));
    editor.courtType = newMode;
    // Redimensionner le canvas
    const dims = COURT_DIMS[newMode];
    canvas.width = dims.w;
    canvas.height = dims.h;
    canvas.setAttribute('data-court', newMode);  // V7.53 : aspect-ratio CSS dynamique
    // Mettre à jour le style CSS du canvas pour le redimensionnement responsive
    canvas.style.maxWidth = '100%';
    canvas.style.height = 'auto';
    const label = newMode === 'half' ? '🏟 Terrain : Demi' : '🏟 Terrain : Complet';
    const btn = document.getElementById('courtToggleBtn');
    if(btn) btn.textContent = label;
    toast(newMode === 'half' ? 'Demi-terrain' : 'Terrain complet');
    render(); renderPhasesList();
  }
  function clearAll(){
    if(!confirm('Tout effacer ?')) return;
    pushUndo();
    editor.phases = [emptyPhase()];
    editor.currentPhase = 0;
    editor.selectedId = null;
    render(); renderPhasesList();
  }
  function editNote(){
    const phase = editor.phases[editor.currentPhase];
    const n = prompt('Note pour cette phase :', phase.note || '');
    if(n !== null){ phase.note = n; toast('Note enregistrée'); }
  }
  function toExo(){
    const title = document.getElementById('edTitle').value;
    render();
    const png = canvas.toDataURL('image/png');
    navigate('exo-creer');
    setTimeout(() => {
      const f = document.getElementById('exoCreerForm');
      if(f){
        f.title.value = title;
        f.org.value = 'Système créé via l\'outil tactique MyBasket';
        f.deroul.value = editor.phases.map((p, i) => `Phase ${i+1} : ${p.players.length} joueur(s)${p.note?' — '+p.note:''}`).join('\n');
        document.getElementById('exoDiagramPng').value = png;
        document.getElementById('exoDiagramPreview').src = png;
        document.getElementById('exoEditorBlock').classList.add('has-diagram');
      }
      toast('Formulaire pré-rempli ✓');
    }, 150);
  }

  // Contexte de retour après l'ouverture de l'éditeur dans un popup.
  // 'exo' = retour vers le formulaire exo-creer (comportement historique)
  // 'sys' = retour vers le formulaire sys-editor (ajoute un fichier dessiné dans _sysEditorState.files)
  let _editorReturnTo = 'exo';

  function setupPopupBindings(){
    const block = document.getElementById('exoEditorBlock');
    if(block) block.addEventListener('click', () => openPopup('exo'));
    const epClose = document.getElementById('epClose');
    if(epClose) epClose.addEventListener('click', () => closePopup(false));
    const epSave = document.getElementById('epSaveBack');
    if(epSave) epSave.addEventListener('click', () => closePopup(true));
  }
  function openPopup(returnTo){
    _editorReturnTo = returnTo || 'exo';
    ensureInit();
    setTimeout(() => {
      const editorSection = document.querySelector('[data-page="plaquette"]');
      const popup = document.getElementById('editorPopup');
      const body = document.getElementById('epBody');
      body.appendChild(editorSection);
      editorSection.classList.add('active');
      editorSection.style.display = 'block';
      popup.classList.add('open');
      popup.style.display = '';  // V7.59 : retirer le inline display:none posé par navigate()
      document.body.style.overflow = 'hidden';
      // Adapter le texte du bouton selon le contexte de retour
      const epSave = document.getElementById('epSaveBack');
      if(epSave){
        epSave.innerHTML = _editorReturnTo === 'sys'
          ? '💾 Sauvegarder & insérer dans le système'
          : _editorReturnTo === 'playbook'
            ? '💾 Sauvegarder & ajouter au playbook'
            : '💾 Sauvegarder & insérer dans l\'exo';
      }
      render();
    }, 50);
  }
  function closePopup(saveDrawing){
    const editorSection = document.querySelector('[data-page="plaquette"]');
    const popup = document.getElementById('editorPopup');
    const main = document.querySelector('main');
    if(saveDrawing){
      // Générer un PNG pour chaque phase
      const savedPhaseIdx = editor.currentPhase;
      const phaseThumbs = [];
      for(let i = 0; i < editor.phases.length; i++){
        editor.currentPhase = i;
        render();
        phaseThumbs.push(canvas.toDataURL('image/png'));
      }
      // Revenir à la phase active et générer le PNG principal (la 1ère par défaut)
      editor.currentPhase = savedPhaseIdx;
      render();
      const png = phaseThumbs[0] || canvas.toDataURL('image/png');
      const title = document.getElementById('edTitle') ? document.getElementById('edTitle').value : '';

      if(_editorReturnTo === 'sys'){
        // === Retour vers le formulaire système (V7.55 : structure identique à exo) ===
        const exportData = {
          phases: editor.phases,
          courtType: editor.courtType || 'half',
          phaseThumbs,
          title
        };
        const json = JSON.stringify(exportData);
        const pngInput = document.getElementById('sysDiagramPng');
        const jsonInput = document.getElementById('sysDiagramJson');
        const preview = document.getElementById('sysDiagramPreview');
        const block = document.getElementById('sysEditorBlock');
        if(pngInput) pngInput.value = png;
        if(jsonInput) jsonInput.value = json;
        if(preview) preview.src = png;
        if(block) block.classList.add('has-diagram');
        // Stocker aussi dans _sysEditorState pour la persistance lors du publish
        if(window._sysEditorState){
          window._sysEditorState.diagramPng = png;
          window._sysEditorState.diagramJson = json;
          window._sysEditorState.phaseImages = phaseThumbs;
        }
        toast(`Dessin inséré ✓ (${phaseThumbs.length} phase${phaseThumbs.length>1?'s':''})`);
      } else if(_editorReturnTo === 'playbook'){
        // === V7.56 : Retour vers la page playbook ===
        const pbId = window._currentPlaybookId;
        if(window.appState && pbId){
          const pb = (window.appState.playbooks || []).find(p => p.id === pbId);
          if(pb){
            if(!pb.plays) pb.plays = [];
            pb.plays.push({
              title: title || `Play ${pb.plays.length + 1}`,
              diagramPng: png,
              diagramJson: JSON.stringify({
                phases: editor.phases,
                courtType: editor.courtType || 'half',
                phaseThumbs,
                title
              }),
              phaseImages: phaseThumbs,
              createdAt: new Date().toISOString()
            });
            if(typeof saveState === 'function') saveState();
            toast(`Play ajouté au playbook ✓ (${phaseThumbs.length} phase${phaseThumbs.length>1?'s':''})`);
          }
        }
      } else {
        // === Retour vers le formulaire exo (comportement historique) ===
        const exportData = {
          phases: editor.phases,
          courtType: editor.courtType || 'half',
          phaseThumbs: phaseThumbs,
          title
        };
        const json = JSON.stringify(exportData);
        document.getElementById('exoDiagramPng').value = png;
        document.getElementById('exoDiagramJson').value = json;
        document.getElementById('exoDiagramPreview').src = png;
        document.getElementById('exoEditorBlock').classList.add('has-diagram');
        toast(`Dessin inséré ✓ (${phaseThumbs.length} phase${phaseThumbs.length>1?'s':''})`);
      }
    }
    main.appendChild(editorSection);
    editorSection.classList.remove('active');
    editorSection.style.display = '';  // V7.55c : retirer le inline display='block' posé par openPopup
    popup.classList.remove('open');
    document.body.style.overflow = '';
    // Naviguer vers la bonne page selon le contexte
    if(_editorReturnTo === 'sys'){
      navigate('sys-editor');
    } else if(_editorReturnTo === 'playbook'){
      const pbId = window._currentPlaybookId;
      if(pbId) navigate('playbook-detail', pbId);
      else navigate('profil');
    } else {
      navigate('exo-creer');
    }
  }
  document.addEventListener('DOMContentLoaded', setupPopupBindings);
  if(document.readyState !== 'loading') setupPopupBindings();

  return { ensureInit, render, editor, openPopup };
})();
</script>
</body>
</html>