/**
 * AURA FARMING — Etapa 22: câmera segura com moderação ao vivo.
 * Visão computacional + combos + proteção de ambiente para menores.
 * O vídeo nunca é enviado; apenas métricas resumidas seguem para o servidor.
 */
(() => {
  'use strict';

  const root = document.querySelector('#farm-game');
  if (!root) return;

  const baseUrl = document.querySelector('meta[name="app-base-url"]')?.content || '';
  const csrf = document.querySelector('meta[name="csrf-token"]')?.content || '';
  const sessionId = root.dataset.sessionId;
  const matchId = Number(root.dataset.matchId || 0);
  const initialStatus = root.dataset.status || 'aguardando';
  const battleId = Number(root.dataset.battleId || 0);
  const competitionId = Number(root.dataset.competitionId || 0);
  const matchMode = root.dataset.mode || 'individual';
  const modelPoseUrl = root.dataset.poseModel;
  const modelHandUrl = root.dataset.handModel;
  const equippedAuraColor = root.dataset.auraColor || '';
  const equippedAuraEffect = root.dataset.auraEffect || '';
  const wasmUrl = `${baseUrl}/assets/vendor/mediapipe/wasm`;
  const safeMinor = root.dataset.safeMinor === '1';
  const safeToken = root.dataset.safeToken || '';
  const safeBlurEnabled = safeMinor && root.dataset.safeBlur !== '0';
  const safePersonLimit = Math.max(1, Number(root.dataset.safePersonLimit || 1));
  const safeNoPersonGrace = Math.max(0, Number(root.dataset.safeNoPersonGrace || 1800));
  const safeMultipleGrace = Math.max(0, Number(root.dataset.safeMultipleGrace || 700));
  const safeStableReady = Math.max(500, Number(root.dataset.safeStableReady || 1200));
  const safeLightingEnabled = safeMinor && root.dataset.safeLighting !== '0';
  const safeLightingMin = Math.max(10, Number(root.dataset.safeLightingMin || 28));
  const safeLightingMax = Math.min(100, Number(root.dataset.safeLightingMax || 96));
  const safeAdditionalFaceBlur = safeMinor && root.dataset.safeAdditionalFaceBlur !== '0';

  function hueFromHex(hex) {
    const match = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex || '');
    if (!match) return null;
    const r=parseInt(match[1],16)/255,g=parseInt(match[2],16)/255,b=parseInt(match[3],16)/255;
    const max=Math.max(r,g,b),min=Math.min(r,g,b),d=max-min;
    if (!d) return 0;
    let h=max===r?((g-b)/d)%6:max===g?(b-r)/d+2:(r-g)/d+4;
    return Math.round((h*60+360)%360);
  }
  const equippedAuraHue = hueFromHex(equippedAuraColor);
  const effectParticleMultiplier = equippedAuraEffect === 'celestial' ? 1.75 : equippedAuraEffect === 'storm' ? 1.45 : equippedAuraEffect === 'sparks' ? 1.25 : 1;

  let sequence = Number(root.dataset.sequence || 0);
  let aura = Number(root.dataset.initialAura || 0);
  let remaining = Number(root.dataset.timeRemaining || 180);
  let elapsed = Math.max(0, 180 - remaining);
  let running = false;
  let finishing = false;
  let stream = null;
  let heartbeatLoop = null;
  let timerLoop = null;
  let fallbackLoop = null;
  let visionAnimation = null;
  let auraAnimation = null;
  let wakeLock = null;
  let lastTimerTick = performance.now();
  let previousFrame = null;
  let fallbackReadings = [];
  let latestMotion = 0;
  let moderationTimer = null;

  let poseLandmarker = null;
  let handLandmarker = null;
  let visionReady = false;
  let visionMode = 'movimento';
  let lastPoseAt = 0;
  let lastHandAt = 0;
  let lastVideoTime = -1;
  let previousPose = null;
  let previousVelocity = 0;
  let holdSignature = '';
  let holdStartedAt = 0;
  let lastRecognizedMove = '';
  let lastMoveAt = 0;
  let signatureHistory = [];
  let currentHandCount = 0;
  let latestPose = null;
  let latestAllPoses = [];
  let latestHands = [];
  let safePersonCount = 0;
  let safeDetectorReady = false;
  let safePrivacyRendering = !safeMinor;
  let safeCameraActive = false;
  let safeLightingScore = safeMinor ? 0 : 100;
  let safeLightingOk = !safeMinor;
  let safeAdditionalFacesBlurred = 0;
  let safeScoringAllowed = !safeMinor;
  let safeState = safeMinor ? 'preflight' : 'active';
  let safeReason = safeMinor ? 'preflight' : 'none';
  let safeCandidateReason = safeReason;
  let safeCandidateSince = performance.now();
  let safeLastReportKey = '';
  let safeLastReportAt = 0;
  let safeReportPending = false;
  let safeRenderAnimation = null;
  let safeLastVideoTime = -1;
  let auraParticles = [];
  let auraWaves = [];
  let auraTrails = [];
  let currentAuraLevel = -1;
  let lastAuraFrame = performance.now();
  let lastParticleSpawn = 0;
  let auraBurstPower = 0;
  let auraBurstHue = 190;
  const reducedMotion = window.matchMedia?.('(prefers-reduced-motion: reduce)').matches || false;
  const visualQuality = reducedMotion ? 0.42 : ((navigator.deviceMemory || 4) <= 2 || (navigator.hardwareConcurrency || 4) <= 4 ? 0.65 : 1);

  const sample = {
    confidence: [], coverage: [], precision: [], fluidity: [], originality: [], difficulty: [],
    poseFrames: 0, handFrames: 0, move: '', poseDetected: false, hands: 0,
  };

  const video = document.querySelector('#farm-camera');
  const safeCanvas = document.querySelector('#safe-camera-canvas');
  const safeContext = safeCanvas?.getContext('2d');
  const safeMaskCanvas = document.createElement('canvas');
  const safeMaskContext = safeMaskCanvas.getContext('2d');
  const safeSubjectCanvas = document.createElement('canvas');
  const safeSubjectContext = safeSubjectCanvas.getContext('2d');
  const safeLightingCanvas = document.createElement('canvas');
  safeLightingCanvas.width = 32; safeLightingCanvas.height = 24;
  const safeLightingContext = safeLightingCanvas.getContext('2d', { willReadFrequently: true });
  const motionCanvas = document.querySelector('#motion-canvas');
  const motionContext = motionCanvas.getContext('2d', { willReadFrequently: true });
  const auraCanvas = document.querySelector('#aura-canvas');
  const auraContext = auraCanvas.getContext('2d');
  const overlay = document.querySelector('#vision-overlay');
  const overlayContext = overlay.getContext('2d');
  const timer = document.querySelector('#game-timer');
  const score = document.querySelector('#aura-score');
  const progress = document.querySelector('#aura-progress-fill');
  const motionFill = document.querySelector('#motion-fill');
  const motionLabel = document.querySelector('#motion-label');
  const syncLabel = document.querySelector('#score-sync');
  const message = document.querySelector('#game-message');
  const countdown = document.querySelector('#countdown-overlay');
  const countdownNumber = document.querySelector('#countdown-number');
  const cameraError = document.querySelector('#camera-error-panel');
  const auraField = document.querySelector('#aura-field');
  const auraAtmosphere = document.querySelector('#aura-atmosphere');
  const auraLevelCard = document.querySelector('#aura-level-card');
  const auraLevelName = document.querySelector('#aura-level-name');
  const auraLevelRange = document.querySelector('#aura-level-range');
  const evolutionOverlay = document.querySelector('#aura-evolution-overlay');
  const evolutionKicker = document.querySelector('#aura-evolution-kicker');
  const evolutionTitle = document.querySelector('#aura-evolution-title');
  const evolutionSubtitle = document.querySelector('#aura-evolution-subtitle');
  const screenFlash = document.querySelector('#aura-screen-flash');
  const aiStatus = document.querySelector('#ai-status');
  const aiStatusText = document.querySelector('#ai-status-text');
  const poseState = document.querySelector('#pose-state');
  const safeGuard = document.querySelector('#safe-camera-guard');
  const safeGuardIcon = document.querySelector('#safe-camera-guard-icon');
  const safeGuardTitle = document.querySelector('#safe-camera-guard-title');
  const safeGuardMessage = document.querySelector('#safe-camera-guard-message');
  const safeBadge = document.querySelector('#safe-camera-badge');
  const comboHud = document.querySelector('#advanced-combo-hud');
  const comboBadge = document.querySelector('#combo-badge');
  const comboValue = document.querySelector('#combo-value');
  const comboHits = document.querySelector('#combo-hits');
  const comboGrade = document.querySelector('#combo-grade');
  const comboEnergyFill = document.querySelector('#combo-energy-fill');
  const comboRhythm = document.querySelector('#combo-rhythm');
  const moveToast = document.querySelector('#move-toast');
  const sequenceToast = document.querySelector('#sequence-toast');
  const sequenceName = document.querySelector('#sequence-name');
  const sequenceBonus = document.querySelector('#sequence-bonus');
  let lastComboHits = 0;
  let lastComboSequences = 0;
  let lastSequenceBonus = 0;
  const metricPrecision = document.querySelector('#metric-precision');
  const metricFluidity = document.querySelector('#metric-fluidity');
  const metricOriginality = document.querySelector('#metric-originality');
  const metricDifficulty = document.querySelector('#metric-difficulty');
  const playerViewers = document.querySelector('#player-viewers');
  const playerLiveViewers = document.querySelector('#player-live-viewers');
  const playerLikes = document.querySelector('#player-likes');
  const playerSuperlikes = document.querySelector('#player-superlikes');
  const playerTotalScore = document.querySelector('#player-total-score');
  const socialToast = document.querySelector('#social-received-toast');
  const moderationButton = document.querySelector('#btn-live-moderation');
  const moderationPanel = document.querySelector('#live-moderation-panel');
  const moderationClose = document.querySelector('#close-live-moderation');
  const moderationViewers = document.querySelector('#live-moderation-viewers');
  let lastLikes = Number(playerLikes?.textContent || 0);
  let lastSuperlikes = Number(playerSuperlikes?.textContent || 0);

  const POSE_CONNECTIONS = [
    [11,12],[11,13],[13,15],[12,14],[14,16],[15,17],[15,19],[15,21],[16,18],[16,20],[16,22],
    [11,23],[12,24],[23,24],[23,25],[25,27],[27,29],[29,31],[24,26],[26,28],[28,30],[30,32],
    [0,1],[1,2],[2,3],[3,7],[0,4],[4,5],[5,6],[6,8],[9,10]
  ];
  const HAND_CONNECTIONS = [
    [0,1],[1,2],[2,3],[3,4],[0,5],[5,6],[6,7],[7,8],[5,9],[9,10],[10,11],[11,12],
    [9,13],[13,14],[14,15],[15,16],[13,17],[17,18],[18,19],[19,20],[0,17]
  ];
  const IMPORTANT = [0,11,12,13,14,15,16,23,24,25,26,27,28];
  const AURA_LEVELS = [
    { min:0, max:0, name:'SEM AURA', range:'0', title:'ENERGIA EM REPOUSO', subtitle:'Comece a se movimentar', hue:190, hue2:220 },
    { min:1, max:100, name:'AURA LATENTE', range:'1–100', title:'AURA LATENTE', subtitle:'A energia começou a surgir', hue:190, hue2:205 },
    { min:101, max:300, name:'AURA DESPERTA', range:'101–300', title:'AURA DESPERTA!', subtitle:'Sua presença já pode ser sentida', hue:184, hue2:255 },
    { min:301, max:600, name:'AURA CARREGADA', range:'301–600', title:'AURA CARREGADA!', subtitle:'Partículas de energia foram ativadas', hue:242, hue2:285 },
    { min:601, max:850, name:'AURA PODEROSA', range:'601–850', title:'AURA PODEROSA!', subtitle:'A energia está se expandindo', hue:282, hue2:326 },
    { min:851, max:999, name:'AURA EXTREMA', range:'851–999', title:'AURA EXTREMA!', subtitle:'O ambiente está reagindo à sua força', hue:326, hue2:28 },
    { min:1000, max:1000, name:'AURA MÁXIMA', range:'1.000', title:'AURA MÁXIMA ATINGIDA!', subtitle:'Estado máximo de energia ativado', hue:42, hue2:188 },
  ];

  const clamp = (value, min = 0, max = 100) => Math.max(min, Math.min(max, Number(value) || 0));
  const average = values => values.length ? values.reduce((sum, n) => sum + n, 0) / values.length : 0;
  const distance = (a, b) => Math.hypot((a?.x || 0) - (b?.x || 0), (a?.y || 0) - (b?.y || 0));
  const midpoint = (a, b) => ({ x: ((a?.x || 0) + (b?.x || 0)) / 2, y: ((a?.y || 0) + (b?.y || 0)) / 2 });
  const visible = point => (point?.visibility ?? point?.presence ?? 1) >= .45;

  function angle(a, b, c) {
    if (!a || !b || !c) return 180;
    const ab = { x: a.x - b.x, y: a.y - b.y };
    const cb = { x: c.x - b.x, y: c.y - b.y };
    const dot = ab.x * cb.x + ab.y * cb.y;
    const mag = Math.hypot(ab.x, ab.y) * Math.hypot(cb.x, cb.y);
    if (!mag) return 180;
    return Math.acos(clamp(dot / mag, -1, 1)) * 180 / Math.PI;
  }

  function pushMetric(list, value, limit = 30) {
    list.push(clamp(value));
    if (list.length > limit) list.shift();
  }

  function formatTime(seconds) {
    const safe = Math.max(0, Math.ceil(seconds));
    return `${Math.floor(safe / 60).toString().padStart(2, '0')}:${(safe % 60).toString().padStart(2, '0')}`;
  }

  function updateTimer() {
    timer.textContent = formatTime(remaining);
    timer.classList.toggle('game-timer--danger', remaining <= 10);
    root.classList.toggle('farm-game--final-seconds', remaining <= 10);
    if (remaining <= 10 && remaining > 0) {
      message.innerHTML = `<strong>ÚLTIMOS ${Math.ceil(remaining)} SEGUNDOS!</strong><span>Farme o máximo que conseguir!</span>`;
      message.classList.add('game-center-message--urgent');
    }
  }

  function auraLevelFor(value) {
    const safe = clamp(value, 0, 1000);
    if (safe >= 1000) return 6;
    if (safe >= 851) return 5;
    if (safe >= 601) return 4;
    if (safe >= 301) return 3;
    if (safe >= 101) return 2;
    if (safe > 0) return 1;
    return 0;
  }

  function showAuraEvolution(level) {
    const data = AURA_LEVELS[level];
    if (!data || !evolutionOverlay) return;
    evolutionKicker.textContent = level >= 6 ? 'ESTADO MÁXIMO' : 'EVOLUÇÃO DE AURA';
    evolutionTitle.textContent = data.title;
    evolutionSubtitle.textContent = data.subtitle;
    evolutionOverlay.hidden = false;
    evolutionOverlay.dataset.level = String(level);
    evolutionOverlay.classList.remove('aura-evolution-overlay--show');
    void evolutionOverlay.offsetWidth;
    evolutionOverlay.classList.add('aura-evolution-overlay--show');
    screenFlash?.classList.remove('aura-screen-flash--active');
    void screenFlash?.offsetWidth;
    screenFlash?.classList.add('aura-screen-flash--active');
    auraBurstPower = level >= 6 ? 1.8 : 0.72 + level * .14;
    auraBurstHue = data.hue;
    auraWaves.push({ life:1, radius:30, width:level >= 6 ? 18 : 9, hue:data.hue });
    if (navigator.vibrate && !reducedMotion) navigator.vibrate(level >= 6 ? [90,45,140,45,220] : [55,35,85]);
    window.setTimeout(() => { evolutionOverlay.hidden = true; }, level >= 6 ? 2200 : 1700);
  }

  function applyAuraLevel(level, animate = true) {
    const data = AURA_LEVELS[level] || AURA_LEVELS[0];
    const previous = currentAuraLevel;
    currentAuraLevel = level;
    root.dataset.auraLevel = String(level);
    auraLevelCard.dataset.level = String(level);
    auraLevelName.textContent = data.name;
    auraLevelRange.textContent = data.range;
    root.style.setProperty('--aura-hue', String(data.hue));
    root.style.setProperty('--aura-hue-2', String(data.hue2));
    if (animate && previous >= 0 && level >= 2 && level > previous) showAuraEvolution(level);
  }

  function updateAura(value, animate = true) {
    aura = clamp(value, 0, 1000);
    score.textContent = Math.round(aura).toLocaleString('pt-BR');
    progress.style.width = `${Math.min(100, aura / 10)}%`;
    const power = Math.max(.10, Math.min(1, aura / 1000));
    auraField.style.opacity = String(.10 + power * .90);
    auraField.style.setProperty('--aura-power', String(power));
    auraAtmosphere?.style.setProperty('--aura-power', String(power));
    const level = auraLevelFor(aura);
    if (level !== currentAuraLevel) applyAuraLevel(level, animate);
    if (aura >= 1000) {
      message.innerHTML = '<strong>AURA MÁXIMA!</strong><span>Você atingiu 1.000 de aura!</span>';
      message.classList.add('game-center-message--maximum');
    }
  }

  function updateMotion(value) {
    latestMotion = clamp(value);
    motionFill.style.width = `${latestMotion}%`;
    let label = 'BAIXO';
    if (latestMotion >= 75) label = 'INSANO';
    else if (latestMotion >= 50) label = 'FORTE';
    else if (latestMotion >= 25) label = 'ATIVO';
    else if (latestMotion >= 8) label = 'LEVE';
    motionLabel.textContent = label;
    motionLabel.dataset.level = label.toLowerCase();
  }

  function setAiStatus(state, text) {
    aiStatus.dataset.state = state;
    aiStatusText.textContent = text;
  }

  function updateMetrics(metrics) {
    metricPrecision.textContent = `${Math.round(metrics.precision)}%`;
    metricFluidity.textContent = `${Math.round(metrics.fluidity)}%`;
    metricOriginality.textContent = `${Math.round(metrics.originality)}%`;
    metricDifficulty.textContent = `${Math.round(metrics.difficulty)}%`;
  }

  function updateSocial(partida, animate = true) {
    if (!partida) return;
    const likes = Number(partida.likes || 0);
    const superlikes = Number(partida.superlikes || 0);
    const viewers = Number(partida.espectadores || 0);
    if (playerViewers) playerViewers.textContent = String(viewers);
    if (playerLiveViewers) playerLiveViewers.textContent = String(viewers);
    if (playerLikes) playerLikes.textContent = String(likes);
    if (playerSuperlikes) playerSuperlikes.textContent = String(superlikes);
    if (playerTotalScore) playerTotalScore.textContent = Number(partida.pontuacao_total || partida.aura || 0).toLocaleString('pt-BR', { maximumFractionDigits: 2 });
    if (animate && socialToast) {
      let text = '';
      if (superlikes > lastSuperlikes) text = '⚡ SUPER LIKE RECEBIDO! +1 AURA SOCIAL';
      else if (likes > lastLikes) text = '❤️ LIKE RECEBIDO! +0,25';
      if (text) {
        socialToast.textContent = text;
        socialToast.classList.remove('social-received-toast--show');
        void socialToast.offsetWidth;
        socialToast.classList.add('social-received-toast--show');
        auraBurstPower = superlikes > lastSuperlikes ? 1.2 : .55;
        if (navigator.vibrate && superlikes > lastSuperlikes) navigator.vibrate([70,35,100]);
      }
    }
    lastLikes = likes;
    lastSuperlikes = superlikes;
  }

  function showMove(code, label, perfect = false) {
    if (!code || code === lastRecognizedMove && performance.now() - lastMoveAt < 1300) return;
    lastRecognizedMove = code;
    lastMoveAt = performance.now();
    sample.move = code;
    moveToast.innerHTML = `${perfect ? '<b>PERFECT MOVE</b>' : '<b>MOVIMENTO RECONHECIDO</b>'}<span>${label}</span>`;
    moveToast.classList.remove('move-toast--show');
    void moveToast.offsetWidth;
    moveToast.classList.add('move-toast--show');
  }


  function comboCallout(multiplier, hits, perfectChain = 0) {
    if (!moveToast) return;
    let title = 'AURA COMBO';
    if (multiplier >= 5) title = 'MAXIMUM AURA';
    else if (multiplier >= 3) title = 'INSANE AURA';
    else if (perfectChain >= 2) title = 'PERFECT CHAIN';
    moveToast.innerHTML = `<b>${title}</b><span>${hits} MOVIMENTOS • x${multiplier}</span>`;
    moveToast.classList.remove('move-toast--show'); void moveToast.offsetWidth; moveToast.classList.add('move-toast--show');
    auraBurstPower = multiplier >= 5 ? 1.65 : multiplier >= 3 ? 1.15 : .72;
    if (navigator.vibrate && !reducedMotion) navigator.vibrate(multiplier >= 5 ? [80,35,120] : [45,25,65]);
  }

  function showSequence(name, bonus) {
    if (!sequenceToast) return;
    sequenceName.textContent = name || 'SEQUÊNCIA ESPECIAL';
    sequenceBonus.textContent = `+${Number(bonus || 0).toLocaleString('pt-BR',{maximumFractionDigits:2})} AURA`;
    sequenceToast.classList.remove('sequence-toast--show'); void sequenceToast.offsetWidth; sequenceToast.classList.add('sequence-toast--show');
    auraBurstPower = 1.45;
    auraWaves.push({ life:1, radius:25, width:14, hue:auraPalette().hue2 });
  }

  function updateAdvancedCombo(partida) {
    const multiplier = Number(partida.combo_atual || 1);
    const hits = Number(partida.combo_hits_atual || 0);
    const grade = String(partida.combo_nota || 'D');
    const rhythm = Number(partida.combo_ritmo_medio || 0);
    const perfectChain = Number(partida.combo_perfect_chain_atual || 0);
    const sequences = Number(partida.combo_sequencias_concluidas || 0);
    comboValue.textContent = `x${multiplier}`;
    comboHits.textContent = `${hits} ${hits === 1 ? 'MOVE' : 'MOVES'}`;
    comboGrade.textContent = grade;
    comboRhythm.textContent = `${Math.round(rhythm)}%`;
    comboEnergyFill.style.width = `${Math.min(100,(hits/8)*100)}%`;
    comboBadge.classList.toggle('combo-badge--active', multiplier >= 2);
    comboHud?.setAttribute('data-grade',grade);
    comboHud?.classList.toggle('advanced-combo-hud--active',hits > 0);
    if (hits > lastComboHits && [3,5,8].includes(hits)) comboCallout(multiplier,hits,perfectChain);
    if (sequences > lastComboSequences) {
      const labels = {ascensao_total:'ASCENSÃO TOTAL',quebra_dimensional:'QUEBRA DIMENSIONAL',renascimento_aura:'RENASCIMENTO DE AURA',fluxo_cosmico:'FLUXO CÓSMICO'};
      showSequence(labels[partida.ultima_sequencia_combo] || 'SEQUÊNCIA ESPECIAL',Math.max(0,Number(partida.bonus_sequencias || 0)-lastSequenceBonus));
    }
    if (hits === 0 && lastComboHits > 0) comboHud?.classList.add('advanced-combo-hud--broken');
    else comboHud?.classList.remove('advanced-combo-hud--broken');
    lastComboHits = hits;
    lastComboSequences = sequences;
    lastSequenceBonus = Number(partida.bonus_sequencias || 0);
  }

  async function api(path, body = {}) {
    const response = await fetch(`${baseUrl}/api/v1/matches/${path}.php`, {
      method: 'POST', credentials: 'same-origin',
      headers: { 'Content-Type': 'application/json', 'X-CSRF-Token': csrf },
      body: JSON.stringify(body),
    });
    const data = await response.json().catch(() => ({}));
    if (!response.ok || !data.ok) throw new Error(data.erro || 'Falha de comunicação com o servidor.');
    return data;
  }

  const wait = ms => new Promise(resolve => setTimeout(resolve, ms));

  function safeMessage(reason) {
    return {
      none: ['✓', 'CÂMERA SEGURA ATIVA', 'Continue sua apresentação livremente.'],
      preflight: ['🛡️', 'VERIFICANDO ENQUADRAMENTO', 'Permaneça sozinho e de frente para a câmera.'],
      no_person: ['👤', 'ENTRE NO ENQUADRAMENTO', 'A pontuação está pausada até uma pessoa ser detectada.'],
      multiple_people: ['👥', 'MAIS DE UMA PESSOA DETECTADA', 'A pontuação foi pausada. Permaneça sozinho no enquadramento.'],
      tab_hidden: ['📱', 'VOLTE PARA A PARTIDA', 'A pontuação está pausada enquanto a tela estiver em segundo plano.'],
      camera_lost: ['📷', 'CÂMERA INTERROMPIDA', 'Reative a câmera para continuar pontuando.'],
      detector_unavailable: ['⚠️', 'PROTEÇÃO INDISPONÍVEL', 'Este aparelho não conseguiu ativar a detecção segura de pessoas.'],
      privacy_unavailable: ['⚠️', 'PROTEÇÃO VISUAL INDISPONÍVEL', 'O desfoque de ambiente não pôde ser ativado.'],
      poor_lighting: ['💡', 'AJUSTE A ILUMINAÇÃO', 'Ilumine o rosto e o corpo sem apontar luz forte diretamente para a câmera.'],
    }[reason] || ['🛡️', 'CÂMERA SEGURA', 'Aguarde a verificação.'];
  }

  function renderSafetyUi() {
    if (!safeMinor || !safeGuard) return;
    const [icon, title, text] = safeMessage(safeReason);
    safeGuardIcon.textContent = icon;
    safeGuardTitle.textContent = title;
    safeGuardMessage.textContent = text;
    safeGuard.hidden = safeScoringAllowed;
    safeGuard.dataset.reason = safeReason;
    safeBadge?.classList.toggle('safe-camera-badge--paused', !safeScoringAllowed);
    root.classList.toggle('farm-game--safety-paused', !safeScoringAllowed);
    if (!safeScoringAllowed) {
      updateMotion(0);
      syncLabel.textContent = `🛡️ ${text}`;
      syncLabel.classList.add('score-sync--safe-pause');
    } else {
      syncLabel.classList.remove('score-sync--safe-pause');
    }
  }

  function safetyPayload() {
    return {
      sessao_id: sessionId,
      safe_token: safeToken,
      safety_state: safeState,
      person_count: safePersonCount,
      detector_ready: safeDetectorReady,
      privacy_rendering: safePrivacyRendering,
      tab_visible: document.visibilityState === 'visible',
      camera_active: safeCameraActive && Boolean(stream?.active),
      lighting_score: safeLightingScore,
      lighting_ok: safeLightingOk,
      additional_faces_blurred: safeAdditionalFacesBlurred,
      safety_frame_ts: Date.now(),
    };
  }

  function applyServerSafety(state) {
    if (!safeMinor || !state) return;
    safeScoringAllowed = Boolean(state.scoring_allowed);
    safeReason = state.reason || (safeScoringAllowed ? 'none' : 'preflight');
    safeState = safeScoringAllowed ? 'active' : 'paused';
    renderSafetyUi();
  }

  async function reportSafety(force = false) {
    if (!safeMinor || !safeToken) return;
    if (safeReportPending) {
      if (!force) return;
      const deadline = Date.now() + 5000;
      while (safeReportPending && Date.now() < deadline) await wait(60);
      if (safeReportPending) throw new Error('A câmera segura não conseguiu sincronizar com o servidor.');
    }
    const payload = safetyPayload();
    const key = [payload.safety_state,payload.person_count,payload.detector_ready?1:0,payload.privacy_rendering?1:0,payload.tab_visible?1:0,payload.camera_active?1:0,payload.lighting_score,payload.lighting_ok?1:0,payload.additional_faces_blurred].join('|');
    if (!force && key === safeLastReportKey && Date.now() - safeLastReportAt < 5000) return;
    safeReportPending = true;
    try {
      const data = await api('safety', payload);
      safeLastReportKey = key;
      safeLastReportAt = Date.now();
      applyServerSafety(data.safe_camera);
    } catch (error) {
      safeScoringAllowed = false;
      safeReason = 'detector_unavailable';
      safeState = 'paused';
      renderSafetyUi();
      if (force) throw error;
    } finally {
      safeReportPending = false;
    }
  }

  function evaluateSafetySignals(personCount, detectorReady = safeDetectorReady, force = false) {
    if (!safeMinor) return;
    let candidate = 'none';
    if (!safeCameraActive || !stream?.active) candidate = 'camera_lost';
    else if (document.visibilityState !== 'visible') candidate = 'tab_hidden';
    else if (!detectorReady) candidate = 'detector_unavailable';
    else if (safeBlurEnabled && !safePrivacyRendering) candidate = 'privacy_unavailable';
    else if (safeLightingEnabled && !safeLightingOk) candidate = 'poor_lighting';
    else if (personCount > safePersonLimit) candidate = 'multiple_people';
    else if (personCount < 1) candidate = 'no_person';

    const now = performance.now();
    if (candidate !== safeCandidateReason) {
      safeCandidateReason = candidate;
      safeCandidateSince = now;
    }
    const grace = candidate === 'no_person' ? safeNoPersonGrace : candidate === 'multiple_people' ? safeMultipleGrace : 0;
    if (!force && candidate !== 'none' && now - safeCandidateSince < grace) return;

    const nextAllowed = candidate === 'none';
    const nextReason = nextAllowed ? 'none' : candidate;
    const nextState = nextAllowed ? (running ? 'active' : 'ready') : (running ? 'paused' : 'preflight');
    const changed = nextAllowed !== safeScoringAllowed || nextReason !== safeReason || nextState !== safeState;
    safeScoringAllowed = nextAllowed;
    safeReason = nextReason;
    safeState = nextState;
    renderSafetyUi();
    if (changed) reportSafety(false);
  }

  function resizeSafeBuffers() {
    if (!safeMinor || !safeCanvas || !video.videoWidth || !video.videoHeight) return;
    const w = video.videoWidth, h = video.videoHeight;
    for (const canvas of [safeCanvas, safeMaskCanvas, safeSubjectCanvas]) {
      if (canvas.width !== w || canvas.height !== h) {
        canvas.width = w;
        canvas.height = h;
      }
    }
  }

  function drawSubjectMask(points) {
    if (!safeMaskContext || !points?.length) return;
    const ctx = safeMaskContext;
    const w = safeMaskCanvas.width, h = safeMaskCanvas.height;
    ctx.clearRect(0,0,w,h);
    ctx.save();
    ctx.fillStyle = '#fff';
    ctx.strokeStyle = '#fff';
    ctx.lineCap = 'round';
    ctx.lineJoin = 'round';
    const p = index => ({x:(points[index]?.x || 0)*w,y:(points[index]?.y || 0)*h});
    const ls=p(11),rs=p(12),lh=p(23),rh=p(24),nose=p(0);
    const shoulder = Math.max(40,Math.hypot(ls.x-rs.x,ls.y-rs.y));
    ctx.filter = 'blur(7px)';
    ctx.lineWidth = Math.max(35,shoulder*.55);
    [[11,13,15],[12,14,16],[23,25,27,29,31],[24,26,28,30,32]].forEach(chain=>{
      ctx.beginPath();
      chain.forEach((idx,i)=>{const q=p(idx);i?ctx.lineTo(q.x,q.y):ctx.moveTo(q.x,q.y);});
      ctx.stroke();
    });
    ctx.beginPath();
    ctx.moveTo(ls.x-shoulder*.22,ls.y-shoulder*.12);
    ctx.lineTo(rs.x+shoulder*.22,rs.y-shoulder*.12);
    ctx.lineTo(rh.x+shoulder*.28,rh.y+shoulder*.20);
    ctx.lineTo(lh.x-shoulder*.28,lh.y+shoulder*.20);
    ctx.closePath();ctx.fill();
    ctx.beginPath();ctx.ellipse(nose.x,nose.y-shoulder*.12,shoulder*.43,shoulder*.53,0,0,Math.PI*2);ctx.fill();
    ctx.restore();
  }

  function updateSafeLighting() {
    if (!safeMinor || !safeLightingEnabled || !safeLightingContext || video.readyState < 2) {
      safeLightingOk = !safeLightingEnabled;
      return safeLightingScore;
    }
    try {
      safeLightingContext.drawImage(video,0,0,safeLightingCanvas.width,safeLightingCanvas.height);
      const pixels=safeLightingContext.getImageData(0,0,safeLightingCanvas.width,safeLightingCanvas.height).data;
      let total=0;
      for(let i=0;i<pixels.length;i+=4) total += .2126*pixels[i] + .7152*pixels[i+1] + .0722*pixels[i+2];
      safeLightingScore=Math.round((total/(pixels.length/4))/255*100);
      safeLightingOk=safeLightingScore>=safeLightingMin && safeLightingScore<=safeLightingMax;
    } catch (_) {
      safeLightingScore=0; safeLightingOk=false;
    }
    return safeLightingScore;
  }

  function blurAdditionalFaces() {
    safeAdditionalFacesBlurred=0;
    if(!safeAdditionalFaceBlur || !safeContext || safePersonCount<=1 || !latestAllPoses?.length)return;
    const w=safeCanvas.width,h=safeCanvas.height;
    latestAllPoses.slice(1).forEach(points=>{
      const nose=points?.[0];const left=points?.[7];const right=points?.[8];
      if(!nose)return;
      const span=Math.max(42,Math.abs(((left?.x??nose.x)-(right?.x??nose.x))*w)*2.4);
      const x=nose.x*w,y=(nose.y-.04)*h;
      safeContext.save();safeContext.beginPath();safeContext.ellipse(x,y,span*.65,span*.82,0,0,Math.PI*2);safeContext.clip();
      safeContext.filter='blur(32px) brightness(.55)';safeContext.drawImage(video,-24,-24,w+48,h+48);safeContext.restore();
      safeAdditionalFacesBlurred++;
    });
  }

  function drawSafeCameraFrame() {
    if (!safeMinor || !safeContext || video.readyState < 2) return;
    resizeSafeBuffers();
    const w=safeCanvas.width,h=safeCanvas.height;
    if (!w || !h) return;
    try {
      safeContext.save();
      safeContext.clearRect(0,0,w,h);
      safeContext.filter = safeBlurEnabled ? 'blur(18px) saturate(.72) brightness(.72)' : 'brightness(.82)';
      safeContext.drawImage(video,-18,-18,w+36,h+36);
      safeContext.restore();

      if (safeBlurEnabled && safePersonCount === 1 && latestPose?.length) {
        safeSubjectContext.clearRect(0,0,w,h);
        safeSubjectContext.filter='none';
        safeSubjectContext.drawImage(video,0,0,w,h);
        drawSubjectMask(latestPose);
        safeSubjectContext.globalCompositeOperation='destination-in';
        safeSubjectContext.drawImage(safeMaskCanvas,0,0,w,h);
        safeSubjectContext.globalCompositeOperation='source-over';
        safeContext.drawImage(safeSubjectCanvas,0,0,w,h);
      }
      blurAdditionalFaces();
      updateSafeLighting();
      safePrivacyRendering = true;
    } catch (_) {
      safePrivacyRendering = false;
    }
  }

  function safeRenderFrame() {
    if (!safeMinor) return;
    drawSafeCameraFrame();
    safeRenderAnimation = requestAnimationFrame(safeRenderFrame);
  }

  async function runSafePreflight() {
    if (!safeMinor) return;
    if (!visionReady || !poseLandmarker) throw new Error('A detecção segura de pessoas não pôde ser iniciada neste aparelho.');
    const deadline = Date.now() + 25000;
    let stableSince = 0;
    safeState='preflight';safeReason='preflight';safeScoringAllowed=false;renderSafetyUi();

    while (Date.now() < deadline) {
      if (video.readyState >= 2 && video.currentTime !== safeLastVideoTime) {
        safeLastVideoTime = video.currentTime;
        const now = performance.now();
        const result = poseLandmarker.detectForVideo(video, Math.round(now));
        latestAllPoses = result.landmarks || [];
        safePersonCount = latestAllPoses.length;
        latestPose = safePersonCount === 1 ? latestAllPoses[0] : null;
        safeDetectorReady = true;
        drawSafeCameraFrame();
        updateSafeLighting();
        evaluateSafetySignals(safePersonCount,true,true);
        if (safePersonCount === 1 && safePrivacyRendering && safeLightingOk && document.visibilityState === 'visible') {
          if (!stableSince) stableSince = Date.now();
          if (Date.now() - stableSince >= safeStableReady) {
            safeScoringAllowed=true;safeReason='none';safeState='ready';renderSafetyUi();
            await reportSafety(true);
            await wait(1100);
            await reportSafety(true);
            return;
          }
        } else {
          stableSince = 0;
        }
      }
      await wait(120);
    }
    throw new Error('Não foi possível validar um enquadramento seguro. Permaneça sozinho, ajuste a iluminação e tente novamente.');
  }

  async function openCamera() {
    if (!navigator.mediaDevices?.getUserMedia) throw new Error('Navegador sem suporte à câmera.');
    stream = await navigator.mediaDevices.getUserMedia({
      video: { facingMode: 'user', width: { ideal: 720 }, height: { ideal: 1280 }, frameRate: { ideal: 30, max: 30 } },
      audio: false,
    });
    video.srcObject = stream;
    await video.play();
    safeCameraActive = true;
    stream.getVideoTracks().forEach(track => track.addEventListener('ended', () => {
      safeCameraActive = false;
      evaluateSafetySignals(safePersonCount, safeDetectorReady, true);
      reportSafety(false);
    }, { once:true }));
    resizeOverlay();
    if (safeMinor && !safeRenderAnimation) safeRenderAnimation = requestAnimationFrame(safeRenderFrame);
    if (!safeMinor) window.dispatchEvent(new CustomEvent('aura:camera-ready', { detail: { stream, token: root.dataset.publicToken || '' } }));
  }

  function resizeOverlay() {
    if (!video.videoWidth || !video.videoHeight) return;
    if (overlay.width !== video.videoWidth || overlay.height !== video.videoHeight) {
      overlay.width = video.videoWidth;
      overlay.height = video.videoHeight;
    }
    if (auraCanvas.width !== video.videoWidth || auraCanvas.height !== video.videoHeight) {
      auraCanvas.width = video.videoWidth;
      auraCanvas.height = video.videoHeight;
    }
    resizeSafeBuffers();
  }

  function stopCamera() {
    stream?.getTracks().forEach(track => track.stop());
    stream = null;
    safeCameraActive = false;
    if (safeRenderAnimation) cancelAnimationFrame(safeRenderAnimation);
    safeRenderAnimation = null;
  }

  async function loadVision() {
    setAiStatus('loading', 'CARREGANDO IA');
    try {
      const module = await import('../vendor/mediapipe/vision_bundle.mjs');
      const resolver = await module.FilesetResolver.forVisionTasks(wasmUrl);
      poseLandmarker = await module.PoseLandmarker.createFromOptions(resolver, {
        baseOptions: { modelAssetPath: modelPoseUrl, delegate: 'GPU' },
        runningMode: 'VIDEO', numPoses: safeMinor ? 2 : 1,
        minPoseDetectionConfidence: .45, minPosePresenceConfidence: .45, minTrackingConfidence: .45,
      }).catch(async () => module.PoseLandmarker.createFromOptions(resolver, {
        baseOptions: { modelAssetPath: modelPoseUrl }, runningMode: 'VIDEO', numPoses: safeMinor ? 2 : 1,
        minPoseDetectionConfidence: .45, minPosePresenceConfidence: .45, minTrackingConfidence: .45,
      }));

      try {
        handLandmarker = await module.HandLandmarker.createFromOptions(resolver, {
          baseOptions: { modelAssetPath: modelHandUrl, delegate: 'GPU' },
          runningMode: 'VIDEO', numHands: 2,
          minHandDetectionConfidence: .45, minHandPresenceConfidence: .45, minTrackingConfidence: .45,
        });
      } catch (_) {
        try {
          handLandmarker = await module.HandLandmarker.createFromOptions(resolver, {
            baseOptions: { modelAssetPath: modelHandUrl }, runningMode: 'VIDEO', numHands: 2,
            minHandDetectionConfidence: .45, minHandPresenceConfidence: .45, minTrackingConfidence: .45,
          });
        } catch (_) {
          handLandmarker = null;
        }
      }

      visionReady = true;
      safeDetectorReady = true;
      visionMode = 'visao';
      setAiStatus('ready', safeMinor ? 'PROTEÇÃO DE PESSOAS ATIVA' : (handLandmarker ? 'CORPO + MÃOS ATIVOS' : 'CORPO ATIVO'));
      poseState.textContent = 'ENTRE NO ENQUADRAMENTO';
      if (running && !visionAnimation) visionAnimation = requestAnimationFrame(visionFrame);
    } catch (error) {
      visionReady = false;
      safeDetectorReady = false;
      if (safeMinor) {
        safeScoringAllowed=false;safeReason='detector_unavailable';safeState='paused';renderSafetyUi();
        setAiStatus('fallback', 'PROTEÇÃO INDISPONÍVEL');
        poseState.textContent = 'CÂMERA SEGURA NÃO INICIADA';
        throw new Error('A câmera segura não conseguiu carregar a detecção de pessoas neste aparelho.');
      }
      console.warn('Aura Farming: IA indisponível; usando fallback.', error);
      visionMode = 'movimento';
      setAiStatus('fallback', 'MODO COMPATÍVEL');
      poseState.textContent = 'ANÁLISE DE MOVIMENTO';
    }
  }

  function analyzeFallbackFrame() {
    if (safeMinor) return;
    if (!running || video.readyState < 2) return;
    motionContext.save();
    motionContext.scale(-1, 1);
    motionContext.drawImage(video, -motionCanvas.width, 0, motionCanvas.width, motionCanvas.height);
    motionContext.restore();
    const frame = motionContext.getImageData(0, 0, motionCanvas.width, motionCanvas.height).data;
    if (previousFrame) {
      let difference = 0, samples = 0;
      for (let i = 0; i < frame.length; i += 16) {
        const currentLum = frame[i] * .299 + frame[i + 1] * .587 + frame[i + 2] * .114;
        const previousLum = previousFrame[i] * .299 + previousFrame[i + 1] * .587 + previousFrame[i + 2] * .114;
        difference += Math.abs(currentLum - previousLum); samples++;
      }
      const normalized = clamp(((samples ? difference / samples : 0) - 1.8) * 4.8);
      fallbackReadings.push(normalized);
      if (fallbackReadings.length > 12) fallbackReadings.shift();
      if (!visionReady || !sample.poseDetected) updateMotion(average(fallbackReadings));
    }
    previousFrame = new Uint8ClampedArray(frame);
  }

  function bodyScale(points) {
    return Math.max(.08, average([distance(points[11], points[12]), distance(points[23], points[24]), distance(midpoint(points[11], points[12]), midpoint(points[23], points[24]))]));
  }

  function poseSignature(points) {
    const values = [
      angle(points[11], points[13], points[15]), angle(points[12], points[14], points[16]),
      angle(points[23], points[25], points[27]), angle(points[24], points[26], points[28]),
      (points[15].y - points[11].y) * 100, (points[16].y - points[12].y) * 100,
      (points[15].x - points[11].x) * 100, (points[16].x - points[12].x) * 100,
    ];
    return values.map(v => Math.round(v / 12)).join(':');
  }

  function originalityFromSignature(signature) {
    const repeats = signatureHistory.filter(item => item === signature).length;
    signatureHistory.push(signature);
    if (signatureHistory.length > 24) signatureHistory.shift();
    return clamp(92 - repeats * 22, 18, 96);
  }

  function poseMovement(points) {
    if (!previousPose) { previousPose = points.map(p => ({...p})); return 0; }
    const scale = bodyScale(points);
    const indices = [11,12,13,14,15,16,23,24,25,26,27,28];
    const value = average(indices.map(i => distance(points[i], previousPose[i]))) / scale * 135;
    previousPose = points.map(p => ({...p}));
    return clamp(value);
  }

  function recognizeMove(points, hands, metrics, signature, now) {
    const ls=points[11], rs=points[12], le=points[13], re=points[14], lw=points[15], rw=points[16];
    const lh=points[23], rh=points[24], lk=points[25], rk=points[26], la=points[27], ra=points[28];
    const shoulderWidth = distance(ls, rs);
    const wristDistance = distance(lw, rw);
    const leftElbow = angle(ls, le, lw), rightElbow = angle(rs, re, rw);
    const leftKnee = angle(lh, lk, la), rightKnee = angle(rh, rk, ra);

    let code='', label='';
    if (hands >= 2 && wristDistance < shoulderWidth * 1.25 && lw.y < lh.y && rw.y < rh.y) {
      code='energy_hands'; label='MÃOS DE ENERGIA';
    } else if (lw.y < ls.y - .10 && rw.y < rs.y - .10) {
      code='power_pose'; label='PODER TOTAL';
    } else if (Math.abs(lw.y - ls.y) < .12 && Math.abs(rw.y - rs.y) < .12 && leftElbow > 145 && rightElbow > 145 && wristDistance > shoulderWidth * 1.8) {
      code='aura_wings'; label='ASAS DE AURA';
    } else if ((lw.y < ls.y - .12 && rw.y > rh.y - .03) || (rw.y < rs.y - .12 && lw.y > lh.y - .03)) {
      code='diagonal_strike'; label='CORTE DIAGONAL';
    } else if (lw.x > rs.x - .08 && rw.x < ls.x + .08 && lw.y > ls.y && lw.y < lh.y && rw.y > rs.y && rw.y < rh.y) {
      code='cross_guard'; label='GUARDA CRUZADA';
    } else if (leftKnee < 125 && rightKnee < 125 && midpoint(lh,rh).y > midpoint(ls,rs).y + .20) {
      code='aura_squat'; label='AGACHAMENTO DE AURA';
    } else {
      if (signature === holdSignature && latestMotion < 10) {
        if (!holdStartedAt) holdStartedAt = now;
        if (now - holdStartedAt > 1300) { code='stable_pose'; label='POSE PERFEITA'; }
      } else { holdSignature = signature; holdStartedAt = now; }
      if (!code && metrics.fluidity > 82 && latestMotion > 24 && Math.abs(lw.x-rw.x) > shoulderWidth * 1.2) {
        code='aura_wave'; label='ONDA DE AURA';
      }
    }
    return { code, label };
  }

  function calculateMetrics(points, now) {
    const visibilities = IMPORTANT.map(i => points[i]?.visibility ?? .7);
    const confidence = average(visibilities) * 100;
    const coverage = IMPORTANT.filter(i => visible(points[i]) && points[i].x > .02 && points[i].x < .98 && points[i].y > .02 && points[i].y < .98).length / IMPORTANT.length * 100;
    const scale = bodyScale(points);
    const motion = poseMovement(points);
    updateMotion(motion);

    const armSymmetry = 100 - clamp(Math.abs(distance(points[11],points[13]) - distance(points[12],points[14])) / scale * 150);
    const legSymmetry = 100 - clamp(Math.abs(distance(points[23],points[25]) - distance(points[24],points[26])) / scale * 150);
    const shoulderLevel = 100 - clamp(Math.abs(points[11].y-points[12].y) * 500);
    const precision = clamp(confidence * .45 + coverage * .25 + armSymmetry * .12 + legSymmetry * .10 + shoulderLevel * .08);

    const acceleration = Math.abs(motion - previousVelocity);
    const fluidity = clamp(100 - acceleration * 1.55 + Math.min(motion, 35) * .35, 12, 98);
    previousVelocity = motion;

    const signature = poseSignature(points);
    const originality = originalityFromSignature(signature);
    const armHeight = clamp(((points[11].y-points[15].y)+(points[12].y-points[16].y)) * 150 + 30);
    const armExtension = clamp((angle(points[11],points[13],points[15]) + angle(points[12],points[14],points[16]) - 180) / 1.8);
    const kneeDifficulty = clamp((160-angle(points[23],points[25],points[27]) + 160-angle(points[24],points[26],points[28])) * .75);
    const difficulty = clamp(armHeight * .35 + armExtension * .35 + kneeDifficulty * .30);

    return { confidence, coverage, precision, fluidity, originality, difficulty, signature, motion };
  }

  function auraBodyCenter() {
    if (latestPose?.length && visible(latestPose[11]) && visible(latestPose[12]) && visible(latestPose[23]) && visible(latestPose[24])) {
      const shoulders = midpoint(latestPose[11], latestPose[12]);
      const hips = midpoint(latestPose[23], latestPose[24]);
      return { x:(shoulders.x + hips.x) / 2 * auraCanvas.width, y:(shoulders.y + hips.y) / 2 * auraCanvas.height, scale:bodyScale(latestPose) * auraCanvas.height * 2.35 };
    }
    return { x:auraCanvas.width * .5, y:auraCanvas.height * .47, scale:auraCanvas.height * .28 };
  }

  function auraPalette(alpha = 1) {
    const level = AURA_LEVELS[Math.max(0, currentAuraLevel)] || AURA_LEVELS[0];
    const hue = equippedAuraHue === null ? level.hue : equippedAuraHue;
    const hue2 = equippedAuraHue === null ? level.hue2 : (equippedAuraHue + (equippedAuraEffect === 'celestial' ? 85 : 32)) % 360;
    return {
      main:`hsla(${hue},100%,62%,${alpha})`,
      second:`hsla(${hue2},100%,66%,${alpha})`,
      white:`rgba(255,255,255,${alpha})`,
      hue,
      hue2,
    };
  }

  function posePoint(index) {
    const point = latestPose?.[index];
    if (!point || !visible(point)) return null;
    return { x:point.x * auraCanvas.width, y:point.y * auraCanvas.height };
  }

  function randomPoseOrigin() {
    const joints = currentAuraLevel >= 4 ? [0,11,12,13,14,15,16,23,24,25,26,27,28] : [11,12,15,16,23,24];
    const point = posePoint(joints[Math.floor(Math.random() * joints.length)]);
    if (point) return point;
    const center = auraBodyCenter();
    return { x:center.x + (Math.random()-.5)*center.scale*.8, y:center.y + (Math.random()-.5)*center.scale*1.6 };
  }

  function spawnAuraParticle(force = 1, origin = null, kind = 'spark') {
    const level = Math.max(1, currentAuraLevel);
    if (auraParticles.length > Math.round(120 * visualQuality)) return;
    const p = origin || randomPoseOrigin();
    const angleValue = -Math.PI/2 + (Math.random()-.5) * (kind === 'burst' ? Math.PI*2 : 1.75);
    const speed = (18 + Math.random()*70) * force * (.72 + level*.08);
    const palette = auraPalette();
    auraParticles.push({
      x:p.x, y:p.y, vx:Math.cos(angleValue)*speed, vy:Math.sin(angleValue)*speed - 10,
      life:1, decay:(.42 + Math.random()*.55) / (kind === 'smoke' ? 2.2 : 1),
      size:(kind === 'smoke' ? 16 : 1.5) + Math.random()*(kind === 'smoke' ? 26 : 4 + level*.7),
      hue:Math.random()>.42 ? palette.hue : palette.hue2,
      kind, spin:(Math.random()-.5)*5,
    });
  }

  function spawnHandTrails() {
    if (!latestPose?.length || currentAuraLevel < 2) return;
    [15,16].forEach(index => {
      const point = posePoint(index);
      if (!point) return;
      auraTrails.push({ x:point.x, y:point.y, life:1, size:7 + currentAuraLevel*2.5, hue:index===15 ? auraPalette().hue : auraPalette().hue2 });
    });
    if (auraTrails.length > Math.round(50 * visualQuality)) auraTrails.splice(0, auraTrails.length - Math.round(50*visualQuality));
  }

  function drawBodyAura(power) {
    const palette = auraPalette();
    const connections = POSE_CONNECTIONS;
    if (latestPose?.length) {
      auraContext.save();
      auraContext.globalCompositeOperation = 'lighter';
      const passes = currentAuraLevel >= 4 ? 3 : 2;
      for (let pass=passes; pass>=1; pass--) {
        auraContext.beginPath();
        connections.forEach(([a,b]) => {
          const p1=posePoint(a), p2=posePoint(b);
          if (!p1 || !p2) return;
          auraContext.moveTo(p1.x,p1.y); auraContext.lineTo(p2.x,p2.y);
        });
        auraContext.lineCap='round';
        auraContext.lineJoin='round';
        auraContext.lineWidth=(10 + power*24) * pass;
        auraContext.strokeStyle=pass===1 ? palette.white : (pass===2 ? palette.main : palette.second);
        auraContext.globalAlpha=pass===1 ? .24+.18*power : .055+.055*power;
        auraContext.shadowBlur=22 + pass*18*power;
        auraContext.shadowColor=pass===3 ? palette.second : palette.main;
        auraContext.stroke();
      }
      auraContext.restore();
    } else {
      const c=auraBodyCenter();
      const gradient=auraContext.createRadialGradient(c.x,c.y,c.scale*.2,c.x,c.y,c.scale*1.2);
      gradient.addColorStop(0,palette.main.replace(/,[^)]+\)$/, ',.12)'));
      gradient.addColorStop(.62,palette.second.replace(/,[^)]+\)$/, ',.09)'));
      gradient.addColorStop(1,'rgba(0,0,0,0)');
      auraContext.fillStyle=gradient;
      auraContext.beginPath(); auraContext.ellipse(c.x,c.y,c.scale*.75,c.scale*1.6,0,0,Math.PI*2); auraContext.fill();
    }
  }

  function drawAuraWaves(dt) {
    const c=auraBodyCenter();
    const palette=auraPalette();
    auraContext.save(); auraContext.globalCompositeOperation='lighter';
    auraWaves.forEach(wave => {
      wave.radius += dt * (95 + currentAuraLevel*35);
      wave.life -= dt * .72;
      auraContext.beginPath();
      auraContext.ellipse(c.x,c.y,wave.radius,wave.radius*1.35,0,0,Math.PI*2);
      auraContext.strokeStyle=`hsla(${wave.hue},100%,70%,${Math.max(0,wave.life)*.42})`;
      auraContext.lineWidth=Math.max(1,wave.width*wave.life);
      auraContext.shadowColor=palette.main; auraContext.shadowBlur=18; auraContext.stroke();
    });
    auraContext.restore();
    auraWaves=auraWaves.filter(w=>w.life>0);
  }

  function drawAuraParticles(dt) {
    auraContext.save(); auraContext.globalCompositeOperation='lighter';
    auraParticles.forEach(p => {
      p.x += p.vx*dt; p.y += p.vy*dt; p.vy -= p.kind==='smoke' ? 3*dt : 8*dt; p.life -= p.decay*dt;
      const alpha=Math.max(0,p.life);
      if (p.kind==='smoke') {
        const g=auraContext.createRadialGradient(p.x,p.y,0,p.x,p.y,p.size);
        g.addColorStop(0,`hsla(${p.hue},100%,68%,${alpha*.10})`); g.addColorStop(1,'rgba(0,0,0,0)');
        auraContext.fillStyle=g; auraContext.beginPath(); auraContext.arc(p.x,p.y,p.size,0,Math.PI*2); auraContext.fill();
      } else {
        auraContext.strokeStyle=`hsla(${p.hue},100%,76%,${alpha*.85})`;
        auraContext.lineWidth=Math.max(.6,p.size*.32); auraContext.shadowColor=`hsl(${p.hue},100%,65%)`; auraContext.shadowBlur=10;
        auraContext.beginPath(); auraContext.moveTo(p.x,p.y); auraContext.lineTo(p.x-p.vx*.045,p.y-p.vy*.045); auraContext.stroke();
        auraContext.fillStyle=`rgba(255,255,255,${alpha*.75})`; auraContext.beginPath(); auraContext.arc(p.x,p.y,p.size*.35,0,Math.PI*2); auraContext.fill();
      }
    });
    auraContext.restore();
    auraParticles=auraParticles.filter(p=>p.life>0 && p.y>-80 && p.x>-80 && p.x<auraCanvas.width+80);

    auraContext.save(); auraContext.globalCompositeOperation='lighter';
    auraTrails.forEach(t=>{
      t.life-=dt*1.8; t.size+=dt*18;
      const g=auraContext.createRadialGradient(t.x,t.y,0,t.x,t.y,t.size);
      g.addColorStop(0,`rgba(255,255,255,${Math.max(0,t.life)*.36})`);
      g.addColorStop(.35,`hsla(${t.hue},100%,70%,${Math.max(0,t.life)*.27})`); g.addColorStop(1,'rgba(0,0,0,0)');
      auraContext.fillStyle=g; auraContext.beginPath(); auraContext.arc(t.x,t.y,t.size,0,Math.PI*2); auraContext.fill();
    });
    auraContext.restore(); auraTrails=auraTrails.filter(t=>t.life>0);
  }

  function renderAura(now) {
    if (!running || !auraCanvas.width) return;
    auraAnimation=requestAnimationFrame(renderAura);
    const dt=Math.min(.05,Math.max(.001,(now-lastAuraFrame)/1000)); lastAuraFrame=now;
    auraContext.clearRect(0,0,auraCanvas.width,auraCanvas.height);
    const power=Math.max(.06,aura/1000);
    drawBodyAura(power);

    if (currentAuraLevel >= 2 && now-lastParticleSpawn > Math.max(24,110-currentAuraLevel*15)/visualQuality) {
      lastParticleSpawn=now;
      const count=Math.max(1,Math.round((currentAuraLevel-1)*visualQuality*.65*effectParticleMultiplier));
      for(let i=0;i<count;i++) spawnAuraParticle(.65+power*.85,null,(currentAuraLevel>=4 && Math.random()<.18)?'smoke':'spark');
      if (latestMotion>18) spawnHandTrails();
    }
    if (auraBurstPower>0) {
      const c=auraBodyCenter();
      const burstCount=Math.round((8+currentAuraLevel*5)*visualQuality*auraBurstPower);
      for(let i=0;i<burstCount;i++) spawnAuraParticle(1.2+auraBurstPower,{x:c.x+(Math.random()-.5)*c.scale*.5,y:c.y+(Math.random()-.5)*c.scale},'burst');
      auraBurstPower=Math.max(0,auraBurstPower-dt*2.1);
    }
    drawAuraWaves(dt); drawAuraParticles(dt);

    if (currentAuraLevel >= 3 && Math.random() < dt*(.25+currentAuraLevel*.18)*(equippedAuraEffect==='storm'||equippedAuraEffect==='celestial'?1.8:1)) auraWaves.push({life:.65,radius:30+Math.random()*45,width:2+currentAuraLevel,hue:Math.random()>.5?auraPalette().hue:auraPalette().hue2});
  }

  function clearOverlay() { overlayContext.clearRect(0, 0, overlay.width, overlay.height); }
  function drawSkeleton(landmarks, connections, color, pointColor, width = 4) {
    if (!landmarks?.length) return;
    overlayContext.save();
    overlayContext.lineWidth = width;
    overlayContext.lineCap = 'round';
    overlayContext.strokeStyle = color;
    overlayContext.shadowColor = color;
    overlayContext.shadowBlur = 11;
    connections.forEach(([a,b]) => {
      const p1=landmarks[a], p2=landmarks[b]; if (!p1 || !p2 || !visible(p1) || !visible(p2)) return;
      overlayContext.beginPath(); overlayContext.moveTo(p1.x*overlay.width,p1.y*overlay.height); overlayContext.lineTo(p2.x*overlay.width,p2.y*overlay.height); overlayContext.stroke();
    });
    overlayContext.fillStyle = pointColor;
    landmarks.forEach(point => {
      if (!visible(point)) return;
      overlayContext.beginPath(); overlayContext.arc(point.x*overlay.width, point.y*overlay.height, width*.85, 0, Math.PI*2); overlayContext.fill();
    });
    overlayContext.restore();
  }

  async function visionFrame(now) {
    if (!running) return;
    visionAnimation = requestAnimationFrame(visionFrame);
    if (!visionReady || video.readyState < 2 || video.currentTime === lastVideoTime) return;
    lastVideoTime = video.currentTime;
    resizeOverlay();

    if (now - lastPoseAt >= 100) {
      lastPoseAt = now;
      try {
        const result = poseLandmarker.detectForVideo(video, Math.round(now));
        latestAllPoses = result.landmarks || [];
        safePersonCount = latestAllPoses.length;
        latestPose = safeMinor ? (safePersonCount === 1 ? latestAllPoses[0] : null) : (latestAllPoses[0] || null);
        if (safeMinor) { updateSafeLighting(); evaluateSafetySignals(safePersonCount, true, false); }
        clearOverlay();
        if (safeMinor && !safeScoringAllowed) {
          sample.poseDetected = false;
          sample.move = '';
          latestHands = [];
          currentHandCount = 0;
          previousPose = null;
          poseState.textContent = safeReason === 'multiple_people' ? 'MÚLTIPLAS PESSOAS' : 'PONTUAÇÃO PAUSADA';
          root.classList.remove('farm-game--pose-ready');
        } else if (latestPose) {
          sample.poseDetected = true; sample.poseFrames++;
          const metrics = calculateMetrics(latestPose, now);
          pushMetric(sample.confidence, metrics.confidence); pushMetric(sample.coverage, metrics.coverage);
          pushMetric(sample.precision, metrics.precision); pushMetric(sample.fluidity, metrics.fluidity);
          pushMetric(sample.originality, metrics.originality); pushMetric(sample.difficulty, metrics.difficulty);
          updateMetrics(metrics);
          poseState.textContent = metrics.coverage >= 75 ? 'CORPO DETECTADO' : 'MELHORE O ENQUADRAMENTO';
          root.classList.toggle('farm-game--pose-ready', metrics.coverage >= 75);
          const recognized = recognizeMove(latestPose, currentHandCount, metrics, metrics.signature, now);
          if (recognized.code) showMove(recognized.code, recognized.label, metrics.precision >= 80 && metrics.fluidity >= 70);
          drawSkeleton(latestPose, POSE_CONNECTIONS, 'rgba(0,231,255,.92)', '#ffffff', 4);
        } else {
          sample.poseDetected = false; poseState.textContent = 'CORPO NÃO DETECTADO';
          root.classList.remove('farm-game--pose-ready');
        }
        if (!safeMinor || safeScoringAllowed) latestHands.forEach(hand => drawSkeleton(hand, HAND_CONNECTIONS, 'rgba(255,71,170,.95)', '#fff8b7', 3));
      } catch (error) { console.warn('Falha de frame de pose', error); }
    }

    if (handLandmarker && (!safeMinor || safeScoringAllowed) && now - lastHandAt >= 250) {
      lastHandAt = now;
      try {
        const handResult = handLandmarker.detectForVideo(video, Math.round(now));
        latestHands = handResult.landmarks || [];
        currentHandCount = Math.min(2, latestHands.length);
        sample.hands = Math.max(sample.hands, currentHandCount);
        sample.handFrames += currentHandCount ? 1 : 0;
      } catch (_) { latestHands=[]; currentHandCount=0; }
    }
  }

  function samplePayload() {
    const sorted = fallbackReadings.slice(-8).sort((a,b)=>a-b);
    const fallback = average(sorted.length >= 5 ? sorted.slice(1,-1) : sorted) || latestMotion;
    const safety = safetyPayload();
    const canScore = !safeMinor || safeScoringAllowed;
    return {
      sessao_id: sessionId,
      safe_token: safeToken,
      safety_state: safety.safety_state,
      person_count: safety.person_count,
      detector_ready: safety.detector_ready,
      privacy_rendering: safety.privacy_rendering,
      tab_visible: safety.tab_visible,
      camera_active: safety.camera_active,
      safety_frame_ts: safety.safety_frame_ts,
      modo: visionMode,
      intensidade: canScore ? Number((visionMode === 'visao' && sample.poseDetected ? latestMotion : fallback).toFixed(2)) : 0,
      sequencia: sequence,
      tempo: Math.floor(elapsed),
      pose_detectada: canScore && visionMode === 'visao' && sample.poseDetected,
      confianca: Number(average(sample.confidence).toFixed(2)),
      cobertura: Number(average(sample.coverage).toFixed(2)),
      precisao: Number(average(sample.precision).toFixed(2)),
      fluidez: Number(average(sample.fluidity).toFixed(2)),
      originalidade: Number(average(sample.originality).toFixed(2)),
      dificuldade: Number(average(sample.difficulty).toFixed(2)),
      maos: sample.hands,
      frames_pose: sample.poseFrames,
      frames_maos: sample.handFrames,
      movimento: sample.move,
    };
  }

  function resetSample() {
    Object.assign(sample, { confidence:[],coverage:[],precision:[],fluidity:[],originality:[],difficulty:[],poseFrames:0,handFrames:0,move:'',poseDetected:(!safeMinor || safeScoringAllowed) && Boolean(latestPose),hands:(!safeMinor || safeScoringAllowed) ? currentHandCount : 0 });
  }

  async function sendHeartbeat() {
    if (!running || finishing) return;
    sequence++;
    const payload = samplePayload();
    syncLabel.textContent = visionMode === 'visao' ? 'Validando movimentos e poses…' : 'Sincronizando movimento…';
    try {
      const data = await api('heartbeat', payload);
      resetSample();
      updateAura(data.partida.aura);
      updateSocial(data.partida);
      remaining = Math.min(remaining, Number(data.partida.tempo_restante));
      elapsed = Number(data.partida.tempo_decorrido);
      updateAdvancedCombo(data.partida);
      applyServerSafety(data.safe_camera);
      syncLabel.textContent = safeMinor && !safeScoringAllowed ? `🛡️ ${safeMessage(safeReason)[2]}` : '✓ Pontuação validada pelo servidor';
      syncLabel.classList.remove('score-sync--error');
      if (data.partida.status === 'finalizada') finishRedirect();
    } catch (error) {
      sequence--; syncLabel.textContent = `⚠ ${error.message}`; syncLabel.classList.add('score-sync--error');
    }
  }

  function startLoops() {
    running = true; lastTimerTick = performance.now();
    if (!safeMinor) fallbackLoop = setInterval(analyzeFallbackFrame, 250);
    heartbeatLoop = setInterval(sendHeartbeat, 2000);
    timerLoop = setInterval(() => {
      const now=performance.now(), delta=Math.max(0,(now-lastTimerTick)/1000); lastTimerTick=now;
      remaining=Math.max(0,remaining-delta); elapsed=Math.min(180,elapsed+delta); updateTimer();
      if (remaining <= 0) finishMatch();
    }, 200);
    if (visionReady) visionAnimation = requestAnimationFrame(visionFrame);
    if (!auraAnimation) { lastAuraFrame=performance.now(); auraAnimation=requestAnimationFrame(renderAura); }
    updateTimer(); updateAura(aura, false);
    syncLabel.textContent = visionReady ? 'IA analisando no próprio aparelho' : 'Modo compatível ativo';
    if (safeMinor) renderSafetyUi();
  }

  function stopLoops() {
    running=false;
    [fallbackLoop,heartbeatLoop,timerLoop].forEach(id=>id&&clearInterval(id));
    fallbackLoop=heartbeatLoop=timerLoop=null;
    if (visionAnimation) cancelAnimationFrame(visionAnimation);
    if (auraAnimation) cancelAnimationFrame(auraAnimation);
    visionAnimation=null; auraAnimation=null;
  }

  async function beginOnServer() {
    const data=await api('begin',{sessao_id:sessionId,safe_token:safeToken});
    remaining=Math.min(180,Number(data.partida.tempo_restante||180)); elapsed=Number(data.partida.tempo_decorrido||0); updateAura(data.partida.aura); updateSocial(data.partida, false); updateAdvancedCombo(data.partida); applyServerSafety(data.safe_camera); startLoops();
  }

  async function waitForBattleStart() {
    if (!battleId) return;
    countdown.hidden = false;
    countdownNumber.textContent = 'VS';
    message.innerHTML = '<strong>AGUARDANDO OPONENTE</strong><span>Os dois jogadores precisam deixar a câmera pronta</span>';
    const ready = await fetch(`${baseUrl}/api/v1/battles/ready.php`, {
      method: 'POST', credentials: 'same-origin',
      headers: { 'Content-Type': 'application/json', 'X-CSRF-Token': csrf },
      body: JSON.stringify({ sessao_id: sessionId }),
    }).then(async response => {
      const data = await response.json().catch(() => ({}));
      if (!response.ok || !data.ok) throw new Error(data.erro || 'Não foi possível preparar a batalha.');
      return data;
    });
    let battle = ready.batalha;
    const deadline = Date.now() + 10 * 60 * 1000;
    while (battle?.status !== 'em_andamento') {
      if (['finalizada','cancelada','expirada'].includes(battle?.status)) throw new Error('A batalha foi encerrada.');
      if (Date.now() > deadline) throw new Error('O adversário não ficou pronto a tempo.');
      await new Promise(resolve => setTimeout(resolve, 1200));
      const response = await fetch(`${baseUrl}/api/v1/battles/state.php?id=${battleId}`, { credentials: 'same-origin' });
      const data = await response.json().catch(() => ({}));
      if (!response.ok || !data.ok) throw new Error(data.erro || 'Falha ao sincronizar a batalha.');
      battle = data.batalha;
      const readyCount = (battle.jogadores || []).filter(player => ['camera_pronta','jogando','finalizado'].includes(player.status)).length;
      countdownNumber.textContent = `${readyCount}/2`;
    }
    message.innerHTML = '<strong>OPONENTE PRONTO!</strong><span>A batalha começará agora</span>';
  }

  async function visualCountdown() {
    countdown.hidden=false;
    for (const value of ['3','2','1','FARME!']) {
      countdownNumber.textContent=value; countdownNumber.classList.remove('countdown-pop'); void countdownNumber.offsetWidth; countdownNumber.classList.add('countdown-pop');
      await new Promise(resolve=>setTimeout(resolve,value==='FARME!'?650:850));
    }
    countdown.hidden=true;
  }

  async function finishMatch() {
    if (finishing) return;
    finishing=true; stopLoops(); motionLabel.textContent='FINALIZADO';
    message.innerHTML='<strong>A PARTIDA TERMINOU!</strong><span>Validando sua pontuação…</span>'; message.classList.add('game-center-message--maximum');
    syncLabel.textContent='Finalizando no servidor…';
    try {
      sequence++;
      const payload=samplePayload(); payload.sequencia=sequence; payload.tempo=180;
      await api('heartbeat',payload).catch(()=>null);
      const data=await api('finish',{sessao_id:sessionId,safe_token:safeToken}); updateAura(data.partida.aura); updateSocial(data.partida);
      setTimeout(()=>{window.location.href=data.redirect||`${baseUrl}/pages/resultado.php?s=${encodeURIComponent(sessionId)}`;},900);
    } catch (error) {
      syncLabel.textContent=error.message; finishing=false; remaining=Math.max(1,remaining); startLoops();
    }
  }

  function finishRedirect() { stopLoops(); stopCamera(); window.location.href=`${baseUrl}/pages/resultado.php?s=${encodeURIComponent(sessionId)}`; }

  async function cancelMatch() {
    if (!confirm('Cancelar esta sessão? A pontuação não será enviada ao ranking.')) return;
    stopLoops();
    try { const data=await api('cancel',{sessao_id:sessionId,safe_token:safeToken}); stopCamera(); window.location.href=data.redirect||`${baseUrl}/pages/farmar.php`; }
    catch(error){ alert(error.message); startLoops(); }
  }


  async function moderationFetch(url, options = {}) {
    const response = await fetch(url, { credentials: 'same-origin', ...options });
    const data = await response.json().catch(() => ({}));
    if (!response.ok || !data.ok) throw new Error(data.erro || 'Falha na moderação ao vivo.');
    return data;
  }

  function renderModerationViewers(viewers = []) {
    if (!moderationViewers) return;
    moderationViewers.textContent = '';
    if (!viewers.length) {
      const empty = document.createElement('span'); empty.textContent = 'Nenhum espectador conectado.'; moderationViewers.append(empty); return;
    }
    viewers.forEach(viewer => {
      const row = document.createElement('article');
      const img = document.createElement('img'); img.src = viewer.avatar || ''; img.alt = '';
      const info = document.createElement('div');
      const name = document.createElement('b'); name.textContent = viewer.nome || viewer.username || 'Espectador';
      const id = document.createElement('small'); id.textContent = viewer.aura_id || '';
      info.append(name, id);
      const kick = document.createElement('button'); kick.type = 'button'; kick.textContent = 'REMOVER';
      const block = document.createElement('button'); block.type = 'button'; block.textContent = 'BLOQUEAR'; block.className = 'is-danger';
      kick.addEventListener('click', () => moderateViewer(Number(viewer.usuario_id), false));
      block.addEventListener('click', () => moderateViewer(Number(viewer.usuario_id), true));
      row.append(img, info, kick, block); moderationViewers.append(row);
    });
  }

  async function loadModerationViewers() {
    if (safeMinor || !matchId || !moderationPanel || moderationPanel.hidden) return;
    try {
      const data = await moderationFetch(`${baseUrl}/api/v1/moderation/viewers.php?partida_id=${matchId}`);
      renderModerationViewers(data.viewers || []);
    } catch (error) {
      if (moderationViewers) moderationViewers.textContent = error.message;
    }
  }

  async function moderateViewer(spectatorId, block) {
    if (!spectatorId) return;
    if (!confirm(block ? 'Remover e bloquear este espectador?' : 'Remover este espectador da transmissão?')) return;
    try {
      const data = await moderationFetch(`${baseUrl}/api/v1/moderation/kick-viewer.php`, {
        method: 'POST', headers: { 'Content-Type': 'application/json', 'X-CSRF-Token': csrf },
        body: JSON.stringify({ partida_id: matchId, spectator_id: spectatorId, block }),
      });
      if (socialToast) { socialToast.textContent = data.mensagem || 'Ação aplicada.'; socialToast.classList.add('social-received-toast--show'); setTimeout(() => socialToast.classList.remove('social-received-toast--show'), 1800); }
      await loadModerationViewers();
    } catch (error) { alert(error.message); }
  }

  function openModerationPanel() {
    if (!moderationPanel) return;
    moderationPanel.hidden = false; loadModerationViewers();
    clearInterval(moderationTimer); moderationTimer = setInterval(loadModerationViewers, 2500);
  }

  function closeModerationPanel() {
    if (!moderationPanel) return;
    moderationPanel.hidden = true; clearInterval(moderationTimer); moderationTimer = null;
  }

  async function requestWakeLock() { try { wakeLock=await navigator.wakeLock?.request('screen'); } catch(_){ wakeLock=null; } }

  async function boot() {
    cameraError.hidden=true;
    applyAuraLevel(auraLevelFor(aura), false);
    try {
      await openCamera(); await requestWakeLock();
      if (safeMinor) {
        await loadVision();
        await runSafePreflight();
      } else {
        await Promise.race([loadVision(), new Promise(resolve=>setTimeout(()=>{ if(!visionReady){setAiStatus('fallback','MODO COMPATÍVEL'); resolve();}},12000))]);
      }
      if(initialStatus==='aguardando'){
        if (battleId) await waitForBattleStart();
        await visualCountdown(); await beginOnServer();
      } else { countdown.hidden=true; startLoops(); }
    } catch(error) {
      stopLoops(); cameraError.hidden=false;
      cameraError.querySelector('p').textContent=error.name==='NotAllowedError'
        ? 'A permissão da câmera foi negada. Autorize a câmera nas configurações do navegador e tente novamente.'
        : (error.message||'Não foi possível abrir a câmera.');
    }
  }

  document.querySelector('#btn-retry-camera')?.addEventListener('click',boot);
  document.querySelector('#btn-cancel-match')?.addEventListener('click',cancelMatch);
  moderationButton?.addEventListener('click', openModerationPanel);
  moderationClose?.addEventListener('click', closeModerationPanel);
  window.addEventListener('resize',resizeOverlay);
  window.addEventListener('pagehide',()=>{clearInterval(moderationTimer);stopLoops();stopCamera();poseLandmarker?.close?.();handLandmarker?.close?.();wakeLock?.release?.().catch(()=>{});});
  document.addEventListener('visibilitychange',()=>{
    if (safeMinor) {
      evaluateSafetySignals(safePersonCount,safeDetectorReady,true);
      reportSafety(false);
    }
    if(document.visibilityState==='visible'&&running){lastTimerTick=performance.now();requestWakeLock();}
  });
  boot();
})();
