// source --> https://elanillounico.com/wp-content/plugins/juego-trivial/assets/js/trivial-app.js?ver=0.4.32 
(function () {
  const root = document.querySelector('[data-trivial-app]');
  if (!root || !window.EAU_TRIVIAL) return;

  let cachedState = null;
  let trackedFinish = '';

  const track = (event, params = {}) => {
    if (typeof window.eauTrack === 'function') window.eauTrack(event, Object.assign({game_name: 'trivial', game_slug: 'trivial'}, params));
  };

  const api = async (path, options = {}) => {
    const res = await fetch(EAU_TRIVIAL.rest + path, {
      method: options.method || 'GET',
      credentials: 'same-origin',
      headers: {'Content-Type': 'application/json', 'X-WP-Nonce': EAU_TRIVIAL.nonce},
      body: options.body ? JSON.stringify(options.body) : undefined
    });
    const data = await res.json();
    if (!res.ok) throw new Error(data.message || 'Error en el juego');
    return data;
  };

  const board = root.querySelector('[data-view="board"]');
  const landing = root.querySelector('[data-view="landing"]');
  const submission = root.querySelector('[data-view="submission"]');
  const rules = root.querySelector('[data-view="rules"]');
  const openSubmissionButton = root.querySelector('[data-action="open-submission"]');
  const closeSubmissionButton = root.querySelector('[data-action="close-submission"]');
  const continueButton = root.querySelector('[data-action="continue"]');
  const currentGameNotice = root.querySelector('[data-current-game]');
  const questionView = root.querySelector('[data-view="question"]');
  const movementView = root.querySelector('[data-view="movement"]');
  const cardsView = root.querySelector('[data-view="cards"]');
  const scoreField = root.querySelector('[data-field="score"]');
  const dieField = root.querySelector('[data-field="die"]');
  const statusField = root.querySelector('[data-field="status"]');
  const messageField = root.querySelector('[data-field="message"]');
  const playerDieImage = root.querySelector('[data-die="player"]');
  const nazgulDieImage = root.querySelector('[data-die="nazgul"]');
  const ringDieImage = root.querySelector('[data-die="ring"]');
  const ringDieWrap = root.querySelector('[data-ring-die]');
  const mapViewport = root.querySelector('[data-map-viewport]');
  const mapCanvas = root.querySelector('[data-map-canvas]');
  const moveTargets = root.querySelector('[data-move-targets]');
  const abandonDialog = root.querySelector('[data-abandon-dialog]');
  const abandonConfirmButton = root.querySelector('[data-abandon-confirm]');
  const abandonCancelButton = root.querySelector('[data-abandon-cancel]');
  let lastState = null;
  let mapScale = 1;
  let questionTimer = null;

  function esc(s) { return String(s || '').replace(/[&<>'"]/g, c => ({'&':'&amp;','<':'&lt;','>':'&gt;',"'":'&#039;','"':'&quot;'}[c])); }
  function assetUrl(path) {
    return String(EAU_TRIVIAL.assets || '').replace(/\/?$/, '/') + path;
  }
  function richHtml(raw) {
    const template = document.createElement('template');
    const source = String(raw || '').replace(/src=(["']?)(?:\.\/)?(?:\/modules\/Trivial\/juego\/)?runas\/runa\.php\?([^"'\s>]+)\1/gi, (_m, quote, query) => {
      return 'src="' + assetUrl('runas/runa.php?' + query.replace(/&amp;/g, '&')) + '"';
    });
    template.innerHTML = source;
    const allowed = new Set(['IMG', 'BR', 'B', 'STRONG', 'I', 'EM', 'U', 'SMALL', 'SUB', 'SUP']);
    template.content.querySelectorAll('*').forEach(el => {
      if (!allowed.has(el.tagName)) {
        el.replaceWith(document.createTextNode(el.textContent || ''));
        return;
      }
      [...el.attributes].forEach(attr => {
        const name = attr.name.toLowerCase();
        if (el.tagName === 'IMG' && ['src', 'alt', 'width', 'height'].includes(name)) return;
        el.removeAttribute(attr.name);
      });
      if (el.tagName === 'IMG') {
        const src = el.getAttribute('src') || '';
        if (!src.startsWith(assetUrl('runas/runa.php?'))) {
          el.remove();
          return;
        }
        el.classList.add('eau-rune-image');
        el.alt = el.alt || 'Runas';
      }
    });
    return template.innerHTML;
  }

  function setMode(mode) {
    root.dataset.mode = mode;
    const playing = mode === 'playing';
    root.classList.toggle('is-playing', playing);
    board.hidden = !playing;
    landing.hidden = playing;
    if (submission) submission.hidden = true;
    if (rules) rules.hidden = playing;
    document.documentElement.classList.toggle('eau-trivial-lock', playing);
    document.body.classList.toggle('eau-trivial-lock', playing);
  }

  function requestFullscreen() {
    if (document.fullscreenElement || !root.requestFullscreen) return;
    root.requestFullscreen().catch(() => {});
  }

  function exitFullscreen() {
    if (document.fullscreenElement && document.exitFullscreen) {
      document.exitFullscreen().catch(() => {});
    }
  }

  function markContinuable(state) {
    cachedState = state && state.has_game ? state : null;
    if (continueButton) continueButton.hidden = !cachedState;
    if (currentGameNotice) currentGameNotice.hidden = !cachedState;
  }

  function render(state) {
    if (!state || !state.has_game) return;
    cachedState = state;
    lastState = state;
    setMode('playing');
    root.classList.toggle('is-moving-frodo', state.status === 'moving_player');
    scoreField.textContent = state.score || 0;
    dieField.textContent = state.dice && state.dice.player ? state.dice.player : '-';
    statusField.textContent = translateStatus(state.status);
    renderDice(state.dice || {});
    if (messageField) messageField.textContent = state.meta && state.meta.last_message ? state.meta.last_message : '';
    renderQuestion(state);
    renderMovement(state);
    renderMoveTargets(state);
    renderCards(state.cards || {});
    placeTokens(state);
    resizeMapCanvas();
    centerOnFrodo(state);
    if (['won', 'lost', 'abandoned'].includes(state.status) && trackedFinish !== state.status) {
      trackedFinish = state.status;
      track('game_finish', {result: state.status === 'won' ? 'win' : state.status === 'lost' ? 'loss' : 'abandoned', score: Number(state.score || 0)});
    }
  }

  function stopQuestionTimer() {
    if (questionTimer) window.clearInterval(questionTimer);
    questionTimer = null;
  }

  function translateStatus(status) {
    return ({active_question:'Pregunta', moving_player:'Movimiento', combat_pending:'Lucha', ring_test:'Anillo', athelas_pending:'Athelas', mount_doom_questions:'Monte del Destino', sam_pending:'Sam', won:'Ganada', lost:'Perdida', abandoned:'Abandonada'})[status] || status || '-';
  }

  function imageUrl(path) {
    return assetUrl('images/' + path);
  }

  function renderDice(dice) {
    const player = Math.max(0, Math.min(6, Number(dice.player || 0)));
    const ring = Math.max(0, Math.min(3, Number(dice.ring || 0)));
    if (playerDieImage) playerDieImage.src = imageUrl('dados/num' + player + '.gif');
    renderNazgulDice(dice.nazgul);
    if (ringDieImage) ringDieImage.src = imageUrl('dados/anillo' + ring + '.gif');
    if (ringDieWrap) ringDieWrap.hidden = ring <= 0;
  }

  function renderNazgulDice(values) {
    if (!nazgulDieImage) return;
    const diceValues = (Array.isArray(values) ? values : [values || 1]).map(v => Math.max(1, Math.min(8, Number(v || 1))));
    const figure = nazgulDieImage.closest('figure');
    if (!figure) {
      nazgulDieImage.src = imageUrl('dados/sauronnum' + diceValues[0] + '.gif');
      return;
    }
    let row = figure.querySelector('[data-nazgul-dice-row]');
    if (!row) {
      row = document.createElement('div');
      row.className = 'eau-nazgul-dice-row';
      row.dataset.nazgulDiceRow = '1';
      figure.insertBefore(row, figure.firstChild);
    }
    row.innerHTML = '';
    diceValues.forEach(value => {
      const img = document.createElement('img');
      img.src = imageUrl('dados/sauronnum' + value + '.gif');
      img.alt = 'Dado de Sauron ' + value;
      img.width = 50;
      img.height = 50;
      row.appendChild(img);
    });
    nazgulDieImage.hidden = true;
  }

  function renderQuestion(state) {
    stopQuestionTimer();
    if (state.status === 'won') {
      questionView.innerHTML = '<h3>Victoria</h3><p>Has destruido el Anillo Único.</p><button class="eau-trivial-button" data-local-exit>Volver a la portada</button>';
      bindLocalExit(questionView); return;
    }
    if (state.status === 'lost') {
      questionView.innerHTML = '<h3>Partida perdida</h3><p>La sombra se ha impuesto. Puedes iniciar una nueva partida desde la portada.</p><button class="eau-trivial-button" data-local-exit>Volver a la portada</button>';
      bindLocalExit(questionView); return;
    }
    if (state.status === 'ring_test') {
      const msg = state.meta && state.meta.ring_test ? state.meta.ring_test.message : 'El Anillo reclama una prueba de voluntad.';
      questionView.innerHTML = '<h3>Prueba del Anillo</h3><p>' + esc(msg) + '</p><div class="eau-answer-list"><button class="eau-answer" data-ring="1">Usar Resistir</button><button class="eau-answer" data-ring="0">No resistir</button></div>';
      questionView.querySelectorAll('[data-ring]').forEach(btn => btn.addEventListener('click', async () => {
        setBusy(btn, true); try { render(await api('ring', {method:'POST', body:{resist: btn.dataset.ring === '1'}})); } catch(e){ alert(e.message); } finally { setBusy(btn, false); }
      }));
      return;
    }
    if (state.status === 'athelas_pending') {
      questionView.innerHTML = '<h3>Athelas</h3><p>Un Nazgûl ha alcanzado a Frodo. Aragorn puede curarlo con Athelas y conducirlo a Rivendel.</p><button class="eau-trivial-button" data-athelas>Usar Athelas</button>';
      const btn = questionView.querySelector('[data-athelas]');
      btn.addEventListener('click', async () => {
        setBusy(btn, true);
        try {
          if (mapViewport) delete mapViewport.dataset.userPanned;
          render(await api('athelas', {method:'POST', body:{}}));
        } catch(e) {
          alert(e.message);
        } finally {
          setBusy(btn, false);
        }
      });
      return;
    }
    if (state.status === 'sam_pending') {
      questionView.innerHTML = '<h3>La decisión de Sam Gamyi</h3><p>Frodo ha sido descubierto en Mordor. Sam puede tomar el Anillo y salvarlo, pero perderás la carta de Sam.</p><button class="eau-trivial-button" data-sam>Continuar con Sam</button>';
      const btn = questionView.querySelector('[data-sam]');
      btn.addEventListener('click', async () => {
        setBusy(btn, true); try { render(await api('sam', {method:'POST', body:{}})); } catch(e){ alert(e.message); } finally { setBusy(btn, false); }
      });
      return;
    }
    if (state.status === 'combat_pending') {
      const c = state.combat || {};
      const cards = state.cards || {};
      const names = {aragorn:'Aragorn', gandalf:'Gandalf', legolas:'Legolas', gimli:'Gimli', boromir:'Boromir'};
      questionView.innerHTML = '<h3>Lucha</h3><p>Enemigo: <strong>' + esc(c.enemy_label || c.enemy || 'Enemigo') + '</strong></p>' +
        '<div class="eau-combat-grid">' + Object.keys(names).map(k => '<label><span><strong>' + names[k] + '</strong><small>Disponibles: ' + esc(cards[k] || 0) + '</small></span><input type="number" min="0" max="' + esc(cards[k] || 0) + '" value="0" data-combat-card="' + k + '"></label>').join('') + '</div>' +
        '<button class="eau-trivial-button" data-combat-resolve>Resolver lucha</button>';
      const btn = questionView.querySelector('[data-combat-resolve]');
      btn.addEventListener('click', async () => {
        const selection = {};
        questionView.querySelectorAll('[data-combat-card]').forEach(i => selection[i.dataset.combatCard] = Number(i.value || 0));
        setBusy(btn, true); try { render(await api('combat', {method:'POST', body:{selection}})); } catch(e){ alert(e.message); } finally { setBusy(btn, false); }
      });
      return;
    }
    if (!['active_question', 'mount_doom_questions'].includes(state.status)) {
      questionView.innerHTML = '<h3>Pregunta</h3><p>No hay pregunta activa en esta fase.</p>';
      return;
    }
    if (!state.question) {
      questionView.innerHTML = '<h3>Pregunta</h3><p>Sin pregunta activa.</p>';
      return;
    }
    const answers = Array.isArray(state.question.answers) ? state.question.answers : [];
    if (!answers.length) {
      questionView.innerHTML = '<h3>Pregunta</h3><p>No se han podido cargar las respuestas de esta pregunta. Inicia una partida nueva para regenerar el estado.</p>';
      return;
    }
    const title = state.status === 'mount_doom_questions' ? 'Monte del Destino' : 'Pregunta';
    const mount = state.meta && state.meta.mount_doom ? '<p class="eau-mini">Final: ' + esc(state.meta.mount_doom.correct || 0) + ' aciertos de ' + esc(state.meta.mount_doom.answered || 0) + ' respuestas.</p>' : '';
    questionView.innerHTML = '<h3>' + title + '</h3>' + mount + renderTimeBar(state.question) + '<p>' + richHtml(state.question.text) + '</p>' +
      '<div class="eau-answer-list">' + answers.map(a => '<button class="eau-answer" data-answer="' + esc(a.key) + '">' + richHtml(a.text) + '</button>').join('') + '</div>';
    startQuestionTimer(state.question);
    questionView.querySelectorAll('[data-answer]').forEach(btn => {
      btn.addEventListener('click', async () => {
        setBusy(btn, true);
        try { render(await api('answer', {method: 'POST', body: {answer_key: btn.dataset.answer}})); }
        catch (e) { alert(e.message); }
        finally { setBusy(btn, false); }
      });
    });
  }

  function renderTimeBar(question) {
    const limit = Math.max(1, Number(question.time_limit || 37));
    const elapsed = Math.max(0, Date.now() / 1000 - Number(question.served_at || Date.now() / 1000));
    const progress = Math.min(1, elapsed / limit);
    const remaining = Math.max(0, Math.ceil(limit - elapsed));
    const bonus = Math.max(0, Math.round(200 - (1.06 * Math.min(140, Math.round(progress * 140)))));
    return '<div class="eau-question-timer" data-question-timer data-limit="' + esc(limit) + '" data-served-at="' + esc(question.served_at || '') + '">' +
      '<div class="eau-question-timer-head"><span>Tiempo</span><strong data-time-left>' + esc(remaining) + 's</strong><em data-time-bonus>+' + esc(bonus) + ' puntos aprox.</em></div>' +
      '<div class="eau-question-timer-track"><span data-time-bar style="width:' + (progress * 100).toFixed(2) + '%"></span></div>' +
      '</div>';
  }

  function startQuestionTimer(question) {
    const timer = questionView.querySelector('[data-question-timer]');
    if (!timer) return;
    const bar = timer.querySelector('[data-time-bar]');
    const left = timer.querySelector('[data-time-left]');
    const bonusEl = timer.querySelector('[data-time-bonus]');
    const limit = Math.max(1, Number(timer.dataset.limit || question.time_limit || 37));
    const servedAt = Number(timer.dataset.servedAt || question.served_at || (Date.now() / 1000));
    const update = () => {
      const elapsed = Math.max(0, Date.now() / 1000 - servedAt);
      const progress = Math.min(1, elapsed / limit);
      const remaining = Math.max(0, Math.ceil(limit - elapsed));
      const legacyWidth = Math.min(140, Math.round(progress * 140));
      const approxBonus = Math.max(0, Math.round(200 - (1.06 * legacyWidth)));
      if (bar) bar.style.width = (progress * 100).toFixed(2) + '%';
      if (left) left.textContent = remaining + 's';
      if (bonusEl) bonusEl.textContent = remaining > 0 ? '+' + approxBonus + ' puntos aprox.' : 'Tiempo agotado';
      timer.classList.toggle('is-low', remaining <= 8 && remaining > 0);
      timer.classList.toggle('is-ended', remaining <= 0);
      if (remaining <= 0) {
        stopQuestionTimer();
      }
    };
    update();
    questionTimer = window.setInterval(update, 250);
  }

  function renderMovement(state) {
    if (state.status !== 'moving_player') {
      movementView.innerHTML = '<h3>Movimiento</h3><p>Responde correctamente para mover a Frodo.</p>';
      return;
    }
    movementView.innerHTML = '<h3>Movimiento</h3><p>Has sacado un ' + esc(state.dice.player) + '.</p>' +
      '<div class="eau-answer-list">' + (state.movement_options || []).map(o => '<button class="eau-move" data-path="' + esc(o.path) + '" data-pos="' + esc(o.pos) + '">' + esc(o.label) + '</button>').join('') + '</div>';
    movementView.querySelectorAll('[data-path]').forEach(btn => {
      btn.addEventListener('click', async () => {
        await moveFrodo(Number(btn.dataset.path), Number(btn.dataset.pos), btn);
      });
    });
  }

  function renderMoveTargets(state) {
    if (!moveTargets) return;
    moveTargets.innerHTML = '';
    if (state.status !== 'moving_player') return;
    (state.movement_options || []).forEach((option, i) => {
      const xy = positionToPx(state, option.path, option.pos);
      const btn = document.createElement('button');
      btn.type = 'button';
      btn.className = 'eau-map-target';
      btn.style.left = (xy.x + 16.5) + 'px';
      btn.style.top = (xy.y + 20) + 'px';
      btn.dataset.path = String(option.path);
      btn.dataset.pos = String(option.pos);
      btn.title = option.label || 'Mover a Frodo';
      btn.textContent = String(i + 1);
      btn.addEventListener('click', () => moveFrodo(Number(option.path), Number(option.pos), btn));
      moveTargets.appendChild(btn);
    });
  }

  async function moveFrodo(path, pos, source) {
    if (source) setBusy(source, true);
    try {
      if (mapViewport) delete mapViewport.dataset.userPanned;
      render(await api('move', {method: 'POST', body: {path, pos}}));
    }
    catch (e) { alert(e.message); }
    finally { if (source) setBusy(source, false); }
  }

  function renderCards(cards) {
    const entries = Object.entries(cards);
    cardsView.innerHTML = '<h3>Cartas</h3><div class="eau-card-grid">' + entries.map(([k, v]) => '<span class="' + (Number(v || 0) <= 0 ? 'is-empty' : '') + '"><img src="' + esc(imageUrl('cartas/' + cardImage(k))) + '" alt="' + esc(labelCard(k)) + '"><em>' + esc(v) + '</em></span>').join('') + '</div>';
  }

  function labelCard(k) { return ({resistir:'Resistir', aragorn:'Aragorn', gandalf:'Gandalf', legolas:'Legolas', gimli:'Gimli', boromir:'Boromir', merry_pippin:'Merry/Pippin', athelas:'Athelas', sam:'Sam'})[k] || k; }
  function cardImage(k) { return ({resistir:'resistir.jpg', aragorn:'aragorn.jpg', gandalf:'gandalf.jpg', legolas:'legolas.jpg', gimli:'gimli.jpg', boromir:'boromir.jpg', merry_pippin:'merrypippin.jpg', athelas:'athelas.jpg', sam:'sam.jpg'})[k] || 'resistir.jpg'; }

  function placeTokens(state) {
    const coords = positionToPx(state, state.frodo.path, state.frodo.pos);
    moveToken('frodo', coords.x, coords.y);
    (state.nazgul || []).forEach((n, i) => {
      const c = positionToPx(state, n.path, n.pos);
      moveToken('nazgul-' + i, c.x, c.y);
    });
  }

  function positionToPx(state, path, pos) {
    const map = state.map || {};
    const coords = map.coords || {};
    const list = coords[String(Math.abs(Number(path) || 1))] || coords[Math.abs(Number(path) || 1)];
    if (list && list[Number(pos || 1) - 1]) {
      const xy = list[Number(pos || 1) - 1];
      return {x: Number(xy[0]) || 0, y: Number(xy[1]) || 0};
    }
    return {x: (map.width || 930) / 2, y: (map.height || 800) / 2};
  }

  function moveToken(name, x, y) {
    const el = root.querySelector('[data-token="' + name + '"]');
    if (!el) return;
    el.style.left = x + 'px'; el.style.top = y + 'px';
  }

  function centerOnFrodo(state) {
    if (!mapViewport || mapViewport.dataset.userPanned === '1') return;
    const xy = positionToPx(state, state.frodo.path, state.frodo.pos);
    mapViewport.scrollLeft = Math.max(0, ((xy.x + 16.5) * mapScale) - (mapViewport.clientWidth / 2));
    mapViewport.scrollTop = Math.max(0, ((xy.y + 20) * mapScale) - (mapViewport.clientHeight / 2));
  }

  function resizeMapCanvas() {
    if (!mapViewport || !mapCanvas) return;
    const map = lastState && lastState.map ? lastState.map : {};
    const mapWidth = Number(map.width || 1000);
    const mapHeight = Number(map.height || 895);
    const availableWidth = Math.max(1, mapViewport.clientWidth);
    const availableHeight = Math.max(1, mapViewport.clientHeight);
    const fitWidth = (availableWidth - 5) / mapWidth;
    const fitHeight = (availableHeight - 5) / mapHeight;
    const isMobile = window.matchMedia && window.matchMedia('(max-width: 840px)').matches;
    mapScale = isMobile
      ? Math.max(0.28, Math.min(1, fitWidth, fitHeight))
      : Math.max(0.65, Math.min(2.5, fitWidth));
    mapCanvas.style.zoom = String(mapScale);
  }

  function enableMapDrag() {
    if (!mapViewport) return;
    let dragging = false;
    let startX = 0;
    let startY = 0;
    let startLeft = 0;
    let startTop = 0;
    mapViewport.addEventListener('pointerdown', ev => {
      if (ev.target.closest('button')) return;
      dragging = true;
      mapViewport.dataset.userPanned = '1';
      mapViewport.classList.add('is-dragging');
      startX = ev.clientX;
      startY = ev.clientY;
      startLeft = mapViewport.scrollLeft;
      startTop = mapViewport.scrollTop;
      mapViewport.setPointerCapture(ev.pointerId);
    });
    mapViewport.addEventListener('pointermove', ev => {
      if (!dragging) return;
      mapViewport.scrollLeft = startLeft - (ev.clientX - startX);
      mapViewport.scrollTop = startTop - (ev.clientY - startY);
    });
    ['pointerup', 'pointercancel', 'lostpointercapture'].forEach(type => {
      mapViewport.addEventListener(type, () => {
        dragging = false;
        mapViewport.classList.remove('is-dragging');
      });
    });
  }

  function setBusy(btn, busy) { btn.disabled = busy; btn.classList.toggle('is-busy', busy); }
  function bindLocalExit(scope) {
    const b = scope.querySelector('[data-local-exit]');
    if (!b) return;
    b.addEventListener('click', () => {
      cachedState = null;
      lastState = null;
      exitFullscreen();
      setMode('landing');
      markContinuable(null);
    });
  }

  root.querySelectorAll('[data-action="start"]').forEach(btn => {
    btn.addEventListener('click', async () => {
      setBusy(btn, true);
      try {
        track('game_start', {difficulty: Number(btn.dataset.difficulty || 0)});
        render(await api('start', {method: 'POST', body: {difficulty: Number(btn.dataset.difficulty || 0)}}));
        requestFullscreen();
      } catch (e) {
        alert(e.message);
      } finally {
        setBusy(btn, false);
      }
    });
  });

  if (continueButton) {
    continueButton.addEventListener('click', async () => {
      setBusy(continueButton, true);
      try {
        render(cachedState || await api('state'));
        requestFullscreen();
      } catch (e) {
        alert(e.message);
      } finally {
        setBusy(continueButton, false);
      }
    });
  }

  root.querySelectorAll('[data-action="exit-fullscreen"]').forEach(btn => btn.addEventListener('click', () => { exitFullscreen(); setMode('landing'); markContinuable(cachedState); }));

  function openAbandonDialog() {
    if (!abandonDialog) return;
    abandonDialog.hidden = false;
    if (abandonCancelButton) abandonCancelButton.focus();
  }

  function closeAbandonDialog() {
    if (!abandonDialog) return;
    abandonDialog.hidden = true;
  }

  root.querySelectorAll('[data-action="abandon-game"]').forEach(btn => {
    btn.addEventListener('click', openAbandonDialog);
  });

  if (abandonCancelButton) {
    abandonCancelButton.addEventListener('click', closeAbandonDialog);
  }

  if (abandonDialog) {
    abandonDialog.addEventListener('click', ev => {
      if (ev.target === abandonDialog) closeAbandonDialog();
    });
  }

  if (abandonConfirmButton) {
    abandonConfirmButton.addEventListener('click', async () => {
      setBusy(abandonConfirmButton, true);
      try {
        await api('abandon', {method: 'POST', body: {}});
        track('game_finish', {result: 'abandoned', score: Number(lastState && lastState.score || 0)});
        cachedState = null;
        lastState = null;
        closeAbandonDialog();
        exitFullscreen();
        setMode('landing');
        markContinuable(null);
      } catch (e) {
        alert(e.message);
      } finally {
        setBusy(abandonConfirmButton, false);
      }
    });
  }

  if (openSubmissionButton && submission) {
    openSubmissionButton.addEventListener('click', () => {
      submission.hidden = false;
      const firstField = submission.querySelector('textarea, input, select, button');
      if (firstField) firstField.focus();
    });
  }

  function closeSubmission() {
    if (!submission) return;
    submission.hidden = true;
    if (openSubmissionButton) openSubmissionButton.focus();
  }

  if (closeSubmissionButton) {
    closeSubmissionButton.addEventListener('click', closeSubmission);
  }

  if (submission) {
    submission.addEventListener('click', ev => {
      if (ev.target === submission) closeSubmission();
    });
  }

  document.addEventListener('keydown', ev => {
    if (ev.key === 'Escape' && abandonDialog && !abandonDialog.hidden) closeAbandonDialog();
    if (ev.key === 'Escape' && submission && !submission.hidden) closeSubmission();
  });

  window.addEventListener('resize', () => {
    resizeMapCanvas();
    if (lastState) centerOnFrodo(lastState);
  });
  document.addEventListener('fullscreenchange', () => {
    setTimeout(() => {
      resizeMapCanvas();
      if (lastState) centerOnFrodo(lastState);
    }, 60);
  });

  const submitForm = root.querySelector('[data-submit-question]');
  if (submitForm) {
    submitForm.addEventListener('submit', async (ev) => {
      ev.preventDefault();
      try { await api('submit-question', {method: 'POST', body: Object.fromEntries(new FormData(submitForm).entries())}); submitForm.reset(); closeSubmission(); alert('Pregunta enviada. Queda pendiente de revisión.'); }
      catch (e) { alert(e.message); }
    });
  }

  enableMapDrag();
  if (EAU_TRIVIAL.loggedIn) { api('state').then(markContinuable).catch(() => {}); }
})();